开发者

Python Requests库知识汇总

开发者 https://www.devze.com 2023-05-01 09:20 出处:网络 作者: 授客
目录快速开始发送请求URL传参响应内容二进制响应内容jsON响应内容原始响应内容自定义请求头更复杂的POST请求More complicated POST requests提交Multipart-Encoded文件响应状态码响应头Cookies重定向与history请求超
目录
  • 快速开始
    • 发送请求
    • URL传参
    • 响应内容
    • 二进制响应内容
    • jsON响应内容
    • 原始响应内容
    • 自定义请求头
    • 更复杂的POST请求More complicated POST requests
    • 提交Multipart-Encoded文件
    • 响应状态码
    • 响应头
    • Cookies
    • 重定向与history
    • 请求超时
    • 错误和异常
  • 高级用法
    • Session对象
    • 请求和响应对象
    • Prepared requests
    • HTTP Basic 验证
    • SSL证书验证
    • 客户端证书
    • CA证书
    • Body内容工作流
    • Keep-Alive
    • 流式上传
    • 分块编码(Chunk-Encoded)请求
    • POST 多个分部编码(Multipart-Encoded)文件
    • 事件钩子(Event Hooks)
    • 自定义身份验证
    • 流式请求
    • 代理
    • SOCKS
    • 编码
    • Link请求头
    • 传输适配器(Transport Adapters)
    • 示例: 指定SSL版本
    • 阻塞或非阻塞
    • 超时

快速开始

发送请求

>>> import requests
>>> r = requests.get('https://api.github.com/events') # GET
>>> r = requests.post('https://httpbin.org/post', data={'key': 'value'}) # POST
>>> r = requests.put('https://httpbin.org/put', data={'key': 'value'}) # PUT
>>> r = requests.delete('https://httpbin.org/delete') # DELETE
>>> r = requests.head('https://httpbin.org/get') # HEAD 
>>> r = requests.options('https://httpbin.org/get') # OPTIONS

URL传参

可以使用params字典参数为URL提供查询字符串参数,例如,访问 https://httpbin.org/get?key1=value1&key2=value2,可使用以下代码:

>>> import requests
>>> payload = {'key1': 'value1', 'key2': 'value2', 'key3':'', 'key4':None}
>>> r = requests.get('https://httpbin.org/get', params=payload)
>>> r.url
https://httpbin.org/get?key2=value2&key1=value1&key3=

需要注意的是,如果字典参数中key值(即URL参数的值为None),则该参数不会添加到URL的查询字符串中。

如果URL查询字符串中,存在重复参数(参数名称相同,参数值不同),则需要将key值设置为由参数值组成的列表,如下:

>>> import requests
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get('https://httpbin.org/get', params=payload)
>>> r.url
https://httpbin.org/get?key1=value1&key2=value2&key2=value3

响应内容

读取服务器响应内容

