py 第二十八天 requests官方文档:高级用法

一、Session对象

Session 对象允许你能够在跨请求的同时保持某些参数,它也会在同一个 Session 实例发出的所有请求之间保持 cookies,并且在此期间使用 urllib3 的 connection pooling(连接池)功能。所以,如果你向同一主机发送多个请求,底层的 TCP 连接将会被重用,从而带来显著的性能提升。

Session 对象拥有那些主要的 Requests API 的所有方法。

下面实验一下在跨请求中保持 cookies:

s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'

Sessions 也可用来为请求方法提供默认数据,这是通过为一个 Session 对象的属性赋值来实现的:

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# both 'x-test' and 'x-test2' are sent
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})

任何你传递给请求方法的字典都会与已经设置的会话层数据进行合并,方法层的参数将覆盖会话的参数。

不过需要注意的是,就算使用了会话,方法层的参数也不会被跨请求保持。下面的例子只会和第一个请求发送 cookies ,而非第二个:

s = requests.Session()

r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'

r = s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'

如果你要手动为会话添加 cookie,可以使用Cookie 系列函数来操纵 Session.cookies

Sessions 还可以被用作上下文管理器:

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

这样就能确保 with 区块退出后会话能被关闭,即使发生了异常也一样。
从字典参数中移除一个值

有时候你可能会想省略字典参数中一些会话层的键。要做到这一点,你只需简单地在方法层参数中将那个键的值设置为 None ,那个键就会被自动省略掉。

包含在一个会话中的所有数据你都可以直接使用。

二、请求和请求对象

任何时候进行了类似requests.get() 的调用,你其实做的就是两件事儿:第一,构建一个 Request 对象,该对象将被发送到某个服务器请求或查询一些资源;第二,一旦 Requests 得到一个从服务器返回的响应就会产生一个 Response 对象。该 Response 对象包含服务器返回的所有信息,也包含你原来创建的 Request 对象。

下面是一个简单的请求,从维基的服务器上获取一些非常重要的信息:

>>> r = requests.get('http://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'}

然而,如果获取我们发送到服务器的请求头部,可以直接访问该请求的 headers 属性

>>> r.request.headers
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}

三、预备请求

当你从 API 或者 Session 调用中收到一个 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)

然而,上述代码会失去 Requests Session对象的一些优势,尤其 Session级别的状态,例如 cookies就不会被应用到你的请求上去。要获取一个带有状态的 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)

当你使用预备请求流的时候,请记住它并不会考虑到用户环境。如果你希望通过设置环境变量来改变请求行为的时候,这可能会带来一些问题。例如:在 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, 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', verify=True)
<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 列表。

如果你将verify设置为 False,Requests 也能忽略对 SSL 证书的验证。

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

默认情况下, 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'

如果你指定了一个错误路径或一个无效的证书,Requests会扔给你一个 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

本地证书的私有 key 必须是解密状态,因为当前 Requests 还不支持使用加密的 key。

六、CA 证书

Requests 默认附带了一套它信任的根证书,来自于 Mozilla trust store。然而,它们只有在每次 Requests 更新时才会被更新。这就意味着如果你固定使用某一版本的 Requests,你的证书有可能已经 OUT 了。

从 Requests 2.4.0 版之后,如果系统中装了 certifi 包,Requests 会试图使用它里边的证书。这样用户就可以在不修改代码的情况下更新他们的可信任证书啦。为了安全起见,我们建议你经常更新 certifi!

七、响应体内容工作流

默认情况下,当你发起网络请求后响应体便会立即被下载。你可以通过 stream参数修改这个行为,推迟下载响应体直到Response.content 属性被访问为止:

tarball_url = 'https://github.com/requests/requests/tarball/master'
r = requests.get(tarball_url, stream=True)

你可以进一步使用Response.iter_content()Response.iter_lines()方法来控制工作流,或者以 Response.raw 从底层 urllib3 的urllib3.HTTPResponse中读取未解码的响应体。

如果你在请求中把 stream参数设为True,Requests 无法将连接释放回连接池,除非你消耗了所有的数据,或者调用了 Response.close()方法。这样会带来连接效率低下的问题。如果你指定stream=True的同时还在部分读取响应体(或者完全没有读取),那么你就应该考虑使用with 语句发送请求,这样可以保证请求一定会被关闭:

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

