Skip to content

requests

基本用法

实例引入

urllib 库中的 urlopen() 方法实际上是以 GET 方式请求网页,而 requests 中国呢相应的方法就是 get() 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import requests
r = requests.get("https://www.baidu.com/")
print(type(r))
print(r.cookies)
print(r.status_code)
print(type(r.text))
print(r.text)
"""
<class 'requests.models.Response'>
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
200
<class 'str'>
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>ç¾åº¦ä¸ä¸ï¼ä½ å°±çé</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=ç¾åº¦ä¸ä¸ class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>æ°é»</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>å°å</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>è§é¢</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>è´´å</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>ç»å½</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">ç»å½</a>');
                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">æå¤äº§å</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>å
³äºç¾åº¦</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使ç¨ç¾åº¦åå¿
读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>æ
                                                                  è§åé¦</a>&nbsp;京ICPè¯030173å·&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
"""

这里我们调用 get() 方法实现与 urlopen() 相同的操作,得到一个 Response 对象,然后分别输出了 Responese 的类型、状态码、响应体的类型、内容以及 Coolies

通过运行结果可以发现,它的返回类型是 requests.models.Response,响应体的类型是字符串 str,Cookies 的类型是 RequestsCookieJar

使用 get() 方法实现一个 GET 请求,这倒不算什么,更方便之处在于其他的请求类型依然可以用一句话来完成

1
2
3
4
5
6
import requests
r = requests.post("http://httpbin.org/post")
r = requests.put("http://httpbin.org/put")
r = requests.delete("http://httpbin.org/delete")
r = requests.head("http://httpbin.org/get")
r = requests.options("http://httpbin.org/get")

这里分别用 post()、put()、delete() 等方法实现了 POST、PUT、DELETE 等请求。

GET 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import requests
r = requests.get("http://httpbin.org/get")
print(r.text)
"""
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.22.0"
  }, 
  "origin": "116.136.20.167, 116.136.20.167", 
  "url": "https://httpbin.org/get"
}
"""

给 GET 请求附加额外信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
data = {
    "name": "germey",
    "age": 22
}
r = requests.get("http://httpbin.org/get", params=data)
print(r.text)
"""
{
  "args": {
    "age": "22", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.22.0"
  }, 
  "origin": "116.136.20.167, 116.136.20.167", 
  "url": "https://httpbin.org/get?name=germey&age=22"
}
"""

通过运行结果可以判断,请求的链接自动被构造成了:https://httpbin.org/get?name=germey&age=22

另外,网页的返回类型实际上是 str 类型,但是它很特殊,是 JSON 格式的。所以,如果想直接解析返回结果,得到一个字典格式的话,可以直接调用 json() 方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import requests
r = requests.get("http://httpbin.org/get")
print(type(r.text))
print(type(r.json()))
print(r.json())
"""
<class 'str'>
<class 'dict'>
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.22.0'}, 'origin': '116.136.20.167, 116.136.20.167', 'url': 'https://httpbin.org/get'}
"""

可以发现,调用 json() 方法,就可以将返回结果是 JSON 格式的字符串转化为字典

但需要注意的是,如果返回结果不是 JSON 格式,便会出现解析错误,抛出 json.decoder.JSONDecoderError 异常

抓取网页

以“知乎”—》“发现”页面为例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import re
import requests
headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
r = requests.get("https://www.zhihu.com/explore", headers=headers)
titles = re.findall(r'class="ExploreSpecialCard-contentTitle"[\s\S]*?data-za-detail-view-id="\d*">([\s\S]*?)</a>', r.text)
for title in titles:
    print(title)
"""
是不是有「鬼」的都叫恐怖片?
电影史上有哪些经典的恐怖片角色?
恐怖片烂梗合集
有哪些一定不能买的耳机?
有哪些便宜但音质特别好的耳机推荐?
有哪些适合在寝室自习看书用的主动降噪耳机?
如何看待华为 2019 年 8 月 9 日正式发布的 HarmonyOS 鸿蒙系统?
华为 2019 年 8 月 10 日发布的荣耀智慧屏?亮点和不足有哪些?
如何评价华为 8 月 9 号发布的 EMUI10?有哪些亮点和不足?
汽车上隐藏了哪些「精妙无比」的设计?
职业赛车手的驾驶技术究竟有多恐怖?
拥有一台高回头率的汽车是一种怎样的体验?
"""

