進階用法

本文檔涵蓋 Requests 的一些更進階的功能。

Session 物件

Session 物件允許您在多個請求之間持久化某些參數。它也會在來自 Session 實例的所有請求中持久化 Cookie,並將使用 urllib3連線池。因此,如果您要向同一主機發出多個請求,底層的 TCP 連線將被重複使用,這可以顯著提高效能(請參閱 HTTP 持續連線)。

Session 物件具有主要 Requests API 的所有方法。

讓我們在多個請求之間持久化一些 Cookie

s = requests.Session()

s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get('https://httpbin.org/cookies')

print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'

Session 也可用於為請求方法提供預設資料。這可以通過向 Session 物件上的屬性提供資料來完成

s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})

# both 'x-test' and 'x-test2' are sent
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})

您傳遞給請求方法的任何字典都將與設定的 Session 層級值合併。方法層級參數會覆蓋 Session 參數。

但是請注意,即使使用 Session,方法層級參數也不會跨請求持久化。此範例只會在第一個請求中發送 Cookie,而不會在第二個請求中發送

s = requests.Session()

r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'

r = s.get('https://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'

如果您想手動將 Cookie 新增到您的 Session,請使用 Cookie 实用工具函数 來操作 Session.cookies

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

with requests.Session() as s:
    s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')

這將確保 Session 在 with 區塊結束後立即關閉,即使發生未處理的例外情況。

從字典參數中移除值

有時您會希望從字典參數中省略 Session 層級的鍵。為此,您只需將該鍵的值設定為方法層級參數中的 None。它將自動被省略。

Session 中包含的所有值都可直接供您使用。請參閱 Session API 文件 以了解更多資訊。

Request 和 Response 物件

每當調用 requests.get() 和類似的方法時,您都在做兩件主要的事情。首先,您正在建構一個 Request 物件,它將被發送到伺服器以請求或查詢某些資源。其次,一旦 Requests 從伺服器收到回應,就會生成一個 Response 物件。Response 物件包含伺服器返回的所有資訊,並且還包含您最初建立的 Request 物件。以下是一個簡單的請求,用於從 Wikipedia 的伺服器獲取一些非常重要的資訊

>>> r = requests.get('https://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'}

但是,如果我們想獲取我們發送給伺服器的標頭,我們只需存取 request,然後存取 request 的標頭

>>> r.request.headers
{'Accept-Encoding': 'identity, deflate, compress, gzip',
'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'}

Prepared Requests

每當您從 API 調用或 Session 調用接收到 Response 物件時,request 屬性實際上是使用的 PreparedRequest。在某些情況下,您可能希望在發送請求之前對 body 或標頭(或任何其他內容)執行一些額外的工作。執行此操作的簡單方法如下

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)

由於您沒有對 Request 物件執行任何特殊操作,因此您可以立即準備它並修改 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)

當您使用 prepared request 流程時,請記住它不會考慮環境。如果您使用環境變數來更改 requests 的行為,這可能會導致問題。例如:在 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)

SSL 憑證驗證

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 則有

>>> requests.get('https://github.com')
<Response [200]>

您可以將 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 实用工具进行處理。

此受信任 CA 清單也可以通過 REQUESTS_CA_BUNDLE 環境變數指定。如果未設定 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'

如果您指定了錯誤的路徑或無效的憑證,您將收到 SSLError

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

警告

您的本地憑證的私鑰必須是未加密的。目前,Requests 不支援使用加密金鑰。

CA 憑證

Requests 使用來自套件 certifi 的憑證。這允許用戶在不更改 Requests 版本的情況下更新其受信任的憑證。

在 2.16 版本之前,Requests 捆綁了一組它信任的根 CA,這些根 CA 來自 Mozilla 信任儲存庫。憑證僅針對每個 Requests 版本更新一次。當未安裝 certifi 時,這會導致在使用顯著舊版本的 Requests 時,憑證捆綁包非常過時。

為了安全起見,我們建議經常升級 certifi!

Body 內容工作流程

預設情況下,當您發出請求時,回應的 body 會立即下載。您可以覆蓋此行為,並延遲下載回應 body,直到您使用 stream 參數存取 Response.content 屬性

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

此時,僅下載了回應標頭,並且連線保持開啟,因此允許我們有條件地檢索內容

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

您可以使用 Response.iter_content()Response.iter_lines() 方法進一步控制工作流程。或者,您可以從底層 urllib3 urllib3.HTTPResponse 讀取未解碼的 body,網址為 Response.raw

