Python网络爬虫技术与实战
上QQ阅读APP看书,第一时间看更新

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]>]