Python3 爬虫入门(一)

0

这几天尝试搜索很多资料,发现了很多很不错的关于Python、关于爬虫的博客和书籍。列举如下,之后如果发现更好的,我也会补充上去。另外,因为爬虫用到的很多知识我之前都有过涉猎,所以有的地方我懂的话笔记里就会一笔带过,资料整理如下:

  • Full Speed Python, 这本书很适合给有编程基础的人入门,它包含了Python基本的语法和对应的习题,快速刷一遍能很快对Python有个了解。
  • Python 3 教程,菜鸟教程也很错,目录分的很细,很浅显也有对应的例子。

读完这两篇文档后,我仍然推荐找一本专门讲Python的书,在继续学习其他内容时抽空将其慢慢读一遍,因为对于Python语言,上述两项只能说讲了一些皮毛,而很多Python的特性以及编程技巧需要通过大量的系统化阅读和练习才能做到信手拈来,再者才能有所创新。

  • Python3网络爬虫系列, 这个是我个人很推荐也是我即将照着学习的Python 爬虫入门教程,本文剩下的内容就是这系列的学习笔记。
  • 爬虫代理服务, 爬虫进阶。
  • Python程序员, Python进阶,该作者还有列出了一个爬虫进阶需要的资料,可以关注他的公众号获得。

当然还有万法之宗——Python3.6.5官方文档,以及其他零零碎碎搜索得到的资料就不赘述了。祝大家好运。

爬虫和Urllib库

安装pip

开始之前先记录下如何安装pip和Python:windows下面安装Python和pip终极教程。

先来个简单的

from urllib import request
import chardet

# 构建Request
req = request.Request("http://fanyi.baidu.com")

# 访问页面,并获取从服务器的回应,如果是HTTPS?
response = request.urlopen(req)
print (response.geturl() + '\n')
print (response.info())
print (response.getcode())

# 读取页面
html = response.read()

# 检测页面使用的编码
charset = chardet.detect(html)

# 解码:获得易于阅读的网页源码
html = html.decode(charset['encoding'])

这个程序中最后的html变量存储了目标网页的源代码打,经过浏览器解析,它就会呈现出你在日常生活中常见的网页。除此之外,关于网页的META标签和HTTP协议的状态码,就不赘述了,有兴趣的话可以读一下HTTP协议的RFC文档: RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1 - IETF Tools,相信我,通读之后会觉得神清气爽。这可算是爬虫界的Hello World程序了。

向百度翻译发送数据并获取结果

如果urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)参数data的值为None,request就生成GET请求,否则则为POST请求。

这里有个值得一提的变化就是百度似乎改了这个接口,原作者所提供的代码已经无法获得正确的翻译结果了。变化如下:

首先,Form_Data的字段新增了sign和token字段,前者应该是根据query字段值生成的,后者则是一个固定字段(只能说是相对固定字段,不同电脑可能是不同的)。因为sign字段是不断变化的,这也决定了除非你能猜出sign字段的生成算法,否则这个代码就是没什么意义了。这是后话了。

接着我遇到的一个问题是,假设我知道我要翻译的APPLE字段对应的sign值是412673.175920,我就能写出如下代码:

from urllib import request
from urllib import parse
import chardet
import json

# 构建Request
req = request.Request("http://fanyi.baidu.com/v2transapi")

Form_Data = {}
Form_Data['from'] = 'en'
Form_Data['to'] = 'zh'
Form_Data['query'] = 'APPLE'
Form_Data['transtype'] = 'translang'
Form_Data['simple_means_flag'] = '3'
Form_Data['sign'] = '26996.297541'
Form_Data['token'] = '185a25508e00cb5d0d1251dfe0066cea'

data = parse.urlencode(Form_Data).encode('utf-8')
response = request.urlopen(req, data)
html = response.read().decode('utf-8')

result = json.loads(html)

print (result)

这段代码返回error:997, 虽然我没在百度翻译的API文档里找到相对应的错误码,但是毫无疑问,代码没有成功。

# 输出结果
{'error': 997, 'from': 'en', 'to': 'zh', 'query': 'APPLE'}

为什么呢?我用wireshark抓取了两次的报文(一次是我的程序运行时的,一次是我用浏览器访问页面时的),找到那条关键的POST请求,然后仔细对比,如图:

Python爬虫百度翻译1.jpg

虽然在Header部分里有很多缺失的字段,但是在我一个一个调试之后发现唯一必要的是红框里那部分Cookie,简单看了下Cookie里的字段,发现看不懂,先不管,把整个Cookie拷贝过来,代码就能工作了,接着发现返回的json文件格式有所变化,调整一下,就能得到想要的结果了。

from urllib import request
from urllib import parse
import chardet
import json

# 构建Request
req = request.Request("http://fanyi.baidu.com/v2transapi")

