介绍
Requests 是使用 Apache2 Licensed 许可证的 基于Python开发的HTTP 库,其在Python内置模块的基础上进行了高度的封装,从而使得Pythoner进行网络请求时,变得美好了许多,使用Requests可以轻而易举的完成浏览器可有的任何操作。
特性:
- Keep-Alive & 连接池
- 国际化域名和 URL
- 带持久 Cookie 的会话
- 浏览器式的 SSL 认证
- 自动内容解码
- 基本/摘要式的身份认证
- 优雅的 key/value Cookie
- 自动解压
- Unicode 响应体
- HTTP(S) 代理支持
- 文件分块上传
- 流下载
- 连接超时
- 分块请求
- 支持 .netrc
安装
1 | pip install requests |
使用
GET
1 | import requests |
POST
1 | # 1、基本POST实例 |
响应内容
requests模块的返回对象是一个Response对象,可以从这个对象中获取需要的信息。下面 r 代表Response对象。
- r.text 文本响应内容
- r.context 二进制响应内容
- r.json() JSON响应内容
- r.raw 原始相应内容
1 | # 文本响应内容 |
定制请求头
如果想要添加HTTP头部,只需要传递一个字典给headers参数即可。注意: 所有的 header 值必须是 string、bytestring 或者 unicode。尽管传递 unicode header 也是允许的,但不建议这样做。
1 | url = 'https://api.github.com/some/endpoint' |
响应状态码
可以通过响应状态码得知请求的结果,一般 200表示请求成功,Requests还附带一个内置的状态码查询对象 request.codes:
1 | 'http://httpbin.org/get') r = requests.get( |
响应头
1 | >>> r.headers |
Cookie
1 | 'http://example.com/some/cookie/setting/url' url = |
超时
你可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应。
1 | 'http://github.com', timeout=0.001) requests.get( |
错误与异常
遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError 异常。
如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status() 会抛出一个 HTTPError 异常。
若请求超时,则抛出一个 Timeout 异常。
若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。
所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException 。
其它请求
1 | requests.get(url, params=None, **kwargs) |
会话对象
会话对象让你能够跨请求保持某些参数。它也会在同一个 Session 实例发出的所有请求之间保持 cookie, 期间使用 urllib3 的 connection pooling 功能。所以如果你向同一主机发送多个请求,底层的 TCP 连接将会被重用,从而带来显著的性能提升。 (参见 HTTP persistent connection).
会话对象具有主要的 Requests API 的所有方法。
我们来跨请求保持一些 cookie:
1 | s = requests.Session() |
会话也可用来为请求方法提供缺省数据。这是通过为会话对象的属性提供数据来实现的:
1 | s = requests.Session() |
任何你传递给请求方法的字典都会与已设置会话层数据合并。方法层的参数覆盖会话的参数。
不过需要注意,就算使用了会话,方法级别的参数也不会被跨请求保持。下面的例子只会和第一个请求发送 cookie ,而非第二个:
1 | s = requests.Session() |
如果你要手动为会话添加 cookie,就使用 Cookie utility 函数 来操纵 Session.cookies。
会话还可以用作前后文管理器:
1 | with requests.Session() as s: |
这样就能确保 with 区块退出后会话能被关闭,即使发生了异常也一样。
从字典参数中移除一个值
有时你会想省略字典参数中一些会话层的键。要做到这一点,你只需简单地在方法层参数中将那个键的值设置为 None ,那个键就会被自动省略掉。
包含在一个会话中的所有数据你都可以直接使用。
SSL证书验证
Requests 可以为 HTTPS 请求验证 SSL 证书,就像 web 浏览器一样。SSL 验证默认是开启的,如果证书验证失败,Requests 会抛出 SSLError:
1 | >>> requests.get('https://requestb.in') |
在该域名上我没有设置 SSL,所以失败了。但 Github 设置了 SSL:
1 | >>> requests.get('https://github.com', verify=True) |
如果你将 verify 设置为 False,Requests 也能忽略对 SSL 证书的验证。
1 | >>> requests.get('https://kennethreitz.org', verify=False) |
你可以为 verify 传入 CA_BUNDLE 文件的路径,或者包含可信任 CA 证书文件的文件夹路径:
1 | >>> requests.get('https://github.com', verify='/path/to/certfile') |
或者将其保持在会话中:
1 | s = requests.Session() |
如果 verify 设为文件夹路径,文件夹必须通过 OpenSSL 提供的 c_rehash 工具处理。
你还可以通过 REQUESTS_CA_BUNDLE 环境变量定义可信任 CA 列表。
客户端证书
你也可以指定一个本地证书用作客户端证书,可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组:
1 | >>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key')) |
或者保持在会话中:
1 | s = requests.Session() |
如果你指定了一个错误路径或一个无效的证书:
1 | >>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem') |
本地证书的私有 key 必须是解密状态。目前,Requests 不支持使用加密的 key。
CA证书
Requests 默认附带了一套它信任的根证书,来自于 Mozilla trust store。然而它们在每次 Requests 更新时才会更新。这意味着如果你固定使用某一版本的 Requests,你的证书有可能已经 太旧了。
从 Requests 2.4.0 版之后,如果系统中装了 certifi 包,Requests 会试图使用它里边的 证书。这样用户就可以在不修改代码的情况下更新他们的可信任证书。
为了安全起见,我们建议你经常更新 certifi!
响应体内容工作流
默认情况下,当你进行网络请求后,响应体会立即被下载。你可以通过 stream 参数覆盖这个行为,推迟下载响应体直到访问 Response.content 属性:
1 | tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' |
此时仅有响应头被下载下来了,连接保持打开状态,因此允许我们根据条件获取内容:
1 | if int(r.headers['content-length']) < TOO_LONG: |
你可以进一步使用 Response.iter_content 和 Response.iter_lines 方法来控制工作流,或者以Response.raw 从底层 urllib3 的 urllib3.HTTPResponse <urllib3.response.HTTPResponse 读取未解码的相应体。
如果你在请求中把 stream 设为 True,Requests 无法将连接释放回连接池,除非你 消耗了所有的数据,或者调用了 Response.close。 这样会带来连接效率低下的问题。如果你发现你在使用stream=True 的同时还在部分读取请求的 body(或者完全没有读取 body),那么你就应该考虑使用 with 语句发送请求,这样可以保证请求一定会被关闭:
1 | with requests.get('http://httpbin.org/get', stream=True) as r: |
保持活动状态(持久连接)
好消息——归功于 urllib3,同一会话内的持久连接是完全自动处理的!同一会话内你发出的任何请求都会自动复用恰当的连接!
注意:只有所有的响应体数据被读取完毕连接才会被释放为连接池;所以确保将 stream 设置为False 或读取 Response 对象的 content 属性。
流式上传
Requests支持流式上传,这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,仅需为你的请求体提供一个类文件对象即可:
1 | with open('massive-body') as f: |
我们强烈建议你用二进制模式(binary mode)打开文件。这是因为 requests 可能会为你提供 header 中的 Content-Length,在这种情况下该值会被设为文件的字节数。如果你用文本模式打开文件,就可能碰到错误。
事件挂钩
Requests有一个钩子系统,你可以用来操控部分请求过程,或信号事件处理。
可用的钩子:
response: 从一个请求产生的响应
你可以通过传递一个 {hook_name: callback_function}
字典给 hooks 请求参数为每个请求分配一个钩子函数:
1 | hooks=dict(response=print_url) |
callback_function 会接受一个数据块作为它的第一个参数。
1 | def print_url(r, *args, **kwargs): |
若执行你的回调函数期间发生错误,系统会给出一个警告。
若回调函数返回一个值,默认以该值替换传进来的数据。若函数未返回任何东西,也没有什么其他的影响。
我们来在运行期间打印一些请求方法的参数:
1 | >>> requests.get('http://httpbin.org', hooks=dict(response=print_url)) |
流式请求
使用 Response.iter_lines() 你可以很方便地对流式 API (例如 Twitter 的流式 API ) 进行迭代。简单地设置 stream 为 True 便可以使用 iter_lines 对相应进行迭代:
1 | import json |
当使用 decode_unicode=True 在 Response.iter_lines() 或 Response.iter_content() 中时,你需要提供一个回退编码方式,以防服务器没有提供默认回退编码,从而导致错误:
1 | r = requests.get('http://httpbin.org/stream/20', stream=True) |
iter_lines 不保证重进入时的安全性。多次调用该方法 会导致部分收到的数据丢失。如果你要在多处调用它,就应该使用生成的迭代器对象:
1 | lines = r.iter_lines() |
代理
如果需要使用代理,你可以通过为任意请求方法提供 proxies 参数来配置单个请求:
1 | import requests |
你也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理。
1 | $ export HTTP_PROXY="http://10.10.1.10:3128" |
若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/
语法:
1 | proxies = { |
要为某个特定的连接方式或者主机设置代理,使用 scheme://hostname 作为 key, 它会针对指定的主机和连接方式进行匹配。
1 | proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'} |
注意,代理 URL 必须包含连接方式。
SOCKS
2.10.0 新版功能.
除了基本的 HTTP 代理,Request 还支持 SOCKS 协议的代理。这是一个可选功能,若要使用, 你需要安装第三方库。
你可以用 pip 获取依赖:
1 | $ pip install requests[socks] |
安装好依赖以后,使用 SOCKS 代理和使用 HTTP 代理一样简单:
1 | proxies = { |
编码方式
当你收到一个响应时,Requests 会猜测响应的编码方式,用于在你调用 Response.text 方法时对响应进行解码。Requests 首先在 HTTP 头部检测是否存在指定的编码方式,如果不存在,则会使用charade 来尝试猜测编码方式。
只有当 HTTP 头部不存在明确指定的字符集,并且 Content-Type 头部字段包含 text 值之时, Requests 才不去猜测编码方式。在这种情况下, RFC 2616 指定默认字符集必须是 ISO-8859-1 。Requests 遵从这一规范。如果你需要一种不同的编码方式,你可以手动设置 Response.encoding 属性,或使用原始的 Response.content。
超时
为防止服务器不能及时响应,大部分发至外部服务器的请求都应该带着 timeout 参数。在默认情况下,除非显式指定了 timeout 值,requests 是不会自动进行超时处理的。如果没有 timeout,你的代码可能会挂起若干分钟甚至更长时间。
连接超时指的是在你的客户端实现到远端机器端口的连接时,Request 会等待的秒数。一个很好的实践方法是把连接超时设为比 3 的倍数略大的一个数值,因为 TCP 数据包重传窗口 (TCP packet retransmission window) 的默认大小是 3。
一旦你的客户端连接到了服务器并且发送了 HTTP 请求,读取超时指的就是客户端等待服务器发送请求的时间。(特定地,它指的是客户端要等待服务器发送字节之间的时间。在 99.9% 的情况下这指的是服务器发送第一个字节之前的时间)。
如果你制订了一个单一的值作为 timeout,如下所示:
1 | r = requests.get('https://github.com', timeout=5) |
这一 timeout 值将会用作 connect 和 read 二者的 timeout。如果要分别制定,就传入一个元组:
1 | r = requests.get('https://github.com', timeout=(3.05, 27)) |
如果远端服务器很慢,你可以让 Request 永远等待,传入一个 None 作为 timeout 值,然后就冲咖啡去吧。
1 | r = requests.get('https://github.com', timeout=None) |
身份认证
本篇文档讨论如何配合 Requests 使用多种身份认证方式。
许多 web 服务都需要身份认证,并且也有多种不同的认证类型。 以下,我们会从简单到复杂概述 Requests 中可用的几种身份认证形式。
基本身份认证
许多要求身份认证的web服务都接受 HTTP Basic Auth。这是最简单的一种身份认证,并且 Requests 对这种认证方式的支持是直接开箱即可用。
以 HTTP Basic Auth 发送请求非常简单:
1 | >>> from requests.auth import HTTPBasicAuth |
事实上,HTTP Basic Auth 如此常见,Requests 就提供了一种简写的使用方式:
1 | >>> requests.get('https://api.github.com/user', auth=('user', 'pass')) |
像这样在一个元组中提供认证信息与前一个 HTTPBasicAuth 例子是完全相同的。
摘要式身份认证
另一种非常流行的 HTTP 身份认证形式是摘要式身份认证,Requests 对它的支持也是开箱即可用的:
1 | >>> from requests.auth import HTTPDigestAuth |
OAuth 1 认证
Oauth 是一种常见的 Web API 认证方式。 requests-oauthlib 库可以让 Requests 用户简单地创建 OAuth 认证的请求:
1 | >>> import requests |
关于 OAuth 工作流程的更多信息,请参见 OAuth 官方网站。 关于 requests-oauthlib 的文档和用例,请参见 GitHub 的 requests_oauthlib 代码库。
OAuth 2 与 OpenID 连接认证
requests-oauthlib 库还可以处理 OAuth 2,OAuth 2 是 OpenID 连接的基础机制。 请查看requests-oauthlib OAuth2 documentation 文档以了解 OAuth 2 的各种认证管理流程:
其他身份认证形式
Requests 的设计允许其他形式的身份认证用简易的方式插入其中。开源社区的成员时常为更复杂或不那么常用的身份认证形式编写认证处理插件。其中一些最优秀的已被收集在 Requests organization 页面中,包括:
- Kerberos
- NTLM
如果你想使用其中任何一种身份认证形式,直接去它们的 GitHub 页面,依照说明进行。
自定义身份认证
Requests 允许你使用自己指定的身份验证机制。
任何传递给请求方法的 auth 参数的可调用对象,在请求发出之前都有机会修改请求。
自定义的身份验证机制是作为 requests.auth.AuthBase 的子类来实现的,也非常容易定义。Requests 在 requests.auth 中提供了两种常见的的身份验证方案: HTTPBasicAuth 和HTTPDigestAuth 。
假设我们有一个web服务,仅在 X-Pizza 头被设置为一个密码值的情况下才会有响应。虽然这不太可能,但就以它为例好了。
1 | from requests.auth import AuthBase |
当一个身份认证模块被附加到一个请求上,在设置 request 期间就会调用该模块。因此__call__
方法必须完成使得身份认证生效的所有事情。一些身份认证形式会额外地添加钩子来提供进一步的功能。
Http Post请求四种请求体实现
Content-Type定义
要了解Content-Type首先我们要先对HTTP/1.1 协议有一定的了解。
众所周知,HTTP/1.1 规定的 HTTP 请求方法有 OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT 8种,其中 POST 一般用来向服务端提交数据。
但是可能很多人不知道的是,虽然HTTP/1.1协议规定 了POST 提交的数据必须放在消息主体(entity-body)中,但并没有规定数据必须使用什么样的编码方式。也就是说,开发者完全可以自己决定消息主体的格式。
但是数据除了请求方发送之外,还要服务端能够解析才有意义。而这个解析操作的第一步通常就是是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体的编码方式,然后再对数据进行对应的解析操作。也就是说请求头中的Content-Type字段用于规定请求体的编码格式,服务端代码需要使用它对接收到的消息主体进行解析。
Content-Type的格式种类
我们前面说了HTTP/1.1没有规定协议编码方式,但是随着协议的应用推广,已经慢慢的形成了四种最常用的编码方式,基本上形成了相应的规范,即基本固定的Content-Type取值application/x-www-form-urlencoded(默认格式)、application/json、text/xml、multipart/form-data,与默认传递的urlencoded、json格式、xml格式、文件格式一 一对应。
接下来我们会每个编码方式的应用场景及Python实现分别进行介绍。我们此次主要使用http://httpbin.org
httpbin是一个专门用来测试 HTTP 请求及响应的网站,其github开源地址是https://github.com/requests/httpbin 作者另外一个开源库就是大名鼎鼎的requests,它也是我们演示代码中重要的部分。
application/x-www-form-urlencoded格式
1 | import requests |
application/json格式
1 | import json |
text/xml数据格式
它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。典型的 XML-RPC(XML Remote Procedure Call) 请求数据是这样的:
1 | import requests |
multipart/form-data数据格式
multipart/form-data主要用于文件上传,当我们使用它时,必须让 form表单的enctype 等于 multipart/form-data。直接来看一个请求示例,主要实现了上传本地的test.txt文件:
1 | import requests |