这里加入了 headers 信息,其中包含了 User-Agent 信息,也就是浏览器标识信息。如果不加这个,知乎会禁止抓取

抓取二进制数据

图片、音频、视频这些文件本质上是由二进制码组成的,由于有特定的保存格式和对应的解析方式,我们才可以看到这些形形色色的多媒体。所以,想要抓取它们,就要拿到它们的二进制码

以 GitHub 站点图标为例来看一下

1
2
3
4
import requests
r = requests.get("https://github.com/favicon.ico")
print(r.text)
print(r.content)

r.text 出现了乱码,r.content 结果前带有一个 b,这代表是 bytes 类型的数据。由于图片是二进制数据,所以前者在打印时转化为 str 类型,也就是图片直接转化为字符串,这理所当然会出现乱码

1
2
3
4
import requests
r = requests.get("https://github.com/favicon.ico")
with open("favicon.ico", "wb") as f:
    f.write(r.content)

运行结束后,可以发现在文件夹中出现了名为 favicon.ico 的图标

同样地,音频和视频文件也可以用这种方法获取

POST 请求

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests
data = {"name": "germey", "age": 22}
r = requests.post("http://httpbin.org/post", data=data)
print(r.text)
"""
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "age": "22", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "18", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.22.0"
  }, 
  "json": null, 
  "origin": "116.136.20.167, 116.136.20.167", 
  "url": "https://httpbin.org/post"
}
"""

可以发现,成功获得了返回结果,其中 form 部分就是提交的数据,这就证明 POST 请求成功发送了

响应

发送请求后,得到的自然是响应。在上面的实例中,我们使用 text 和 content 获取了响应的内容。此外,还有很多属性和方法可以用来获取其他信息,比如状态码、响应头、Cookies 等。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import requests
r = requests.get("http://www.baidu.com")
print(type(r.status_code), r.status_code)
print(type(r.headers), r.headers)
print(type(r.url), r.url)
print(type(r.history), r.history)
print(type(r.cookies), r.cookies)
"""
<class 'int'> 200
<class 'requests.structures.CaseInsensitiveDict'> {'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Connection': 'Keep-Alive', 'Content-Encoding': 'gzip', 'Content-Type': 'text/html', 'Date': 'Thu, 22 Aug 2019 23:52:18 GMT', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:36 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 'str'> http://www.baidu.com/
<class 'list'> []
<class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
"""

状态码用来判断请求是否成功,而 requests 还提供了一个内置的状态码查询对象 requests.code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import requests
r = requests.get("https://www.baidu.com")
print(r.status_code == requests.codes.ok)
print(r.status_code == requests.codes['\o/'])
print(r.status_code == requests.codes['✓'])
"""
True
True
True
"""

信息性状态码
100: continue
101: switching_protocols
102: processing
103: checkpoint
122: uri_too_long , request_uri_too_long

成功状态码
200: ok , okay , all_ok , all_okay , all_good , \o/ , ✓
201: created
202: accepted
203: non_authoritative_info , non_authoritative_information
204: no_content
205: reset_content , reset
206: partial_content , partial
207: multi_status , multiple_status , multi_stati , multiple_stati
208: already_reported
226: im_used

重定向状态码
300: multiple_choices
301: moved_permanently , moved , \o-
302: found
303: see_other , other
304: not_modified
305: use_proxy
306: switch_proxy
307: temporary_redirect , temporary_moved , temporary
308: permanent_redirect , resume_incomplete , resume

客户端错误状态码
400: bad_request , bad
401: unauthorized
402: payment_required , payment
403: forbidden
404: not_found , -o-
405: method_not_allowed , not_allowed
406: not_acceptable
407: proxy_authentication_required , proxy_auth , proxy_authentication
408: request_timeout , timeout
409: conflict
410: gone
411: length_required
412: precondition_failed , precondition
413: request_entity_too_large
414: request_uri_too_large
415: unsupported_media_type , unsupported_media , media_type
416: requested_range_not_satisfiable , requested_range , range_not_satisfiable
417: expectation_failed
418: im_a_teapot , teapot , i_am_a_teapot
421: misdirected_request
422: unprocessable_entity , unprocessable
423: locked
424: failed_dependency , dependency
425: unordered_collection , unordered
426: upgrade_required , upgrade
428: precondition_required , precondition
429: too_many_requests , too_many
431: header_fields_too_large , fields_too_large
444: no_response , none
449: retry_with , retry
450: blocked_by_windows_parental_controls , parental_controls
451: unavailable_for_legal_reasons , legal_reasons
499: client_closed_request

