小甲鱼 发表于 2017-9-5 02:48:54

Requests 库帮助文档(小甲鱼版)


为什么小甲鱼要重新翻译该文档?
呃……你们如果看过非转基因的、纯天然植物饲养的官方中文文档应该就知道为什么了:



不是我闲得蛋疼,是我觉得这样的官方文档,你们也看不下去……

再说了,我们还有大家所喜闻乐见的鱼式冷幽默,哇咔咔{:10_264:}

好了,以下是正文:

Requests 是唯一一个比 Python 亲儿子还好用的 HTTP 库,无毒无副作用,聪明的程序员必备家居良品!

注意:

我们更提倡你使用 Python3 而不是 Python2。如果你此时还在用 Python2,那么你可能需要考虑升级你的应用程序了,因为 Python2 已经 OUT 了;如果你使用的是 Python3,恭喜,你是一枚与时俱进的程序员!
—— Kenneth Reitz
下面,先让你们见识一下 Requests 的强大:

>>> import requests
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json()
{u'private_gists': 419, u'total_private_repos': 77, ...}
实现类似功能,Python 的亲儿子要麻烦许多(注意,上面 requests 是一句代码而已):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import urllib2

gh_url = 'https://api.github.com'

req = urllib2.Request(gh_url)

password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, gh_url, 'user', 'pass')

auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)

urllib2.install_opener(opener)

handler = urllib2.urlopen(req)

print handler.getcode()
print handler.headers.getheader('content-type')

# ------
# 200
# 'application/json'
Requests 允许你发送纯粹的 HTTP/1.1 请求,无需额外的操作。也就是说不需要在 ULR 中添加额外的查询语句,或者对 POST 提交的数据进行编码。另外,Keep-alive 和 HTTP 连接池都是 100% 自动进行的。Requests 能这么牛逼,都是多亏了 urllib3 的助攻!


功能特性

Requests 完全满足当今 web 的需求:


[*]Keep-Alive & 连接池
[*]国际化域名和 URLs
[*]带 Cookie 的持久会话
[*]浏览器式的 SSL 认证
[*]内容自动解码
[*]基本/摘要式身份认证
[*]优雅的键/值 Cookie
[*]自动解压
[*]Unicode 响应体
[*]支持 HTTP(S) 代理
[*]文件分块上传
[*]流下载
[*]连接超时
[*]分块请求
[*]支持 .netrc

Requests 正式支持 Python 2.6-2.7 和 Python 3.4-3.7,并且在 PyPy 上可以很好的运行哦~


下面这些牛逼的团队正在使用我们的产品:

Twitter、Spotify、Microsoft、Amazon、Lyft、BuzzFeed、Reddit、NSA、英国皇室、Amazon、Google、Twilio、Mozilla、Heroku、PayPal、NPR、奥巴马政府、Transifex、Native Instruments、Washington Post、Twitter、SoundCloud、Kippt、Readability、以及若干不愿意公开身份的联邦政府机构都在内部偷偷地使用我们的产品。

Armin Ronacher ——
Requests 诠释了什么才是完美的 API。

Matt DeBoard ——
我要想办法把 Kenneth Reitz 的 requests 一字不漏的做成纹身。

Daniel Greenfeld ——
感谢 Kenneth Reitz 的 Requests 库,刚刚用 10 行代码就完爆了原来 1200 行意大利面代码,今天真是爽呆了!

Kenny Meyers ——
Python HTTP:疑惑与否,都去用 Requests 吧。简单、优美,并符合 Python 风格。

Requests 是 Python 所有库中最受欢迎的一个,不是我吹,每个月有 13,000,000 下载量,全世界优秀的人都在使用它,你还等什么?!

哦,对了,你可能早就听人们这么描述程序员 ——“话少钱多死得早,毫无情趣女友跑”……

但伟大的 Kenneth Reitz(Requests 的作者)却扛起厚厚的一块板砖,用实际行动生疼的砸了所有人的脸。

告诉世界:程序员才是最牛逼的潜力股!



(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)
开发哲学和协议
开发哲学

Requests 是以 PEP20(Python 之婵) 为指导思想开发的:


