Python爬虫之Requests库详解

一个爬虫,最重要的一步就是向服务器发出请求,然后获取响应。这个过程很繁琐、很复杂,幸好还有Requests库。

全文4000多字,如果你想看一个胖子的故事,可直接翻到最后。

提要

1. Requests库2. Requests库的7种方法3. request()方法的参数说明3.1  params3.2  data3.3  json3.4  headers3.5  cookies3.6  auth3.7  files3.8  timeout3.9  proxies3.10 allow_redirects3.11  stream3.12  verify3.13 cert4. Response对象的属性和方法4.1 status_code属性4.2 headers属性4.3 encoding属性4.4 apparent_encoding属性4.5 text属性4.6 content属性4.7 cookies属性4.8 url属性4.9 iter_content()方法4.10 close()方法4.11 json()方法4.12 raise_for_status()方法5. 一个胖子的蜕变

1. Requests库

事实上,Python标准库中提供了urllib、urllib2等模块可以用于完成HTTP请求等相关功能,然而这些模块提供的API实在是太渣,体验十分不友好。

正如Requests官方网站警告的那样:“非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子症、啃文档症、抑郁、头疼、甚至死亡。”

因此我们推荐@KennethReitz编写的Requests库。

一些用户体验:

Armin Ronacher

Requests 是一个完美的例子,它证明了通过恰到好处的抽象,API 可以写得多么优美。

Matt DeBoard

我要想个办法,把 @kennethreitz 写的 Python requests 模块做成纹身。一字不漏。

Daniel Greenfeld

感谢 @kennethreitz 的 Requests 库,刚刚用 10 行代码炸掉了 1200 行意大利面代码。今天真是爽呆了!

下面我们将正式介绍Requests库的用法。

2. Requests库的7种方法

Requests库提供了7种方法,说明如下:

requests.request():构造一个请求,支撑以下各种方法的基础方法

requests.get():获取HTML网页的主要方法,对应于HTTP的GET

requests.head():获取HTML网页头信息的方法,对应HTTP的HEAD

requests.post():向HTML网页提交POST请求的方法,对应HTTP的POST

requests.put():向HTML网页提交PUT请求的方法,对应于HTTP的PUT

requests.patch():向HTML网页提交局部修改请求,对应于HTTP的PATCH

requests.delete():向HTML页面提交删除请求,对应于HTTP的DELETE

事实上,后面6种方法都是在调用request()方法,因此Requests库相当于只有1种方法。这一点我们从其源代码中可以了解到:

1def get(url, params=None, **kwargs): 2    kwargs.setdefault(allow_redirects, True) 3    return request(get, url, params=params, **kwargs) 4 5 6def options(url, **kwargs): 7    kwargs.setdefault(allow_redirects, True) 8    return request(options, url, **kwargs) 91011def head(url, **kwargs):12    kwargs.setdefault(allow_redirects, False)13    return request(head, url, **kwargs)141516def post(url, data=None, json=None, **kwargs):17    return request(post, url, data=data, json=json, **kwargs)181920def put(url, data=None, **kwargs):21    return request(put, url, data=data, **kwargs)222324def patch(url, data=None, **kwargs):25    return request(patch, url, data=data, **kwargs)262728def delete(url, **kwargs):29    return request(delete, url, **kwargs)

对于爬虫来说,我们常用到的是get()和post()方法,因此后面的介绍将以这两种方法为主。(实际上它们的原理都差不多)

3. request()方法的参数说明

request()方法的语法如下:

1requests.request(method, url, **kwargs)

method参数,即发送HTTP请求的方法,对应上述6种方法;url即发送请求的网址。这两个参数为必需参数。

例如我们以“GET”方法访问百度,那么相应的代码就是:

1import requests23r = requests.request("get","")

这样我们就得到了一个名为r的Response对象。我们可以从这个对象中获取想要的信息。

下面我们对可选参数**kwargs进行说明:

3.1  params

字典或字节序列,作为参数增加到url中,该参数常用于get请求。

(豆瓣)

(百度)

例如我们想在豆瓣上搜索有关“西游记”的信息,一种方法就是手动拼接url字符串,“?q=西游记”。

另一种方法就是将其变成字典形式作为params参数进行传递:

1params = {"q":"西游记"}2r = requests.request("get","", params = params)

把么在发送请求时Request会自动将params参数拼接成完整的url:“?q=西游记”。

