高级用法

本文档涵盖了一些更高级的请求功能。

会话对象

会话对象允许您在请求之间保留某些参数。它还跨会话实例发出的所有请求保持cookie,并将使用 urllib3connection pooling . 因此,如果您向同一个主机发出多个请求,则底层TCP连接将被重用,这可能导致性能显著提高(请参见 HTTP persistent connection

会话对象具有主请求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'})

传递给请求方法的任何字典都将与设置的会话级值合并。方法级参数重写会话参数。

但是请注意,方法级参数将 not 即使使用会话,也要跨请求持久化。此示例只发送带有第一个请求的cookie,而不发送带有第二个请求的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 functions 操纵 Session.cookies .

会话还可以用作上下文管理器:

with requests.Session() as s:
    s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')

这将确保会话在 with 即使发生未处理的异常,也会退出块。

从dict参数中移除值

有时,您需要从dict参数中省略会话级键。为此,只需将该键的值设置为 None 在方法级别参数中。它将自动省略。

会话中包含的所有值对您都是直接可用的。见 Session API Docs 学习更多。

请求和响应对象

无论何时调用给 requests.get() 朋友们,你们做了两件大事。首先,您正在构造 Request 对象,将发送到服务器以请求或查询某些资源。第二,A 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'}

准备好的请求

每当你收到 Response 对象来自API调用或会话调用, 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.* .

但是,上述代码将失去具有请求的一些优势。 Session 对象。特别地, Session -级别状态(如cookies)不会应用于您的请求。得到一个 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)

当您使用准备好的请求流时,请记住它不考虑环境。如果使用环境变量来更改请求的行为,这可能会导致问题。例如:中指定的自签名SSL证书 REQUESTS_CA_BUNDLE 不考虑。因此,一个 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证书验证

请求验证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的证书的CA捆绑文件或目录的路径:

>>> requests.get('https://github.com', verify='/path/to/certfile')

或持久性:

s = requests.Session()
s.verify = '/path/to/certfile'

备注

如果 verify 设置为目录的路径,则该目录必须已使用 c_rehash OpenSSL随附的实用程序。

还可以通过 REQUESTS_CA_BUNDLE 环境变量。如果 REQUESTS_CA_BUNDLE 未设置, CURL_CA_BUNDLE 将用作备用。

如果设置了 verify 假::

>>> requests.get('https://kennethreitz.org', verify=False)
<Response [200]>

注意什么时候 verify 设置为 False ,请求将接受服务器提供的任何TLS证书,并将忽略主机名不匹配和/或过期的证书,这将使应用程序易受中间人(MitM)攻击。正在将验证设置为 False 可能在本地开发或测试期间有用。

默认情况下, verify 设置为真。选择权 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

警告

本地证书的私钥 must 未加密。当前,请求不支持使用加密密钥。

CA证书

请求使用包中的证书 certifi . 这允许用户在不更改请求版本的情况下更新其可信证书。

在版本2.16之前,请求捆绑了一组它信任的根CA,源于 Mozilla trust store . 对于每个请求版本,证书只更新一次。什么时候? certifi 未安装,这导致在使用明显较旧版本的请求时出现非常过时的证书捆绑包。

为了安全起见,我们建议经常升级certifi!

正文内容工作流

默认情况下,当您发出请求时,响应的主体将立即下载。您可以覆盖此行为并推迟下载响应正文,直到您访问 Response.content 属性 stream 参数::

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() 方法。或者,您也可以从底层URLLIB3中读取未编码的主体。 urllib3.HTTPResponseResponse.raw .

如果你设置 streamTrue 在发出请求时,请求无法释放回池的连接,除非您使用所有数据或调用 Response.close . 这可能导致连接效率低下。如果您发现自己在使用时部分阅读请求正文(或根本不阅读请求正文) stream=True ,您应该在 with 确保始终关闭的语句:

with requests.get('https://httpbin.org/get', stream=True) as r:
    # Do things with the response here.

保持活力

好消息由于URLLIB3,在一个会话中保持活动是100%自动的!您在会话中提出的任何请求都将自动重用适当的连接!

请注意,只有在读取完所有主体数据后,才会将连接释放回池以便重用;请确保 streamFalse 或阅读 content 性质 Response 对象。

流式上传

请求支持流上载,这允许您发送大型流或文件而不将其读取到内存中。要传输和上传,只需为您的身体提供一个类似文件的对象:

with open('massive-body', 'rb') as f:
    requests.post('http://some.url/streamed', data=f)