[*]Beautiful is better than ugly.(美丽优于丑陋)
[*]Explicit is better than implicit.(直白优于含蓄)
[*]Simple is better than complex.(简单优于复杂)
[*]Complex is better than complicated.(复杂优于繁琐)
[*]Readability counts.(可读性很重要)

如果你想为 Requests 的开发做贡献,那么需要谨记上面的箴言!


Apache2 协议

现在你找到的许多开源项目都是以 GPL 协议发布的。虽然 GPL 有它自己的一席之地,但我们不建议使用这个协议。因为一旦项目发行于 GPL 协议之后,就不能应用于任何本身没开源的商业产品中。

MIT、BSD、ISC、Apache2 协议都是优秀的替代品,它们允许你的开源软件自由地应用在私有闭源的软件中。

因此,Requests 的发布协议为 Apache2 License。


Requests 协议

Copyright 2017 Kenneth Reitz

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)
安装 Requests
这部分文档包含了 Requests 的安装过程,使用任何软件的第一步就是正确地安装它。

pipenv install requests

要安装 Requests,只需要在你的终端上运行下面这条简单的命令即可:

$ pipenv install requests
如果你的机子上还没有安装 pipenv(啧啧),那么请先安装 pipenv(pipenv 是比 pip 更高级的工具,可以简化依赖关系的管理,在终端上使用 pip install pipenv 命令即可安装);当然你也可以使用 pip 来安装……


获取源代码

Requests 正热火朝天地在不断进化中,你可以在 Github 上获取该项目的源代码。

你可以选择克隆公共仓库的源代码:

$ git clone git://github.com/requests/requests.git
或者直接下载压缩包:

$ curl -OL https://github.com/requests/requests/tarball/master
# 对于 Windows 用户来说,你可以将上述链接的“tarball”换成“zipball”,这样下载下来的就是一个 zip 压缩包。

获得代码之后,你就可以轻松的将它嵌入到你的 python 包里,或者安装到你的 site-packages:

$ cd requests
$ pip install
(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)
快速上手
已经迫不及待了吗?{:10_248:}

本页内容将指引你快速地开始使用 Requests。

让我们先从一些简单的示例开始吧!


发送请求

使用 Requests 发送网络请求非常简单。

一开始先要导入 Requests 库:

>>> import requests
然后,尝试获取某个网页。本例子中,我们尝试获取 Github 的公共时间轴:

>>> r = requests.get('https://api.github.com/events')
现在,我们有一个名为 r 的 Response 对象。我们可以从这个对象中获取所有想要的信息。

Requests 简便的 API 意味着所有 HTTP 请求类型都是显而易见的。

例如,你可以这样发送一个 HTTP POST 请求:

>>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
很漂亮,对吧?那么其他 HTTP 请求类型:PUT,DELETE,HEAD 以及 OPTIONS 又是如何的呢?

都是一样的简单:

>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
>>> r = requests.delete('http://httpbin.org/delete')
>>> r = requests.head('http://httpbin.org/get')
>>> r = requests.options('http://httpbin.org/get')
帅呆了,对吧?但这也仅仅只是 Requests 的冰山一角而已!


传递 URL 参数

我们经常需要通过 URL 的查询字符串传递某种数据,如果是手工构建 URL,那么数据是以键/值对的形式置于 URL 中的。例如,httpbin.org/get?key=val。

通过 params 关键字参数,Requests 允许你将这些参数打包为一个字符串字典的形式传递,比如你想传递 key1=value1 和 key2=value2 参数到 httpbin.org/get 中,你可以将代码写成下面这样:

>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.get("http://httpbin.org/get", params=payload)
看,URL 已经被正确地编码:

>>> print(r.url)
http://httpbin.org/get?key2=value2&key1=value1
注意,字典里值为 None 的键都不会被添加到 URL 的查询字符串里。

你还可以将一个列表作为值传入,Requests 会自动帮你展开:

>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}

>>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
http://httpbin.org/get?key1=value1&key2=value2&key2=value3

响应内容

我们可以读取服务器响应的内容。再次以 GitHub 的时间轴作为例子:

>>> import requests

>>> r = requests.get('https://api.github.com/events')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests 会自动解码来自服务器的内容,大多数 unicode 字符集都能够被自动地解码。