req.add_header('Cookie','BAIDUID=42F6DD1CC8665CEF88C2A26C1F0F504C:FG=1; locale=zh; \
    to_lang_often=%5B%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%2C%7B%22\
    value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%5D; REALTIME_TRANS_SWITCH=1; \
    FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; \
    Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1523998024,1524007914; \
    BIDUPSID=42F6DD1CC8665CEF88C2A26C1F0F504C; PSTM=1524011246; \
    H_PS_PSSID=1463_21084_18560_20718; \
from_lang_often=%5B%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%2C%7B%22value%22%3A%22it%22%2C%22\
text%22%3A%22%u610F%u5927%u5229%u8BED%22%7D%2C%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%5D; \
    pgv_pvi=3705798656; pgv_si=s8210368512; \
    Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1524015722')

Form_Data = {}
Form_Data['from'] = 'en'
Form_Data['to'] = 'zh'
Form_Data['query'] = 'APPLE'
Form_Data['transtype'] = 'translang'
Form_Data['simple_means_flag'] = '3'
Form_Data['sign'] = '26996.297541'
Form_Data['token'] = '185a25508e00cb5d0d1251dfe0066cea'

data = parse.urlencode(Form_Data).encode('utf-8')
response = request.urlopen(req, data)
html = response.read().decode('utf-8')

result = json.loads(html)

print (result['trans_result']['data'][0]['dst'])

虽然代码暂时能工作了,但是我们至少还剩两个问题:

  1. 我看不懂Cookie里的字段的含义,虽然我换了几个'query'和'sign'对,能够成功获得翻译结果。(哪天我看懂了Cookie里的字段含义我会回来补充的。)不过对于这点,我也有点想法,我在网上搜到了一下通过注册百度账号来使用其API的方法,里面涉及到了一些APPID之类的字段以及其生成算法,而我们在网页上用的,就是这个字段的一个特殊值,我不确定两者是否有关系。然而我并不想给百度提供任何我的信息,就此作罢。
  2. 第二个问题就是sign的问题,如我上文所述,如果猜不到sign的生成算法,这段代码都是没有意义的。

那么怎么解决第二个问题呢?

答案是很tricky,就是采用手机端的访问方式,这里我只能说一句,为了反爬虫,百度改了PC客户端访问的方式却忘了改手机端访问,是不是手机端代码太复杂没法改?

from urllib import request
from urllib import parse
import chardet
import json

# 构建Request
req = request.Request("http://fanyi.baidu.com/basetrans")
req.add_header("User-Agent","Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36")

Form_Data = {}
Form_Data['from'] = 'en'
Form_Data['to'] = 'zh'
Form_Data['query'] = 'mean'

data = parse.urlencode(Form_Data).encode('utf-8')
response = request.urlopen(req, data)
html = response.read().decode('utf-8')

result = json.loads(html)

print (result['trans'][0]['dst'])

可见,这段代码里Form_Data不必包含sign和token字段,另外手机端访问的话,url和user-agent头内容都是不一样的,具体参见代码。这段代码我是从通过爬虫实现百度在线翻译这里看到的,为了解决sign的问题我搜了很多文档,这篇文章是唯一一个提供有效代码,感谢,尽管我也很好奇作者这个URL是哪找来的。另外原作者用了requests库,我试了下和用urllib库产生的post请求是一样的,也算是提供另一个思路吧。

之后我去试了下Goolge翻译,发现也有一个sign的问题存在,和百度翻译一样,是一个12位的小数(XXXXXX.XXXXXX)。以后如果接触到这种算法我再来补充吧。

urllib.error异常

不管是GET还是POST,如果包有问题,服务器都会返回一个异常,根据异常不同,用于标记的返回码也会不同。注意这和上文所述的error:997是不一样的,虽然上文的包中收到了错误码,但是从报文的收发也就是HTTP协议的角度来说,这次传输是正常的。就好比如果我打电话给你跟你说你写的程序出问题了,你要被开除了,虽然内容不好,但是电话线路是好的。

而这一节中的urllib.error异常指的就是电话线路异常。理解一下这句话:虽然上述报文返回了error:997,但是POST请求返回的状态码是200 OK。在互联网上,客户端的请求可能来自于世界各地,每当服务端接受到请求后,都会在返回的报文里携带一个状态码,最常见的就是200 OK,表示一切正常,还有404 Not Found,表示服务器没有客户端请求的这个页面。同样,其他状态码可以参考RFC。上文所述的getcode()就能得到这个状态码。

urllib.error有三种异常, URLError, HTTPError和ContentTooShortError,文档上也没明确说具体什么情况会导致哪种异常,但其实从字面意思理解就行了。最后一个是新加的异常,一般在Content-Length字段的值和实际接受到的字段不符时出现,这种情况大多出现在网络不稳定的情况下。我们可以用try-except语句来捕捉并区别这三种异常:

from urllib import request
from urllib import error
import chardet

# 构建Request
req = request.Request("http://fanyi.baidu.com")

# 访问页面,并获取从服务器的回应,如果是HTTPS?
try:
    response = request.urlopen(req)
except error.URLError as e:
    if hasattr(e, 'code'):
        print ("HTTPError")
        print (e.code)
    elif hasattr(e, 'reason'):
        print ("URLError")
        print (e.reason)
    else:
        print ("ContentTooShortError")
        # 代码,一般是Reload函数

# 其他代码
# ...

本文代码于最后运行于2018-04-17。

你可能感兴趣的:(Python3 爬虫入门(一))