python的扩展包requests的高级用法

时间:2021-08-11 15:10:52

  Python 标准库中的 urllib2 模块提供了你所需要的大多数 HTTP 功能,但是它的 API 太渣了。它是为另一个时代、另一个互联网所创建的。它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务。

Requests 完全满足如今网络的需求。

  • 国际化域名和 URLs
  • Keep-Alive & 连接池
  • 持久的 Cookie 会话
  • 类浏览器式的 SSL 加密认证
  • 基本/摘要式的身份认证
  • 优雅的键/值 Cookies
  • 自动解压
  • Unicode 编码的响应体
  • 多段文件上传
  • 连接超时
  • 支持 .netrc
  • 适用于 Python 2.6—3.4
  • 线程安全

会话对象:

  会话就是session,session的实现是基于cookie的,所以会话对象能够跨请求保持一些参数,也可以在同一个session实例发出的所有请求之间保持cookies。

import requests
s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r=s.get('http://httpbin.org/cookies')

会话对象也可以为其你去提供缺省数据,通过会话对象的属性提哦给你数据来实现的

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'})

会话对象传入的参数会自动覆盖初始化中的默认参数。

请求与响应对象

任何时候调用requests.*()你都在做两件主要的事情。其一,你在构建一个 Request 对象, 该对象将被发送到某个服务器请求或查询一些资源。其二,一旦 requests 得到一个从 服务器返回的响应就会产生一个 Response 对象。该响应对象包含服务器返回的所有信息, 也包含你原来创建的 Request 对象。

r = requests.get('http://www.baidu.com')
r.headers

获取响应头的信息。查看http协议了解响应头的内容

下面的代码是获取请求的内容

r.request.headers

Prepared Requests

当你从一个api请求或者一个session请求接受到一个响应对象的时候,请求的参数实际上是PreparedRequest 对象使用的,如果你想在提交请求之前在请求体body或者请求头header做一些操作的时候。

from requests import Request, Session

s = Session()
req = Request('GET', url,
data=data,
headers=header
)
prepped = req.prepare() # do something with prepped.body
# do something with prepped.headers resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
) print(resp.status_code)

可以理解。Requests对象其实就是当PreparedRequest 没有被修改时直接提交的。最后的请求方式是 requests.* 或者 Session.*.

上面的代码在使用Requests的session对象时很可能会丢失一些优势,session级别的状态,比如说cookie就不会在request请求中,用Session.prepare_request()代替Request.prepare() 就可以完美的实现session级别的状态。

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
# do something with prepped.headers resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
) print(resp.status_code)

SSL证书验证

SSL证书通过在客户端浏览器Web服务器之间建立一条SSL安全通道(Secure socket layer(SSL)安全协议是由Netscape Communication公司设计开发。该安全协议主要用来提供对用户和服务器的认证;对传送的数据进行加密和隐藏;确保数据在传送中不被改变,即数据的完整性,现已成为该领域中全球化的标准。由于SSL技术已建立到所有主要的浏览器WEB服务器程序中,因此,仅需安装服务器证书就可以激活该功能了)。即通过它可以激活SSL协议,实现数据信息在客户端和服务器之间的加密传输,可以防止数据信息的泄露。保证了双方传递信息的安全性,而且用户可以通过服务器证书验证他所访问的网站是否是真实可靠。

Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。要想检查某个主机的SSL证书,你可以使用 verify 参数:

requests.get('https://kennethreitz.com', verify=True)

在该域名上我没有设置SSL,所以失败了。但Github设置了SSL:

requests.get('https://github.com', verify=True)

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

requests.get('https://kennethreitz.com', verify=False)

默认情况下, verify 是设置为True的。选项 verify 仅应用于主机证书。

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

requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key'))

如果你指定了一个错误路径或一个无效的证书:

requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem')
SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib

响应体内容工作流

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

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

此时仅有响应头被下载下来了,连接保持打开状态,因此允许我们根据条件获取内容:

if int(r.headers['content-length']) < TOO_LONG:
content = r.content
...

当讲一个request请求的stream=True时,连接connection不会被返回放入到连接池中去除非你读取完requests中所有的数据或者直接调用Requests.close()方法。这必然导致连接的效率很低,当只需要响应的正文部分(或者什么都不需要读取),可以试试contextlib.closing

from contextlib import closing

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

保持活动状态(持久连接)

好消息 - 归功于urllib3,同一会话内的持久连接是完全自动处理的!同一会话内你发出的任何请求都会自动复用恰当的连接!只有所有的响应体数据被读取完毕连接才会被释放为连接池;所以确保将 stream设置为 False 或读取 Response 对象的 content 属性。

流式上传

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

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

块编码请求

对于出去和进来的请求,Requests也支持分块传输编码。要发送一个块编码的请求,仅需为你的请求体提供一个生成器(或任意没有具体长度(without a length)的迭代器)

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

多文件同时上传

当需要在一次请求中上传多个文件的时候,如:

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

这时候可以将文件设置成一个元组列表(文件名称,文件信息)。

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

流式请求

使用 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:
print(json.loads(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_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")

若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 语法:

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

编码方式

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

只有当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中的这些动词以及Github API提供了详细示例。

我将从最常使用的动词GET开始。HTTP GET是一个幂等的方法,从给定的URL返回一个资源。因而, 当你试图从一个web位置获取数据之时,你应该使用这个动词。一个使用示例是尝试从Github上获取 关于一个特定commit的信息。假设我们想获取Requests的commit a050faf 的信息。我们可以 这样去做

import requests
r = requests.get('https://api.github.com/repos/kennethreitz/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特性

>>> r = requests.get('https://api.github.com/repos/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

使用https://api.github.com/repos/kennethreitz/requests/issues/482为例

r = requests.get('https://api.github.com/repos/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

好,我们来告诉这个叫肯尼思的家伙,这个例子应该放在快速上手指南中。根据GitHub API文档, 其方法是POST到该话题。我们来试试看

>>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"})
>>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/482/comments"
>>> r = requests.post(url=url, data=body)
>>> r.status_code
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/kennethreitz/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'

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

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

响应头链接字段

许多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'}

Blocking Or Non-Blocking:阻塞、非阻塞

使用默认传输适配器,不提供任何形式的非阻塞IO请求。响应。内容属性将阻塞,直到整个反应已经被下载。如果你需要更多的粒度,库的流特性(见流式请求)允许您检索响应的小批量。然而,这些调用仍然阻止。

如果你担心使用阻塞IO,有很多的项目,将请求与Python的一个异步性框架。两个优秀的例子是grequests和requests-futures。

Timeouts:超时

大多数请求外部服务器应该有一个超时,以防服务器没有响应及时。没有超时,那么您的代码就会挂几分钟或者更多。

连接超时的秒数请求将等待你的客户建立一个连接到一个远程计算机(对应于connect())调用套接字。它是一个很好的实践设置连接超时略大于3的倍数,这是默认的TCP数据包传输窗口。

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

如果你为超时指定一个值,如下:

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

超时的值将被应用到连接和读取超时。指定一个元组如果你想单独设置值:

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

如果远程服务器非常缓慢,你可以告诉请求永远等待响应,通过没有作为一个超时值,然后等待

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