服务端错误状态码
500: internal_server_error , server_error , /o\ , ✗
501: not_implemented
502: bad_gateway
503: service_unavailable , unavailable
504: gateway_timeout
505: http_version_not_supported , http_version
506: variant_also_negotiates
507: insufficient_storage
509: bandwidth_limit_exceeded , bandwidth
510: not_extended
511: network_authentication_required , network_auth , network_authentication

高级用法

文件上传

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests
files = {"file": open("favicon.ico", "rb")}
r = requests.post("http://httpbin.org/post", files=files)
print(r.text)
"""
{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "data:application/octet-stream;base64,AAABAAIAEBAAAAEAIAAoBQAAJgAAACAgAAABACAAKBQAAE4FAAAoAAAA...AAAAAAAAAAA="
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "6665", 
    "Content-Type": "multipart/form-data; boundary=9feb12f84763ed10d3bc75ebaf9b468d", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.22.0"
  }, 
  "json": null, 
  "origin": "116.136.20.167, 116.136.20.167", 
  "url": "https://httpbin.org/post"
}
"""

Cookies

1
2
3
4
5
6
7
8
9
import requests
r = requests.get("https://www.baidu.com")
print(r.cookies)
for k, v in r.cookies.items():
    print(f"{k} = {v}")
"""
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
BDORZ = 27315
"""

这里我们首先调用 cookies 属性即可成功得到 Cookies,可以发现它是 RequestCookieJar 类型。然后用 items() 方法将其转化为元组组成的列表,遍历输出每一个 Cookie 的名称和值,实现 Cookie 的遍历解析

可以直接用 Cookie 来维持登录状态,下面以知乎为例来说明。首先登录知乎,将 Headers 中的 Cookie 内容复制下来