警告

强烈建议您在 binary mode . 这是因为请求可能试图提供 Content-Length 头,如果它这样做,这个值将被设置为 bytes 在文件中。如果在中打开文件,可能会发生错误。 文本模式.

块编码请求

请求还支持传出和传入请求的分块传输编码。要发送一个区块编码的请求,只需为您的主体提供一个生成器(或任何没有长度的迭代器)::

def gen():
    yield 'hi'
    yield 'there'

requests.post('http://some.url/chunked', data=gen())

对于分块编码的响应,最好使用 Response.iter_content() . 在理想的情况下你会 stream=True 根据请求,在这种情况下,可以通过调用 iter_content 用一个 chunk_size 参数 None . 如果要设置块的最大大小,可以设置 chunk_size 任何整数的参数。

发布多个多部分编码文件

您可以在一个请求中发送多个文件。例如,假设要将图像文件上载到具有多个文件字段“images”的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',
  ...
}

警告

强烈建议您在 binary mode . 这是因为请求可能试图提供 Content-Length 头,如果它这样做,这个值将被设置为 bytes 在文件中。如果在中打开文件,可能会发生错误。 文本模式.

事件钩子

请求有一个钩子系统,您可以使用它来操作请求过程的一部分,或者信号事件处理。

可用挂钩:

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

A Session 可以有多个钩子,将按添加钩子的顺序调用这些钩子。

自定义身份验证

请求允许您指定自己的身份验证机制。

任何作为 auth 请求方法的参数将有机会在发送请求之前对其进行修改。

身份验证实现是 AuthBase ,并且易于定义。请求提供了两个常见的身份验证方案实现 requests.authHTTPBasicAuthHTTPDigestAuth .

Let's pretend that we have a web service that will only respond if the 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

然后,我们可以使用我们的比萨认证提出请求:

>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>

流式处理请求

Response.iter_lines() 您可以轻松地迭代流式API,如 Twitter Streaming API . 简单设置 streamTrue 并使用 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 = request.Session()
session.proxies.update(proxies)

session.get('http://example.org')

如上所述,当在python中没有覆盖代理配置时,默认情况下,请求依赖于由标准环境变量定义的代理配置。 http_proxyhttps_proxyno_proxycurl_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')

SOCKS

在 2.10.0 版本加入.

除了基本的HTTP代理之外,请求还支持使用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是一致的,curl使用该方案来决定是在客户机上还是在代理服务器上进行DNS解析。如果要解析代理服务器上的域,请使用 socks5h 作为方案。

顺从

请求旨在符合所有相关规范和RFC,在这些规范和RFC的符合性不会给用户带来困难。对规范的这种关注可能会导致一些行为,对于不熟悉相关规范的人来说,这些行为可能是不寻常的。

编码

当您收到响应时,请求会猜测在您访问 Response.text 属性。请求将首先检查HTTP头中的编码,如果不存在编码,则将使用 chardet 试图猜测编码。

只有当HTTP头中没有显式字符集时,请求才会这样做。 and 这个 Content-Type 标题包含 text . 在这种情况下, RFC 2616 指定默认字符集必须为 ISO-8859-1 . 在这种情况下,请求遵循规范。如果需要不同的编码,可以手动设置 Response.encoding 属性,或使用原始 Response.content .

HTTP动词

请求提供对几乎所有HTTP谓词的访问:get、options、head、post、put、patch和delete。下面提供了使用GitHub API在请求中使用这些不同动词的详细示例。

我们将从最常用的动词开始:get。HTTP GET是一个等幂方法,它从给定的URL返回资源。因此,当您试图从Web位置检索数据时,应该使用这个动词。一个示例用法是尝试从GitHub获取有关特定提交的信息。假设我们想要承诺 a050faf 关于请求。We would get it like so:

>>> 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-options动词来查看我们刚才使用的URL上支持哪些类型的HTTP方法。

>>> verbs = requests.options(r.url)
>>> verbs.status_code
500

呃,什么?那没用!结果发现,GitHub和许多API提供者一样,实际上并没有实现Options方法。这是一个令人恼火的疏忽,但没关系,我们可以只使用无聊的文档。但是,如果Github正确实现了选项,那么它们应该返回头中允许的方法,例如

>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS

谈到文档,我们看到提交所允许的唯一其他方法是post,它创建了一个新的提交。当我们使用请求回购时,我们可能应该避免向它发送含人邮件。相反,让我们来讨论一下GitHub的问题功能。

