高级用法¶
本篇文档涵盖了 Requests 的一些高级特性。
会话Session
对象¶
Session
对象允许您跨请求保持某些参数。它还保存会话实例的所有请求的cookies,并将使用urllib3的连接池。因此,如果您向同一主机发出多个请求,底层的TCP连接将被重用,这会导致性能显著提高(请参见HTTP persistent connection
)。
会话Session
对象具有主要的 Requests API 的所有方法。
我们来跨请求保存一些 cookie:
s = requests.Session()
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
会话也可用来为请求方法提供缺省数据。这是通过为会话对象的属性提供数据来实现的:
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# both 'x-test' and 'x-test2' are sent
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})
任何传递给请求方法的字典都会与已设置会话层数据合并。方法层数据覆盖会话的数据。
但请注意,即使使用会话,方法级参数也不会跨请求保持。这个例子只在第一个请求中了包含cookie,而第二个请求没有:
s = requests.Session()
r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'
如果你要手动为会话添加 cookie,可以使用 Cookie utility 函数 来操纵 Session.cookies。
会话也可以用作上下文管理器:
with requests.Session() as s:
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
这将确保会话在with
块退出后立即关闭,即使发生未处理的异常也是如此。
从字典中移除一个值:
有时你会想省略字典参数中一些会话层的键。要做到这一点,你只需简单地在方法层参数中将那个键的值设置为 None ,那个键就会被自动省略掉。
会话中包含的所有值都可以直接使用,更多细节请阅读会话 API 文档。
请求和响应对象¶
任何时候进行了类似 requests.get() 的调用,你都主要在做两件事:一,你在构建一个 Request 对象, 该对象将被发送到某个服务器请求或查询一些资源;二,一旦 requests 得到一个从服务器返回的响应就会产生一个 Response 对象该,响应对象包含服务器返回的所有信息,也包含你原来创建的 Request 对象。如下是一个简单的请求,从维基百科服务器获取一些重要信息:
>>> r = requests.get('https://en.wikipedia.org/wiki/Monty_Python')
如果我们想访问服务器返回给我们的响应头部信息,我们会这样做:
>>> r.headers
{'content-length': '56170', 'x-content-type-options': 'nosniff', 'x-cache':
'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet', 'content-encoding':
'gzip', 'age': '3080', 'content-language': 'en', 'vary': 'Accept-Encoding,Cookie',
'server': 'Apache', 'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT',
'connection': 'close', 'cache-control': 'private, s-maxage=0, max-age=0,
must-revalidate', 'date': 'Thu, 14 Jun 2012 12:59:39 GMT', 'content-type':
'text/html; charset=UTF-8', 'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128,
MISS from cp1010.eqiad.wmnet:80'}
但是,如果我们想获取发送给服务器的请求头部信息,我们可以简单地访问请求,然后访问请求的头部:
>>> r.request.headers
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}
准备的请求 (Prepared Request)¶
当你从 API 或者会话调用中收到一个 Response
对象时,request
属性其实是使用了PreparedRequest
。有时在发送请求之前,你需要对 body 或者 header (或者别的什么东西)做一些额外处理,下面演示了一个简单的做法:
from requests import Request, Session
s = Session()
req = Request('POST', url, data=data, headers=headers)
prepped = req.prepare()
# do something with prepped.body
prepped.body = 'No, I want exactly this as the body.'
# do something with prepped.headers
del prepped.headers['Content-Type']
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
上面代码中,没有对Request
对象执行任何特殊操作,只是获取PreparedRequest
对象并进行了修改。,然后把它和别的参数一起发送到requests.*
或者Session.*
。
然而,上述代码会失去 RequestsSession
对象的一些优势, 尤其 Session 级别的状态,例如 cookie 就不会被应用到你的请求上去。要获取一个带有状态的 PreparedRequest
, 请用 Session.prepare_request()
取代 Request.prepare()
的调用,如下所示:
from requests import Request, Session
s = Session()
req = Request('GET', url, data=data, headers=headers)
prepped = s.prepare_request(req)
# do something with prepped.body
prepped.body = 'Seriously, send exactly these bytes.'
# do something with prepped.headers
prepped.headers['Keep-Dead'] = 'parrot'
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
使用准备好的请求流时,请记住,它不考虑环境变量。如果使用环境变量更改请求的行为,这可能会导致问题。例如:将不考虑“请求包”中指定的自签名SSL证书。结果SSL:证书验证失败
被抛出。您可以通过将环境设置显式合并到会话中来避免此行为:
from requests import Request, Session
s = Session()
req = Request('GET', url)
prepped = s.prepare_request(req)
# Merge environment settings into session
settings = s.merge_environment_settings(prepped.url, {}, None, None, None)
resp = s.send(prepped, **settings)
print(resp.status_code)
SSL证书验证¶
和Web浏览器一样, Requests库验证HTTPS请求的SSL证书。默认情况下已启用SSL验证,如果无法验证证书,则Requests库将抛出SSLError
异常:
>>> requests.get('https://requestb.in')
requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
我在这个域上没有SSL设置,所以它抛出一个异常。太好了!不过,GitHub设置了 SSL:
>>> requests.get('https://github.com')
<Response [200]>
您可以为verify
传入 CA_BUNDLE 文件的路径,或者包含可信任 CA 证书文件的文件夹路径:
>>> requests.get('https://github.com', verify='/path/to/certfile')
或将其保存在会话中:
s = requests.Session()
s.verify = '/path/to/certfile'
注解
如果 verify 设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理。
你还可以通过 REQUESTS_CA_BUNDLE
环境变量定义可信任 CA 列表。如果未设置,将使用CURL_CA_BUNDLE
作为备用。如果将verify
设置为False,则Requests库忽将忽略SSL验证:
>>> requests.get('https://kennethreitz.org', verify=False)
<Response [200]>
注意,verify
设置为False
时,Requests库将接受服务器提供的任何tls证书,并将忽略HostNameMitches
或过期证书,这将使您的应用程序容易受到中间人(MitM)攻击。在本地开发或测试期间,将verify
设置为False
将很有用。
默认情况下verify
为True
。选项verify
仅适用于主机证书。
客户端证书¶
您还可以指定一个本地证书用作客户端证书,可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组:
>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key'))
<Response [200]>
或保存在会话中:
s = requests.Session()
s.cert = '/path/client.cert'
如果指定了错误的路径或无效的证书,您将得到一个SSLError
异常:
>>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
警告
本地证书的私钥必须为未加密。目前,Requests库不支持使用加密密钥。
CA证书¶
Requests库使用包certifi中的证书,允许用户在不更改Requests版本的情况下更新受信任的证书。
在2.16版本之前,Requests绑定了一组它信任的根CA,这些CA来自Mozilla信任存储库。对于每个Requests版本,证书仅更新一次。当没有安装certifi
时,使用老版本的Requests导致证书包非常过时。为了安全起见,我们建议经常升级certifi包!