1
2
3
4
5
6
7
8
import requests
headers = {
    'Cookie': '_zap=0de86f83-1426-4513-8355-cdebb81c2fd9; _xsrf=a35831f7-0900-417a-b853-db8eb6103416; d_c0="ABBhdq2Y8A-PTv4Oc-3VLibHyPI5N-063ik=|1566624188"; capsion_ticket="2|1:0|10:1566646488|14:capsion_ticket|44:YmMxNWRmNmE1OWNkNGFhYmI4ZmI1MzQyZTFiMDk2NGI=|a900a5b25eab36a241b711f3805de40928c61e6cd0fedf36347fd3407fee0012"; z_c0="2|1:0|10:1566646490|4:z_c0|92:Mi4xV1FjNUF3QUFBQUFBRUdGMnJaandEeVlBQUFCZ0FsVk4ybTVPWGdBVEJPNk9felcaFd2RERQc2dSRWg2amNvS2VB|282e60e7437382a60b580afe923752bb618aec5f055c3308c7b3941f49d88114"; tst=r; tgw_l7_route=f2979fdd289e2265b2f12e4f4a478330',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
r = requests.get("https://www.zhihu.com", headers=headers)
print(r.text)

如果结果中包含了登录后的结果,就证明登录成功了

也可以通过 cookies 参数来设置,不过这样就需要构造 RequestsCookieJar 对象,而且需要分割一下 cookies。这相对繁琐,不过效果是相同的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import requests
cookies = '_zap=0de86f83-1426-4513-8355-cdebb81c2fd9; _xsrf=a35831f7-0900-417a-b853-db8eb6103416; d_c0="ABBhdq2Y8A-PTv4Oc-3VLibHyPI5N-063ik=|1566624188"; capsion_ticket="2|1:0|10:1566646488|14:capsion_ticket|44:YmMxNWRmNmE1OWNkNGFhYmI4ZmI1MzQyZTFiMDk2NGI=|a900a5b25eab36a241b711f3805de40928c61e6cd0fdf36347fd3407fee0012"; z_c0="2|1:0|10:1566646490|4:z_c0|92:Mi4xV1FjNUF3QUFBQUFBRUdGMnJaandEeVlBQUFCZ0FsVk4ybTVPWGdBVEJPNk9felczaFd2RERQc2dSRWg2amNvS2VB|282e60e7437382a60b580afe923752bb618aec5f055c3308c7b3941f49d88114"; tst=r; tgw_l7_route=18884ea8e9aef06cacc0556da5cb4bf1'
jar = requests.cookies.RequestsCookieJar()
headers = {
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
for cookie in cookies.split(';'):
    k, v = cookies.split('=', 1)
    jar.set(k, v)
r = requests.get("https://www.zhihu.com", headers=headers, cookies=jar)
print(r.text)

这里首先新建一个 RequestCookieJar 对象,然后将复制下来的 cookies 利用 split() 方法分割,接着利用 set() 方法设置好每个 Cookie 的 key 和 value,然后通过调用 requests 的 get() 方法并传递给 cookies 参数即可。

会话维持

在 requests 中,如果直接利用 get() 或 post() 等方法的确可以做到模拟网页的请求,但是这实际上是相当于不同的会话,也就是说相当于你用了两个浏览器打开了不同的页面

设想一个这样一个场景,第一个请求利用 post() 方法登录了某个网站,第二次想获取成功登录后的自己的个人信息,你又用了一次 get() 方法去请求个人信息页面。实际上,这相当于打开了两个浏览器,是两个完全不相关的会话,能成功获取个人信息吗?那当然不能

虽然在两次请求时设置一样的 cookies 可以解决这个问题,但是这样显得很繁琐,有更简单的解决方法

解决这个问题的主要方法就是维持同一个会话,也就是相当于打开一个新的浏览器选项卡而不是新开一个浏览器。但是还不想每次都设置 cookies,这时候就有了新的利器--Session 对象

利用它,我们可以方便地维护一个会话,而且不用担心 cookies 的问题,它会帮我们自动处理好。

1
2
3
4
5
6
7
8
9
import requests
requests.get('http://httpbin.org/cookies/set/number/123456789')
r = requests.get('http://httpbin.org/cookies')
print(r.text)
"""
{
  "cookies": {}
}
"""

这里我们请求了一个测试网址 http://httpbin.org/cookies/set/number/123456789。请求这个网址时,可以设置一个 cookie,名称叫做 number,内容是 123456789,随后又请求了 http://httpbin.org/cookies,此网址可以获取当前的 Cookies

这样并没有成功获取到设置的 Cookies

再用 Session 试试看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import requests
s = requests.Session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)
"""
{
  "cookies": {
    "number": "123456789"
  }
}
"""

成功获取!

所以,利用 Session,可以做到模拟同一个会话而不用担心 Cookies 的问题。它通常用于模拟登录成功之后再进行下一步的操作

Session 在平常用得非常广泛,可以用于模拟在一个浏览器中打开同一站点的不同页面

SSL 证书验证

requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,我们可以使用 verify 参数控制是否检查此证书。其实如果不加 verify 参数的话,默认是 True,会自动验证

可以设置 verify 参数为 False 忽略检查证书

也可以指定一个本地证书用作客户端证书,这可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组

1
2
3
import requests
response = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))
print(r.status_code)

上面的代码是演示示例,需要有 crt 和 key 文件,并且指定它们的路径。注意,本地私有证书的 key 必须是解密状态,加密状态的 key 是不支持的

代理设置

对于某些网站,在测试的时候请求几次,能正常获取内容。但是一旦开始大规模爬取,对于大规模且频繁的请求,网站可能会弹出验证码,或者跳转到登录验证页面,更甚者可能会直接封禁客户端的 IP,导致一定时间段内无法访问

那么,为了防止这种情况发生,我们需要设置代理来解决这个问题,这就需要用到 proxies 参数。可以用这样的方式设置

1
2
3
4
5
6
import requests
proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080"
}
requests.get("https://www.taobaocom", proxies=proxies)

当然,直接运行这个示例可能不行,因为这个代理可能是无效的,请换成自己的有效代理试验一下

若代理需要使用 HTTP Basic Auth,可以使用类似 http://user:password@host:port 这样的语法来设置代理