请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 r.text 的时候,Requests 会使用其推测出来的文本编码进行解码。你可以通过查看 r.encoding 属性来得知 Requests 使用了什么编码,还能对它进行修改:

>>> r.encoding
'utf-8'
>>> r.encoding = 'ISO-8859-1'
如果人为地改变了编码,那么每当访问 r.text,Request 都将使用 r.encoding 的新值进行代替。在某些情况下,你还可以应用一些特殊的逻辑计算来获得文本的编码。比如 HTTP 和 XML 自身就可以指定编码,如果是这样的话,你应该使用 r.content 来获取编码,然后设置 r.encoding 为相应的编码值。这样就能使用正确的编码来解析 r.text 了。

在你需要的情况下,Requests 也可以使用定制的编码。如果你创建了自己的编码,并已经使用了 codecs 模块进行注册,就可以轻松地使用这个解码器名称作为 r.encoding 的值, 然后交由 Requests 来为你处理。


二进制响应内容

你也能以字节的方式访问响应的内容,对于非文本请求:

>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests 会自动为你解码 gzip 和 deflate 传输编码(为了提高效率,很多服务器将内容进行压缩后再传输)的响应数据。

例如,从服务器返回的二进制数据中创建出一张图片,你可以使用如下代码:

>>> from PIL import Image
>>> from io import BytesIO

>>> i = Image.open(BytesIO(r.content))

JSON 响应内容

Requests 内置了一个 JSON 解码器,可以帮你处理 JSON 格式的数据:
>>> import requests

>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
如果 JSON 解码失败, r.json() 就会抛出一个异常。例如响应内容是 204(意思是无内容)的时候;或者当响应内容包含无效的 JSON 数据时,尝试访问 r.json() 就会抛出 ValueError: No JSON object could be decoded 异常。

需要注意的是,成功调用 r.json() 并不意味着就是响应成功了……因为有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 同样会被解码后返回。所以若要检查发送的请求是否成功,请使用 r.raise_for_status() 函数或者检查 r.status_code 的值。


原始响应内容

在少数情况下,你可能想要获取来自服务器的原始套接字响应信息,那么你可以通过访问 r.raw 实现。但是在你这么干之前,请先确保在初始请求中设置了 stream=True。具体你可以这么做:

>>> r = requests.get('https://api.github.com/events', stream=True)

>>> r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>

>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
然而在通常情况下,你应该以下面的模式将文本流保存到文件中:

with open(filename, 'wb') as fd:
    for chunk in r.iter_content(chunk_size=128):
      fd.write(chunk)
Response.iter_content 将会帮你处理大量由于直接使用 Response.raw 而不得不处理的内容。 当使用流下载时,我们推荐使用上面代码来获取内容。注意,请根据你的实际情况设置 chunk_size 的值。


自定义请求头

如果你想为请求添加 HTTP 头部,只需要简单地传递一个字典给 headers 参数就可以了。

例如,在前一个示例中我们没有指定 user-agent,现在我们为其加上:

>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}

>>> r = requests.get(url, headers=headers)
定制 header 的优先级低于某些特定的信息源,例如:


[*]如果在 .netrc 中设置了用户认证信息,使用 headers= 设置的授权就不会生效。而如果设置了 auth= 参数,.netrc 的设置就无效了。
[*]如果被重定向到别的主机,header 的授权就会被删除。
[*]header 代理授权会被 URL 中提供的代理身份覆盖掉。
[*]在我们能判断内容长度的情况下,header 的 Content-Length 会被改写。

此外,Requests 不会基于自定义 header 而改变自己的行为。只不过在最后的请求中,所有的 header 信息都会被传递进去。

注意:所有的 header 值必须是 string、bytestring 或者 unicode。尽管传递 unicode header 也是允许的,但我们不建议你这样做。


更复杂的 POST 请求

通常,你经常会需要发送一些编码为表单形式的数据 —— 比如一个 HTML 表单。要实现这个,只需要简单地传递一个字典给 data 参数,这样你的数据字典在发出请求时就会自动编码为表单形式:

>>> payload = {'key1': 'value1', 'key2': 'value2'}

>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print(r.text)
{
...
"form": {
    "key2": "value2",
    "key1": "value1"
},
...
}
你还可以为 data 参数传入一个元组列表。在表单中如果多个元素使用同一个 key,那么这种方式尤其有效:

