高级用法 — 请求文档
高级用法
本文档涵盖了一些 Requests 更高级的功能。
会话对象
Session 对象允许您跨请求保留某些参数。 它还在从会话实例发出的所有请求中保留 cookie,并将使用 urllib3
的 连接池 。 因此,如果您向同一主机发出多个请求,底层 TCP 连接将被重用,这可能会导致性能显着提升(请参阅 HTTP 持久连接 )。
Session 对象具有主请求 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"}}'
会话还可用于向请求方法提供默认数据。 这是通过向 Session 对象上的属性提供数据来完成的:
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 实用程序函数 来操作 Session.cookies。
会话也可以用作上下文管理器:
with requests.Session() as s:
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
这将确保在退出 with
块后立即关闭会话,即使发生未处理的异常也是如此。
从字典参数中删除一个值
有时您会希望从 dict 参数中省略会话级键。 为此,您只需在方法级参数中将该键的值设置为 None
。 它会自动被省略。
您可以直接使用会话中包含的所有值。 请参阅 会话 API 文档 以了解更多信息。
请求和响应对象
每当给 requests.get()
和朋友打电话时,您都在做两件大事。 首先,您正在构建一个 Request
对象,该对象将被发送到服务器以请求或查询某些资源。 其次,一旦 Requests 从服务器得到响应,就会生成一个 Response
对象。 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'}
准备好的请求
每当您从 API 调用或会话调用收到 Response 对象时,request
属性实际上是使用的 PreparedRequest
。 在某些情况下,您可能希望在发送请求之前对正文或标题(或其他任何内容)做一些额外的工作。 简单的配方如下:
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.*
。
但是,上面的代码将失去拥有 Requests Session 对象的一些优势。 特别是,Session 级别的状态(例如 cookie)不会应用于您的请求。 要获得应用该状态的 PreparedRequest,请将 Request.prepare() 的调用替换为对 Session.prepare_request() 的调用,如下所示:
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)
当您使用准备好的请求流时,请记住它没有考虑环境。 如果您使用环境变量来更改请求的行为,这可能会导致问题。 例如:在 REQUESTS_CA_BUNDLE
中指定的自签名 SSL 证书将不被考虑。 结果是 SSL: CERTIFICATE_VERIFY_FAILED
被抛出。 您可以通过将环境设置显式合并到会话中来解决此行为:
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 证书验证
Requests 验证 HTTPS 请求的 SSL 证书,就像 Web 浏览器一样。 默认情况下,启用 SSL 验证,如果无法验证证书,请求将抛出 SSLError:
>>> requests.get('https://requestb.in')
requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
我在这个域上没有 SSL 设置,所以它抛出一个异常。 优秀。 GitHub 确实如此:
>>> 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
实用程序处理该目录。
这个受信任的 CA 列表也可以通过 REQUESTS_CA_BUNDLE
环境变量指定。 如果未设置 REQUESTS_CA_BUNDLE
,则 CURL_CA_BUNDLE
将用作后备。
如果您将 verify
设置为 False,请求也可以忽略验证 SSL 证书:
>>> requests.get('https://kennethreitz.org', verify=False)
<Response [200]>
请注意,当 verify
设置为 False
时,请求将接受服务器提供的任何 TLS 证书,并将忽略主机名不匹配和/或过期的证书,这将使您的应用程序容易受到人为攻击中间人 (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 中的证书。 这允许用户在不更改请求版本的情况下更新他们的可信证书。
在 2.16 版之前,Requests 捆绑了一组它信任的根 CA,这些根 CA 来自 Mozilla 信任库 。 对于每个请求版本,证书仅更新一次。 如果未安装 certifi
,则在使用明显较旧版本的 Requests 时,这会导致非常过时的证书包。
为了安全起见,我们建议经常升级证书!
正文内容工作流程
默认情况下,当您发出请求时,会立即下载响应正文。 您可以覆盖此行为并推迟下载响应正文,直到您使用 stream
参数访问 Response.content 属性:
tarball_url = 'https://github.com/psf/requests/tarball/master'
r = requests.get(tarball_url, stream=True)
此时只下载了响应头并且连接保持打开状态,因此允许我们使内容检索有条件:
if int(r.headers['content-length']) < TOO_LONG:
content = r.content
...
您可以使用 Response.iter_content() 和 Response.iter_lines() 方法进一步控制工作流程。 或者,您可以从 Response.raw 处的底层 urllib3 urllib3.HTTPResponse
读取未解码的正文。
如果在发出请求时将 stream
设置为 True
,则请求无法将连接释放回池,除非您消耗所有数据或调用 Response.close。 这可能会导致连接效率低下。 如果您在使用 stream=True
时发现自己部分读取了请求正文(或根本不读取它们),则应在 with
语句中发出请求以确保它始终关闭:
with requests.get('https://httpbin.org/get', stream=True) as r:
# Do things with the response here.
活着
好消息——多亏了 urllib3,保持活动在一个会话中是 100% 自动的! 您在会话中发出的任何请求都将自动重用适当的连接!
请注意,只有在读取所有主体数据后,连接才会释放回池以供重用; 请务必将 stream
设置为 False
或读取 Response
对象的 content
属性。
流式上传
请求支持流式上传,这允许您发送大流或文件而无需将它们读入内存。 要流式传输和上传,只需为您的身体提供一个类似文件的对象:
with open('massive-body', 'rb') as f:
requests.post('http://some.url/streamed', data=f)
警告
强烈建议您以 二进制模式 打开文件。 这是因为 Requests 可能会尝试为您提供 Content-Length
标头,如果这样做,此值将设置为文件中 字节 的数量。 如果以 文本模式 打开文件,可能会出现错误。
块编码请求
请求还支持传出和传入请求的分块传输编码。 要发送块编码请求,只需为您的主体提供一个生成器(或任何没有长度的迭代器):
def gen():
yield 'hi'
yield 'there'
requests.post('http://some.url/chunked', data=gen())
对于分块编码的响应,最好使用 Response.iter_content() 迭代数据。 在理想情况下,您将在请求上设置 stream=True
,在这种情况下,您可以通过使用 [X160X 的 chunk_size
参数调用 iter_content
来逐块迭代]。 如果要设置块的最大大小,可以将 chunk_size
参数设置为任意整数。
POST 多个多部分编码文件
您可以在一个请求中发送多个文件。 例如,假设您要将图像文件上传到具有多个文件字段“图像”的 HTML 表单:
<input type="file" name="images" multiple="true" required="true"/>
为此,只需将文件设置为 (form_field_name, file_info)
元组列表:
>>> url = 'https://httpbin.org/post'
>>> multiple_files = [
... ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
... ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
>>> r = requests.post(url, files=multiple_files)
>>> r.text
{
...
'files': {'images': 'data:image/png;base64,iVBORw ....'}
'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
...
}
警告
强烈建议您以 二进制模式 打开文件。 这是因为 Requests 可能会尝试为您提供 Content-Length
标头,如果这样做,此值将设置为文件中 字节 的数量。 如果以 文本模式 打开文件,可能会出现错误。
事件挂钩
Requests 有一个钩子系统,您可以使用它来操纵请求过程的某些部分或信号事件处理。
可用挂钩:
response
:- 从请求生成的响应。
您可以通过将 {hook_name: callback_function}
字典传递给 hooks
请求参数,为每个请求分配一个钩子函数:
hooks={'response': print_url}
callback_function
将接收一个数据块作为它的第一个参数。
def print_url(r, *args, **kwargs):
print(r.url)
您的回调函数必须处理自己的异常。 任何未处理的异常都不会静默传递,因此应由调用请求的代码处理。
如果回调函数有返回值,则假定是替换传入的数据。 如果该函数不返回任何内容,则不会影响其他任何内容。
def record_hook(r, *args, **kwargs):
r.hook_called = True
return r
让我们在运行时打印一些请求方法参数:
>>> requests.get('https://httpbin.org/', hooks={'response': print_url})
https://httpbin.org/
<Response [200]>
您可以向单个请求添加多个挂钩。 让我们一次调用两个钩子:
>>> r = requests.get('https://httpbin.org/', hooks={'response': [print_url, record_hook]})
>>> r.hook_called
True
您还可以向 Session
实例添加钩子。 您添加的任何钩子都将在对会话发出的每个请求中被调用。 例如:
>>> s = requests.Session()
>>> s.hooks['response'].append(print_url)
>>> s.get('https://httpbin.org/')
https://httpbin.org/
<Response [200]>
一个 Session
可以有多个钩子,它们会按照添加的顺序被调用。
自定义身份验证
Requests 允许您指定自己的身份验证机制。
任何作为 auth
参数传递给请求方法的可调用对象都将有机会在分派请求之前修改请求。
身份验证实现是 AuthBase 的子类,并且易于定义。 Requests 在 requests.auth
中提供了两种常见的认证方案实现:HTTPBasicAuth 和 HTTPDigestAuth。
让我们假设我们有一个 Web 服务,该服务仅在 X-Pizza
标头设置为密码值时才会响应。 不太可能,但就随它去吧。
from requests.auth import AuthBase
class PizzaAuth(AuthBase):
"""Attaches HTTP Pizza Authentication to the given Request object."""
def __init__(self, username):
# setup any auth-related data here
self.username = username
def __call__(self, r):
# modify and return the request
r.headers['X-Pizza'] = self.username
return r
然后,我们可以使用我们的 Pizza Auth 发出请求:
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>
流式请求
使用 Response.iter_lines(),您可以轻松地迭代流式 API,例如 Twitter Streaming API。 只需将 stream
设置为 True
并使用 iter_lines 迭代响应:
import json
import requests
r = requests.get('https://httpbin.org/stream/20', stream=True)
for line in r.iter_lines():
# filter out keep-alive new lines
if line:
decoded_line = line.decode('utf-8')
print(json.loads(decoded_line))
将 decode_unicode=True 与 Response.iter_lines() 或 Response.iter_content() 一起使用时,您需要在服务器的事件中提供后备编码不提供一个:
r = requests.get('https://httpbin.org/stream/20', stream=True)
if r.encoding is None:
r.encoding = 'utf-8'
for line in r.iter_lines(decode_unicode=True):
if line:
print(json.loads(line))
警告
iter_lines 不是可重入安全的。 多次调用此方法会导致部分接收到的数据丢失。 如果您需要从多个地方调用它,请改用生成的迭代器对象:
lines = r.iter_lines()
# Save the first line for later or just skip it
first_line = next(lines)
for line in lines:
print(line)
代理
如果您需要使用代理,您可以使用 proxies
参数为任何请求方法配置单个请求:
import requests
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)
或者,您可以为整个 Session 配置一次:
import requests
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
session = requests.Session()
session.proxies.update(proxies)
session.get('http://example.org')
如上所示,当 python 中的代理配置未被覆盖时,默认情况下 Requests 依赖于由标准环境变量 http_proxy
、https_proxy
、no_proxy
和 curl_ca_bundle
。 还支持这些变量的大写变体。 因此,您可以设置它们来配置请求(仅设置与您的需求相关的那些):
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get('http://example.org')
要对代理使用 HTTP 基本身份验证,请在上述任何配置条目中使用 http://user:password@host/ 语法:
$ export HTTPS_PROXY="http://user:pass@10.10.1.10:1080"
$ python
>>> proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}
警告
将敏感的用户名和密码信息存储在环境变量或受版本控制的文件中存在安全风险,因此强烈不鼓励这样做。
要为特定方案和主机提供代理,请使用 scheme://hostname 表单作为密钥。 这将匹配对给定方案和确切主机名的任何请求。
proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
请注意,代理 URL 必须包含方案。
最后,请注意,为 https 连接使用代理通常需要您的本地计算机信任代理的根证书。 默认情况下,请求信任的证书列表可以通过以下方式找到:
from requests.utils import DEFAULT_CA_BUNDLE_PATH
print(DEFAULT_CA_BUNDLE_PATH)
您可以通过将标准 curl_ca_bundle
环境变量设置为另一个文件路径来覆盖此默认证书包:
$ export curl_ca_bundle="/usr/local/myproxy_info/cacert.pem"
$ export https_proxy="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get('https://example.org')
袜子
2.10.0 版中的新功能。
除了基本的 HTTP 代理之外,Requests 还支持使用 SOCKS 协议的代理。 这是一项可选功能,需要在使用前安装其他第三方库。
您可以从 pip
获取此功能的依赖项:
$ python -m pip install requests[socks]
一旦安装了这些依赖项,使用 SOCKS 代理就像使用 HTTP 代理一样简单:
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}
使用方案 socks5
会导致 DNS 解析发生在客户端上,而不是在代理服务器上。 这与 curl 是一致的,它使用该方案来决定是在客户端还是代理上进行 DNS 解析。 如果要解析代理服务器上的域,请使用 socks5h
作为方案。
合规
Requests 旨在符合所有相关规范和 RFC,而该合规性不会给用户带来困难。 这种对规范的关注可能会导致一些对不熟悉相关规范的人来说可能看起来不寻常的行为。
编码
当您收到响应时,Requests 会在您访问 Response.text 属性时猜测用于解码响应的编码。 请求将首先检查 HTTP 标头中的编码,如果不存在,将使用 charset_normalizer 或 chardet 尝试猜测编码。
如果安装了 chardet
,则 requests
使用它,但是对于 python3 chardet
不再是强制依赖项。 chardet
库是 LGPL 许可的依赖项,请求的某些用户不能依赖强制 LGPL 许可的依赖项。
当您安装 request
而不额外指定 [use_chardet_on_py3]]
,并且 chardet
尚未安装时,requests
使用 charset-normalizer
(MIT 许可)猜测编码。 对于 Python 2,requests
仅使用 chardet
并且是那里的强制依赖项。
请求不会猜测编码的唯一时间是如果 HTTP 标头 和 中不存在显式字符集,则 Content-Type
标头包含 text
。 在这种情况下,RFC 2616 指定默认字符集必须是 ISO-8859-1
。 在这种情况下,请求遵循规范。 如果您需要不同的编码,您可以手动设置 Response.encoding 属性,或使用原始的 Response.content。
HTTP 动词
Requests 提供对几乎所有 HTTP 动词的访问:GET、OPTIONS、HEAD、POST、PUT、PATCH 和 DELETE。 下面提供了使用 GitHub API 在请求中使用这些不同动词的详细示例。
我们将从最常用的动词开始:GET。 HTTP GET 是一种幂等方法,它从给定的 URL 返回资源。 因此,它是您在尝试从 Web 位置检索数据时应该使用的动词。 一个示例用法是尝试从 GitHub 获取有关特定提交的信息。 假设我们希望在请求上提交 a050faf
。 我们会像这样得到它:
>>> import requests
>>> r = requests.get('https://api.github.com/repos/psf/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我们应该确认 GitHub 响应正确。 如果有,我们想弄清楚它是什么类型的内容。 这样做:
>>> if r.status_code == requests.codes.ok:
... print(r.headers['content-type'])
...
application/json; charset=utf-8
因此,GitHub 返回 JSON。 太好了,我们可以使用 r.json 方法将其解析为 Python 对象。
>>> commit_data = r.json()
>>> print(commit_data.keys())
['committer', 'author', 'url', 'tree', 'sha', 'parents', 'message']
>>> print(commit_data['committer'])
{'date': '2012-05-10T11:10:50-07:00', 'email': 'me@kennethreitz.com', 'name': 'Kenneth Reitz'}
>>> print(commit_data['message'])
makin' history
到此为止,就这么简单。 好吧,让我们稍微研究一下 GitHub API。 现在,我们可以查看文档,但如果我们改用 Requests 可能会更有趣。 我们可以利用 Requests OPTIONS 动词来查看我们刚刚使用的 url 支持哪些类型的 HTTP 方法。
>>> verbs = requests.options(r.url)
>>> verbs.status_code
500
呃,什么? 那是没有帮助的! 事实证明,GitHub 与许多 API 提供商一样,实际上并未实现 OPTIONS 方法。 这是一个令人讨厌的疏忽,但没关系,我们可以使用无聊的文档。 但是,如果 GitHub 正确实现了 OPTIONS,则它们应该在标头中返回允许的方法,例如
>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS
转到文档,我们看到允许提交的唯一其他方法是 POST,它创建一个新的提交。 当我们使用 Requests 存储库时,我们可能应该避免向它发送笨拙的 POSTS。 相反,让我们使用 GitHub 的问题功能。
添加此文档是为了响应 问题 #482。 鉴于此问题已经存在,我们将以此为例。 让我们从获取它开始。
>>> r = requests.get('https://api.github.com/repos/psf/requests/issues/482')
>>> r.status_code
200
>>> issue = json.loads(r.text)
>>> print(issue['title'])
Feature any http verb in docs
>>> print(issue['comments'])
3
酷,我们有三个评论。 让我们来看看他们中的最后一个。
>>> r = requests.get(r.url + '/comments')
>>> r.status_code
200
>>> comments = r.json()
>>> print(comments[0].keys())
['body', 'url', 'created_at', 'updated_at', 'user', 'id']
>>> print(comments[2]['body'])
Probably in the "advanced" section
嗯,这似乎是一个愚蠢的地方。 让我们发表评论告诉海报他很傻。 海报到底是谁?
>>> print(comments[2]['user']['login'])
kennethreitz
好的,让我们告诉这个 Kenneth 家伙,我们认为这个例子应该放在快速入门指南中。 根据 GitHub API 文档,执行此操作的方法是 POST 到线程。 我们开始做吧。
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/psf/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
404
咦,这很奇怪。 我们可能需要进行身份验证。 那会很痛苦吧? 错误的。 Requests 可以轻松使用多种形式的身份验证,包括非常常见的基本身份验证。
>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password')
>>> r = requests.post(url=url, data=body, auth=auth)
>>> r.status_code
201
>>> content = r.json()
>>> print(content['body'])
Sounds great! I'll get right on it.
杰出的。 哦,等等,不! 我想补充一点,这需要我一段时间,因为我必须去喂我的猫。 要是我能编辑这条评论就好了! 令人高兴的是,GitHub 允许我们使用另一个 HTTP 动词 PATCH 来编辑此评论。 让我们这样做。
>>> print(content[u"id"])
5804413
>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."})
>>> url = u"https://api.github.com/repos/psf/requests/issues/comments/5804413"
>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200
优秀。 现在,为了折磨这个肯尼斯家伙,我决定让他出汗而不告诉他我正在做这件事。 这意味着我想删除此评论。 GitHub 允许我们使用非常恰当的 DELETE 方法删除评论。 让我们摆脱它。
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
优秀。 全没了。 我想知道的最后一件事是我使用了多少速率限制。 让我们来了解一下。 GitHub 在标题中发送该信息,因此我不会下载整个页面,而是发送一个 HEAD 请求来获取标题。
>>> r = requests.head(url=url, auth=auth)
>>> print(r.headers)
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...
优秀。 是时候编写一个以各种令人兴奋的方式滥用 GitHub API 的 Python 程序了,超过 4995 次。
自定义动词
有时,您可能正在使用一个服务器,无论出于何种原因,该服务器允许使用甚至需要使用上面未涵盖的 HTTP 动词。 一个例子是某些 WEBDAV 服务器使用的 MKCOL 方法。 不要担心,这些仍然可以与请求一起使用。 这些使用内置的 .request
方法。 例如:
>>> r = requests.request('MKCOL', url, data=data)
>>> r.status_code
200 # Assuming your call was correct
利用它,您可以使用服务器允许的任何方法动词。
链接头
许多 HTTP API 都具有链接标头。 它们使 API 更具自我描述性和可发现性。
GitHub 在其 API 中将这些用于 分页 ,例如:
>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10'
>>> r = requests.head(url=url)
>>> r.headers['link']
'<https://api.github.com/users/kennethreitz/repos?page=2&per_page=10>; rel="next", <https://api.github.com/users/kennethreitz/repos?page=6&per_page=10>; rel="last"'
请求将自动解析这些链接头并使其易于使用:
>>> r.links["next"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'}
>>> r.links["last"]
{'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
传输适配器
从 v1.0.0 开始,Requests 已转向模块化内部设计。 这样做的部分原因是为了实现传输适配器,最初 描述在这里 。 传输适配器提供了一种机制来定义 HTTP 服务的交互方法。 特别是,它们允许您应用每个服务的配置。
请求附带一个传输适配器,HTTPAdapter。 此适配器使用强大的 urllib3 库提供与 HTTP 和 HTTPS 的默认请求交互。 每当请求 Session 被初始化时,其中一个附加到 HTTP 的 Session 对象,一个附加到 HTTPS。
请求使用户能够创建和使用他们自己的提供特定功能的传输适配器。 创建后,传输适配器可以挂载到会话对象,以及它应该应用于哪些 Web 服务的指示。
>>> s = requests.Session()
>>> s.mount('https://github.com/', MyAdapter())
mount 调用将传输适配器的特定实例注册到前缀。 挂载后,使用 URL 以给定前缀开头的会话发出的任何 HTTP 请求都将使用给定的传输适配器。
实现传输适配器的许多细节超出了本文档的范围,但请查看下一个简单 SSL 用例的示例。 除此之外,您可以查看子类化 BaseAdapter。
示例:特定 SSL 版本
请求团队做出了一个特定的选择,即使用底层库 (urllib3) 中默认的任何 SSL 版本。 通常这很好,但有时,您可能会发现自己需要连接到使用与默认版本不兼容的版本的服务端点。
为此,您可以使用传输适配器,方法是采用 HTTPAdapter 的大部分现有实现,并添加一个参数 ssl_version,该参数可以传递到 urllib3。 我们将创建一个传输适配器来指示库使用 SSLv3:
import ssl
from urllib3.poolmanager import PoolManager
from requests.adapters import HTTPAdapter
class Ssl3HttpAdapter(HTTPAdapter):
""""Transport adapter" that allows us to use SSLv3."""
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(
num_pools=connections, maxsize=maxsize,
block=block, ssl_version=ssl.PROTOCOL_SSLv3)
阻塞还是非阻塞?
使用默认传输适配器,请求不提供任何类型的非阻塞 IO。 Response.content 属性将阻塞,直到整个响应被下载。 如果您需要更多粒度,库的流功能(请参阅 Streaming Requests)允许您一次检索更少量的响应。 但是,这些调用仍然会阻塞。
如果您担心阻塞 IO 的使用,有很多项目将请求与 Python 的异步框架之一相结合。 一些很好的例子是 requests-threads、grequests、requests-futures 和 httpx。
标题排序
在不寻常的情况下,您可能希望以有序的方式提供标题。 如果您将 OrderedDict
传递给 headers
关键字参数,这将为标题提供排序。 然而,,请求使用的默认标头的顺序将是首选,这意味着如果您覆盖 headers
关键字参数中的默认标头,与其他标头相比,它们可能会出现乱序在那个关键字参数中。
如果这是有问题的,用户应该考虑通过将 Session 设置为自定义 OrderedDict
来设置 Session 对象的默认标头。 该排序将始终是首选。
超时
大多数对外部服务器的请求都应该附加超时,以防服务器没有及时响应。 默认情况下,除非明确设置超时值,否则请求不会超时。 如果没有超时,您的代码可能会挂起几分钟或更长时间。
connect 超时是请求将等待您的客户端与套接字上的远程机器(对应于 connect())调用建立连接的秒数。 将连接超时设置为略大于 3 的倍数是一个很好的做法,这是默认的 TCP 数据包重传窗口 。
一旦您的客户端连接到服务器并发送 HTTP 请求,read 超时是客户端等待服务器发送响应的秒数。 (具体来说,它是客户端将在 之间等待从服务器发送的 个字节的秒数。 在 99.9% of 的情况下,这是服务器发送第一个字节之前的时间)。
如果为超时指定单个值,如下所示:
r = requests.get('https://github.com', timeout=5)
超时值将应用于 connect
和 read
超时。 如果要单独设置值,请指定一个元组:
r = requests.get('https://github.com', timeout=(3.05, 27))
如果远程服务器非常慢,您可以通过传递 None 作为超时值然后检索一杯咖啡来告诉请求永远等待响应。
r = requests.get('https://github.com', timeout=None)