八、保持活动状态

好消息 —— 归功于 urllib3,在同一会话内的持久连接是 100% 自动处理的!同一会话中发出的任何请求都会自动复用恰当的连接!

注意:只有等到所有的响应体数据被读取完毕后,连接才能被释放回连接池以待重用;所以,请确保将 stream参数设置为 False 或读取 Response 对象的content属性。

九、流上传

Requests 支持以流的形式上传数据,该特性允许你发送尺寸巨大的数据流或文件而无需先把它们读入内存。要使用流上传,仅需为你的请求体提供一个类文件对象即可:

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

我们强烈建议你用二进制模式(binary mode)来打开文件。因为 Requests 可能会为你提供头部信息中的 Content-Length,在这种情况下该值会被设置为文件的字节数。如果你用文本模式打开文件,就可能会出现错误

十、分块编码请求

Requests 也支持分块传输编码,要发送一个分块编码请求,仅需为你的请求体提供一个生成器(或任意没有具体长度的迭代器):

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参数设为任意整数。

十一、POST 多个分块编码的文件

你可以在一个请求中发送多个文件。例如,假设你要上传多个图像文件到一个 HTML 表单中,可以使用一个叫做 "images" 的多文件 field 即可:

<input type="file" name="images" multiple="true" required="true"/>

实现它,只需要在一个包含元组的列表中指定文件,其中元组结构为 (form_field_name, file_info)

>>> url = 'http://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 有一个钩子系统,你可以用它来控制部分请求过程,或信号事件处理。

可用的钩子:
response:
从一个 Request 中产生的响应。

你可以通过传递一个 {hook_name: callback_function}字典给 hooks请求参数为每个请求分配一个钩子函数:

hooks=dict(response=print_url)

callback_function() 函数会接受一个数据块作为它的第一个参数。

def print_url(r, *args, **kwargs):
    print(r.url)

如果在执行回调函数的时候发生错误,系统会抛出一个警告。

如果回调函数返回一个值,默认以该值替换传进来的数据;如果函数并未返回任何东西,则没有什么其他影响。

我们来在运行期间打印一些请求方法的参数:

>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
http://httpbin.org
<Response [200]>

十三、自定义认证

Requests 允许你使用自定义身份验证机制。

任何传递给请求方法的 auth 参数的可调用对象,在请求发出前都有机会进行修改。

自定义身份验证机制是作为 requests.auth.AuthBase的子类来实现的,也非常容易定义。Requests 在requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuthHTTPDigestAuth

假设我们现在有一个 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

然后就可以使用我们的 PizzaAuth 来进行网络请求了:

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

十四、Requests 流

使用Response.iter_lines() 方法可以很方便地对流 API(例如 Twitter 的流 API )进行迭代。简单地设置 stream 参数为True 即可使用 iter_lines() 方法进行迭代:

import json
import requests

r = requests.get('http://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))

当在 Response.iter_lines()Response.iter_content()方法中使用 decode_unicode=True 时,你需要提供一个回退编码,以防服务器没有提供默认编码而导致错误:

r = requests.get('http://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()
# 保存第一行以供后面使用,或者直接跳过
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)

你也可以通过环境变量HTTP_PROXYHTTPS_PROXY来配置代理:

$ 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 Basic Auth,使用http://user:password@host/的语法:

proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}

要为某个特定的连接方式或者主机设置代理,可以直接将 scheme://hostname 作为 key 使用,它会针对指定的主机和连接方式进行匹配:

proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}

注意,代理 URL 中必须指明连接方式。

十六、SOCKS

除了基本的 HTTP 代理,Request 还支持 SOCKS 协议的代理。这是一个可选功能,需要安装第三方库方可使用,你可以使用 pip 安装该功能:

$ pip install requests[socks]

使用 SOCKS 代理就和使用 HTTP 代理一样简单:

proxies = {
    'http': 'socks5://user:pass@host:port',
    'https': 'socks5://user:pass@host:port'
}

使用 socks5 连接方式使得 DNS 解析在客户端进行而不是代理服务器;如果你希望域名的 DNS 解析是在代理服务器上进行的,那么请使用 socks5h 连接方式代替。

十七、编码