在需要传递非常多的参数时,将更能体现出这种操作的优越性。例如上面的百度。

3.2  data

字典、字节序列或文件对象,作为Request的内容,常用于发送post请求。

在我们进行登录、注册等有需要提交表单的操作时,就需要用到该参数。例如,当我们使用email登录人人网时,就需要传递此参数。

1loginurl = """2postdata = {"email": "[email protected]", 3            "password": ""}4r = requests.requests("post", loginurl, data = postdata)5

当然上述请求可能并不会成功,因为还缺少一些关键的参数。

3.3  json

JSON格式的数据,作为Request的内容,常用于post请求。

JSON即JavaScript 对象表示法(JavaScript Object Notation),它是是存储和交换文本信息的语法。其形式类似于python中的字典:

1{"email": "[email protected]", "password": ""}

但需要注意的是,字典是一种数据类型,而JSON一个字符串。

各种语言中都有处理JSON数据相关的模块。Python中常用json模块,后面我们会进行介绍。

3.4  headers

字典,HTTP定制头。

想一下,当我们用手机、ipad、电脑同时访问百度的主页(同一个网址),百度返回的页面是一样么?

当然不一样。那么百度的服务器是如何知道我们是用什么终端进行访问的呢?

答案就是headers参数中的“user-agent”。如果我们不设置heasers参数,那么“user-agent”就默认为"python-requests/2.19.1"。这就相当于明目张胆地告诉服务器,这个请求来自于python的爬虫程序。设置了反爬程序的服务器就可能会拒绝响应。

因此,我们可以通过定制headers参数将自己伪装成浏览器。

伪装成Chrome浏览器的设置:

1headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"}

伪装成iPhoneX的设置;

1headers = {"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1"}

伪装成安卓手机的设置:

1headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36"}3.5  cookies

字典或CookieJar对象,用于设定Request中的cookie。

可以使用cookie字典传递cookies参数,如:

1cookies = {"cookies_are": "working"}2r = requests.request("get", url, cookies = cookies)

也可将cookie放到headers参数中进行传递:

1headers = {"cookie": "cookies_are=working"}2r = requests.request("get", url, headers = headers)3.6  auth

元组,用于进行HTTP中身份的验证。

Request中的自定义的身份验证机制是通过requests.auth.AuthBase 的子类来实现的,也非常容易定义。Requests 在 requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuth和 HTTPDigestAuth 。

如果对方服务器的验证规则也是基于HTTPBasicAuth(事实上可能性并不大),那么我们就可以通过auth参数来进行身份验证。

1r = requests.request("get", url, auth=HTTPBasicAuth([email protected], ))

当然验证规则也可以由用户自定义。

3.7  files

字典或元组列表类型,用于传输文件。

例如我们需要将多个图像文件进行上传,就需要设置该参数:

1multiple_files = {2    "images": ("fig1.png", open("figPath1", "rb")),3    "images": ("fig2.jpeg", open("figPath2", "rb"), "image/jpeg"),4  "images": ("fig3.jpeg", open("figPath3", "rb"), "image/jpeg", {"refer": "localhost"})}5r = requests.request("post", url, files = multiple_file)

这个字典的键就是发送post请求时的字段名,而字典的值(元组)则是描述发送文件的信息。这个元组的每一个字段代表的意思为:

1("filename", "fileobject", "content-type", "headers")

files参数也可以以元组列表的形式进行设置:

1multiple_files = [2    ("images": ("fig1.png", open("figPath1", "rb"))),3    ("images": ("fig2.jpeg", open("figPath2", "rb"), "image/jpeg")),4  ("images": ("fig3.jpeg", open("figPath3", "rb"), "image/png", {"refer": "localhost"}))]

注意:当我们传递文件对象参数时,一定要以二进制“rb”形式打开文件,否者可能会出错。

3.8  timeout

浮点数或元组类型,用于设定超时时间,单位为秒。

如果timeout为浮点数,那么代表数据返回时最多等待的时间;如果timeout为元组,那么元组中的第一个值为连接的超时时间,第二个值为数据返回时最多等待的时间。

为防止服务器不能及时响应,大部分发至外部服务器的请求都应该带着 timeout 参数。在默认情况下,除非显式指定了timeout 值,requests 是不会自动进行超时处理的。如果没有 timeout,你的代码可能会挂起若干分钟甚至更长时间。

3.9  proxies

字典类型,用于设定访问代理服务器,可以增加登录认证。

如果我们总是用一个IP地址频繁地给服务器发送请求,那么很容易被封掉。因此我们可以通过设置代理来发发送请求。

如下格式:

1proxies = {2    "http": ":3128",3    "https": ":1080"4}5r = requests.request("get", url, proxies=proxies)3.10 allow_redirects

Ture/False,默认为True,是否允许重定向。

重定向就是服务器将网络请求重新定向到其他位置(其他网页,其他网站等)。例如我们访问“” 将会重定向到“”。

3.11  stream

True/False,流模式,默认为False,使用非流模式,即获取内容立即下载。

试想一下,如果我们发送了请求用于获取一部高清蓝光的大电影,那么默认情况下,这个响应内容便会立即发送过来,我们这边会立即接收。这无疑会占据很大的内容,甚至导致程序崩溃。

理智的做法就是服务器一点一点发,我们一点一点收。

1r = requests.request("get", url, stream=True)2f = open("big_moive.mp4", "wb")3for line in r.iter_content():4    if line:5        f.write(line)6r.close()    # 关闭连接      7f.close()    # 关闭文件

我们也可以使用with自动关闭连接和文件:

1from contextlib import closing23with open("big_moive.mp4", "wb") as f:4    with closing(requests.request("get", url, stream=True)) as r:5        for line in r.iter_content():6            f.write(line)3.12  verify

True/False,或为证书的路径,默认为True,用于认证SSL证书。

对于爬取大多数网站(如知乎、百度)证书都是可带可不带。

3.13 cert

如果需要携带本地SSL证书,则用参数cert进行指定本地证书的路径(字符串格式的),一般为.pem结尾。

我们通过上述参数的设置,几乎可以模拟浏览器的任何请求。

4. Response对象的属性和方法

我们发送一个请求之后,服务器返回的内容就是一个Response对象。

1import requests23hd = {User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36}4url = 5r = requests.request(GET, url, headers = hd)67print(type(r))        # r的类型

<class requests.models.Response>

下面将详细介绍Response对象一些重要的属性和方法:

4.1 status_code属性

用于返回请求的状态码:

1print(r.status_code)  # 状态码

200

状态码为200,表明请求成功。一般来说,状态码为2XX表示成功,3XX表示重定向,这两个都是成功;4XX表示请求错误,比如我们常见的404,5XX表示服务器错误,这两个都算失败。

更简单一点的,可以使用ok属性,如果staus_code小于等于400,则为True;否则为False

1r.ok

True

4.2 headers属性

用于返回返回响应头部信息

1print(r.headers)      # 头部信息

结果如下:

{Bdpagetype: 1, Bdqid: 0x8f3dae23000bb82c, Cache-Control: private, Connection: Keep-Alive, Content-Encoding: gzip, Content-Type: text/html, Cxy_all: baidu+f19859cbf3daf67d45713, Date: Wed, 27 Feb 2019 13:52:17 GMT, Expires: Wed, 27 Feb 2019 13:52:02 GMT, Server: BWS/1.1, Set-Cookie: delPer=0; path=/; domain=.baidu.com, BDSVRTM=0; path=/, BD_HOME=0; path=/, H_PS_PSSID=1458_21078_18559_28558_28519_28415_27542; path=/; domain=.baidu.com, Strict-Transport-Security: max-age=, Vary: Accept-Encoding, X-Ua-Compatible: IE=Edge,chrome=1, Transfer-Encoding: chunked}4.3 encoding属性

Requests会根据响应头部信息中的Content-Type来判断文件的编码方式。如果没有找到相应的字段,就默认为“ISO-8859-1”。

1print(r.encoding)

ISO-8859-1

4.4 apparent_encoding属性

事实上有好多网页并不是以ISO-8859-1进行编码的。Requests同样提供依靠charade来猜测编码的操作。幸运的是,它猜的非常准。

1print(r.apparent_encoding)   #r.text

utf-8

的确,百度的页面使用“UTF-8”进行编码的。

4.5 text属性

以文本形式返回响应内容。

1r.encoding = utf-82r.text

文本太长,不予展示。

4.6 content属性

以字节的形式返回响应内容。

1r.content4.7 cookies属性

如果响应中包含一些cookie,可以用此属性快速访问。

1r.cookies

结果如下:

<RequestsCookieJar[Cookie(version=0, name=H_PS_PSSID, value=1458_21078_18559_28558_28519_28415_27542, port=None, port_specified=False, domain=.baidu.com, domain_specified=True, domain_initial_dot=True, path=/, path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name=delPer, value=0, port=None, port_specified=False, domain=.baidu.com, domain_specified=True, domain_initial_dot=True, path=/, path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name=BDSVRTM, value=0, port=None, port_specified=False, domain=www.baidu.com, domain_specified=False, domain_initial_dot=False, path=/, path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False), Cookie(version=0, name=BD_HOME, value=0, port=None, port_specified=False, domain=www.baidu.com, domain_specified=False, domain_initial_dot=False, path=/, path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={}, rfc2109=False)]>

可以看出,其类型是一个CookieJar对象。

以字典的形式查看cookie

1dict(r.cookies)

{H_PS_PSSID: 1458_21078_18559_28558_28519_28415_27542,delPer: 0,BDSVRTM: 0,BD_HOME: 0}

4.8 url属性

用于返回响应的url。由于重定向的存在,可能会出现我们请求的地址和响应的地址并不 一样的情况。

1r.url

/

4.9 iter_content()方法

上文中我们已经做过介绍,当使用流模式(stream=True)接收数据时,便可以采用该方法,一边接收数据,一边处理数据。

4.10 close()方法

用于关闭连接。

4.11 json()方法

Requests 中也有一个内置的 JSON 解码器,可以帮助我们处理 JSON 数据。如果响应的内容中有JSON编码的内容,将返回一个列表类型的数据,其中列表的元素为字典类型。

4.12 raise_for_status()方法

如果出现异常将会抛出一个“HTTPError”类的异常。

1r.raise_for_status()

None

显然我们的响应是正常的。这一方法常用于对响应结果的判断。

5. 一个胖子的蜕变

前面我们介绍了Requests库的一些基本用法,包括request()方法的参数以及Response对象常用的属性和方法。接下来我们简单介绍一下Requests库的作者Kenneth Reitz的故事——一个胖子的蜕变。

Kenneth是美国人,2011年加入有“云服务鼻祖”之称的Heroku公司工作。年仅28岁的他,就当上了总架构师,估值几亿,还用Python语言写了一个Requests库,成了编程领域的世界大师级人物。不到而立之年,就拿着高薪,被称为代码界的天才。

在“世界最大的代码界Facebook交友网站”GitHub上,他更是排名世界前五。是代码界当之无愧的宇宙级网红。

但其实在3年前,他一直有个困扰——胖。那时的Kenneth ,已经超重5年了,最胖的时候是2013年的8月,那时候的他足足有255磅(大约230斤)。起初还不觉得胖点有什么不好,也就颜值差点儿。

(这是减肥前的K神)

可是发展到后来,过度的肥胖引起了持续衰弱性偏头痛,对于一个靠大脑吃饭的技术男来说,脑子不清醒可怎么破?因为胖,即使挣再多钱,都还没女孩子看上,可是如果再让脂肪毁了脑子,这一辈子不就废了么。

于是,在2013年11月,Kenneth决定减肥,拿起相机,顺便记录下这一切。

一天天坚持下去,渐渐地,Kenneth肚子上肥肉开始变少……脸部的轮廓也越发清晰,6个月之后,他把自拍照po到FB上,引来了以前老同学的惊呼,“Kenneth,这真的是你吗?你看起来棒极了……”

(减肥后的K神)

原本他的目标体重其实是190磅。而此时的Kenneth,体重降到了150磅,减掉了足足100磅。

虽然经历了6个月的煎熬,3年的打磨,但是当看到自己从里到外都变得健康时,Kenneth觉得一切都值了。

Kenneth的个人网站上有一张PERSONAL VALUES的图片,其中的几句话我很喜欢。

Lifes not a race, but theres no speed limit either.生活不是一场赛跑,但也没有速度限制。

只要你决心做一件事情,谁也阻挡不了你!

Python之网络爬虫

Python之正则表达式—re模块面向对象的用法

Python之正则表达式——re模块函数式用法

正则表达式极速入门

Python之迭代文件内容

Python之基本文件操作

Python之直面异常

Python之处理异常

为什么说“人生苦短,我用Python”?