>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.text
<class 'str'> [{"id":"27579847062","type":"PushEvent","actor":{"...

requests 将自动解码来自服务器的内容。大多数unicode字符集都是无缝解码的。

当你发出请求时,requests会根据HTTP头对响应的编码进行有依据的猜测。当你访问r.text时,将使用requests猜测的文本编码。可以使用r.encoding属性查找请求使用的编码,并对其进行更改:

>>> r.encoding # 输出:utf-8
r.encoding = 'ISO-8859-1'

如果更改编码,则每当调用r.text时,requests都将使用新的r.encoding的值。在任何情况下,你都可以应用特殊逻辑来确定内容的编码。例如,html和XML可以在其正文中指定其编码。在这种情况下,你应该使用r.content查找编码,然后设置r.encoding。这将允许你使用具有正确编码的r.text

requests还将在需要时使用自定义编码。如果你已经创建了自己的编码并将其注册到codecs模块,则可以简单地使用codec名称作为r.encoding的值,而requests将为你处理解码。

二进制响应内容

对于非文本请求,还可以以字节的形式访问响应体(当然,文本请求也可以):

>>> r.content
b'[{"id":"27581220674","type":"IssueCommentEvent","actor":{"id":327807...

requests会自动解码gzipdeflate传输编码。

如果安装了类似 brotli 或 brotlicffi的Brotil类库,Requets也会自动界面br传输编码

如果Brotli库(如[Brotli])为您自动解码br传输编码(https://pypi.org/project/brotli)或brotliffi已安装。

例如,可以使用以下代码,从请求返回的二进制数据创建图像:

from PIL import Image
from io import BytesIO
img = Image.open(BytesIO(r.content))

JSON响应内容

可使用内置的JSON解码器,处理JSON数据:

>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.json() # JSON
[{'id': '27609416600', 'type': 'PushEvent', ... 

如果JSON解码失败,r.json()将抛出异常。例如,如果响应得到一个204(无内容),或者如果响应包含无效的JSON,则r.json()会抛出requests.exceptions.JSONDecodeError。此封android装的异常可能会因为不同python版本和JSON序列化库可能引发的多个异常提供互操作性。

需要注意的是,调用r.json()的成功调用并不表示响应的成功。一些服务器可能会在失败的响应中返回JSON对象(例如,HTTP 500的错误详细信息)。这样的JSON将被解码并返回。要检查请求是否成功,请使用r.raise_for_status()或检查r.status_code

原始响应内容

可以通过访问r.raw访问服务器返回的原始socket响应。如果希望这样做,确保在初始请求中设置 stream=True:

>>> import requests
>>> r = requests.get('https://api.github.com/events', stream=True)
>>> r.raw
<urllib3.response.HTTPResponse object at 0x0000018DB1704D30>
>>> r.raw.read(10)
b'\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将处理很多你在直接使用Resort.raw时需要处理的事情。当流式传输下载时,以上是检索内容的首选和推荐方法。请注意,chunk_size可以自由调整为更适合你使用场景的数字。

注意

关于使用 Response.iter_contentResponse.raw的重要注意事项。 Response.iter_content将自动解码gzipdeflate传输编码。Response.raw是一个原始字节流--它不会转换响应内容。如果确实需要访问返回的字节,请使用Response.raw

自定义请求头

如果您想向请求中添加HTTP头,只需向headers参数传递一个dict即可,例如:

>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = requests.get(url, headers=headers)

注意:自定义请求头的优先级低于更具体的信息源。例如:

  • 如果在.netrc中指定了凭据,则使用headers=设置的Authorization请求头将被覆盖,而凭据又将被auth=参数覆盖。请求将在~/.netrc~/_netrcNETRC环境变量指定的路径处中搜索netrc文件。
  • 如果从主机重定向,将删除Authorization请求头。
  • Proxy-Authorization请求头将被URL中提供的代理凭据覆盖。
  • 当我们可以确定内容的长度时,将覆盖Content-Length请求头。

此外,请求根本不会根据指定的自定义请求头更改其行为。请求头仅是简单的传递到最终请求中。

注意:所有请求头值必须是字符串、字节字符串或unicode。虽然允许,但建议避免传递unicode请求头值。

更复杂的POST请求More complicated POST requests

通常,如果发送一些表单编码(form-encoded)的数据--就像一个HTML表单。为此,只需将字典传递给data参数即可。发送请求时,将自动对字典数据进行表单编码:

>>> import requests
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("https://httpbin.org/post", data=payload)
>>> r.text
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "key1": "value1", 
    "key2": "value2"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "23", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.27.1", 
    "X-Amzn-Trace-Id": "Root=1-6409fe3b-0cb4118319f09ab3187402bc"
  }, 
  "json": null, 
  "origin"编程客栈: "183.62.127.25", 
  "url": "https://httpbin.org/post"
}

data参数中,为每个键可以具有多个值。这可以通过将data设置为元组列表或以列表为值的字典来实现。当表单中有多个元素使用相同的键时,这特别有用:

>>> import requests
>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
>>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
>>> payload_dict = {'key1': ['value1', 'value2']}
>>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)
>>> r1.text
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "key1": [
      "value1", 
      "value2"
    ]
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "23", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.27.1", 
    "X-Amzn-Trace-Id": "Root=1-6409ff49-11b8232a7cc81fc0290ec4c4"
  }, 
  "json": null, 
  "origin": "183.62.127.25", 
  "url": "https://httpbin.org/post"
}
>>> re.text == r2.text
True

有时,你可能想发送未经表单编码的数据,则需要传入string类型的数据,而不是dictstring数据将被直接提交。

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

>>> import requests
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload))

请注意,上述代码不会添加Content-Type请求头(特别是不会将其设置为application/json)。如果需要设置那个请求头('Content-Type': 'application/json,发送json请求体),并且不想自己对dict进行编码,你也可以直接使用json参数传递它,它将自动被编码:

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, json=payload)

