https://blog.csdn.net/qq_25134989/article/details/78800209
快速上手
迫不及待了嗎?本頁內容為如何入門 Requests 提供了很好的指引。其假設你已經安裝了 Requests。如果還沒有,去安裝一節看看吧。
首先,確認一下:
讓我們從一些簡單的示例開始吧。
發送請求
使用 Requests 發送網絡請求非常簡單。
一開始要導入 Requests 模塊:
>>> import requests
然后,嘗試獲取某個網頁。本例子中,我們來獲取 Github 的公共時間線:
>>> r = requests.get('https://github.com/timeline.json')
現在,我們有一個名為 r 的 Response 對象。我們可以從這個對象中獲取所有我們想要的信息。
Requests 簡便的 API 意味着所有 HTTP 請求類型都是顯而易見的。例如,你可以這樣發送一個 HTTP POST 請求:
>>> r = requests.post("http://httpbin.org/post")
漂亮,對吧?那么其他 HTTP 請求類型:PUT,DELETE,HEAD 以及 OPTIONS 又是如何的呢?都是一樣的簡單:
>>> r = requests.put("http://httpbin.org/put") >>> r = requests.delete("http://httpbin.org/delete") >>> r = requests.head("http://httpbin.org/get") >>> r = requests.options("http://httpbin.org/get")
都很不錯吧,但這也僅是 Requests 的冰山一角呢。
傳遞 URL 參數
你也許經常想為 URL 的查詢字符串(query string)傳遞某種數據。如果你是手工構建 URL,那么數據會以鍵/值對的形式置於 URL 中,跟在一個問號的后面。例如, httpbin.org/get?key=val。 Requests 允許你使用 params 關鍵字參數,以一個字符串字典來提供這些參數。舉例來說,如果你想傳遞 key1=value1 和 key2=value2 到 httpbin.org/get ,那么你可以使用如下代碼:
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.get("http://httpbin.org/get", params=payload)
通過打印輸出該 URL,你能看到 URL 已被正確編碼:
>>> print(r.url) http://httpbin.org/get?key2=value2&key1=value1
注意字典里值為 None 的鍵都不會被添加到 URL 的查詢字符串里。
你還可以將一個列表作為值傳入:
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']} >>> r = requests.get('http://httpbin.org/get', params=payload) >>> print(r.url) http://httpbin.org/get?key1=value1&key2=value2&key2=value3
響應內容
我們能讀取服務器響應的內容。再次以 GitHub 時間線為例:
>>> import requests >>> r = requests.get('https://github.com/timeline.json') >>> r.text u'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests 會自動解碼來自服務器的內容。大多數 unicode 字符集都能被無縫地解碼。
請求發出后,Requests 會基於 HTTP 頭部對響應的編碼作出有根據的推測。當你訪問 r.text 之時,Requests 會使用其推測的文本編碼。你可以找出 Requests 使用了什么編碼,並且能夠使用r.encoding 屬性來改變它:
>>> r.encoding 'utf-8' >>> r.encoding = 'ISO-8859-1'
如果你改變了編碼,每當你訪問 r.text ,Request 都將會使用 r.encoding 的新值。你可能希望在使用特殊邏輯計算出文本的編碼的情況下來修改編碼。比如 HTTP 和 XML 自身可以指定編碼。這樣的話,你應該使用 r.content 來找到編碼,然后設置 r.encoding 為相應的編碼。這樣就能使用正確的編碼解析 r.text 了。
在你需要的情況下,Requests 也可以使用定制的編碼。如果你創建了自己的編碼,並使用 codecs模塊進行注冊,你就可以輕松地使用這個解碼器名稱作為 r.encoding 的值, 然后由 Requests 來為你處理編碼。
二進制響應內容
你也能以字節的方式訪問請求響應體,對於非文本請求:
>>> r.content b'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests 會自動為你解碼 gzip 和 deflate 傳輸編碼的響應數據。
例如,以請求返回的二進制數據創建一張圖片,你可以使用如下代碼:
>>> from PIL import Image >>> from io import BytesIO >>> i = Image.open(BytesIO(r.content))
JSON 響應內容
Requests 中也有一個內置的 JSON 解碼器,助你處理 JSON 數據:
>>> import requests >>> r = requests.get('https://github.com/timeline.json') >>> r.json() [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
如果 JSON 解碼失敗, r.json() 就會拋出一個異常。例如,響應內容是 401 (Unauthorized),嘗試訪問 r.json() 將會拋出 ValueError: No JSON object could be decoded 異常。
需要注意的是,成功調用 r.json() 並**不**意味着響應的成功。有的服務器會在失敗的響應中包含一個 JSON 對象(比如 HTTP 500 的錯誤細節)。這種 JSON 會被解碼返回。要檢查請求是否成功,請使用 r.raise_for_status() 或者檢查 r.status_code 是否和你的期望相同。
原始響應內容
在罕見的情況下,你可能想獲取來自服務器的原始套接字響應,那么你可以訪問 r.raw。 如果你確實想這么干,那請你確保在初始請求中設置了 stream=True。具體你可以這么做:
>>> r = requests.get('https://github.com/timeline.json', stream=True) >>> r.raw <requests.packages.urllib3.response.HTTPResponse object at 0x101194810> >>> r.raw.read(10) '\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'
但一般情況下,你應該以下面的模式將文本流保存到文件:
with open(filename, 'wb') as fd: for chunk in r.iter_content(chunk_size): fd.write(chunk)
使用 Response.iter_content 將會處理大量你直接使用 Response.raw 不得不處理的。 當流下載時,上面是優先推薦的獲取內容方式。 Note that chunk_size can be freely adjusted to a number that may better fit your use cases.
定制請求頭
如果你想為請求添加 HTTP 頭部,只要簡單地傳遞一個 dict 給 headers 參數就可以了。
例如,在前一個示例中我們沒有指定 content-type:
>>> url = 'https://api.github.com/some/endpoint' >>> headers = {'user-agent': 'my-app/0.0.1'} >>> r = requests.get(url, headers=headers)
注意: 定制 header 的優先級低於某些特定的信息源,例如:
- 如果在
.netrc中設置了用戶認證信息,使用 headers= 設置的授權就不會生效。而如果設置了auth=參數,``.netrc`` 的設置就無效了。 - 如果被重定向到別的主機,授權 header 就會被刪除。
- 代理授權 header 會被 URL 中提供的代理身份覆蓋掉。
- 在我們能判斷內容長度的情況下,header 的 Content-Length 會被改寫。
更進一步講,Requests 不會基於定制 header 的具體情況改變自己的行為。只不過在最后的請求中,所有的 header 信息都會被傳遞進去。
注意: 所有的 header 值必須是 string、bytestring 或者 unicode。盡管傳遞 unicode header 也是允許的,但不建議這樣做。
更加復雜的 POST 請求
通常,你想要發送一些編碼為表單形式的數據——非常像一個 HTML 表單。要實現這個,只需簡單地傳遞一個字典給 data 參數。你的數據字典在發出請求時會自動編碼為表單形式:
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.post("http://httpbin.org/post", data=payload) >>> print(r.text) { ... "form": { "key2": "value2", "key1": "value1" }, ... }
你還可以為 data 參數傳入一個元組列表。在表單中多個元素使用同一 key 的時候,這種方式尤其有效:
>>> payload = (('key1', 'value1'), ('key1', 'value2')) >>> r = requests.post('http://httpbin.org/post', data=payload) >>> print(r.text) { ... "form": { "key1": [ "value1", "value2" ] }, ... }
很多時候你想要發送的數據並非編碼為表單形式的。如果你傳遞一個 string 而不是一個 dict,那么數據會被直接發布出去。
例如,Github API v3 接受編碼為 JSON 的 POST/PATCH 數據:
>>> import json >>> url = 'https://api.github.com/some/endpoint' >>> payload = {'some': 'data'} >>> r = requests.post(url, data=json.dumps(payload))
此處除了可以自行對 dict 進行編碼,你還可以使用 json 參數直接傳遞,然后它就會被自動編碼。這是 2.4.2 版的新加功能:
>>> url = 'https://api.github.com/some/endpoint' >>> payload = {'some': 'data'} >>> r = requests.post(url, json=payload)
POST一個多部分編碼(Multipart-Encoded)的文件
Requests 使得上傳多部分編碼文件變得很簡單:
>>> url = 'http://httpbin.org/post' >>> files = {'file': open('report.xls', 'rb')} >>> r = requests.post(url, files=files) >>> r.text { ... "files": { "file": "<censored...binary...data>" }, ... }
你可以顯式地設置文件名,文件類型和請求頭:
>>> url = 'http://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 { ... "files": { "file": "<censored...binary...data>" }, ... }
如果你想,你也可以發送作為文件來接收的字符串:
>>> url = 'http://httpbin.org/post' >>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')} >>> r = requests.post(url, files=files) >>> r.text { ... "files": { "file": "some,data,to,send\\nanother,row,to,send\\n" }, ... }
如果你發送一個非常大的文件作為 multipart/form-data 請求,你可能希望將請求做成數據流。默認下 requests 不支持, 但有個第三方包 requests-toolbelt 是支持的。你可以閱讀 toolbelt 文檔來了解使用方法。
在一個請求中發送多文件參考 高級用法 一節。
警告
我們強烈建議你用二進制模式(binary mode)打開文件。這是因為 Requests 可能會試圖為你提供 Content-Length header,在它這樣做的時候,這個值會被設為文件的字節數(bytes)。如果用文本模式(text mode)打開文件,就可能會發生錯誤。
響應狀態碼
我們可以檢測響應狀態碼:
>>> r = requests.get('http://httpbin.org/get') >>> r.status_code 200
為方便引用,Requests還附帶了一個內置的狀態碼查詢對象:
>>> r.status_code == requests.codes.ok True
如果發送了一個錯誤請求(一個 4XX 客戶端錯誤,或者 5XX 服務器錯誤響應),我們可以通過Response.raise_for_status() 來拋出異常:
>>> bad_r = requests.get('http://httpbin.org/status/404') >>> bad_r.status_code 404 >>> bad_r.raise_for_status() Traceback (most recent call last): File "requests/models.py", line 832, in raise_for_status raise http_error requests.exceptions.HTTPError: 404 Client Error
但是,由於我們的例子中 r 的 status_code 是 200 ,當我們調用 raise_for_status() 時,得到的是:
>>> r.raise_for_status() None
一切都挺和諧哈。
響應頭
我們可以查看以一個 Python 字典形式展示的服務器響應頭:
>>> r.headers { 'content-encoding': 'gzip', 'transfer-encoding': 'chunked', 'connection': 'close', 'server': 'nginx/1.0.4', 'x-runtime': '148ms', 'etag': '"e1ca502697e5c9317743dc078f67693f"', 'content-type': 'application/json' }
但是這個字典比較特殊:它是僅為 HTTP 頭部而生的。根據 RFC 2616, HTTP 頭部是大小寫不敏感的。
因此,我們可以使用任意大寫形式來訪問這些響應頭字段:
>>> r.headers['Content-Type'] 'application/json' >>> r.headers.get('content-type') 'application/json'
它還有一個特殊點,那就是服務器可以多次接受同一 header,每次都使用不同的值。但 Requests 會將它們合並,這樣它們就可以用一個映射來表示出來,參見 RFC 7230:
A recipient MAY combine multiple header fields with the same field name into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma.
接收者可以合並多個相同名稱的 header 欄位,把它們合為一個 "field-name: field-value" 配對,將每個后續的欄位值依次追加到合並的欄位值中,用逗號隔開即可,這樣做不會改變信息的語義。
重定向與請求歷史
默認情況下,除了 HEAD, Requests 會自動處理所有重定向。
可以使用響應對象的 history 方法來追蹤重定向。
Response.history 是一個 Response 對象的列表,為了完成請求而創建了這些對象。這個對象列表按照從最老到最近的請求進行排序。
例如,Github 將所有的 HTTP 請求重定向到 HTTPS:
>>> r = requests.get('http://github.com') >>> r.url 'https://github.com/' >>> r.status_code 200 >>> r.history [<Response [301]>]
如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通過allow_redirects 參數禁用重定向處理:
>>> r = requests.get('http://github.com', allow_redirects=False) >>> r.status_code 301 >>> r.history []
如果你使用了 HEAD,你也可以啟用重定向:
>>> r = requests.head('http://github.com', allow_redirects=True) >>> r.url 'https://github.com/' >>> r.history [<Response [301]>]
超時
你可以告訴 requests 在經過以 timeout 參數設定的秒數時間之后停止等待響應。基本上所有的生產代碼都應該使用這一參數。如果不使用,你的程序可能會永遠失去響應:
>>> requests.get('http://github.com', timeout=0.001) Traceback (most recent call last): File "<stdin>", line 1, in <module> requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
注意
timeout 僅對連接過程有效,與響應體的下載無關。 timeout 並不是整個下載響應的時間限制,而是如果服務器在 timeout 秒內沒有應答,將會引發一個異常(更精確地說,是在timeout 秒內沒有從基礎套接字上接收到任何字節的數據時)If no timeout is specified explicitly, requests do not time out.
錯誤與異常
遇到網絡問題(如:DNS 查詢失敗、拒絕連接等)時,Requests 會拋出一個 ConnectionError 異常。
如果 HTTP 請求返回了不成功的狀態碼, Response.raise_for_status() 會拋出一個 HTTPError 異常。
若請求超時,則拋出一個 Timeout 異常。
若請求超過了設定的最大重定向次數,則會拋出一個 TooManyRedirects 異常。
所有Requests顯式拋出的異常都繼承自 requests.exceptions.RequestException 。
高級用法
本篇文檔涵蓋了 Requests 的一些高級特性。
會話對象
會話對象讓你能夠跨請求保持某些參數。它也會在同一個 Session 實例發出的所有請求之間保持 cookie, 期間使用 urllib3 的 connection pooling 功能。所以如果你向同一主機發送多個請求,底層的 TCP 連接將會被重用,從而帶來顯著的性能提升。 (參見 HTTP persistent connection).
會話對象具有主要的 Requests API 的所有方法。
我們來跨請求保持一些 cookie:
s = requests.Session() s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') r = s.get("http://httpbin.org/cookies") print(r.text) # '{"cookies": {"sessioncookie": "123456789"}}'
會話也可用來為請求方法提供缺省數據。這是通過為會話對象的屬性提供數據來實現的:
s = requests.Session() s.auth = ('user', 'pass') s.headers.update({'x-test': 'true'}) # both 'x-test' and 'x-test2' are sent s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
任何你傳遞給請求方法的字典都會與已設置會話層數據合並。方法層的參數覆蓋會話的參數。
不過需要注意,就算使用了會話,方法級別的參數也不會被跨請求保持。下面的例子只會和第一個請求發送 cookie ,而非第二個:
s = requests.Session() r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'}) print(r.text) # '{"cookies": {"from-my": "browser"}}' r = s.get('http://httpbin.org/cookies') print(r.text) # '{"cookies": {}}'
如果你要手動為會話添加 cookie,就使用 Cookie utility 函數 來操縱 Session.cookies。
會話還可以用作前后文管理器:
with requests.Session() as s: s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
這樣就能確保 with 區塊退出后會話能被關閉,即使發生了異常也一樣。
從字典參數中移除一個值
有時你會想省略字典參數中一些會話層的鍵。要做到這一點,你只需簡單地在方法層參數中將那個鍵的值設置為 None ,那個鍵就會被自動省略掉。
包含在一個會話中的所有數據你都可以直接使用。學習更多細節請閱讀 會話 API 文檔。
請求與響應對象
任何時候進行了類似 requests.get() 的調用,你都在做兩件主要的事情。其一,你在構建一個 Request對象, 該對象將被發送到某個服務器請求或查詢一些資源。其二,一旦 requests 得到一個從服務器返回的響應就會產生一個 Response 對象。該響應對象包含服務器返回的所有信息,也包含你原來創建的 Request 對象。如下是一個簡單的請求,從 Wikipedia 的服務器得到一些非常重要的信息:
>>> r = requests.get('http://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'}
然而,如果想得到發送到服務器的請求的頭部,我們可以簡單地訪問該請求,然后是該請求的頭部:
>>> r.request.headers {'Accept-Encoding': 'identity, deflate, compress, gzip', 'Accept': '*/*', 'User-Agent': 'python-requests/0.13.1'}
准備的請求 (Prepared Request)
當你從 API 或者會話調用中收到一個 Response 對象時,request 屬性其實是使用了PreparedRequest。有時在發送請求之前,你需要對 body 或者 header (或者別的什么東西)做一些額外處理,下面演示了一個簡單的做法:
from requests import Request, Session s = Session() req = Request('GET', url, data=data, headers=header ) prepped = req.prepare() # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code)
由於你沒有對 Request 對象做什么特殊事情,你立即准備和修改了 PreparedRequest 對象,然后把它和別的參數一起發送到 requests.* 或者 Session.*。
然而,上述代碼會失去 Requests Session 對象的一些優勢, 尤其 Session 級別的狀態,例如 cookie 就不會被應用到你的請求上去。要獲取一個帶有狀態的 PreparedRequest, 請用Session.prepare_request() 取代 Request.prepare() 的調用,如下所示:
from requests import Request, Session s = Session() req = Request('GET', url, data=data headers=headers ) prepped = s.prepare_request(req) # do something with prepped.body # do something with prepped.headers resp = s.send(prepped, stream=stream, verify=verify, proxies=proxies, cert=cert, timeout=timeout ) print(resp.status_code)
SSL 證書驗證
Requests 可以為 HTTPS 請求驗證 SSL 證書,就像 web 瀏覽器一樣。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 設置了 SSL:
>>> requests.get('https://github.com', verify=True) <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 工具處理。
你還可以通過 REQUESTS_CA_BUNDLE 環境變量定義可信任 CA 列表。
如果你將 verify 設置為 False,Requests 也能忽略對 SSL 證書的驗證。
>>> requests.get('https://kennethreitz.org', verify=False) <Response [200]>
默認情況下, verify 是設置為 True 的。選項 verify 僅應用於主機證書。
# 對於私有證書,你也可以傳遞一個 CA_BUNDLE 文件的路徑給 verify。你也可以設置 #REQUEST_CA_BUNDLE 環境變量。
客戶端證書
你也可以指定一個本地證書用作客戶端證書,可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key')) <Response [200]>
或者保持在會話中:
s = requests.Session() s.cert = '/path/client.cert'
如果你指定了一個錯誤路徑或一個無效的證書:
>>> 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
警告
警告
本地證書的私有 key 必須是解密狀態。目前,Requests 不支持使用加密的 key。
CA 證書
Requests 默認附帶了一套它信任的根證書,來自於 Mozilla trust store。然而它們在每次 Requests 更新時才會更新。這意味着如果你固定使用某一版本的 Requests,你的證書有可能已經 太舊了。
從 Requests 2.4.0 版之后,如果系統中裝了 certifi 包,Requests 會試圖使用它里邊的 證書。這樣用戶就可以在不修改代碼的情況下更新他們的可信任證書。
為了安全起見,我們建議你經常更新 certifi!
響應體內容工作流
默認情況下,當你進行網絡請求后,響應體會立即被下載。你可以通過 stream 參數覆蓋這個行為,推遲下載響應體直到訪問 Response.content 屬性:
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' r = requests.get(tarball_url, stream=True)
此時僅有響應頭被下載下來了,連接保持打開狀態,因此允許我們根據條件獲取內容:
if int(r.headers['content-length']) < TOO_LONG: content = r.content ...
你可以進一步使用 Response.iter_content 和 Response.iter_lines 方法來控制工作流,或者以Response.raw 從底層 urllib3 的 urllib3.HTTPResponse <urllib3.response.HTTPResponse 讀取未解碼的相應體。
如果你在請求中把 stream 設為 True,Requests 無法將連接釋放回連接池,除非你 消耗了所有的數據,或者調用了 Response.close。 這樣會帶來連接效率低下的問題。如果你發現你在使用stream=True 的同時還在部分讀取請求的 body(或者完全沒有讀取 body),那么你就應該考慮使用 with 語句發送請求,這樣可以保證請求一定會被關閉:
with requests.get('http://httpbin.org/get', stream=True) as r: # 在此處理響應。
保持活動狀態(持久連接)
好消息——歸功於 urllib3,同一會話內的持久連接是完全自動處理的!同一會話內你發出的任何請求都會自動復用恰當的連接!
注意:只有所有的響應體數據被讀取完畢連接才會被釋放為連接池;所以確保將 stream 設置為False 或讀取 Response 對象的 content 屬性。
流式上傳
Requests支持流式上傳,這允許你發送大的數據流或文件而無需先把它們讀入內存。要使用流式上傳,僅需為你的請求體提供一個類文件對象即可:
with open('massive-body') as f: requests.post('http://some.url/streamed', data=f)
警告
警告
我們強烈建議你用二進制模式(binary mode)打開文件。這是因為 requests 可能會為你提供 header 中的 Content-Length,在這種情況下該值會被設為文件的字節數。如果你用文本模式打開文件,就可能碰到錯誤。
塊編碼請求
對於出去和進來的請求,Requests 也支持分塊傳輸編碼。要發送一個塊編碼的請求,僅需為你的請求體提供一個生成器(或任意沒有具體長度的迭代器):
def gen(): yield 'hi' yield 'there' 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):
>>> url = 'http://httpbin.org/post' >>> multiple_files = [ ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] >>> r = requests.post(url, files=multiple_files) >>> r.text { ... 'files': {'images': 'data:image/png;base64,iVBORw ....'} 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a', ... }
警告
警告
我們強烈建議你用二進制模式(binary mode)打開文件。這是因為 requests 可能會為你提供 header 中的 Content-Length,在這種情況下該值會被設為文件的字節數。如果你用文本模式打開文件,就可能碰到錯誤。
事件掛鈎
Requests有一個鈎子系統,你可以用來操控部分請求過程,或信號事件處理。
可用的鈎子:
-
response: - 從一個請求產生的響應
你可以通過傳遞一個 {hook_name: callback_function} 字典給 hooks 請求參數為每個請求分配一個鈎子函數:
hooks=dict(response=print_url)
callback_function 會接受一個數據塊作為它的第一個參數。
def print_url(r, *args, **kwargs): print(r.url)
若執行你的回調函數期間發生錯誤,系統會給出一個警告。
若回調函數返回一個值,默認以該值替換傳進來的數據。若函數未返回任何東西,也沒有什么其他的影響。
我們來在運行期間打印一些請求方法的參數:
>>> requests.get('http://httpbin.org', hooks=dict(response=print_url)) http://httpbin.org <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 對相應進行迭代:
import json import requests r = requests.get('http://httpbin.org/stream/20', stream=True) for line in r.iter_lines(): # filter out keep-alive new lines if line: decoded_line = line.decode('utf-8') print(json.loads(decoded_line))
當使用 decode_unicode=True 在 Response.iter_lines() 或 Response.iter_content() 中時,你需要提供一個回退編碼方式,以防服務器沒有提供默認回退編碼,從而導致錯誤:
r = requests.get('http://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() # 保存第一行以供后面使用,或者直接跳過 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)
你也可以通過環境變量 HTTP_PROXY 和 HTTPS_PROXY 來配置代理。
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="http://10.10.1.10:1080"
$ python
>>> import requests
>>> requests.get("http://example.org")
若你的代理需要使用HTTP Basic Auth,可以使用 http://user:password@host/ 語法:
proxies = { "http": "http://user:pass@10.10.1.10:3128/", }
要為某個特定的連接方式或者主機設置代理,使用 scheme://hostname 作為 key, 它會針對指定的主機和連接方式進行匹配。
proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
注意,代理 URL 必須包含連接方式。
合規性
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 的信息。我們可以這樣去做:
>>> import requests >>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我們應該確認 GitHub 是否正確響應。如果正確響應,我們想弄清響應內容是什么類型的。像這樣去做:
>>> if (r.status_code == requests.codes.ok): ... print r.headers['content-type'] ... application/json; charset=utf-8
可見,GitHub 返回了 JSON 數據,非常好,這樣就可以使用 r.json 方法把這個返回的數據解析成 Python 對象。
>>> commit_data = r.json() >>> print commit_data.keys() [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message'] >>> print commit_data[u'committer'] {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'} >>> print commit_data[u'message'] makin' history
到目前為止,一切都非常簡單。嗯,我們來研究一下 GitHub 的 API。我們可以去看看文檔,但如果使用 Requests 來研究也許會更有意思一點。我們可以借助 Requests 的 OPTIONS 動詞來看看我們剛使用過的 url 支持哪些 HTTP 方法。
>>> verbs = requests.options(r.url) >>> verbs.status_code 500
額,這是怎么回事?毫無幫助嘛!原來 GitHub,與許多 API 提供方一樣,實際上並未實現 OPTIONS 方法。這是一個惱人的疏忽,但沒關系,那我們可以使用枯燥的文檔。然而,如果 GitHub 正確實現了 OPTIONS,那么服務器應該在響應頭中返回允許用戶使用的 HTTP 方法,例如:
>>> verbs = requests.options('http://a-good-website.com/api/cats') >>> print verbs.headers['allow'] GET,HEAD,POST,OPTIONS
轉而去查看文檔,我們看到對於提交信息,另一個允許的方法是 POST,它會創建一個新的提交。由於我們正在使用 Requests 代碼庫,我們應盡可能避免對它發送笨拙的 POST。作為替代,我們來玩玩 GitHub 的 Issue 特性。
本篇文檔是回應 Issue #482 而添加的。鑒於該問題已經存在,我們就以它為例。先獲取它。
>>> r = requests.get('https://api.github.com/requests/kennethreitz/requests/issues/482') >>> r.status_code 200 >>> issue = json.loads(r.text) >>> print(issue[u'title']) Feature any http verb in docs >>> print(issue[u'comments']) 3
Cool,有 3 個評論。我們來看一下最后一個評論。
>>> r = requests.get(r.url + u'/comments') >>> r.status_code 200 >>> comments = r.json() >>> print comments[0].keys() [u'body', u'url', u'created_at', u'updated_at', u'user', u'id'] >>> print comments[2][u'body'] Probably in the "advanced" section
嗯,那看起來似乎是個愚蠢之處。我們發表個評論來告訴這個評論者他自己的愚蠢。那么,這個評論者是誰呢?
>>> print comments[2][u'user'][u'login'] kennethreitz
好,我們來告訴這個叫 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/requests/requests/issues/482/comments" >>> r = requests.post(url=url, data=body) >>> r.status_code 404
額,這有點古怪哈。可能我們需要驗證身份。那就有點糾結了,對吧?不對。Requests 簡化了多種身份驗證形式的使用,包括非常常見的 Basic Auth。
>>> from requests.auth import HTTPBasicAuth >>> auth = HTTPBasicAuth('fake@example.com', 'not_a_real_password') >>> r = requests.post(url=url, data=body, auth=auth) >>> r.status_code 201 >>> content = r.json() >>> print(content[u'body']) Sounds great! I'll get right on it.
太棒了!噢,不!我原本是想說等我一會,因為我得去喂我的貓。如果我能夠編輯這條評論那就好了!幸運的是,GitHub 允許我們使用另一個 HTTP 動詞 PATCH 來編輯評論。我們來試試。
>>> print(content[u"id"]) 5804413 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."}) >>> url = u"https://api.github.com/repos/requests/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
這樣你就可以使用服務器要求的任意方法動詞了。
響應頭鏈接字段
許多 HTTP API 都有響應頭鏈接字段的特性,它們使得 API 能夠更好地自我描述和自我顯露。
GitHub 在 API 中為 分頁 使用這些特性,例如:
>>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10' >>> r = requests.head(url=url) >>> r.headers['link'] '<https://api.github.com/users/kennethreitz/repos?page=2&per_page=10>; rel="next", <https://api.github.com/users/kennethreitz/repos?page=6&per_page=10>; rel="last"'
Requests 會自動解析這些響應頭鏈接字段,並使得它們非常易於使用:
>>> r.links["next"] {'url': 'https://api.github.com/users/kennethreitz/repos?page=2&per_page=10', 'rel': 'next'} >>> r.links["last"] {'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'}
傳輸適配器
從 v1.0.0 以后,Requests 的內部采用了模塊化設計。部分原因是為了實現傳輸適配器(Transport Adapter),你可以看看關於它的最早描述。傳輸適配器提供了一個機制,讓你可以為 HTTP 服務定義交互方法。尤其是它允許你應用服務前的配置。
Requests 自帶了一個傳輸適配器,也就是 HTTPAdapter。 這個適配器使用了強大的 urllib3,為 Requests 提供了默認的 HTTP 和 HTTPS 交互。每當 Session 被初始化,就會有適配器附着在Session 上,其中一個供 HTTP 使用,另一個供 HTTPS 使用。
Request 允許用戶創建和使用他們自己的傳輸適配器,實現他們需要的特殊功能。創建好以后,傳輸適配器可以被加載到一個會話對象上,附帶着一個說明,告訴會話適配器應該應用在哪個 web 服務上。
>>> s = requests.Session() >>> s.mount('http://www.github.com', MyAdapter())
這個 mount 調用會注冊一個傳輸適配器的特定實例到一個前綴上面。加載以后,任何使用該會話的 HTTP 請求,只要其 URL 是以給定的前綴開頭,該傳輸適配器就會被使用到。
傳輸適配器的眾多實現細節不在本文檔的覆蓋范圍內,不過你可以看看接下來這個簡單的 SSL 用例。更多的用法,你也許該考慮為 BaseAdapter 創建子類。
示例: 指定的 SSL 版本
Requests 開發團隊刻意指定了內部庫(urllib3)的默認 SSL 版本。一般情況下這樣做沒有問題,不過是不是你可能會需要連接到一個服務節點,而該節點使用了和默認不同的 SSL 版本。
你可以使用傳輸適配器解決這個問題,通過利用 HTTPAdapter 現有的大部分實現,再加上一個ssl_version 參數並將它傳遞到 urllib3 中。我們會創建一個傳輸適配器,用來告訴 urllib3 讓它使用 SSLv3:
import ssl from requests.adapters import HTTPAdapter from requests.packages.urllib3.poolmanager import PoolManager 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 有所顧慮,還有很多項目可以供你使用,它們結合了 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,如下所示:
r = requests.get('https://github.com', timeout=5)
這一 timeout 值將會用作 connect 和 read 二者的 timeout。如果要分別制定,就傳入一個元組:
r = requests.get('https://github.com', timeout=(3.05, 27))
如果遠端服務器很慢,你可以讓 Request 永遠等待,傳入一個 None 作為 timeout 值,然后就沖咖啡去吧。
r = requests.get('https://github.com', timeout=None)
身份認證
本篇文檔討論如何配合 Requests 使用多種身份認證方式。
許多 web 服務都需要身份認證,並且也有多種不同的認證類型。 以下,我們會從簡單到復雜概述 Requests 中可用的幾種身份認證形式。
基本身份認證
許多要求身份認證的web服務都接受 HTTP Basic Auth。這是最簡單的一種身份認證,並且 Requests 對這種認證方式的支持是直接開箱即可用。
以 HTTP Basic Auth 發送請求非常簡單:
>>> from requests.auth import HTTPBasicAuth >>> requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass')) <Response [200]>
事實上,HTTP Basic Auth 如此常見,Requests 就提供了一種簡寫的使用方式:
>>> requests.get('https://api.github.com/user', auth=('user', 'pass')) <Response [200]>
像這樣在一個元組中提供認證信息與前一個 HTTPBasicAuth 例子是完全相同的。
摘要式身份認證
另一種非常流行的 HTTP 身份認證形式是摘要式身份認證,Requests 對它的支持也是開箱即可用的:
>>> from requests.auth import HTTPDigestAuth >>> url = 'http://httpbin.org/digest-auth/auth/user/pass' >>> requests.get(url, auth=HTTPDigestAuth('user', 'pass')) <Response [200]>
OAuth 1 認證
Oauth 是一種常見的 Web API 認證方式。 requests-oauthlib 庫可以讓 Requests 用戶簡單地創建 OAuth 認證的請求:
- ::
-
>>> import requests >>> from requests_oauthlib import OAuth1>>> url = 'https://api.twitter.com/1.1/account/verify_credentials.json' >>> auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET', ... 'USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')>>> requests.get(url, auth=auth) <Response [200]>
關於 OAuth 工作流程的更多信息,請參見 OAuth 官方網站。 關於 requests-oauthlib 的文檔和用例,請參見 GitHub 的 requests_oauthlib 代碼庫。
OAuth 2 與 OpenID 連接認證
requests-oauthlib 庫還可以處理 OAuth 2,OAuth 2 是 OpenID 連接的基礎機制。 請查看requests-oauthlib OAuth2 documentation 文檔以了解 OAuth 2 的各種認證管理流程:
其他身份認證形式
Requests 的設計允許其他形式的身份認證用簡易的方式插入其中。開源社區的成員時常為更復雜或不那么常用的身份認證形式編寫認證處理插件。其中一些最優秀的已被收集在 Requests organization 頁面中,包括:
如果你想使用其中任何一種身份認證形式,直接去它們的 GitHub 頁面,依照說明進行。
新的身份認證形式
如果你找不到所需要的身份認證形式的一個良好實現,你也可以自己實現它。Requests 非常易於添加你自己的身份認證形式。
要想自己實現,就從 AuthBase 繼承一個子類,並實現 __call__() 方法:
>>> import requests >>> class MyAuth(requests.auth.AuthBase): ... def __call__(self, r): ... # Implement my authentication ... return r ... >>> url = 'http://httpbin.org/get' >>> requests.get(url, auth=MyAuth()) <Response [200]>
當一個身份認證模塊被附加到一個請求上,在設置 request 期間就會調用該模塊。因此 __call__方法必須完成使得身份認證生效的所有事情。一些身份認證形式會額外地添加鈎子來提供進一步的功能。
摘自:
http://docs.python-requests.org/zh_CN/latest/user/quickstart.html#id2
http://docs.python-requests.org/zh_CN/latest/user/advanced.html
http://docs.python-requests.org/zh_CN/latest/user/authentication.html