>>> payload = (('key1', 'value1'), ('key1', 'value2'))
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
    "key1": [
      "value1",
      "value2"
    ]
},
...
}
很多时候你想要发送的数据并非表单形式的编码。如果你传递一个 string 而不是一个 dict,那么数据会被直接发送出去。

例如,GitHub API v3 接受编码为 JSON 的 POST/PATCH 数据:

>>> import json

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, data=json.dumps(payload))
除了可以自己对 dict 进行编码,你还可以使用 json 参数直接传递数据,它将被自动编码(Requests 2.4.2 版新增特性):

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, json=payload)

POST 多部分编码(Multipart-Encoded)的文件

Requests 使得上传多部分编码文件变得很简单:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
    "file": "<censored...binary...data>"
},
...
}
你可以显式地设置文件名,文件类型和请求头:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
    "file": "<censored...binary...data>"
},
...
}
除此之外,你还可以发送作为文件来接收的字符串:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
    "file": "some,data,to,send\\nanother,row,to,send\\n"
},
...
}
在某种情况下,你可能需要发送一个非常大的文件作为 multipart/form-data 请求,你可能希望将请求当作数据流处理。默认情况下 requests 是不支持这种操作的,但有个第三方包 requests-toolbelt 可以拓展这项需求。你可以通过阅读 toolbelt 的文档来了解具体的用法。

在一个请求中发送多个文件,请参考【高级用法】一节(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)。

警告

我们强烈建议你用二进制模式(binary mode)打开文件。因为 Requests 可能会试图为你提供 Content-Length 头,当它这样做的时候,这个值会被设为文件的字节数(bytes)。如果用文本模式(text mode)打开文件,就可能会发生错误。
aaa
响应状态码

Requests 可以检测响应的状态码:

>>> r = requests.get('http://httpbin.org/get')
>>> r.status_code
200
为方便引用,Requests 自带了一个内置的状态码查询对象:

>>> r.status_code == requests.codes.ok
True
如果发送的请求导致错误(一个 4XX 客户端错误,或者 5XX 服务器错误响应),可以通过 Response.raise_for_status() 来抛出异常:

>>> bad_r = requests.get('http://httpbin.org/status/404')
>>> bad_r.status_code
404

>>> bad_r.raise_for_status()
Traceback (most recent call last):
File "requests/models.py", line 832, in raise_for_status
    raise http_error
requests.exceptions.HTTPError: 404 Client Error
但是,由于上面的例子中 r 的 status_code 是 200 ,当我们调用 raise_for_status() 时,得到的是:

>>> r.raise_for_status()
None
一切都挺和谐的哈~


响应头

我们可以通过一个 Python 字典地形式来查看服务器响应头:

>>> r.headers
{
    'content-encoding': 'gzip',
    'transfer-encoding': 'chunked',
    'connection': 'close',
    'server': 'nginx/1.0.4',
    'x-runtime': '148ms',
    'etag': '"e1ca502697e5c9317743dc078f67693f"',
    'content-type': 'application/json'
}
这个字典比较特殊:它是仅为 HTTP 头部而生的。根据 RFC 7230 规定,HTTP 头部信息是不缺分大小写的。因此,我们可以使用任意的大小写形式来访问这些响应头字段:

>>> r.headers['Content-Type']
'application/json'

>>> r.headers.get('content-type')
'application/json'
它还有一个特殊的地方,就是服务器可以多次发送同一个 header,但每次都使用不同的值。Requests 会将它们合并,然后通过字典将映射关系表示出来,参见 RFC 7230:

接收者可以合并多个相同名称的 header 栏目,把它们合为一个“field-name: field-value”的键值对,将每个后续的栏目值依次追加到合并的栏目值中,并使用逗号隔开,这样做就不会改变信息的语义。
aaa
Cookies

如果某个响应中包含一些 Cookies,你可以快速访问它们:

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']
'example_cookie_value'
要想发送你自己的 cookies 给服务器,可以使用 cookies 参数实现:

>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='FishC')

>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "FishC"}}'
返回的 Cookies 是 RequestsCookieJar 类的对象,它的行为和字典类似,适合跨域名或跨路径使用。你还可以把 CookieJar 对象传递到 Requests 中去:

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