注意,如果提供了data,或者file参数,json 参数将被自动忽略。

提交Multipart-Encoded文件

Request让上传Multipart编码文件变得简单:

>>> import requests
>>> url = 'https://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.text
{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "#!/usr/bin/env python\r\n# -*- coding:utf-8 -*-\r\n\r\n#!/usr/bin/env python\r\n# -*- coding:utf-8 -*-\r\n\r\nfrom multiprocessing import Pool\r\nfrom threading import Thread\r\nfrom concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor..."
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "3035", 
    "Content-Type": "multipart/form-data; boundary=9ef4437cb1e14427fcba1c42943509cb", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.27.1", 
    "X-Amzn-Trace-Id": "Root=1-640a03df-1a0a5ce972ce410378cda7a2"
  }, 
  "json": null, 
  "origin": "183.62.127.25", 
  "url": "https://httpbin.org/post"
}

可以显示的设置文件名称,内容类型,请求头:

>>> url = 'https://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
{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "data:application/vnd.ms-excel;base64,UEsDBBQAAAAAAHy8iFMAAAAAAA...=="
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "9667", 
    "Content-Type": "multipart/form-data; boundary=ff85e1018eb5232f7dcab2b2bc5ffa50", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.27.1", 
    "X-Amzn-Trace-Id": "Root=1-640def51-43cc213e33437a0e60255add"
  }, 
  "json": null, 
  "origin": "183.62.127.25", 
  "url": "https://httpbin.org/post"
}

如果想发送一些字符串,以文件的方式被接收:

>>> url = 'https://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}
>>> r = requests.post(url, files=files)
>>> r.text
{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "some,data,to,send\nanother,row,to,send\n"
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "184", 
    "Content-Type": "multipart/form-data; boundary=2bfe430e025860528e29c893a09f1198", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.27.1", 
    "X-Amzn-Trace-Id": "Root=1-640df132-247947ca699e9da35c588f2d"
  }, 
  "json": null, 
  "origin": "183.62.127.25", 
  "url": "https://httpbin.org/post"
}

如果你将一个非常大的文件作为multipart/form-data请求提交,你可能需要流式传输该请求。默认情况下,requests不支持此功能,但有一个单独的包支持此功能——requests toolbelt。阅读toolbelt文档获取有关如何使用它的详细信息。

要在一个请求中发送多个文件,请参阅高级章节。

警告

强烈建议以二进制模式打开文件。这是因为requests可能会尝试为你提供Content-Length请求头,如果这样做,该请求头值将被设置为文件中的字节数。如果以文本模式打开文件,可能会发生错误。

响应状态码

>>> import requests
>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code
200

以便于参考,requests还附带一个内置的状态代码查找对象:

>>> r = requests.get('https://httpbin.org/get')
>>> r.status_code == requests.codes.ok
True

如果请求出错4XX客户端错误或5XX服务器错误响应),我们可以使用response.raise_for_status()抛出错误:

>>> import requests
>>> bad_r = requests.get('https://httpbin.org/status/404')
>>> bad_r.status_code
404
>>> bad_r.raise_for_status()
Traceback (most recent call last):
  File "D:/codePojects/test.py", line 12, in <module>
    bad_r.raise_for_status()
  File "D:\Program Files (x86)\python36\lib\site-packages\requests\models.py", line 960, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: NOT FOUND for url: https://httpbin.org/status/404

但是,如果r.status_code 为200raise_for_status() 将返回None

>>> r.raise_for_status()
None

响应头

>>> r.headers
{
    'content-encoding': 'gzip',
    'transfer-encoding': 'chunked',
    'connection': 'close',
    'server': 'nginx/1.0.4',
    'x-runtime': '148ms',
    'etag': '"e1ca502697e5c9317743dc078f67693f"',
    'content-type': 'application/json'
}

根据RFC 7230, HTTP请求头大小写不敏感,所以,我们可以使用任何大写。因此,我们可以使用任意大小写来访问请求头:

>>> r.headers['Content-Type']
'application/json'
>>> r.headers.get('content-type')
'application/json'

Cookies

如果响应包含Cookie,可以快速访问它们:

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['example_cookie_name'] # 如果存在名为 example_cookie_name的cookie的话
'example_cookie_value'

可以使用cookies 参数将cookie发送给服务器:

>>> url = 'https://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{\n  "cookies": {\n    "cookies_are": "working"\n  }\n}\n'

