高級用法
本篇文檔涵蓋了 Requests 的一些高級特性。
會話對象
會話對象讓你能夠跨請求保持某些參數。它也會在同一個 Session 實例發出的所有請求之間保持 cookie, 期間使用 urllib3
的 connection pooling 功能。所以如果你向同一主機發送多個請求,底層的 TCP 連接將會被重用,從而帶來顯著的性能提升。 (參見 HTTP persistent connection).
會話對象具有主要的 Requests API 的所有方法。
我們來跨請求保持一些 cookie:
1 s = requests.Session() 2 3 s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') 4 r = s.get("http://httpbin.org/cookies") 5 6 print(r.text) 7 # '{"cookies": {"sessioncookie": "123456789"}}'
會話也可用來為請求方法提供缺省數據。這是通過為會話對象的屬性提供數據來實現的:
1 s = requests.Session() 2 s.auth = ('user', 'pass') 3 s.headers.update({'x-test': 'true'}) 4 5 # both 'x-test' and 'x-test2' are sent 6 s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
任何你傳遞給請求方法的字典都會與已設置會話層數據合並。方法層的參數覆蓋會話的參數。
不過需要注意,就算使用了會話,方法級別的參數也不會被跨請求保持。下面的例子只會和第一個請求發送 cookie ,而非第二個:
1 s = requests.Session() 2 3 r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'}) 4 print(r.text) 5 # '{"cookies": {"from-my": "browser"}}' 6 7 r = s.get('http://httpbin.org/cookies') 8 print(r.text) 9 # '{"cookies": {}}'
如果你要手動為會話添加 cookie,就使用 Cookie utility 函數 來操縱 Session.cookies
。
會話還可以用作前后文管理器:
1 with requests.Session() as s:
2 s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
這樣就能確保 with
區塊退出后會話能被關閉,即使發生了異常也一樣。
從字典參數中移除一個值
有時你會想省略字典參數中一些會話層的鍵。要做到這一點,你只需簡單地在方法層參數中將那個鍵的值設置為 None
,那個鍵就會被自動省略掉。
包含在一個會話中的所有數據你都可以直接使用。學習更多細節請閱讀 會話 API 文檔。
請求與響應對象
任何時候進行了類似 requests.get() 的調用,你都在做兩件主要的事情。其一,你在構建一個 Request 對象, 該對象將被發送到某個服務器請求或查詢一些資源。其二,一旦 requests
得到一個從服務器返回的響應就會產生一個 Response
對象。該響應對象包含服務器返回的所有信息,也包含你原來創建的 Request
對象。如下是一個簡單的請求,從 Wikipedia 的服務器得到一些非常重要的信息:
1 >>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
如果想訪問服務器返回給我們的響應頭部信息,可以這樣做:
1 >>> r.headers 2 {'content-length': '56170', 'x-content-type-options': 'nosniff', 'x-cache': 3 'HIT from cp1006.eqiad.wmnet, MISS from cp1010.eqiad.wmnet', 'content-encoding': 4 'gzip', 'age': '3080', 'content-language': 'en', 'vary': 'Accept-Encoding,Cookie', 5 'server': 'Apache', 'last-modified': 'Wed, 13 Jun 2012 01:33:50 GMT', 6 'connection': 'close', 'cache-control': 'private, s-maxage=0, max-age=0, 7 must-revalidate', 'date': 'Thu, 14 Jun 2012 12:59:39 GMT', 'content-type': 8 'text/html; charset=UTF-8', 'x-cache-lookup': 'HIT from cp1006.eqiad.wmnet:3128, 9 MISS from cp1010.eqiad.wmnet:80'}
然而,如果想得到發送到服務器的請求的頭部,我們可以簡單地訪問該請求,然后是該請求的頭部:
1 >>> r.request.headers 2 {'Accept-Encoding': 'identity, deflate, compress, gzip', 3 'Accept': '*/*', 'User-Agent': 'python-requests/0.13.1'}
准備的請求 (Prepared Request)
當你從 API 或者會話調用中收到一個 Response
對象時,request
屬性其實是使用了 PreparedRequest
。有時在發送請求之前,你需要對 body 或者 header (或者別的什么東西)做一些額外處理,下面演示了一個簡單的做法:
1 from requests import Request, Session 2 3 s = Session() 4 req = Request('GET', url, 5 data=data, 6 headers=header 7 ) 8 prepped = req.prepare() 9 10 # do something with prepped.body 11 # do something with prepped.headers 12 13 resp = s.send(prepped, 14 stream=stream, 15 verify=verify, 16 proxies=proxies, 17 cert=cert, 18 timeout=timeout 19 ) 20 21 print(resp.status_code)
由於你沒有對 Request
對象做什么特殊事情,你立即准備和修改了 PreparedRequest
對象,然后把它和別的參數一起發送到 requests.*
或者 Session.*
。
然而,上述代碼會失去 Requests Session
對象的一些優勢, 尤其 Session
級別的狀態,例如 cookie 就不會被應用到你的請求上去。要獲取一個帶有狀態的 PreparedRequest
, 請用 Session.prepare_request()
取代 Request.prepare()
的調用,如下所示:
1 from requests import Request, Session 2 3 s = Session() 4 req = Request('GET', url, 5 data=data 6 headers=headers 7 ) 8 9 prepped = s.prepare_request(req) 10 11 # do something with prepped.body 12 # do something with prepped.headers 13 14 resp = s.send(prepped, 15 stream=stream, 16 verify=verify, 17 proxies=proxies, 18 cert=cert, 19 timeout=timeout 20 ) 21 22 print(resp.status_code)
SSL 證書驗證
Requests 可以為 HTTPS 請求驗證 SSL 證書,就像 web 瀏覽器一樣。SSL 驗證默認是開啟的,如果證書驗證失敗,Requests 會拋出 SSLError:
1 >>> requests.get('https://requestb.in') 2 requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
在該域名上我沒有設置 SSL,所以失敗了。但 Github 設置了 SSL:
1 >>> requests.get('https://github.com', verify=True) 2 <Response [200]>
你可以為 verify
傳入 CA_BUNDLE 文件的路徑,或者包含可信任 CA 證書文件的文件夾路徑:
1 >>> requests.get('https://github.com', verify='/path/to/certfile')
或者將其保持在會話中:
1 s = requests.Session() 2 s.verify = '/path/to/certfile'
注解
如果 verify
設為文件夾路徑,文件夾必須通過 OpenSSL 提供的 c_rehash 工具處理。
你還可以通過 REQUESTS_CA_BUNDLE
環境變量定義可信任 CA 列表。
如果你將 verify
設置為 False,Requests 也能忽略對 SSL 證書的驗證。
1 >>> requests.get('https://kennethreitz.org', verify=False) 2 <Response [200]>
默認情況下, verify
是設置為 True 的。選項 verify
僅應用於主機證書。
# 對於私有證書,你也可以傳遞一個 CA_BUNDLE 文件的路徑給 verify
。你也可以設置 # REQUEST_CA_BUNDLE
環境變量。
客戶端證書
你也可以指定一個本地證書用作客戶端證書,可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
1 >>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key')) 2 <Response [200]>
或者保持在會話中:
1 s = requests.Session() 2 s.cert = '/path/client.cert'
如果你指定了一個錯誤路徑或一個無效的證書:
1 >>> requests.get('https://kennethreitz.org', cert='/wrong_path/client.pem') 2 SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib
警告
警告
本地證書的私有 key 必須是解密狀態。目前,Requests 不支持使用加密的 key。
CA 證書
Requests 默認附帶了一套它信任的根證書,來自於 Mozilla trust store。然而它們在每次 Requests 更新時才會更新。這意味着如果你固定使用某一版本的 Requests,你的證書有可能已經 太舊了。
從 Requests 2.4.0 版之后,如果系統中裝了 certifi 包,Requests 會試圖使用它里邊的 證書。這樣用戶就可以在不修改代碼的情況下更新他們的可信任證書。
為了安全起見,我們建議你經常更新 certifi!
響應體內容工作流
默認情況下,當你進行網絡請求后,響應體會立即被下載。你可以通過 stream
參數覆蓋這個行為,推遲下載響應體直到訪問 Response.content
屬性:
1 tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' 2 r = requests.get(tarball_url, stream=True)
此時僅有響應頭被下載下來了,連接保持打開狀態,因此允許我們根據條件獲取內容:
1 if int(r.headers['content-length']) < TOO_LONG: 2 content = r.content 3 ...
你可以進一步使用 Response.iter_content
和 Response.iter_lines
方法來控制工作流,或者以 Response.raw
從底層 urllib3 的 urllib3.HTTPResponse <urllib3.response.HTTPResponse
讀取未解碼的相應體。
如果你在請求中把 stream
設為 True
,Requests 無法將連接釋放回連接池,除非你 消耗了所有的數據,或者調用了 Response.close
。 這樣會帶來連接效率低下的問題。如果你發現你在使用 stream=True
的同時還在部分讀取請求的 body(或者完全沒有讀取 body),那么你就應該考慮使用 with 語句發送請求,這樣可以保證請求一定會被關閉:
1 with requests.get('http://httpbin.org/get', stream=True) as r: 2 # 在此處理響應。
保持活動狀態(持久連接)
好消息——歸功於 urllib3,同一會話內的持久連接是完全自動處理的!同一會話內你發出的任何請求都會自動復用恰當的連接!
注意:只有所有的響應體數據被讀取完畢連接才會被釋放為連接池;所以確保將 stream
設置為 False
或讀取 Response
對象的 content
屬性。
流式上傳
Requests支持流式上傳,這允許你發送大的數據流或文件而無需先把它們讀入內存。要使用流式上傳,僅需為你的請求體提供一個類文件對象即可:
1 with open('massive-body') as f: 2 requests.post('http://some.url/streamed', data=f)
警告
警告
我們強烈建議你用二進制模式(binary mode)打開文件。這是因為 requests 可能會為你提供 header 中的 Content-Length
,在這種情況下該值會被設為文件的字節數。如果你用文本模式打開文件,就可能碰到錯誤。
塊編碼請求
對於出去和進來的請求,Requests 也支持分塊傳輸編碼。要發送一個塊編碼的請求,僅需為你的請求體提供一個生成器(或任意沒有具體長度的迭代器):
1 def gen(): 2 yield 'hi' 3 yield 'there' 4 5 requests.post('http://some.url/chunked', data=gen())
對於分塊的編碼請求,我們最好使用 Response.iter_content()
對其數據進行迭代。在理想情況下,你的 request 會設置 stream=True
,這樣你就可以通過調用 iter_content
並將分塊大小參數設為 None
,從而進行分塊的迭代。如果你要設置分塊的最大體積,你可以把分塊大小參數設為任意整數。
POST 多個分塊編碼的文件
你可以在一個請求中發送多個文件。例如,假設你要上傳多個圖像文件到一個 HTML 表單,使用一個多文件 field 叫做 "images":
<input type="file" name="images" multiple="true" required="true"/>
要實現,只要把文件設到一個元組的列表中,其中元組結構為 (form_field_name, file_info)
:
1 >>> url = 'http://httpbin.org/post' 2 >>> multiple_files = [ 3 ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), 4 ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] 5 >>> r = requests.post(url, files=multiple_files) 6 >>> r.text 7 { 8 ... 9 'files': {'images': 'data:image/png;base64,iVBORw ....'} 10 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a', 11 ... 12 }
警告
警告
我們強烈建議你用二進制模式(binary mode)打開文件。這是因為 requests 可能會為你提供 header 中的 Content-Length
,在這種情況下該值會被設為文件的字節數。如果你用文本模式打開文件,就可能碰到錯誤。
事件掛鈎
Requests有一個鈎子系統,你可以用來操控部分請求過程,或信號事件處理。
可用的鈎子:
-
response
: - 從一個請求產生的響應
你可以通過傳遞一個 {hook_name: callback_function}
字典給 hooks
請求參數為每個請求分配一個鈎子函數:
1 hooks=dict(response=print_url)
callback_function
會接受一個數據塊作為它的第一個參數。
1 def print_url(r, *args, **kwargs): 2 print(r.url)
若執行你的回調函數期間發生錯誤,系統會給出一個警告。
若回調函數返回一個值,默認以該值替換傳進來的數據。若函數未返回任何東西,也沒有什么其他的影響。
我們來在運行期間打印一些請求方法的參數:
1 >>> requests.get('http://httpbin.org', hooks=dict(response=print_url)) 2 http://httpbin.org 3 <Response [200]>
自定義身份驗證
Requests 允許你使用自己指定的身份驗證機制。
任何傳遞給請求方法的 auth
參數的可調用對象,在請求發出之前都有機會修改請求。
自定義的身份驗證機制是作為 requests.auth.AuthBase
的子類來實現的,也非常容易定義。Requests 在 requests.auth
中提供了兩種常見的的身份驗證方案: HTTPBasicAuth
和 HTTPDigestAuth
。
假設我們有一個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
然后就可以使用我們的PizzaAuth來進行網絡請求:
>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth')) <Response [200]>
流式請求
使用 Response.iter_lines()
你可以很方便地對流式 API (例如 Twitter 的流式 API ) 進行迭代。簡單地設置 stream
為 True
便可以使用 iter_lines
對相應進行迭代:
1 import json 2 import requests 3 4 r = requests.get('http://httpbin.org/stream/20', stream=True) 5 6 for line in r.iter_lines(): 7 8 # filter out keep-alive new lines 9 if line: 10 decoded_line = line.decode('utf-8') 11 print(json.loads(decoded_line))
當使用 decode_unicode=True 在 Response.iter_lines()
或 Response.iter_content()
中時,你需要提供一個回退編碼方式,以防服務器沒有提供默認回退編碼,從而導致錯誤:
1 r = requests.get('http://httpbin.org/stream/20', stream=True) 2 3 if r.encoding is None: 4 r.encoding = 'utf-8' 5 6 for line in r.iter_lines(decode_unicode=True): 7 if line: 8 print(json.loads(line))
警告
警告
iter_lines
不保證重進入時的安全性。多次調用該方法 會導致部分收到的數據丟失。如果你要在多處調用它,就應該使用生成的迭代器對象:
1 lines = r.iter_lines() 2 # 保存第一行以供后面使用,或者直接跳過 3 4 first_line = next(lines) 5 6 for line in lines: 7 print(line)
代理
如果需要使用代理,你可以通過為任意請求方法提供 proxies
參數來配置單個請求:
1 import requests 2 3 proxies = { 4 "http": "http://10.10.1.10:3128", 5 "https": "http://10.10.1.10:1080", 6 } 7 8 requests.get("http://example.org", proxies=proxies)
你也可以通過環境變量 HTTP_PROXY
和 HTTPS_PROXY
來配置代理。
1 $ export HTTP_PROXY="http://10.10.1.10:3128" 2 $ export HTTPS_PROXY="http://10.10.1.10:1080" 3 4 $ python 5 >>> import requests 6 >>> requests.get("http://example.org")
若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 語法:
1 proxies = { 2 "http": "http://user:pass@10.10.1.10:3128/", 3 }
要為某個特定的連接方式或者主機設置代理,使用 scheme://hostname 作為 key, 它會針對指定的主機和連接方式進行匹配。
1 proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
注意,代理 URL 必須包含連接方式。
SOCKS
2.10.0 新版功能.
除了基本的 HTTP 代理,Request 還支持 SOCKS 協議的代理。這是一個可選功能,若要使用, 你需要安裝第三方庫。
你可以用 pip
獲取依賴:
1 $ pip install requests[socks]
安裝好依賴以后,使用 SOCKS 代理和使用 HTTP 代理一樣簡單:
1 proxies = { 2 'http': 'socks5://user:pass@host:port', 3 'https': 'socks5://user:pass@host:port' 4 }
合規性
Requests 符合所有相關的規范和 RFC,這樣不會為用戶造成不必要的困難。但這種對規范的考慮導致一些行為對於不熟悉相關規范的人來說看似有點奇怪。
編碼方式
當你收到一個響應時,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
的信息。我們可以這樣去做:
1 >>> import requests 2 >>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我們應該確認 GitHub 是否正確響應。如果正確響應,我們想弄清響應內容是什么類型的。像這樣去做:
1 >>> if (r.status_code == requests.codes.ok): 2 ... print r.headers['content-type'] 3 ... 4 application/json; charset=utf-8
可見,GitHub 返回了 JSON 數據,非常好,這樣就可以使用 r.json
方法把這個返回的數據解析成 Python 對象。
1 >>> commit_data = r.json() 2 3 >>> print commit_data.keys() 4 [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message'] 5 6 >>> print commit_data[u'committer'] 7 {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'} 8 9 >>> print commit_data[u'message'] 10 makin' history
到目前為止,一切都非常簡單。嗯,我們來研究一下 GitHub 的 API。我們可以去看看文檔,但如果使用 Requests 來研究也許會更有意思一點。我們可以借助 Requests 的 OPTIONS 動詞來看看我們剛使用過的 url 支持哪些 HTTP 方法。
1 >>> verbs = requests.options(r.url) 2 >>> verbs.status_code 3 500
額,這是怎么回事?毫無幫助嘛!原來 GitHub,與許多 API 提供方一樣,實際上並未實現 OPTIONS 方法。這是一個惱人的疏忽,但沒關系,那我們可以使用枯燥的文檔。然而,如果 GitHub 正確實現了 OPTIONS,那么服務器應該在響應頭中返回允許用戶使用的 HTTP 方法,例如:
1 >>> verbs = requests.options('http://a-good-website.com/api/cats') 2 >>> print verbs.headers['allow'] 3 GET,HEAD,POST,OPTIONS
轉而去查看文檔,我們看到對於提交信息,另一個允許的方法是 POST,它會創建一個新的提交。由於我們正在使用 Requests 代碼庫,我們應盡可能避免對它發送笨拙的 POST。作為替代,我們來玩玩 GitHub 的 Issue 特性。
本篇文檔是回應 Issue #482 而添加的。鑒於該問題已經存在,我們就以它為例。先獲取它。
1 >>> r = requests.get('https://api.github.com/requests/kennethreitz/requests/issues/482') 2 >>> r.status_code 3 200 4 5 >>> issue = json.loads(r.text) 6 7 >>> print(issue[u'title']) 8 Feature any http verb in docs 9 10 >>> print(issue[u'comments']) 11 3
Cool,有 3 個評論。我們來看一下最后一個評論。
1 >>> r = requests.get(r.url + u'/comments') 2 >>> r.status_code 3 200 4 >>> comments = r.json() 5 >>> print comments[0].keys() 6 [u'body', u'url', u'created_at', u'updated_at', u'user', u'id'] 7 >>> print comments[2][u'body'] 8 Probably in the "advanced" section
嗯,那看起來似乎是個愚蠢之處。我們發表個評論來告訴這個評論者他自己的愚蠢。那么,這個評論者是誰呢?
1 >>> print comments[2][u'user'][u'login'] 2 kennethreitz
好,我們來告訴這個叫 Kenneth 的家伙,這個例子應該放在快速上手指南中。根據 GitHub API 文檔,其方法是 POST 到該話題。我們來試試看。
1 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it!"}) 2 >>> url = u"https://api.github.com/repos/requests/requests/issues/482/comments" 3 4 >>> r = requests.post(url=url, data=body) 5 >>> r.status_code 6 404
額,這有點古怪哈。可能我們需要驗證身份。那就有點糾結了,對吧?不對。Requests 簡化了多種身份驗證形式的使用,包括非常常見的 Basic Auth。
1 >>> from requests.auth import HTTPBasicAuth 2 >>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password') 3 4 >>> r = requests.post(url=url, data=body, auth=auth) 5 >>> r.status_code 6 201 7 8 >>> content = r.json() 9 >>> print(content[u'body']) 10 Sounds great! I'll get right on it.
太棒了!噢,不!我原本是想說等我一會,因為我得去喂我的貓。如果我能夠編輯這條評論那就好了!幸運的是,GitHub 允許我們使用另一個 HTTP 動詞 PATCH 來編輯評論。我們來試試。
1 >>> print(content[u"id"]) 2 5804413 3 4 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."}) 5 >>> url = u"https://api.github.com/repos/requests/requests/issues/comments/5804413" 6 7 >>> r = requests.patch(url=url, data=body, auth=auth) 8 >>> r.status_code 9 200
非常好。現在,我們來折磨一下這個叫 Kenneth 的家伙,我決定要讓他急得團團轉,也不告訴他是我在搗蛋。這意味着我想刪除這條評論。GitHub 允許我們使用完全名副其實的 DELETE 方法來刪除評論。我們來清除該評論。
1 >>> r = requests.delete(url=url, auth=auth) 2 >>> r.status_code 3 204 4 >>> r.headers['status'] 5 '204 No Content'
很好。不見了。最后一件我想知道的事情是我已經使用了多少限額(ratelimit)。查查看,GitHub 在響應頭部發送這個信息,因此不必下載整個網頁,我將使用一個 HEAD 請求來獲取響應頭。
1 >>> r = requests.head(url=url, auth=auth) 2 >>> print r.headers 3 ... 4 'x-ratelimit-remaining': '4995' 5 'x-ratelimit-limit': '5000' 6 ...
很好。是時候寫個 Python 程序以各種刺激的方式濫用 GitHub 的 API,還可以使用 4995 次呢。
定制動詞
有時候你會碰到一些服務器,處於某些原因,它們允許或者要求用戶使用上述 HTTP 動詞之外的定制動詞。比如說 WEBDAV 服務器會要求你使用 MKCOL 方法。別擔心,Requests 一樣可以搞定它們。你可以使用內建的 .request
方法,例如:
1 >>> r = requests.request('MKCOL', url, data=data) 2 >>> r.status_code 3 200 # Assuming your call was correct
這樣你就可以使用服務器要求的任意方法動詞了。
響應頭鏈接字段
許多 HTTP API 都有響應頭鏈接字段的特性,它們使得 API 能夠更好地自我描述和自我顯露。
GitHub 在 API 中為 分頁 使用這些特性,例如:
1 >>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10' 2 >>> r = requests.head(url=url) 3 >>> r.headers['link'] 4 '<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 會自動解析這些響應頭鏈接字段,並使得它們非常易於使用:
1 >>> r.links["next"] 2 {'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'} 3 4 >>> r.links["last"] 5 {'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
傳輸適配器
從 v1.0.0 以后,Requests 的內部采用了模塊化設計。部分原因是為了實現傳輸適配器(Transport Adapter),你可以看看關於它的最早描述。傳輸適配器提供了一個機制,讓你可以為 HTTP 服務定義交互方法。尤其是它允許你應用服務前的配置。
Requests 自帶了一個傳輸適配器,也就是 HTTPAdapter
。 這個適配器使用了強大的 urllib3,為 Requests 提供了默認的 HTTP 和 HTTPS 交互。每當 Session
被初始化,就會有適配器附着在 Session
上,其中一個供 HTTP 使用,另一個供 HTTPS 使用。
Request 允許用戶創建和使用他們自己的傳輸適配器,實現他們需要的特殊功能。創建好以后,傳輸適配器可以被加載到一個會話對象上,附帶着一個說明,告訴會話適配器應該應用在哪個 web 服務上。
1 >>> s = requests.Session() 2 >>> s.mount('http://www.github.com', MyAdapter())
這個 mount 調用會注冊一個傳輸適配器的特定實例到一個前綴上面。加載以后,任何使用該會話的 HTTP 請求,只要其 URL 是以給定的前綴開頭,該傳輸適配器就會被使用到。
傳輸適配器的眾多實現細節不在本文檔的覆蓋范圍內,不過你可以看看接下來這個簡單的 SSL 用例。更多的用法,你也許該考慮為 BaseAdapter
創建子類。
示例: 指定的 SSL 版本
Requests 開發團隊刻意指定了內部庫(urllib3)的默認 SSL 版本。一般情況下這樣做沒有問題,不過是不是你可能會需要連接到一個服務節點,而該節點使用了和默認不同的 SSL 版本。
你可以使用傳輸適配器解決這個問題,通過利用 HTTPAdapter 現有的大部分實現,再加上一個 ssl_version 參數並將它傳遞到 urllib3
中。我們會創建一個傳輸適配器,用來告訴 urllib3
讓它使用 SSLv3:
1 import ssl 2 3 from requests.adapters import HTTPAdapter 4 from requests.packages.urllib3.poolmanager import PoolManager 5 6 7 class Ssl3HttpAdapter(HTTPAdapter): 8 """"Transport adapter" that allows us to use SSLv3.""" 9 10 def init_poolmanager(self, connections, maxsize, block=False): 11 self.poolmanager = PoolManager(num_pools=connections, 12 maxsize=maxsize, 13 block=block, 14 ssl_version=ssl.PROTOCOL_SSLv3)
阻塞和非阻塞
使用默認的傳輸適配器,Requests 不提供任何形式的非阻塞 IO。 Response.content
屬性會阻塞,直到整個響應下載完成。如果你需要更多精細控制,該庫的數據流功能(見 流式請求) 允許你每次接受少量的一部分響應,不過這些調用依然是阻塞式的。
如果你對於阻塞式 IO 有所顧慮,還有很多項目可以供你使用,它們結合了 Requests 和 Python 的某個異步框架。典型的優秀例子是 grequests 和 requests-futures。
Header 排序
在某些特殊情況下你也許需要按照次序來提供 header,如果你向 headers
關鍵字參數傳入一個 OrderedDict
,就可以向提供一個帶排序的 header。然而,Requests 使用的默認 header 的次序會被優先選擇,這意味着如果你在 headers
關鍵字參數中覆蓋了默認 header,和關鍵字參數中別的 header 相比,它們也許看上去會是次序錯誤的。
如果這個對你來說是個問題,那么用戶應該考慮在 Session
對象上面設置默認 header,只要將 Session
設為一個定制的 OrderedDict
即可。這樣就會讓它成為優選的次序。
超時(timeout)
為防止服務器不能及時響應,大部分發至外部服務器的請求都應該帶着 timeout 參數。在默認情況下,除非顯式指定了 timeout 值,requests 是不會自動進行超時處理的。如果沒有 timeout,你的代碼可能會掛起若干分鍾甚至更長時間。
連接超時指的是在你的客戶端實現到遠端機器端口的連接時(對應的是`connect()`_),Request 會等待的秒數。一個很好的實踐方法是把連接超時設為比 3 的倍數略大的一個數值,因為 TCP 數據包重傳窗口 (TCP packet retransmission window) 的默認大小是 3。
一旦你的客戶端連接到了服務器並且發送了 HTTP 請求,讀取超時指的就是客戶端等待服務器發送請求的時間。(特定地,它指的是客戶端要等待服務器發送字節之間的時間。在 99.9% 的情況下這指的是服務器發送第一個字節之前的時間)。
如果你制訂了一個單一的值作為 timeout,如下所示:
1 r = requests.get('https://github.com', timeout=5)
這一 timeout 值將會用作 connect
和 read
二者的 timeout。如果要分別制定,就傳入一個元組:
1 r = requests.get('https://github.com', timeout=(3.05, 27))
如果遠端服務器很慢,你可以讓 Request 永遠等待,傳入一個 None 作為 timeout 值,然后就沖咖啡去吧。
1 r = requests.get('https://github.com', timeout=None)