重定向和历史

默认情况下除了 HEAD,Requests 会自动处理所有重定向。

可以使用响应对象的 history 属性来追踪重定向。

Response.history 是一个 Response 对象的列表,这些对象是为了完成请求而创建的。这个对象列表是按照从旧到新的请求进行排序。

例如,Github 将所有的 HTTP 请求重定向到 HTTPS:

>>> r = requests.get('http://github.com')

>>> r.url
'https://github.com/'

>>> r.status_code
200

>>> r.history
[<Response >]
如果你使用的是 GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 allow_redirects 参数禁用重定向处理:

>>> r = requests.get('http://github.com', allow_redirects=False)

>>> r.status_code
301

>>> r.history
[]
如果你使用了 HEAD,你也可以启用重定向:

>>> r = requests.head('http://github.com', allow_redirects=True)

>>> r.url
'https://github.com/'

>>> r.history
[<Response >]

超时

你可以告诉 Requests 在经过以 timeout 参数设定的秒数后停止等待响应,基本上所有的生产代码都应该使用这一参数,否则你的程序可能会永远地卡在那里:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
注意:

timeout 仅对连接过程有效,与响应体的下载时间无关。因为 timeout 并不是整个下载响应的时间限制,而是如果服务器在 timeout 秒内没有应答,将会引发一个异常(更精确地说,是在 timeout 秒内没有从基础套接字上接收到任何字节的数据时)。如果没有明确填写 timeout 的时间,那么 requests 默认是没有超时限制的。
aaa
错误与异常

遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError 异常。

如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status() 会抛出一个 HTTPError 异常。

若请求超时,则抛出一个 Timeout 异常。

若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。

所有 Requests 显式抛出的异常都继承自 requests.exceptions.RequestException。

(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)
高级用法
本篇文档涵盖了 Requests 的一些高级特性。


Session 对象

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

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 ,那个键就会被自动省略掉。
包含在一个会话中的所有数据你都可以直接使用。学习更多细节请阅读 会话 API 文档。


请求和响应对象

任何时候进行了类似 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 >
你可以为 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 >
默认情况下, verify 是设置为 True,verify 选项仅应用于主机证书。


客户端证书

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

>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key'))
<Response >
或者将其保持在会话中:

s = requests.Session()
s.cert = '/path/client.cert'
如果你指定了一个错误路径或一个无效的证书,Requests 会扔给你一个 SSLError:

>>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem')
SSLError: _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
警告

本地证书的私有 key 必须是解密状态,因为当前 Requests 还不支持使用加密的 key。
aaa
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)
此时仅有响应头被下载下来,连接保持打开状态,因此允许我们根据条件获取内容:

if int(r.headers['content-length']) < TOO_LONG:
content = r.content
...
你可以进一步使用 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,在这种情况下该值会被设置为文件的字节数。如果你用文本模式打开文件,就可能会出现错误。
aaa
分块编码请求

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',
...
}
警告

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

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 >

自定义认证

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

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

自定义身份验证机制是作为 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
然后就可以使用我们的 PizzaAuth 来进行网络请求了:

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

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)
aaa
代理

如果需要使用代理,你可以通过为任意请求方法提供 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_PROXY 和 HTTPS_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")
To use HTTP Basic Auth with your proxy, use the http://user:password@host/ syntax:

如果代理需要使用 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

Requests 2.10.0 版新增功能。

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

你可以使用 pip 安装该功能所需的依赖:

$ pip install requests
安装好依赖以后,使用 SOCKS 代理就和使用 HTTP 代理一样简单:

proxies = {
    'http': 'socks5://user:pass@host:port',
    'https': 'socks5://user:pass@host:port'
}
使用 socks5 连接方式使得 DNS 解析在客户端进行而不是代理服务器;如果你希望域名的 DNS 解析是在代理服务器上进行的,那么请使用 socks5h 连接方式代替。


合规性

为了不给你们的使用造成不必要的困难,Requests 的设计几乎符合所有相关的规范和 RFCs。

但请理解,有时候为了考虑迎合规范的需求,Requests 可能会做出一些大家看着有点奇怪的行为……


编码

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

注:关于使用 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())


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