前面我们说过,当你收到一个响应时,Requests 会猜测响应的编码方式,用于在你调用 Response.text()方法时进行解码。Requests 首先在 HTTP 头部检测是否存在指定的编码方式,如果不存在,则会使用 chardet 来尝试猜测编码方式。

只有当 HTTP 头部中没有明确指定字符集,并且 Content-Type 头部字段包含 text 值时,Requests 才不会去猜测编码。在这种情况下,[RFC 2616] 指定默认字符集必须是 ISO-8859-1,Requests 遵从这一规范。如果你需要一种不同的编码方式,你可以手动设置 Response.encoding 属性,或使用原始的 Response.content

十八、HTTP 动词

Requests 提供了几乎所有 HTTP 动词的功能:GET、OPTIONS、HEAD、POST、PUT、PATCH 和 DELETE。下面我们通过一些详细的例子,演示在 Requests 中如何使用这些动词。

我们从最常使用的动词 GET 开始。HTTP GET 是一个幂等方法(所谓幂等方法,是指该方法多次调用返回的效果是一致的),它的作用是从给定的 URL 返回一个资源。当你试图从一个 web 位置获取数据时,你应该使用这个动词。

下面这个例子尝试从 GitHub 上获取关于一个指定的 commit。假设我们像通过 Requests 来获取 commit a050faf 的数据,可以这样做:

