requests是python實現的最簡單易用的HTTP庫,建議爬蟲使用requests
import requests url = "https://api.github.com/events"
獲取某個網頁
import requests r = requests.get("https://api.github.com/events") print(r) # <Response [200]> print(type(r)) # <class 'requests.models.Response'> print(r.status_code) # 200
各種請求
# 發送一個 HTTP POST 請求: r = requests.post("http://httpbin.org/post",data = {'key':'value'}) r = requests.delete('http://httpbin.org/delete') # 發送一個 HTTP delete 請求: r = requests.head('http://httpbin.org/get') # 發送一個 HTTP head 請求: r = requests.options('http://httpbin.org/get') # 發送一個 HTTP options 請求:
`
get 傳遞URL參數
?+鍵值對
response1 = requests.get("http://httpbin.org/get?key1=value1") print(response1.url) #http://httpbin.org/get?key1=value1
requests提供了params關鍵字參數來傳遞參數
parameter = { "key1":"value1", "key2":"value2" } response2 = requests.get("http://httpbin.org/get",params = parameter) print(response2.url) # http://httpbin.org/get?key1=value1&key2=value2
還可以將一個列表作為值傳入
parameter = { "key1":"value1", "key2":["value21","value22"] } response3 = requests.get("http://httpbin.org/get",params = parameter) print(response3.url) # http://httpbin.org/get?key1=value1&key2=value21&key2=value22
注意字典里值為 None 的鍵都不會被添加到 URL 的查詢字符串里。
parameter = { "key1":"value", "key2":None } response4 = requests.get("http://httpbin.org/get",params = parameter) print(response4.url) #http://httpbin.org/get?key1=value
響應內容
我們能讀取服務器響應的內容。再次以 GitHub 時間線為例:
Requests 會自動解碼來自服務器的內容。大多數 unicode 字符集都能被無縫地解碼。
response = requests.get("https://api.github.com/events") print(response) # <Response [200]> # print(response.text) # Json格式
請求發出后,Requests 會基於 HTTP 頭部對響應的編碼作出有根據的推測。當訪問 r.text 之時,Requests 會使用其推測的文本編碼。可以找出 Requests 使用了什么編碼,並且能夠使用 r.encoding 屬性來改變它:
print(response.encoding) # utf-8 #使用 r.content 來找到編碼,然后設置 r.encoding 為相應的編碼 print(response.encoding) # ISO-8859-1 #改變編碼 response.encoding = 'ISO-8859-1' #二進制響應內容 #以字節的方式訪問請求響應體,對於非文本請求: print(type(response.content)) print(type(response.text))
response.text返回的是Unicode型的數據。---文本
response.content返回的是bytes型也就是二進制的數據。-----圖片等
但是兩者打印輸出是一樣的
Json響應內容
Requests 中有一個內置的 JSON 解碼器,處理 JSON 數據
response = requests.get(url) # print(response.json()) #json數據 # json成功調用並不意外者響應成功,有的服務器會在失敗的響應中包含一個 JSON 對象(比如 HTTP 500 的錯誤細節需要status_code判斷 print(response.status_code) #200 print(response.raise_for_status()) #none
原始響應
暫未看懂,先略過
定制請求頭
如果想為請求添加 HTTP 頭部,只要簡單地傳遞一個 dict 給 headers 參數就可以了。
以知乎為例子
response =requests.get("https://www.zhihu.com") print(response.text) #報錯
此時會報錯,因為訪問知乎需要頭部信息,在谷歌瀏覽器輸入chrome://version,就可以得到用戶代理

import requests new_headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36" } response = requests.get("https://www.zhihu.com",headers = new_headers) print(response.text) #正常輸出
這樣就可以正常的訪問知乎了
Post請求
發送一些編碼為表單形式的數據——非常像一個 HTML 表單。要實現這個,只需簡單地傳遞一個字典給 data 參數。數據字典在發出請求時會自動編碼為表單形式:通過在發送post請求時添加一個data參數,這個data參數可以通過字典構造成,這樣對於發送post請求就非常方便
payload = { "key1":"value1", "key2":"value2" } response = requests.post("http://httpbin.org/post",data = payload) print(response.text)
還可以為 data 參數傳入一個元組列表。在表單中多個元素使用同一 key 的時候,這種方式尤其有效,字典會第二個值覆蓋第一個值
payload = (("key1","value1"),("key1","value2")) response = requests.post("http://httpbin.org/post",data = payload) print(response.text)
響應
可以通過response獲得很多屬性,例子如下
import requests response = requests.get("http://www.baidu.com") print(type(response.status_code),response.status_code) #< class 'int'> 200 print(type(response.headers),response.headers) # 頭部信息 print(type(response.cookies),response.cookies) #<class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> print(type(response.url),response.url) # <class 'str'> http://www.baidu.com/ print(type(response.history),response.history) # <class 'list'> []
狀態碼判斷,requests還附帶了一個內置的狀態碼查詢對象
主要有如下內容:
100: ('continue',), 101: ('switching_protocols',), 102: ('processing',), 103: ('checkpoint',), 122: ('uri_too_long', 'request_uri_too_long'), 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\o/', '✓'), 201: ('created',), 202: ('accepted',), 203: ('non_authoritative_info', 'non_authoritative_information'), 204: ('no_content',), 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 208: ('already_reported',), 226: ('im_used',), Redirection. 300: ('multiple_choices',), 301: ('moved_permanently', 'moved', '\o-'), 302: ('found',), 303: ('see_other', 'other'), 304: ('not_modified',), 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 308: ('permanent_redirect', 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 Client Error. 400: ('bad_request', 'bad'), 401: ('unauthorized',), 402: ('payment_required', 'payment'), 403: ('forbidden',), 404: ('not_found', '-o-'), 405: ('method_not_allowed', 'not_allowed'), 406: ('not_acceptable',), 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 408: ('request_timeout', 'timeout'), 409: ('conflict',), 410: ('gone',), 411: ('length_required',), 412: ('precondition_failed', 'precondition'), 413: ('request_entity_too_large',), 414: ('request_uri_too_large',), 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 417: ('expectation_failed',), 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 421: ('misdirected_request',), 422: ('unprocessable_entity', 'unprocessable'), 423: ('locked',), 424: ('failed_dependency', 'dependency'), 425: ('unordered_collection', 'unordered'), 426: ('upgrade_required', 'upgrade'), 428: ('precondition_required', 'precondition'), 429: ('too_many_requests', 'too_many'), 431: ('header_fields_too_large', 'fields_too_large'), 444: ('no_response', 'none'), 449: ('retry_with', 'retry'), 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 499: ('client_closed_request',), Server Error. 500: ('internal_server_error', 'server_error', '/o\', '✗'), 501: ('not_implemented',), 502: ('bad_gateway',), 503: ('service_unavailable', 'unavailable'), 504: ('gateway_timeout',), 505: ('http_version_not_supported', 'http_version'), 506: ('variant_also_negotiates',), 507: ('insufficient_storage',), 509: ('bandwidth_limit_exceeded', 'bandwidth'), 510: ('not_extended',), 511: ('network_authentication_required', 'network_auth', 'network_authentication'),
例子·
import requests response= requests.get("http://www.baidu.com") if response.status_code == requests.codes.ok: print("訪問成功") # 可以直接使用狀態碼,更方便 if response.status_code == 200: print("訪問成功")
POST一個多部分編碼(Multipart-Encoded)的文件
Requests 使得上傳多部分編碼文件變得很簡單:
文件上傳,實現方法和其他參數類似,也是構造一個字典然后通過files參數傳遞
import requests url = 'http://httpbin.org/post' files = {"files":open('test.py', 'rb')} response = requests.post(url,files = files) print(response.text)
可以顯式地設置文件名,文件類型和請求頭:
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
也可以發送作為文件來接收的字符串:
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
Cookie
如果某個響應中包含一些 cookie,可以快速訪問它們:
import requests response = requests.get("http://www.baidu.com") print(response.cookies) for key,value in response.cookies.items(): print(key+"="+value)
要想發送的cookies到服務器,可以使用 cookies 參數:
import requests url = 'http://httpbin.org/cookies' cookies = dict(cookies_are='working') response = requests.get(url, cookies=cookies) print(response.text)
Cookie 的返回對象為 RequestsCookieJar,它的行為和字典類似,但接口更為完整,適合跨域名跨路徑使用。還可以把 Cookie Jar 傳到 Requests 中:
jar = requests.cookies.RequestsCookieJar() jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies') jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere') url = 'http://httpbin.org/cookies' response = requests.get(url, cookies=jar) print(response.text)
重定向與請求歷史
- 默認情況下,除了 HEAD, Requests 會自動處理所有重定向。
- 可以使用響應對象的 history 方法來追蹤重定向。
- Response.history 是一個 Response 對象的列表,為了完成請求而創建了這些對象。這個對象列表按照從最老到最近的請求進行排序。例如,Github 將所有的 HTTP 請求重定向到 HTTPS:
response = requests.get('http://github.com') print(response.url) # 'https://github.com/' print(response.status_code) # 200 print(response.history) # [<Response [301]>]
如果使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么可以通過 allow_redirects 參數禁用重定向處理:
response = requests.get('http://github.com',allow_redirects=False) print(response.url) # 'https://github.com/' print(response.status_code) # 300 print(response.history) # []
如果使用了 HEAD,也可以啟用重定向:
response = requests.head('http://github.com',allow_redirects=True) print(response.url) # 'https://github.com/' print(response.status_code) # 200 print(response.history) # [<Response [301]>]
超時
可以告訴 requests 在經過以 timeout 參數設定的秒數時間之后停止等待響應。基本上所有的生產代碼都應該使用這一參數。如果不使用,的程序可能會永遠失去響應:
response1 = requests.get('http://github.com', timeout=100) print(response1) #<Response [200]> response2 = requests.get('http://github.com', timeout=0.1) print(response2) # 報錯ReadTimeout
timeout 僅對連接過程有效,與響應體的下載無關。 timeout 並不是整個下載響應的時間限制,而是如果服務器在 timeout 秒內沒有應答,將會引發一個異常(更精確地說,是在 timeout 秒內沒有從基礎套接字上接收到任何字節的數據時)If no timeout is specified explicitly, requests do not time out.
錯誤與異常
http://www.python-requests.org/en/master/api/#exceptions
- 遇到網絡問題(如:DNS 查詢失敗、拒絕連接等)時,Requests 會拋出一個 ConnectionError 異常。
- 如果 HTTP 請求返回了不成功的狀態碼, Response.raise_for_status() 會拋出一個 HTTPError 異常。
- 若請求超時,則拋出一個 Timeout 異常。
- 若請求超過了設定的最大重定向次數,則會拋出一個 TooManyRedirects 異常。
- 所有Requests顯式拋出的異常都繼承自 requests.exceptions.RequestException 。
會話對象
會話對象讓能夠跨請求保持某些參數。它也會在同一個 Session 實例發出的所有請求之間保持 cookie, 期間使用 urllib3 的 connection pooling 功能。所以如果向同一主機發送多個請求,底層的 TCP 連接將會被重用,從而帶來顯著的性能提升。 (參見 https://en.wikipedia.org/wiki/HTTP_persistent_connection).會話對象具有主要的 Requests API 的所有方法。
我們來跨請求保持一些 cookie:
session = requests.Session() session.get('http://httpbin.org/cookies/set/sessioncookie/123456789') response = session.get("http://httpbin.org/cookies") print(type(response)) # <class 'requests.models.Response'> print(response.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 ,那個鍵就會被自動省略掉。
請求與響應對象
任何時候進行了類似 requests.get() 的調用,都在做兩件主要的事情。其一,在構建一個 Request 對象, 該對象將被發送到某個服務器請求或查詢一些資源。其二,一旦 requests 得到一個從服務器返回的響應就會產生一個 Response 對象。該響應對象包含服務器返回的所有信息,也包含原來創建的 Request 對象。如下是一個簡單的請求,從 Wikipedia 的服務器得到一些非常重要的信息:
# response = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
如果想訪問服務器返回給我們的響應頭部信息,可以這樣做:
print(response.headers)
然而,如果想得到發送到服務器的請求的頭部,我們可以簡單地訪問該請求,然后是該請求的頭部:
print(response.request.headers)
准備的請求 (Prepared Request)
從 API 或者會話調用中收到一個 Response 對象時,request 屬性其實是使用了 PreparedRequest。有時在發送請求之前,需要對 body 或者 header (或者別的什么東西)做一些額外處理,下面演示了一個簡單的做法:
s = requests.Session() req = requests.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:
response = requests.get('https://requestb.in') print(response) # 拋出異常 SSLError: response = requests.get('https://github.com', verify=True) print(response)
為了避免這種情況的發生可以通過verify=False但是這樣是可以訪問到頁面,但是會提示:InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)
解決方法為:
import requests from requests.packages import urllib3 urllib3.disable_warnings() # 就這一句就可以解決 response = requests.get("https://www.12306.cn",verify=False) print(response.status_code)
可以為 verify 傳入 CA_BUNDLE 文件的路徑,或者包含可信任 CA 證書文件的文件夾路徑:
requests.get('https://github.com', verify='路徑')
或者將其保存在會話中:
s = requests.Session() s.verify = '路徑'
注意:如果 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
警告本地證書的私有 key 必須是解密狀態。目前,Requests 不支持使用加密的 key。
響應體內容工作流
默認情況下,當進行網絡請求后,響應體會立即被下載。可以通過 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))
當使用 <cite>decode_unicode=True</cite> 在 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
參數來配置單個請求:
mport 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,可以使用 <cite>http://user:password@host/</cite> 語法:
proxies = { "http": "http://user:pass@10.10.1.10:3128/", }
要為某個特定的連接方式或者主機設置代理,使用 <cite>scheme://hostname</cite> 作為 key, 它會針對指定的主機和連接方式進行匹配。
proxies = {'http://10.20.1.128': 'http://10.10.1.10:5323'}
注意,代理 URL 必須包含連接方式。
SOCKS
2.10.0 新版功能.
除了基本的 HTTP 代理,Request 還支持 SOCKS 協議的代理。這是一個可選功能,若要使用, 你需要安裝第三方庫。
pip install requests[socks]
安裝好依賴以后,使用 SOCKS 代理和使用 HTTP 代理一樣簡單:
proxies = { 'http': 'socks5://user:pass@host:port', 'https': 'socks5://user:pass@host:port' }
合規性
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.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 到該話題。我們來試試看。
>>> 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)
作者:在努力中
作者:田小田txt
鏈接:https://www.jianshu.com/p/ecb4d54ad8cf
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。