
3.3.1 request库的基本使用
利用request发送网络请求也非常简单。request库中的get()方法类似于urllib中的urlopen()方法。关于requests.get()的使用方法,请参考例3-11。
【例3-11】requests.get()方法实例
1 import requests 2 result = requests.get('https://www.baidu.com/') 3 print(type(result)) 4 print(r.status_code)
运行结果如下:
<class 'requests.models.Response'> 200
上面的例子中,我们调用get()方法来实现与urlopen()相同的操作,得到一个Response对象,然后分别输出了Response的类型以及状态码。对于HTTP请求类型,PUT、DELETE、HEAD以及OPTIONS都能通过如下代码进行HTTP请求:
>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'}) >>> r = requests.delete('http://httpbin.org/delete') >>> r = requests.head('http://httpbin.org/get') >>> r = requests.options('http://httpbin.org/get')*
requests会自动解码来自服务器的内容(实例如下所示),大多数unicode字符集都能被无缝地解码。
【例3-12】requests解码实例
1 import requests 2 r = requests.get('https://api.github.com/events') 3 print(r.text)
请求发出后,requests会基于HTTP头部对响应的编码来做出有根据的推测。当访问r.text时,requests会使用其推测的文本编码。我们可以找出requests使用了什么编码,并且能够使用r.encoding属性来改变它:
>>> r.encoding >>> r.encoding = 'ISO-8859-1'
如果我们改变了编码,那么每当访问r.text时,request都会使用r.encoding的新值。我们可能希望在使用特殊逻辑计算出文本编码的情况下修改编码。比如HTTP和XML自身可以指定编码。这样的话,我们应该使用r.content来找到编码,然后设置r.encoding为相应的编码。这样就能使用正确的编码来解析r.text了。
在需要的情况下,requests也可以使用定制的编码。如果创建了自己的编码,并使用codecs模块进行注册,那么就可以使用这个解码器名称作为r.encoding的值,然后由requests来为我们处理编码。
1.传递URL参数
我们也许经常想为URL的查询字符串传递某种数据。如果我们是手工构建URL,那么数据会以键/值对的形式置于URL中,跟在一个问号的后面;例如,httpbin.org/get?key=val。requests允许使用params关键字参数,并以一个字符串字典来提供这些参数。举例来说,如果想传递key1=value1和key2=value2到httpbin.org/get,那么我们可以使用如下代码:
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.get("http://httpbin.org/get", params=payload) >>> print(r.url)
通过打印输出该URL,能看到URL已被正确编码:
http://httpbin.org/get?key2=value2&key1=value1
注意,字典里值为None的键都不会被添加到URL的查询字符串里。
还可以将一个列表作为值传入:
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']} >>> r = requests.get('http://httpbin.org/get', params=payload) >>> print(r.url)
输出结果为:
http://httpbin.org/get?key1=value1&key2=value2&key2=value3
关于构造URL参数的GET请求的方式,请参考例3-13。
【例3-13】GET传参实例
1 import requests 2 data = { 3 'name': 'Ivan', 4 'age': 23 5 } 6 r = requests.get("http://httpbin.org/get", params=data) 7 print(r.text))
运行结果为:
{ "args": { "age": "22", "name": "germey" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "Python-requests/2.14.2" }, "origin": "113.9.216.26, 113.9.216.26", "url": "https://httpbin.org/get?name=germey&age=22" }
根据运行结果判断,该请求返回了一个JSON格式的数据。另外,网页的返回类型实际上是str类型,但它很特殊,是JSON格式的。所以,如果想直接解析返回结果,从而得到一个字典格式的话,可以直接调用json()方法。示例如下:
1 import requests 2 r = requests.get("http://httpbin.org/get") 3 print(type(r.text)) 4 print(r.json()) 5 print(type(r.json()))
运行结果如下:
<class 'str'> {'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbi n.org', 'User-Agent': 'Python-requests/2.14.2'}, 'origin': '113.9.216.26, 113.9.216.26', 'ur l':'https://httpbin.org/get'} <class 'dict'>
如果JSON解码失败,r.json()就会抛出一个异常。例如,响应内容是401(Unauthorized),尝试访问r.json()将会抛出ValueError:NoJSONobjectcouldbedecoded异常。
需要注意的是,成功调用r.json()并不意味着响应的成功。有的服务器会在失败的响应中包含一个JSON对象(比如HTTP500的错误细节)。这种JSON会被解码返回。要检查请求是否成功,则请使用r.raise_for_status()或者检查r.status_code是否和之前的期望相同。
2.抓取二进制响应内容
requests支持以字节的方式访问请求响应体,对于非文本请求:
>>> r.content b'[{"repository":{"open_issues":0,"url":"https://github.com/...
requests会自动为我们解码gzip和deflate传输编码的响应数据。
例如,以请求返回的二进制数据来保存一张图片(即抓取站点图标),可以使用如下代码。
【例3-14】抓取站点图标
1 import requests 2 r = requests.get("https://baidu.com/favicon.ico") 3 with open('favicon.ico', 'wb') as f: 4 f.write(r.content)
运行结果如图3-2所示。
图3-2 favicon.ico
3.定制headers
在爬取的过程中,有些网站不设置headers(请求头),这样便不能正常请求该网站。我们以抓取知乎的“发现”页面为例,来看下不添加headers的情况,如下所示。
【例3-15】未添加headers的抓取
1 import requests 2 r = requests.get("https://www.zhihu.com/explore") 3 print(r.text)
运行结果如下:
<html> <head><title>400 Bad Request</title></head> <body bgcolor="white"> <center><h1>400 Bad Request</h1></center> <hr><center>openresty</center> </body> </html>
由响应信息可知,请求访问失败,但加上headers信息并给User-Agent赋值,那就没问题了,我们也能在headers里添加其他信息。
【例3-16】添加header抓取
1 import requests 2 headers = { 3 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.6 4 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36' 5 } 6 r = requests.get("https://www.zhihu.com/explore", headers=headers) 7 print(r.text)
注意,定制headers的优先级低于某些特定的信息源。
1)如果在.netrc中设置了用户认证信息,那么使用headers=设置的授权就不会生效。而如果设置了auth=参数,.netrc的设置就无效了。
2)如果被重定向到别的主机,那么授权header就会被删除。
3)代理授权header会被URL中提供的代理身份覆盖掉。
进一步讲,requests不会基于定制header的具体情况来改变自己的行为。只不过在最后的请求中,所有的header信息都会被传递进去。
注意 所有的header值都必须是string、bytestring或者unicode。尽管传递unicode header也是允许的,但不建议这样做。
4.POST请求
HTTP只有POST和GET两种命令模式。通常情况下,POST是被设计用来向服务器发送数据的,而GET则是被设计用来从服务器获取数据。POST和GET的区别如下:
1)POST和GET都可以带着参数请求,不过GET请求的参数会在url上显示出来。
2)POST请求的参数不会直接显示,而是会隐藏起来。像账号密码这种私密的信息,就应该用POST的请求。
3)GET请求会应用于获取网页数据,比如requests.get()。POST请求则应用于向网页提交数据,比如提交表单类型数据(像账号密码就是网页表单的数据)。
POST请求分为json(application/json)类型和表单类型(x-www-form-urlencoded)。
(1)json类型的POST请求
关于json类型的POST请求,具体语法如下:
import requests url = "http://test" data = '{"key":"value"}' # 字符串格式 res = requests.post(url=url,data=data)print(res.text)
(2)表单类型的POST请求
要实现表单类型的POST请求,只需简单地传递一个字典给data参数。数据字典在发出请求时会自动编码为表单形式:
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.post("http://httpbin.org/post", data=payload) >>> print(r.text) { ... "form": { "key2": "value2", "key1": "value1" }, ... }
我们还可以为data参数传入一个元组列表。当表单中有多个元素使用同一key时,这种方式尤其有效:
>>> payload = (('key1', 'value1'), ('key1', 'value2')) >>> r = requests.post('http://httpbin.org/post', data=payload)
通过data参数传入一个元组列表,具体方法如以下示例所示。
【例3-17】POST请求实例
1 import requests 2 payload = (('key1', 'value1'), ('key2', 'value2')) 3 r = requests.post('http://httpbin.org/post', data=payload) 4 print(r.text)
运行结果如下:
{ "args": {}, "data": "", "files": {}, "form": { "key1": "value1", "key2": "value2" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "23", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Python-requests/2.14.2" }, "json": null, "origin": "113.9.216.26, 113.9.216.26", "url": "https://httpbin.org/post" }
由结果可知已经成功获得了返回结果,其中form部分就是POST提交的数据,这就表明已成功发送POST请求。
requests中的POST使得上传多部分编码文件也变得很简单,实例如下所示。
【例3-18】上传文件实例
1 import requests 2 4files = {'file': open('test.img', 'rb')} 3 r = requests.post("http://httpbin.org/post", files=files) 4 print(r.text)
代码对应的文件夹下存在一个名为“test.img”的图像文件,运行结果如下:
{ "args": {}, "data": "", "files": { "file": "data:application/octet-stream;base64, AAABAAEAQEAAA...=" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "17105", "Con tent-Type": "multipart/form-data; boundary=e44ad266ca7748709e57e3c75a6e3 f0b", "Host": "httpbin.org", "User-Agent": "Python-requests/2.14.2" }, "json": null, "origin": "113.9.216.26, 113.9.216.26", "url": "https://httpbin.org/post" }
5.响应
发送POST请求后,得到的自然就是响应。在上面的实例中,我们能够使用text等获取响应的内容。此外,还有很多属性和方法可以用来获取其他信息,比如状态码、响应头、Cookies等。获取响应的实例如下所示。
【例3-19】获取响应的实例
1 import requests 2 r = requests.get('http://www.baidu.com') 3 print(type(r.status_code), r.status_code) 4 print(type(r.url), r.url) 5 print(type(r.headers), r.headers) 6 print(type(r.cookies), r.cookies) 7 print(type(r.history), r.history)
运行结果如下:
<class 'int'> 200 <class 'str'> http://www.baidu.com/ <class 'requests.structures.CaseInsensitiveDict'> {'Cache-Control': 'private, no-cache, n o-store, proxy-revalidate, no-transform', 'Connection': 'Keep-Alive', 'Content-Encoding': gzip', 'Content-Type': 'text/html', 'Date': 'Mon, 27 May 2019 09:11:05 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:32 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Transfer-Encoding': 'chunked'} <class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> <class 'list'> []
上面的实例分别打印输出status_code属性得到状态码,输出headers属性得到响应头,输出cookies属性得到Cookies,输出url属性得到URL,输出history属性得到请求历史。
状态码status_code是用以表示网页服务器超文本传输协议响应状态的3位数字代码,而request库还提供了一个内置的状态码查询对象requests.codes,查询实例如下所示。
【例3-20】查询对象实例
1 import requests 2 r = requests.get('http://www.baidu.com') 3 exit() if not r.status_code == requests.codes.ok else print('请求成功')
运行结果如下:
请求成功
6.Cookies
在爬虫中可能会涉及Cookies问题,如果某个响应中包含一些Cookies,那么利用request可以快速访问。获取微博主页Cookies实例如下。
【例3-21】获取微博主页Cookies
1 import requests 2 r = requests.get("https://www.weibo.com") 3 print(r.cookies) 4 for key, value in r.cookies.items(): 5 print(key + '=' + value)
运行结果如下:
<Req uestsCookieJar[<Cookie login=609423641c81693ee710ee69b0d0e34c for passport. weibo.com/>]> login=609423641c81693ee710ee69b0d0e34c
通过get()方法能够得到响应,再调用cookies属性即可成功得到Cookie。在一些情况下,能够通过Cookies进行模拟登录,下面以登录知乎首页为例来进行说明。首先我们需要登录豆瓣,然后将headers中的Cookie复制下来,如图3-3所示。
把headers中的cookie属性值换成刚刚自己复制的Cookie,然后发送请求,实例如下。
【例3-22】利用Cookie登录知乎
1 import requests 2 headers = {'Cookie': '_zap=1e90c32c-f107-47fd-a2c1-dc4825387b14; d_c0="AEAhl 3 cghjQ6PTgJrYt-jCx-yeEe-6Mw-N1I=|1542769374"; __gads=ID=8eb3ce731a878f92: 4 T=1543836189:S=ALNI_MZxN9fZ2D4pwM9SS8_NirbIK4oJqg; __utmv=51854390. 5 100--|2=registration_date=20150718=1^3=entry_date=20150718=1; _xsrf=Q9RcouJP 6 KBgXlRzOLXXnzgVqqtp7Ayya; __utma=51854390.411081451.1546934847.154693 7 4847.1555068299.2; __utmz=51854390.1555068299.2.2.utmcsr=google|utmccn=(org 8 anic)|utmcmd=organic|utmctr=(not%20provided); q_c1=066cdd6027e747c9ba0b0992 9 871bc2d2|1556706765000|1542769503000; tst=r; capsion_ticket="2|1:0|10:15588729 10 97|14:capsion_ticket|44:MDg3ZWNmZTA1Yjc4NGNjYzllMWFiMGFiMGVjYTFjY1 11 WY=|ded192ebf001f60c61038f19ed833bdff76614fe7d9d1085dc3affe338ee4313"; r_c 12 ap_id="MGVkMzkwNmMzYmY2NGJkZThkNGZhOTYyMDJkZjI3ZWE=|15588730 13 01|f1f76f832f2bcb0fb7e3826176380b8bdcc4f278"; cap_id="NTY2NGJkMWRiNzky 14 NDU3Mzg0NzYwMWU0ZGVlNGI5OTM=|1558873001|fd54d23d073c3a90c8e25ddc 15 9f80d6fbfdfc4a75"; l_cap_id="NDIyYmNmMTg3MjRkNGY1OGEyZjM3MzBhOG 16 M0MGYwZDk=|1558873001|1e0ff372de6dc4b633a19240434562e2345b6bd5"; z_c0 17 =Mi4xY2dqZkFRQUFBQUFBUUNHVnlDR05EaGNBQUFCaEFsVk5zZEhYWFFD 18 Q3ZQai1EeEpOSjQ5UXVFSlZ5TWd4YzlfY1VR|1558873009|ec1375d69afd9634232 19 90200dcc30cfe362019de; tgw_l7_route=6936aeaa581e37ec1db11b7e1aef240e', 20 'Host': 'www.zhihu.com', 21 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWeb 22 Kit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36', 23 } 24 r = requests.get('https://www.zhihu.com', headers=headers) 25 print(r.text)
图3-3 复制Cookie
部分运行结果如下所示,说明登录成功。
<!doctype html> <html lang="zh" data-hairline="true" data-theme="light"><head><meta charSet="utf-8"/><title data-react-helmet="true">首页 - 知乎</title><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"/><meta name="renderer" content="webkit"/><meta name="force-rendering" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/><meta name="google-site-verification" content="FTeR0c8arOPKh8c5DYh_9uu98_zJbaWw53J-Sch9MTg"/><meta name="description" property="og:description" content="有问题,上知乎。知乎,可信赖的问答社区,以让每个人高效获得可信赖的解答为使命。知乎凭借认真、专业和友善的社区氛围,结构化、易获得的优质内容,基于问答的内容生产方式和独特的社区机制,吸引、聚集了各行各业中大量的亲历者、内行人、领域专家、领域爱好者,将高质量的内容透过人的节点来成规模地生产和分享。用户通过问答等交流方式建立信任和连接,打造和提升个人影响力,并发现、获得新机会。"/><link rel="shortcut icon" type="image/x-icon" href="https://static.zhihu.com/static/favicon.ico"/><link rel="search" type="application/opensearchdescription+xml"
7.重定向与请求历史
默认情况下,除了Head,requests会自动处理所有重定向。可以使用响应对象的history方法来追踪重定向。Response.history是一个Response对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最旧到最新的请求进行排序。
例如,GitHub将所有的HTTP请求重定向到HTTPS:
>>> r = requests.get('http://github.com') >>> r.url
运行结果如下:
'https://github.com/' >>> r.status_code
运行结果如下:
200
提示 网络请求status_code(状态码)200,表示服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
>>> r.history
运行结果如下:
[<Response [301]>]
提示 网络请求status_code(状态码)301,表示请求的网页已被移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。应使用此代码通知搜索引擎蜘蛛网页或网站已被移动到新位置。
如果你使用的是GET、OPTIONS、POST、PUT、PATCH或者DELETE,那么你可以通过allow_redirects参数禁用重定向处理:
>>> r = requests.get('http://github.com', allow_redirects=False) >>> r.status_code
301
>>> r.history
[]
地址结果为空,代表禁用重定向
如果你使用了HEAD,那么你也可以启用重定向:
>>> r = requests.head('http://github.com', allow_redirects=True) >>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]