>>> print(commit_data)
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)
Feature any http verb in docs

>>> print(issue)
3
酷,有三个评论,我们看下最后一个说的什么:

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

>>> comments = r.json()

>>> print(comments.keys())


>>> print(comments)
Probably in the "advanced" section
嗯,看起来似乎是个愚蠢的回复。现在让我们发表个评论来告诉这位评论者他到底有愚蠢……那么,这个评论者是谁呢?

>>> print(comments)
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)
Sounds great! I'll get right on it.
太棒了!噢,不!我原本是想说等我一会,因为我得去喂我的猫(没想到作者也是猫奴(>^ω^<)喵)。如果我能够编辑这条评论那就好了!幸运的是,GitHub 允许我们使用另一个 HTTP 动词 PATCH 来编辑评论。试试看吧:

>>> print(content)
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'
...
很好。是时候写个 Python 程序以各种刺激的方式滥用 GitHub 的 API 了,因为上面显示我们还可以使用 4995 次呢!


自定义动词

有时候你会碰到一些服务器,基于某些原因,它们或者要求用户使用上述 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 创建子类。


案例:指定 SSL 版本

Requests 开发团队刻意指定了内部库(urllib3)的默认 SSL 版本。一般情况下这样做没有问题,不过有时候你可能会需要连接到一个服务节点,而该节点使用了和默认不同的 SSL 版本。

你可以使用传输适配器解决这个问题,通过利用 HTTPAdapter 现有的大部分实现,再加上一个 ssl_version 参数并将它传递到 urllib3 中。我们会创建一个传输适配器,用来告诉 urllib3 让它使用 SSLv3:

import ssl

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager


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)

阻塞和非阻塞

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

如果你对于阻塞式 IO 有所顾虑,还有很多项目可以供你使用,它们结合了 Requests 和 Python 的某个异步框架。典型的优秀例子是 requests-threads、grequests 和 requests-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)
(TIPS:点击页面上方的目录可以跳转到其他章节哦^_^)
身份认证
这一篇我们讨论五花八门的身份认证方式在 Requests 下的使用。

许多 web 服务都需要进行身份认证,并且认证的种类也是千奇百怪的。所以,下面我们从简单到复杂给大家概述一下 Requests 中可以使用的一些身份认证方式。


基本认证

许多要求身份认证的 web 服务都接受 HTTP Basic Auth。这是最简单的一种身份认证,并且 Requests 对这种认证方式的支持直接是“开箱即用”的程度。

发送带 HTTP Basic Auth 的请求非常简单:

>>> from requests.auth import HTTPBasicAuth
>>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
<Response >
事实上,由于 HTTP Basic Auth 随处可见,所以 Requests 也就为其提供了简写的使用方式:

>>> requests.get('https://api.github.com/user', auth=('user', 'pass'))
<Response >
像这样直接在一个元组中提供认证信息与上面那个 HTTPBasicAuth 例子的效果是完全相同的。


netrc 认证

如果认证方法没有收到 auth 参数,Requests 将尝试从用户的 netrc 文件中获取需要的认证身份(netrc 文件会覆盖 headers= 中指定的原始 HTTP 认证信息)。

如果找到了域名对应的认证信息,就会以 HTTP Basic Auth 的形式发送请求。


摘要式认证

另一种非常流行的 HTTP 身份认证形式是摘要式认证,Requests 对它的支持也是“开箱即用”:

>>> from requests.auth import HTTPDigestAuth
>>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
>>> requests.get(url, auth=HTTPDigestAuth('user', 'pass'))
<Response >

OAuth 1 认证

Oauth 是一种常见的 Web APIs 认证方式。requests-oauthlib 库可以让 Requests 简单地创建 OAuth 认证的请求:

>>> import requests
>>> from requests_oauthlib import OAuth1

>>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json'
>>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET',
...               'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')

>>> requests.get(url, auth=auth)
<Response >
关于 OAuth 工作流程的更多信息,请参见 OAuth 官方网站。关于 requests-oauthlib 库的文档和用例,请参见 GitHub 的 requests_oauthlib 代码库。


OAuth 2 与 OpenID 连接认证

requests-oauthlib 库还可以处理 OAuth 2,OAuth 2 是 OpenID Cennect 的基础机制。请查看 requests-oauthlib OAuth2 文档以了解 OAuth 2 的各种认证管理流程:


