Python Requets庫學習總結

授客發表於2023-04-28

快速開始

傳送請求

>>> 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傳輸編碼。

如果安裝了類似 brotlibrotlicffi的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。此封裝的異常可能會因為不同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_code200, raise_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
>>> r.history
[<Response [302]>]

如果使用HEAD,GET, OPTIONSPOSTPUTPATCH 或者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 environment 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檔案的路徑或者目錄:

>>> 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列表。如果未設定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')) < 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。簡單的設定 streamTrue 並且使用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=TrueResponse.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_proxy, https_proxy, no_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和確切主機名的任何請求。

proxies = {'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_normalizerchardet嘗試猜測編碼。

如果安裝了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)