如果在發出請求時將 stream 設定為 True,則除非您使用所有資料或調用 Response.close,否則 Requests 無法將連線釋放回池中。這可能會導致連線效率低下。如果您發現自己在使用 stream=True 時部分讀取請求 body(或完全不讀取),則應在 with 語句中發出請求,以確保它始終關閉

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

Keep-Alive

好消息 — 由於 urllib3,keep-alive 在 Session 中是 100% 自動的!您在 Session 中發出的任何請求都將自動重複使用適當的連線!

請注意,只有在讀取所有 body 資料後,連線才會釋放回池中以供重複使用;請務必將 stream 設定為 False 或讀取 Response 物件的 content 屬性。

串流上傳

Requests 支援串流上傳,這允許您發送大型串流或檔案,而無需將它們讀取到記憶體中。要串流和上傳,只需為您的 body 提供一個類似檔案的物件

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

警告

強烈建議您以 二進位模式 開啟檔案。這是因為 Requests 可能會嘗試為您提供 Content-Length 標頭,如果它這樣做,則此值將設定為檔案中的位元組數。如果您以文字模式開啟檔案,則可能會發生錯誤。

Chunk-Encoded 請求

Requests 也支援傳出和傳入請求的 Chunked 傳輸編碼。要發送 chunk-encoded 請求,只需為您的 body 提供一個產生器(或任何沒有長度的迭代器)

def gen():
    yield 'hi'
    yield 'there'

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

對於 chunked 編碼的回應,最好使用 Response.iter_content() 迭代資料。在理想情況下,您將在請求中設定 stream=True,在這種情況下,您可以通過調用 iter_content 並將 chunk_size 參數設定為 None 來逐塊迭代。如果您想設定區塊的最大大小,您可以將 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
{
  ...
  'files': {'images': 'data:image/png;base64,iVBORw ....'}
  'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a',
  ...
}

警告

強烈建議您以 二進位模式 開啟檔案。這是因為 Requests 可能會嘗試為您提供 Content-Length 標頭,如果它這樣做,則此值將設定為檔案中的位元組數。如果您以文字模式開啟檔案,則可能會發生錯誤。

事件掛鉤

Requests 有一個掛鉤系統,您可以使用它來操作請求過程的各個部分,或發出事件處理訊號。

可用的掛鉤

response:

從 Request 生成的回應。

您可以通過將 {hook_name: callback_function} 字典傳遞給 hooks 請求參數,在每個請求的基礎上分配掛鉤函數

hooks={'response': print_url}

callback_function 將接收一塊資料作為其第一個參數。

def print_url(r, *args, **kwargs):
    print(r.url)

您的回呼函數必須處理自己的例外情況。任何未處理的例外情況都不會被靜默傳遞,因此應由調用 Requests 的程式碼處理。

如果回呼函數傳回值,則假定它是要替換傳入的資料。如果函數沒有傳回任何內容,則不會影響其他任何內容。

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 實例。然後,您新增的任何掛鉤都將在對 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

然後,我們可以使用我們的 Pizza Auth 發出請求

>>> 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=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)

代理

如果您需要使用代理,您可以通過任何請求方法的 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)

或者,您可以為整個 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.getproxies 返回的那些代理)覆蓋。為了確保在存在環境代理的情況下使用代理,請如最初在上面解釋的那樣,在所有個別請求上顯式指定 proxies 參數。

有關詳細資訊,請參閱 #2018

當未如上所示為每個請求覆蓋代理配置時,Requests 依賴於標準環境變數 http_proxyhttps_proxyno_proxyall_proxy 定義的代理配置。也支援這些變數的大寫變體。因此,您可以設定它們來配置 Requests(僅設定與您的需求相關的變數)

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

要將 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/'}

警告

將敏感的使用者名稱和密碼資訊儲存在環境變數或版本控制的檔案中是一種安全風險,強烈建議不要這樣做。

要為特定方案和主機提供代理,請對鍵使用 scheme://hostname 形式。這將匹配對給定方案和確切主機名稱的任何請求。

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

請注意,代理 URL 必須包含方案。

最後,請注意,使用代理進行 https 連線通常需要您的本地機器信任代理的根憑證。預設情況下,Requests 信任的憑證清單可以在以下位置找到

from requests.utils import DEFAULT_CA_BUNDLE_PATH
print(DEFAULT_CA_BUNDLE_PATH)