[*]Web Application Flow
[*]Mobile Application Flow
[*]Legacy Application Flow
[*]Backend Application Flow


其他认证形式

Requests 的设计架构允许你将其他形式的身份认证以简易的方式插入其中。开源社区的成员时常为更复杂或不那么常用的身份认证形式编写认证处理插件。其中一些最优秀的已被收集在 Requests organization 页面中,包括:


[*]Kerberos
[*]NTLM

如果你想使用其中任何一种身份认证形式,直接去到它们的 GitHub 页面,依照说明安装使用即可。


新的认证形式

如果你还是找不到所需要的身份认证形式或者觉得现有的你不满意,你也可以自己来尝试实现它。Requests 的设计使得你非常容易就可以添加自己的身份认证模块。

要想自己来实现,就从 AuthBase 继承一个子类,并实现 __call__() 方法:

>>> import requests
>>> class MyAuth(requests.auth.AuthBase):
...   def __call__(self, r):
...         # Implement my authentication
...         return r
...
>>> url = 'http://httpbin.org/get'
>>> requests.get(url, auth=MyAuth())
<Response >
当一个身份认证模块被附加到一个请求上时,在设置 request 期间就会调用该模块。因此,__call__ 方法必须完成使得身份认证生效的所有事情。一些身份认证形式会额外地添加钩子来提供进一步的功能。

最后,你还可以在 Requests organization 页面的 auth.py 文件中找到更多示例。

—— 完 ——

不二如是 发表于 2017-9-5 07:15:10

理解Web程序之间“通信的基本协议”相当重要

因为它让我们理解了Web应用程序的内部工作.

#鱼C出品,又要造福一方百姓了{:10_275:}

alltolove 发表于 2017-9-5 07:38:25

使用这个太简单好用了

旋转风 发表于 2017-9-12 09:32:01

甲鱼出品,必属精品{:10_256:}

walter1981 发表于 2017-9-12 16:20:57

好东西呀,看一下

walter1981 发表于 2017-9-12 16:25:00

小甲鱼,有没有专门讲request的课,哪怕一节也可以。期待中

小甲鱼 发表于 2017-9-12 17:05:50

walter1981 发表于 2017-9-12 16:25
小甲鱼,有没有专门讲request的课,哪怕一节也可以。期待中

你看我都在做文档了,肯定是有的啦~~~

llxdgchk 发表于 2017-9-19 23:00:29

支持了。顶一下。。。

qwc3000 发表于 2017-9-29 16:00:39

好希望我能有50鱼币啊 ……

一叶知秋非本意 发表于 2017-10-16 19:47:23

好想学习啊 但是没鱼币 刚看了 怎么弄豆瓣爬取TOP250 但是request 模块不会弄 哎伤心

一颗青豆 发表于 2017-10-18 12:08:00

请赐我一只雄性程序猿, 会解忧会卖萌的那种///>..<///

hostmi 发表于 2017-10-19 16:11:13

一颗青豆 发表于 2017-10-18 12:08
请赐我一只雄性程序猿, 会解忧会卖萌的那种///>..

会好多好多。。。你自己慢慢发现

MiraiKuna 发表于 2017-10-20 11:11:01

{:10_266:} 开心,正在学习爬虫。使用新模块大概能节省不少繁琐的功夫?

黑白色的枫 发表于 2017-10-25 15:27:23

{:10_245:}30鱼币

Beavan 发表于 2017-10-26 10:14:09

下载来了学习学习{:10_257:}

古尔丹 发表于 2017-10-26 17:37:45

怎么下载啊

zhouzhouanker 发表于 2017-11-4 14:15:29

晕了,我是新手,哪里是终端啊,,,,,,能不能截个屏...

瞬秒爆加速 发表于 2017-11-8 01:21:23

刚学会一些BeautifulSoup,requests,就看见教程更新了

流月飞星 发表于 2017-11-8 09:21:01

{:10_245:}

eXps 发表于 2017-11-8 12:44:03

{:10_281:}小甲鱼太棒啦
页: [1] 2 3 4 5 6 7 8
查看完整版本: Requests 库帮助文档(小甲鱼版)