Cookies are returned in a RequestsCookieJar, which acts like a dict but also offers a more complete interface, suitable for use over multiple domains or paths. Cookie jars can also be passed in to requests:

返回的Cookie存储在RequestsCookieJar中,其作用类似于dict,同时提供了一个更完整的接口,适合在多个域或路径上使用。Cookie jar也可以传递给请求:

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
Cookie(version=0, name='tasty_cookie', value='yum', port=None, port_specified=False, domain='httpbin.org', domain_specified=True, domain_initial_dot=False, path='/cookies', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
Cookie(version=0, name='gross_cookie', value='blech', port=None, port_specified=False, domain='httpbin.org', domain_specified=True, domain_initial_dot=False, path='/elsewhere', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)
>>> url = 'https://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

重定向与history

默认情况下,requests将对除HEAD之外的所有请求执行位置重定向(如果需要重定向的话)。

我们可以使用Response对象的history属性来跟踪重定向。

Response.history列表包含为完成请求而创建的Response对象。列表按响应的先后顺序排序。

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

>>> r = requests.get('http://gitee.com/')
>>> r.url
'https://gitee.com/'
>>> r.status_code
200
>&g编程客栈t;> r.history
[<Response [302]>]

如果使用HEAD,GET, OPTIONS, POST, PUT, PATCH 或者DELETE,可以使用 allow_redirects参数禁止重定向:

>>> r = requests.get('http://gitee.com/', allow_redirects=False)
>>> r.status_code
302
>>> r.history
[]
>>> r = requests.head('http://gitee.com/', allow_redirects=False)
>>> r.url
'http://gitee.com/'
>>> r.status_code
302
>>> r.history
[]
>>> r = requests.head('http://gitee.com/', allow_redirects=True)
>>> r.status_code
200
>>> r.url
'https://gitee.com/'
>>> r.history
[<Response [302]>]

请求超时

可以使用timeout参数告诉requests在给定的秒数后停止等待响应。几乎所有的生产代码都应该在几乎所有的请求中使用此参数。否则会导致程序无限期挂起:

>>> requests.get('https://gitee.com/', timeout=0.1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool(host='gitee.com', port=443): Read timed out. (read timeout=0.1)

注意:

timeout不是整个响应的下载时间限制;相反,如果服务器在timeout秒内没有发出响应(更准确地说,如果在timeout秒内底层socket没有接收到任何字节数据),则会引发异常。如果未明确指定timeout,则请求不会超时。

错误和异常

如果出现网络问题(例如DNS故障、拒绝连接等),requests将抛出ConnectionError异常。

如果HTTP请求返回了失败的状态代码,Response.raise_for_statu()将抛出HTTPError

如果请求超时,则会抛出Timeout异常。

如果请求超过了配置的最大重定向次数,则会抛出TooManyRedirects异常。

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

高级用法

Session对象

Session对象允许你跨请求保持某些参数,以及Session实例发出的所有请求的cookie,并将使用urllib3的[连接池](https://urllib3.readthedocs.io/en/latest/reference/index.html#module-urllib3.connectionpool)。因此,如果你向同一主机发出多个请求,将复用底层TCP连接,这可能会显著提高性能(请参见HTTP持久连接)。

Session对象具有主要 requests API的所有方法。

让我们在请求之间保持一些cookie:

>>> s = requests.Session()
>>> s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
<Response [200]>
>>> r = s.get('https://httpbin.org/cookies')
>>> r.text
'{\n  "cookies": {\n    "sessioncookie": "123456789"\n  }\n}\n'
>>>

Seesion对象还可以用于向请求方法提供默认数据。这是通过向Session对象属性提供数据来实现的:

>>> s = requests.Session()
>>> s.auth = ('user', 'pass')
>>> s.headers.update({'x-test': 'true'})
# 'x-test'和'x-test2'请求头随请求发送了
>>> s.headers.update({'x-test': 'true'})
>>> s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})
<Response [200]>

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

然而,请注意,即使使用会话,方法级参数也不能跨请求保持。本示例将只在发送第一个请求发送cookie,而不发送第二个请求

>>> s = requests.Session()
>>> r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
>>> r.text
'{\n  "cookies": {\n    "from-my": "browser"\n  }\n}\n'
>>> r = s.get('https://httpbin.org/cookies')
>>> r.text
'{\n  "cookies": {}\n}\n'

Cookie utility functions to manipulate Session.cookies

如果想手动向Session添加Cookie,那么使用 Cookie utility functions来操作Session.cookies

Session对象也可以用作上下文管理器

>>> with requests.Session() as s:
...     s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
...
<Response [200]>
>>>

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

Remove a Value From a Dict Parameter
Sometimes you'll want to omit session-level keys from a dict parameter. To do this, you simply set that key's value to `None` in the method-level parameter. It will automatically be omitted.
从字典参数中删除值
有时,你需要从dict参数中忽略会话级别的键。为此,只需在方法级参数中将该键的值设置为“None”即可。它将被自动忽略。

Session中包含的所有值都可以直接使用。参见Session API Docs了解更多信息。

请求和响应对象

示例:获取响应头和请求头

>>> r = s.get('https://httpbin.org')
>>> r.headers # 获取响应头
{'Date': 'Mon, 13 Mar 2023 15:43:41 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '9593', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true'}
>>> r.request.headers
{'User-Agent': 'python-requests/2.27.1', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'Cookie': 'sessioncookie=123456789'}
>>>

Prepared requests

每当收到来自某个API调用或者Session调用的Response对象,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)