>>> import requests
>>> r = requests.get('https://api.github.com/repos/requests/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())
[u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']

>>> print(commit_data[u'committer'])
{u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'}

>>> print(commit_data[u'message'])
makin' history

到目前为止,一切都非常简单。现在,我们来研究一下 GitHub 的 API,我们可以翻查文档,但如果使用 Requests 来研究也许会更有意思一些。我们可以借助 Requests 的 OPTIONS动词来看看我们刚使用过的 url 支持哪些 HTTP 方法。

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

什么?这是什么鬼?!返回的信息毫无帮助嘛!原来 GitHub 与许多 API 提供方一样,实际上并没有实现 OPTIONS 方法。真是让人感到气愤!但没关系,我们还可以查找那枯燥的文档。不过话说回来,如果 GitHub 正确地实现了 OPTIONS,那么服务器应该在响应头中返回允许用户使用的 HTTP 方法,例如:

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

转而去查看文档,我们看到对于提交信息,另一个允许的方法是 POST,它会创建一个新的提交。由于我们正在使用 Requests 代码库,我们应尽可能避免对它发送笨拙的 POST。作为替代,我们来玩玩 GitHub 的 Issue 功能。

下面代码访问的是 [Issue #482]该问题已经存在,我们就以它为例讲解:

>>> r = requests.get('https://api.github.com/requests/kennethreitz/requests/issues/482')
>>> r.status_code
200

>>> issue = json.loads(r.text)

>>> print(issue[u'title'])
Feature any http verb in docs

>>> print(issue[u'comments'])
3

酷,有三个评论,我们看下最后一个说的什么:

>>> r = requests.get(r.url + u'/comments')
>>> r.status_code
200

>>> comments = r.json()

>>> print(comments[0].keys())
[u'body', u'url', u'created_at', u'updated_at', u'user', u'id']

>>> print(comments[2][u'body'])
Probably in the "advanced" section

嗯,看起来似乎是个愚蠢的回复。现在让我们发表个评论来告诉这位评论者他到底有愚蠢……那么,这个评论者是谁呢?

>>> print(comments[2][u'user'][u'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/requests/requests/issues/482/comments"

>>> r = requests.post(url=url, data=body)
>>> r.status_code
404

额,怎么返回 404 了呢?有点诡异……可能我们需要验证身份……那就有点麻烦了,对吧?不对。Requests 简化了多种身份验证形式的使用,包括非常常见的 Basic Auth。

>>> 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[u'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/requests/requests/issues/comments/5804413"

>>> r = requests.patch(url=url, data=body, auth=auth)
>>> r.status_code
200

非常棒!现在,我们来折磨一下这个叫做 Kenneth 的家伙,我决定要让他急得团团转,也不告诉他背后到底是谁在捣蛋……我的意思说时我现在想删除这条评论了……

GitHub 允许我们使用 DELETE 方法来删除评论:

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

很好,评论消失了。最后一件我想知道的事情是我已经使用了多少限额(ratelimit)。查查看,GitHub 在响应头部发送这个信息,因此不必下载整个网页,我将使用一个 HEAD 请求来获取响应头:

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

十九、自定义动词

有时候你会碰到一些服务器,基于某些原因,它们或者要求用户使用上述 HTTP 动词之外的自定义动词。比如说 WEBDAV 服务器会要求你使用 MKCOL 方法。不过别担心,Requests 一样可以搞定它们。你可以使用内建的.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"'

Requests 会自动解析这些响应头链接字段,并使得它们非常易于使用:

>>> 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 的内部采用了模块化设计。部分原因是为了实现传输适配器(Transport Adapters),你可以看看关于它的最早描述。传输适配器提供了一个机制,让你可以为 HTTP 服务定义交互方法,尤其是它允许你应用服务前的配置。

Requests 自带了一个传输适配器,也就是 HTTPAdapter。这个适配器使用了强大的 urllib3 库,为 Requests 提供了默认的 HTTP 和 HTTPS 交互。每当 Session 被初始化,就会有适配器附着在 Session 上,其中一个供 HTTP 使用,另一个供 HTTPS 使用。

Requests 允许用户创建和使用他们自己的传输适配器,实现他们需要的特殊功能。创建好以后,传输适配器可以被加载到一个会话对象上,附带着一个说明,告诉会话适配器应该应用在哪个 web 服务上。

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

这个 mount 调用会注册一个传输适配器的特定实例到一个前缀上面。加载以后,任何使用该会话的 HTTP 请求,只要其 URL 是以给定的前缀开头,该传输适配器就会被使用到。

传输适配器的众多实现细节不在本文档的覆盖范围内,不过你可以看看接下来这个简单的 SSL 案例。更多的用法,你也许该考虑为 BaseAdapter 创建子类。

二十二、阻塞和非阻塞

使用默认的传输适配器,Requests 不提供任何形式的非阻塞 IO。Response.content 属性会阻塞,直到整个响应下载完成。如果你需要更多精细控制,该库的数据流功能(见上面流请求章节) 允许你每次接受少量的一部分响应,不过这些调用依然是阻塞式的。

如果你对于阻塞式 IO 有所顾虑,还有很多项目可以供你使用,它们结合了 Requests 和 Python 的某个异步框架。典型的优秀例子是 requests-threadsgrequestsrequests-futures

二十三、Header 排序

在某些特殊情况下你也许需要按照次序来提供 headers,如果你向 headers 关键字参数传入一个 OrderedDict,就可以向提供一个带排序的 headers。然而,Requests 使用的默认 headers 的次序会被优先选择,这意味着如果你在 headers 关键字参数中覆盖了默认 headers,和关键字参数中别的 headers 相比,它们也许看上去会是次序错误的。

如果这个对你来说是个问题,那么用户应该考虑在 Session 对象上面设置默认 headers,只要将 Session 设为一个定制的 OrderedDict 即可。这样就会让它成为优选的次序。

二十四、超时

为防止服务器不能及时响应,大部分发至外部服务器的请求都应该带着 timeout 参数。在默认情况下,除非显式指定了 timeout 值,requests 是不会自动进行超时处理的。如果没有 timeout,你的代码可能会挂起若干分钟甚至更长时间。

连接超时指的是在你的客户端实现到远端机器端口的连接时(对应的是 connect()),Request 会等待的秒数。一个很好的实践方法是把连接超时设为比 3 的倍数略大的一个数值,因为 TCP 数据包重传窗口 的默认大小是 3。

一旦你的客户端连接到了服务器并且发送了 HTTP 请求,读取超时指的就是客户端等待服务器发送请求的时间(它指的是客户端要等待服务器发送字节之间的时间。在 99.9% 的情况下这指的是服务器发送第一个字节之前的时间)。

如果你指定了一个值作为 timeout,像这样:

r = requests.get('https://github.com', timeout=5)

timeout 还可以分别指定 connect 和 read 两者的超时时间,只需要传入一个元组即可:

r = requests.get('https://github.com', timeout=(3.05, 27))

如果远端服务器很慢,你可以让 Requests 一直等下去……传入一个 None 作为 timeout 值:

r = requests.get('https://github.com', timeout=None)

https://fishc.com.cn/thread-95893-1-1.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342