1
2
3
4
5
import requests
proxies = {
  "http": "http://user:password@10.10.1.10:3128/",
}
requests.get("https://www.taobaocom", proxies=proxies)

除了基本的 HTTP 代理外,requests 还支持 SOCKS 协议的代理

1
2
3
4
5
6
import requests
proxies = {
  "http": "socks5://user:password@host:port/",
  "https": "socks5://user:password@host:port/",
}
requests.get("https://www.taobaocom", proxies=proxies)

超时设置

在本机网络状况不好或者服务器网络响应太慢甚至无响应时,我们可能会等待特别久的时间才可能收到响应,甚至到最后收不到响应而报错。为了防止服务器不能及时响应,应该设置一个超时时间,即超过了这个时间还没有得到响应,那就报错。这需要用到 timeout 参数。这个时间的计算是发出请求到服务器返回响应的时间

1
2
3
import requests
r = requests.get("https://www.taobao.com", timeout=1)
print(r.status_code)

通过这样的方式,我们可以将超时时间设置为 1 秒,如果 1 秒内没有响应,那就抛出异常。

实际上,请求分为两个阶段,即连接(connect)和读取(read)

上面设置的 timeout 将用作连接和读取这二者的 timeout 总和

如果要分别指定,就可以传入一个元组

1
r = requests.get("https://www.taobao.com", timeout=(5, 30))

如果想永久等待,可以直接将 timeout 设置为 None,或者不设置直接留空,因为默认是 None。这样的话,如果服务器还在运行,但是响应特别慢,那就慢慢等吧,它永远不会返回超时错误的。

1
r = requests.get("https://www.taobao.com", timeout=None)

或直接不加参数

1
r = requests.get("https://www.taobao.com")

身份验证

可以使用 requests 自带的身份验证功能

1
2
3
4
import requests
from requests.auth import HTTPBasicAuth
r = requests.get("http://localhost:5000", auth=HTTPBasicAuth('username', 'password'))
print(r.status_code)

如果用户名和密码正确的话,请求时就会自动验证成功,会返回 200 状态码;如果验证失败,则会返回 401 状态码

当然,如果参数都传一个 HTTPBasicAuth 类,就显得有点繁琐了,所以 requests 提供了一个更简单的写法,可以直接传入一个元组,它会默认使用 HTTPBasicAuth 这个类来验证

1
2
3
import requests
r = requests.get("http://localhost:5000", auth=('username', 'password'))
print(r.status_code)

此外,requests 还提供了其他验证方式,如 OAuth 验证,不过此时需要安装 oauth 包,安装命令如下:

pip install requests_oauthlib

使用 OAuth1 验证的方法如下

1
2
3
4
5
import requests
from requests_oauthlib import OAuth1
url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
auth = OAuth1('YOUR_APP_KEY', 'YOUR_OAUTH_TOKEN_SECRET', 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')
requests.get(url, auth=auth)

Prepared Request

在 urllib 中,我们可以将请求表示为数据结构,其中各个参数都可以通过一个 Request 对象来表示。

这在 requests 里同样可以做到,这个数据结构就叫 Prepared Request。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from requests import Request, Session
url = 'http://httpbin.org/post'
data = {
    'name': 'germey'
}
headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'
}
s = Session()
req = Request('POST', url, data=data, headers=headers)
prepared = s.prepare_request(req)
r = s.send(prepared)
print(r.text)
"""
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
  }, 
  "json": null, 
  "origin": "116.136.20.167, 116.136.20.167", 
  "url": "https://httpbin.org/post"
}
"""

这里我们引入了 Request,然后用 url、data 和 headers 参数构造了一个 Request 对象,这时需要再调用 Session 的 prepare_request() 方法将其转换为一个 Prepared Request 对象,然后调用 send() 方法发送即可

可以看到,达到了同样的 POST 请求效果

有了 Request 这个对象,就可以将请求当作独立的对象来看待,这样在进行队列调度时会非常方便

异常处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import requests
from requests.exceptions import ReadTimeout, HTTPError, RequestException
for i in range(50):
    try:
        response = requests.get('https://www.baidu.com', timeout=0.02)
        print(response.status_code)
    except ReadTimeout:
        print('timeout')
    except HTTPError:
        print('httperror')
    except RequestException:
        print('reqerror')

requests-html

https://github.com/psf/requests-html