However, the above code will lose some of the advantages of having a requests Session object. In particular, Session-level state such as cookies will not get applied to your request. To get a PreparedRequest with that state applied, replace the call to Request.prepare() with a call to Session.prepare_request(), like this:

由于你没有对Request对象执行任何特殊操作,因此您可以立即prepare它并修改PreparedRequest对象。然后将其与发送给requests.*Session.*的其它参数一起发送。

然而,上述代码将失去使用requests Session对象的一些优点。特别是Session级别的状态,比如cookie将不会应用于你的请求。如果需要获取应用了那些状态的 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)

When you are using the prepared request flow, keep in mind that it does not take into account the environment. This can cause problems if you are using environment variables to change the behaviour of requests. For example: Self-signed SSL certificates specified in REQUESTS_CA_BUNDLE will not be taken into account. As a result an SSL: CERTIFICATE_VERIFY_FAILED is thrown. You can get around this behaviour by explicitly merging the environment settings into your session:

当你使用prepared request请求时,请记住它没有考虑环境。如果你正使用环境变量来更改请求的行为,这可能会导致问题。例如:在REQUESTS_CA_BUNDLE中指定的自签名SSL证书将不起作用,结果引发了SSL:CERTIFICATE_VERIFY_FAILED。你可以通过将环境设置显式合并到Session中来避免这种行为:

from requests import Request, Session
s = Session()
req = Request('GET', url)
prepped = s.prepare_request(req)
# Merge environmhttp://www.devze.coment settings into session
settings = s.merge_environment_settings(prepped.url, {}, None, None, None)
resp = s.send(prepped, **settings)
print(resp.status_code)

HTTP Basic 验证

>>> from requests.auth import HTTPBasicAuth
>>> auth = HTTPBasicAuth('your_username', 'your_password')
>>> r = requests.post(url='you_target_url', data=body, auth=auth)

SSL证书验证

requests验证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'

你可以使用verify参数传递拥有受信任CA的证书的CA_BUNDLE文件的路径或者目录:

&gt;&gt;&gt; requests.get('https://github.com', ver开发者_C培训ify='/path/to/certfile')

或者

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

注意:

如果verify设置为目录的路径,则必须使用OpenSSL提供的c_rehash实用程序处理该目录。

还可以通过REQUESTS_CA_BUNDLE环境变量指定此受信任CA列表。如果未设置REQUESTS_CA_BUNDLE,将使用CURL_CA_BUNDLE 

如果将verify设置为False,则requests也可以忽略SSL证书验证:

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

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

默认情况下,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不支持加密的私钥

CA证书

Reuests使用来自certific包中的证书. 这允许用户在不更改Requests版本的情况下更新其受信任的证书。

在2.16版本之前,Requests捆绑了一组其信任的根CA证书,证书来源于Mzillatruststore。每个Request版本只更新一次证书。当未安装certific时,当使用较旧版本的requests时,这会导致证书包非常过时。

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

Body内容工作流

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

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

此时,仅仅响应头被下载,且连接保持打开状态,因此,允许我们有条件的检索内容:

if int(r.headers.get('content-length')) &lt; TOO_LONG:
  content = r.content
  ...