添加此文档是为了响应 Issue #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

好吧,让我们告诉这个肯尼斯人,我们认为这个例子应该放在快速入门指南中。根据GithubAPI文档,实现这一点的方法是发布到线程。我们来做吧。

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

啊,真奇怪。我们可能需要验证。那会很痛,对吧?错了。请求使得使用多种形式的身份验证变得容易,包括非常常见的基本身份验证。

>>> 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允许我们使用非常恰当的命名删除方法删除注释。我们把它处理掉。

>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'

杰出的。都消失了。我最不想知道的是我用了多少钱。让我们看看。Github在头文件中发送这些信息,所以我不会下载整个页面,而是发送一个头文件请求来获取头文件。

>>> r = requests.head(url=url, auth=auth)
>>> print(r.headers)
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...

杰出的。是时候编写一个以各种令人兴奋的方式滥用GithubAPI的python程序了,4995次。

自定义动词

您可能会不时地使用一个服务器,无论出于什么原因,该服务器允许使用甚至需要使用上面未介绍的HTTP谓词。其中一个例子是一些WebDAV服务器使用的mkcol方法。不要担心,这些仍然可以用于请求。它们利用了内置的 .request 方法。例如::

>>> r = requests.request('MKCOL', url, data=data)
>>> r.status_code
200 # Assuming your call was correct

利用这个,您可以使用服务器允许的任何方法动词。

传输适配器

从v1.0.0开始,请求已转移到模块化内部设计。这样做的部分原因是为了实现传输适配器,最初 described here . 传输适配器提供了一种为HTTP服务定义交互方法的机制。特别是,它们允许您应用每个服务配置。

请求随单个传输适配器一起提供, HTTPAdapter . 此适配器提供默认请求与HTTP和HTTPS的交互,使用强大的 urllib3 类库。每当一个请求 Session 已初始化,其中一个附加到 Session 对象用于HTTP,一个用于HTTPS。

请求允许用户创建和使用自己的传输适配器,这些适配器提供特定的功能。一旦创建了传输适配器,就可以将其安装到会话对象上,同时指示它应该应用于哪个Web服务。

>>> s = requests.Session()
>>> s.mount('https://github.com/', MyAdapter())

mount调用将传输适配器的特定实例注册到前缀。一旦装入,任何使用该会话的HTTP请求(其URL以给定前缀开头)都将使用给定的传输适配器。

实现传输适配器的许多细节超出了本文档的范围,但请看下一个简单的SSL用例示例。除此之外,您还可以查看子类化 BaseAdapter .

示例:特定的SSL版本

请求团队做出了一个特定的选择,使用基础库中默认的任何SSL版本。( urllib3 )通常情况下,这是可以的,但您可能会发现自己需要连接到使用与默认版本不兼容的服务端点。

为此,可以使用传输适配器,方法是获取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 在下载整个响应之前,属性将被阻止。如果您需要更大的粒度,库的流功能(请参见 流式处理请求 )允许您一次检索较小数量的响应。但是,这些调用仍将阻塞。

如果您关心阻塞IO的使用,有很多项目将请求与Python的异步框架之一相结合。一些很好的例子有 requests-threadsgrequestsrequests-futures ,以及 httpx

报头排序

在异常情况下,您可能希望以有序的方式提供报头。如果你通过 OrderedDictheaders 关键字参数,它将为头提供排序。 然而, 请求使用的默认头的顺序将是首选的,这意味着如果您重写 headers 关键字参数,与该关键字参数中的其他头相比,它们可能出现顺序错误。

如果这有问题,用户应该考虑在 Session 对象,通过设置 Session 风俗习惯 OrderedDict . 最好是订购。

超时

大多数对外部服务器的请求都应该附加一个超时,以防服务器没有及时响应。默认情况下,除非显式设置超时值,否则请求不会超时。如果没有超时,代码可能会挂起几分钟或更长时间。

这个 connect Timeout是请求等待客户端建立到远程计算机的连接的秒数(对应于 connect() )打开插座。将连接超时设置为略大于3的倍数(默认值)是一个很好的实践。 TCP packet retransmission window .

一旦客户机连接到服务器并发送了HTTP请求, read Timeout是客户端等待服务器发送响应的秒数。(具体来说,它是客户机将等待的秒数 between 从服务器发送的字节数。在99.9%的情况下,这是服务器发送第一个字节之前的时间)。

如果为超时指定单个值,如下所示:

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)