一个爬虫,最重要的一步就是向服务器发出请求,然后获取响应。这个过程很繁琐、很复杂,幸好还有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 jsonJSON格式的数据,作为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_redirectsTure/False,默认为True,是否允许重定向。
重定向就是服务器将网络请求重新定向到其他位置(其他网页,其他网站等)。例如我们访问“” 将会重定向到“”。
3.11 streamTrue/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 verifyTrue/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.okTrue
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.textutf-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”?