您可以使用 Response.iter_content() 和Response.iter_lines() 方法进一步控制工作流。或者,可以从位于Response.raw的底层的urllib3.HTTPResponse 中读取未编码的主体.

如果在发出请求时将stream设置为True,则requests无法释放连接回连接池,除非读取完所有数据或调用Response.close。这可能导致连接效率低下。如果你发现自己在使用stream=True时部分读取请求体(或根本没有读取它们),则应在with语句中发出请求,以确保连接最终处于关闭状态:

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

Keep-Alive

多亏了urllib3keep-alive在Session中是100%自动的!你在Session发出的任何请求都将自动重用合适的连接!

注意,只有在读取了所有响应体数据后,才会将连接释放回连接池以供重用;请确保将stream设置为False或读取Response对象的content属性。

流式上传

requests支持流式上传,允许发送大型流或文件,而无需将其读入内存。要流式传输和上传,只需为请求体提供一个类似文件的对象:

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

警告

强烈建议以二进制模式打开文件。这是因为requests可能会尝试为你提供Content-Length请求头,如果这样做,该请求头值将被设置为文件中的字节数。如果以文本模式打开文件,可能会发生错误。

分块编码(Chunk-Encoded)请求

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

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

对于分块编码请求的响应,最好使用Response.iter_content()对数据进行迭代。在理想情况下,将在请求上设置stream=True,在这种情况下,可以通过使用值为Nonechunk_size参数调用iter_content来逐块迭代。如果要设置块的最大大小,可以将chunk_size参数设置为任意目标大小整数。

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

你可以在一个请求中发送多个文件。例如,假设你要将图像文件上载到具有多个文件字段“images”的HTML表单:

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

为此,只需将files设置为(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
>>> r.text
'{\n  "args": {}, \n  "data": "", \n  "files": {\n    "images": "data:image/png;base64,ivbORw0KGgoAAAAN...=="\n  }, \n  "form": {}, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Content-Length": "1800", \n    "Content-Type": "multipart/form-data; boundary=771ef90459071106c5f47075cbca2659", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.27.1", \n    "X-Amzn-Trace-Id": "Root=1-641122ea-10a6271f0fdf488c70cf90e9"\n  }, \n  "json": null, \n  "origin": "183.62.127.25", \n  "url": "https://httpbin.org/post"\n}\n'

事件钩子(Event Hooks)

requests拥有一个hook系统,可用于控制请求过程的部分,或者信号事件处理。

可用的hooks:

  • response:

    请求生成的响应

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

hooks={'response': print_url}

callback_function将接收一数据块(a chunk of data)作为其第一个参数。

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

如果Session实例可个钩子函数,那么将按钩子的添加顺序调用这些钩子。

自定义身份验证

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

然后,发送请求

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

流式请求

使用Response.iter_lines() ,可以很轻易的迭代流式API,比如 Twitter Streaming API。简单的设置 stream 为 True 并且使用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)

代理

如果你需要使用代理,可在任何请求方法的proxys参数中为单个请求配置代理

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 = requests.Session()
session.proxies.update(proxies)
session.get('http://example.org')

警告

session.proxies提供的值可能被环境代理(由urllib.request.getproxys返回的值)覆盖,所以为了确保在环境代理存在的情况下,也使用给定代理,显示为所有单个请求指定proxies参数,如上述一开始所述。

如果没有为请求设置proxies请求参数的情况下,requests会尝试读取由标准环境变量 http_proxyhttps_proxyno_proxyall_proxy定义的代理配置。这些变量名称可大写。所以,可以通过这些变量配置为请求设置代理(请根据实际需要配置):

linux:

$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ export ALL_PROXY="socks5://10.10.1.10:3434"
$ python
>>> import requests
>>> requests.get('http://example.org')

win:

set HTTP_PROXY=http://10.10.1.10:3128
>>> 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/'}

警告

将敏感的用户名和密码信息存储在环境变量或版本控制的文件中会带来安全风险,强烈建议不要这样做。

如果要为特定shema和主机提供代理,请使用scheme://hostnameproxies字典参数的键来设置代理。这将匹配给定scheme和确切主机名的任何请求。

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

注意,代理URL必须包含schema。

最后需要注意的,为https连接设置代理,通常需要所在本机机器信任代理根证书。默认的,可以通过以下代码查找requests信任的证书列表:

from requests.utils import DEFAULT_CA_BUNDLE_PATH
print(DEFAULT_CA_BUNDLE_PATH)