您可以通過將 REQUESTS_CA_BUNDLE(或 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 使用方案來決定是否在用戶端或代理上執行 DNS 解析。如果您想在代理伺服器上解析網域,請使用 socks5h 作為方案。

符合性

Requests 旨在符合所有相關規範和 RFC,只要這種符合性不會給用戶帶來困難。這種對規範的關注可能會導致某些行為對於那些不熟悉相關規範的人來說似乎不尋常。

編碼

當您收到回應時,Requests 會猜測在您存取 Response.text 屬性時用於解碼回應的編碼。Requests 將首先檢查 HTTP 標頭中是否存在編碼,如果不存在,則將使用 charset_normalizerchardet 來嘗試猜測編碼。

如果安裝了 chardetrequests 會使用它,但是對於 python3,chardet 不再是強制性依賴項。chardet 程式庫是 LGPL 授權的依賴項,並且某些 requests 用戶不能依賴強制性 LGPL 授權的依賴項。

當您安裝 requests 而不指定 [use_chardet_on_py3] 額外項,並且 chardet 尚未安裝時,requests 使用 charset-normalizer (MIT 授權) 來猜測編碼。

Requests 不會猜測編碼的唯一情況是 HTTP 標頭中不存在顯式字元集,並且 Content-Type 標頭包含 text。在這種情況下,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 獲取有關特定提交的資訊。假設我們想要 Requests 上的提交 a050faf。我們將像這樣獲取它

>>> import requests
>>> r = requests.get('https://api.github.com/repos/psf/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())
['committer', 'author', 'url', 'tree', 'sha', 'parents', 'message']

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

>>> print(commit_data['message'])
makin' history

到目前為止,都很簡單。好吧,讓我們稍微研究一下 GitHub API。現在,我們可以查看文件,但如果我們改用 Requests,可能會更有趣。我們可以利用 Requests OPTIONS 動詞來查看我們剛才使用的 URL 上支援哪些 HTTP 方法。

>>> verbs = requests.options(r.url)
>>> verbs.status_code
500

咦,什麼?這沒用!事實證明,像許多 API 提供商一樣,GitHub 實際上並未實作 OPTIONS 方法。這是一個令人惱火的疏忽,但沒關係,我們可以只使用枯燥的文件。但是,如果 GitHub 正確地實作了 OPTIONS,它們應該在標頭中返回允許的方法,例如

>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print(verbs.headers['allow'])
GET,HEAD,POST,OPTIONS

查看文件,我們看到提交允許的唯一其他方法是 POST,它會建立新的提交。由於我們正在使用 Requests 儲存庫,我們可能應該避免對其進行笨拙的 POST。相反,讓我們玩一下 GitHub 的 Issues 功能。

此文件是為了回應 Issue #482 而新增的。鑑於此問題已存在,我們將使用它作為範例。讓我們從獲取它開始。

>>> r = requests.get('https://api.github.com/repos/psf/requests/issues/482')
>>> r.status_code
200

>>> issue = json.loads(r.text)

>>> print(issue['title'])
Feature any http verb in docs

>>> print(issue['comments'])
3

酷,我們有三個評論。讓我們看看最後一個。

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

>>> comments = r.json()

>>> print(comments[0].keys())
['body', 'url', 'created_at', 'updated_at', 'user', 'id']

>>> print(comments[2]['body'])
Probably in the "advanced" section

嗯,這似乎是一個愚蠢的地方。讓我們發表評論告訴發布者他很愚蠢。發布者是誰?

>>> print(comments[2]['user']['login'])
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/psf/requests/issues/482/comments"

>>> r = requests.post(url=url, data=body)
>>> r.status_code
404

咦,真奇怪。我們可能需要驗證身份。這會很麻煩,對吧?錯了。Requests 可以輕鬆使用多種形式的身份驗證,包括非常常見的基本身份驗證。

>>> 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['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/psf/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

利用這個方法,您可以使用伺服器允許的任何方法動詞。

傳輸适配器

從 v1.0.0 版本開始,Requests 已轉向模組化內部設計。這樣做的部分原因是為了實作傳輸适配器,最初在此處描述。傳輸适配器提供了一種機制,用於定義 HTTP 服務的互動方法。特別是,它們允許您應用每個服務的配置。

Requests 附帶一個單一的傳輸适配器,即 HTTPAdapter。此适配器使用強大的 urllib3 函式庫,提供 Requests 與 HTTP 和 HTTPS 的預設互動。每當初始化 Requests Session 時,就會將其中一個附加到 Session 物件,一個用於 HTTP,另一個用於 HTTPS。

Requests 允許使用者建立和使用他們自己的傳輸适配器,以提供特定的功能。一旦建立,傳輸适配器可以掛載到 Session 物件,並指示它應適用於哪些網路服務。

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

mount 呼叫將傳輸适配器的特定實例註冊到前綴。掛載後,任何使用該 session 發出的 HTTP 請求,只要其 URL 以給定的前綴開頭,都將使用給定的傳輸适配器。

注意

將根據最長前綴匹配選擇适配器。請注意,諸如 https://# 之類的前綴也將匹配 https://#.other.comhttps://#@other.com。建議以 / 終止完整的主機名稱。

實作傳輸适配器的許多細節超出了本文檔的範圍,但請查看下一個範例以了解簡單的 SSL 用例。對於更多資訊,您可以考慮子類別化 BaseAdapter

範例:特定 SSL 版本

Requests 團隊已做出明確選擇,使用底層函式庫 (urllib3) 中的預設 SSL 版本。通常這沒問題,但有時您可能會發現自己需要連線到使用與預設版本不相容的服務端點。

您可以使用傳輸适配器來做到這一點,方法是採用 HTTPAdapter 的大部分現有實作,並新增一個參數 *ssl_version*,該參數會傳遞給 urllib3。我們將建立一個傳輸适配器,指示函式庫使用 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 不會重試失敗的連線。但是,可以使用強大的功能陣列(包括退避)在 Requests Session 中實作自動重試,方法是使用 urllib3.util.Retry 類別

from urllib3.util import Retry
from requests import Session
from requests.adapters import HTTPAdapter

s = Session()
retries = Retry(
    total=3,
    backoff_factor=0.1,
    status_forcelist=[502, 503, 504],
    allowed_methods={'POST'},
)
s.mount('https://', HTTPAdapter(max_retries=retries))

阻塞或非阻塞?

在預設傳輸适配器的情況下,Requests 不提供任何形式的非阻塞 IO。Response.content 屬性將會阻塞,直到整個回應下載完成。如果您需要更細緻的控制,函式庫的串流功能(請參閱 串流請求)允許您一次檢索較少量的回應。但是,這些呼叫仍然會阻塞。

如果您擔心使用阻塞 IO,那麼有很多專案將 Requests 與 Python 的非同步框架之一結合使用。一些出色的範例包括 requests-threadsgrequestsrequests-futureshttpx

標頭排序

在不尋常的情況下,您可能希望以有序的方式提供標頭。如果您將 OrderedDict 傳遞給 headers 關鍵字引數,那將為標頭提供排序。但是,Requests 使用的預設標頭的排序將優先,這表示如果您在 headers 關鍵字引數中覆寫預設標頭,則它們的順序可能與該關鍵字引數中的其他標頭相比顯得錯亂。

如果這有問題,使用者應考慮在 Session 物件上設定預設標頭,方法是將 Session.headers 設定為自訂的 OrderedDict。該排序將永遠優先。

超時

大多數對外部伺服器的請求都應附加超時,以防伺服器未及時回應。預設情況下,除非明確設定超時值,否則 requests 不會超時。如果沒有超時,您的程式碼可能會停滯數分鐘或更長時間。

connect 超時是 Requests 將等待您的用戶端與遠端機器建立連線(對應於 socket 上的 connect() 呼叫)的秒數。最好將連線超時設定為略大於 3 的倍數,這是預設的 TCP 封包重傳視窗

一旦您的用戶端連線到伺服器並傳送 HTTP 請求,read 超時是用戶端將等待伺服器傳送回應的秒數。(具體來說,這是用戶端將等待伺服器傳送的位元組之間的秒數。在 99.9% 的情況下,這是伺服器傳送第一個位元組之前的時間)。

如果您為超時指定單一值,像這樣

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

超時值將同時應用於 connectread 超時。如果您想單獨設定這些值,請指定一個元組

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

如果遠端伺服器非常慢,您可以告訴 Requests 永遠等待回應,方法是傳遞 None 作為超時值,然後去喝杯咖啡。

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

注意

連線超時適用於每次嘗試連線到 IP 位址。如果網域名稱存在多個位址,則底層的 urllib3 將依序嘗試每個位址,直到其中一個成功連線。這可能會導致有效的總連線超時比指定的時間長數倍,例如,沒有回應的伺服器同時具有 IPv4 和 IPv6 位址,其感知到的超時時間將加倍,因此在設定連線超時時請考慮到這一點。

注意

連線超時和讀取超時都不是 實際時間。這表示如果您啟動請求,查看時間,然後查看請求完成或超時的時間,則實際時間可能大於您指定的時間。