通过将 REQUESTS_CA_BUNDLE (or CURL_CA_BUNDLE) 环境变量设置为另一个文件路径,可以覆盖此证书路径:

$ export REQUESTS_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代理之外,requests还支持使用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使用scheme来决定是在客户端还是代理服务器上进行DNS解析。如果要解析代理服务器上的域,请使用socks5h作为scheme

编码

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

如果安装了chardetrequests将使用它,但对于python3来说,chardet不再是强制依赖项。

当安装requests时,没有指定 [use_chardet_on_py3],并且chardet尚未安装时,requests将使用charset normalizer来猜测编码。

requests不会猜测编码的唯一情况是HTTP请求头中不存在显示字符集且Content-Type请求头包含text。在这种情况下,RFC 2616指定默认字符集必须是ISO-8859-1。requests遵循该规范。如果需要不同的编码,您可以手动设置Response.conding属性,或使用原始Response.content

Link请求头

许多HTTP API具有link请求头。它们使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/user/119893/repos?page=2&per_page=10>; rel="next", <https://api.github.com/user/119893/repos?page=5&per_page=10>; rel="last"'

requests 将自动解析这link请求头并且让它们更容易被使用:

>>> r.links["next"]
{'url': 'https://api.github.com/user/119893/repos?page=2&per_page=10', 'rel': 'next'}
>>> r.links["last"]
{'url': 'https://api.github.com/user/119893/repos?page=5&per_page=10', 'rel': 'last'}

传输适配器(Transport Adapters)

从v1.0.0开始,requests 已模块化内部设计。这样做的部分原因是为了实现传输适配器,最初在此处描述. 传输适配器提供了一种机制来定义某个HTTP服务的交互方法。特别是,它们允许你应用每个服务的配置。

requests附带单个传输适配器HTTPAdapter. 此适配器使用功能强大的urllib3提供与HTTP和HTTPS的默认请求交互。当初始化 requests Session 时,其中一个附加到Session 对象表示HTTP,一个表示HTTPS。

户能够创建和使用自己的具备特定功能的传输适配器。一旦创建,传输适配器就可以加载到会话对象,并指示它应该应用于哪些web服务。

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

上述mount调用将传输适配器的指定实例注册到URL前缀中。一旦挂载,使用该session发起的,URL以给定前缀开头的任何HTTP请求都将使用给定的传输适配器。

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

示例: 指定SSL版本

The requests team has made a specific choice to use whatever SSL version is default in the underlying library (urllib3). Normally this is fine, but from time to time, you might find yourself needing to connect to a service-endpoint that uses a version that isn’t compatible with the default.

You can use Transport Adapters for this by taking most of the existing implementation of HTTPAdapter, and adding a parameter ssl_version that gets passed-through to urllib3. We’ll make a Transport Adapter that instructs the library to use SSLv3:

默认情况下,requests选择使用底层urllib3库中默认的SSL版本。 通常情况下,这是可以的,但有时,您可能会发现自己需要连接到使用与默认版本不兼容的SSL版本的服务端。

为此,可以通过继承HTTPAdapter实现自定义传输适配器,

示例:编写一个适配器,指示库使用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)

阻塞或非阻塞

有了默认的传输适配器,requests就不会提供任何类型的非阻塞IO。Response.content属性将阻塞,直到下载完整个响应为止。如果你需要更大的粒度,则库的流式传输功能(请参阅流式传输请求)允许单次接收较小数量的响应那日。然而,这些调用仍然是阻塞。

如果您关心阻塞IO的使用,那么有很多项目将请求与Python的异步框架结合在一起。一些很好的例子是 requests-threads, grequests, requests-futures, 和httpx.

超时

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

连接超时是requests等待客户端建立与远程计算机的socke连接的秒数。将连接超时设置为略大于3的倍数是一种很好的做法,因为3秒是默认的TCP数据包重传窗口.

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

如果需要为请求设置一个超时时间,可以为timeout参数指定一个具体的时间值:

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

该超时时间将同时应用于连接超时和读取超时。如果想为连接超时和读取超时分别设置不同的等待时间,可以指定一个元组:

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

如果服务很慢,想让requests一直等待响应直到获取响应,可以指定timeout参数值为None

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

到此这篇关于Python Requests库学习总结的文章就介绍到这了,更多相关Python Requests库内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

精彩评论

暂无评论...
验证码 换一张
取 消