requests是python實現(xiàn)的最簡單易用的HTTP庫,建議爬蟲使用requests
import requests
url = "https://api.github.com/events"
獲取某個網(wǎng)頁
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
各種請求
# 發(fā)送一個 HTTP POST 請求:
r = requests.post("http://httpbin.org/post",data = {'key':'value'})
r = requests.delete('http://httpbin.org/delete') # 發(fā)送一個 HTTP delete 請求:
r = requests.head('http://httpbin.org/get') # 發(fā)送一個 HTTP head 請求:
r = requests.options('http://httpbin.org/get') # 發(fā)送一個 HTTP options 請求:
`
get 傳遞URL參數(shù)
?+鍵值對
response1 = requests.get("http://httpbin.org/get?key1=value1")
print(response1.url)
#http://httpbin.org/get?key1=value1
requests提供了params關鍵字參數(shù)來傳遞參數(shù)
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
響應內(nèi)容
我們能讀取服務器響應的內(nèi)容。再次以 GitHub 時間線為例:
Requests 會自動解碼來自服務器的內(nèi)容。大多數(shù) unicode 字符集都能被無縫地解碼。
response = requests.get("https://api.github.com/events")
print(response) # <Response [200]>
# print(response.text) # Json格式
請求發(fā)出后,Requests 會基于 HTTP 頭部對響應的編碼作出有根據(jù)的推測。當訪問 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'
#二進制響應內(nèi)容
#以字節(jié)的方式訪問請求響應體,對于非文本請求:
print(type(response.content))
print(type(response.text))
response.text返回的是Unicode型的數(shù)據(jù)。---文本
response.content返回的是bytes型也就是二進制的數(shù)據(jù)。-----圖片等
但是兩者打印輸出是一樣的
Json響應內(nèi)容
Requests 中有一個內(nèi)置的 JSON 解碼器,處理 JSON 數(shù)據(jù)
response = requests.get(url)
# print(response.json()) #json數(shù)據(jù)
# json成功調(diào)用并不意外者響應成功,有的服務器會在失敗的響應中包含一個 JSON 對象(比如 HTTP 500 的錯誤細節(jié)需要status_code判斷
print(response.status_code) #200
print(response.raise_for_status()) #none
原始響應
暫未看懂,先略過
定制請求頭
如果想為請求添加 HTTP 頭部,只要簡單地傳遞一個 dict 給 headers 參數(shù)就可以了。
以知乎為例子
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請求
發(fā)送一些編碼為表單形式的數(shù)據(jù)——非常像一個 HTML 表單。要實現(xiàn)這個,只需簡單地傳遞一個字典給 data 參數(shù)。數(shù)據(jù)字典在發(fā)出請求時會自動編碼為表單形式:通過在發(fā)送post請求時添加一個data參數(shù),這個data參數(shù)可以通過字典構造成,這樣對于發(fā)送post請求就非常方便
payload = {
"key1":"value1",
"key2":"value2"
}
response = requests.post("http://httpbin.org/post",data = payload)
print(response.text)
還可以為 data 參數(shù)傳入一個元組列表。在表單中多個元素使用同一 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'> []
狀態(tài)碼判斷,requests還附帶了一個內(nèi)置的狀態(tài)碼查詢對象
主要有如下內(nèi)容:
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("訪問成功")
# 可以直接使用狀態(tài)碼,更方便
if response.status_code == 200:
print("訪問成功")
POST一個多部分編碼(Multipart-Encoded)的文件
Requests 使得上傳多部分編碼文件變得很簡單:
文件上傳,實現(xiàn)方法和其他參數(shù)類似,也是構造一個字典然后通過files參數(shù)傳遞
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
也可以發(fā)送作為文件來接收的字符串:
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)
要想發(fā)送的cookies到服務器,可以使用 cookies 參數(shù):
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 對象的列表,為了完成請求而創(chuàng)建了這些對象。這個對象列表按照從最老到最近的請求進行排序。例如,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 參數(shù)禁用重定向處理:
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 在經(jīng)過以 timeout 參數(shù)設定的秒數(shù)時間之后停止等待響應?;旧纤械纳a(chǎn)代碼都應該使用這一參數(shù)。如果不使用,的程序可能會永遠失去響應:
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 秒內(nèi)沒有應答,將會引發(fā)一個異常(更精確地說,是在 timeout 秒內(nèi)沒有從基礎套接字上接收到任何字節(jié)的數(shù)據(jù)時)If no timeout is specified explicitly, requests do not time out.
錯誤與異常
http://www.python-requests.org/en/master/api/#exceptions
- 遇到網(wǎng)絡問題(如:DNS 查詢失敗、拒絕連接等)時,Requests 會拋出一個 ConnectionError 異常。
- 如果 HTTP 請求返回了不成功的狀態(tài)碼, Response.raise_for_status() 會拋出一個 HTTPError 異常。
- 若請求超時,則拋出一個 Timeout 異常。
- 若請求超過了設定的最大重定向次數(shù),則會拋出一個 TooManyRedirects 異常。
- 所有Requests顯式拋出的異常都繼承自 requests.exceptions.RequestException 。
會話對象
會話對象讓能夠跨請求保持某些參數(shù)。它也會在同一個 Session 實例發(fā)出的所有請求之間保持 cookie, 期間使用 urllib3 的 connection pooling 功能。所以如果向同一主機發(fā)送多個請求,底層的 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"}}
會話也可用來為請求方法提供缺省數(shù)據(jù)。這是通過為會話對象的屬性提供數(shù)據(jù)來實現(xiàn)的:
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'})
任何傳遞給請求方法的字典都會與已設置會話層數(shù)據(jù)合并。方法層的參數(shù)覆蓋會話的參數(shù)。
不過需要注意,就算使用了會話,方法級別的參數(shù)也不會被跨請求保持。下面的例子只會和第一個請求發(fā)送 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 函數(shù) 來操縱 Session.cookies。
會話還可以用作前后文管理器:
with requests.Session() as s:
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
這樣就能確保 with 區(qū)塊退出后會話能被關閉,即使發(fā)生了異常也一樣
從字典參數(shù)中移除一個值有時會想省略字典參數(shù)中一些會話層的鍵。要做到這一點,只需簡單地在方法層參數(shù)中將那個鍵的值設置為 None ,那個鍵就會被自動省略掉。
請求與響應對象
任何時候進行了類似 requests.get() 的調(diào)用,都在做兩件主要的事情。其一,在構建一個 Request 對象, 該對象將被發(fā)送到某個服務器請求或查詢一些資源。其二,一旦 requests 得到一個從服務器返回的響應就會產(chǎn)生一個 Response 對象。該響應對象包含服務器返回的所有信息,也包含原來創(chuàng)建的 Request 對象。如下是一個簡單的請求,從 Wikipedia 的服務器得到一些非常重要的信息:
# response = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
如果想訪問服務器返回給我們的響應頭部信息,可以這樣做:
print(response.headers)
然而,如果想得到發(fā)送到服務器的請求的頭部,我們可以簡單地訪問該請求,然后是該請求的頭部:
print(response.request.headers)
準備的請求 (Prepared Request)
從 API 或者會話調(diào)用中收到一個 Response 對象時,request 屬性其實是使用了 PreparedRequest。有時在發(fā)送請求之前,需要對 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 對象,然后把它和別的參數(shù)一起發(fā)送到 requests.* 或者 Session.*。
然而,上述代碼會失去 Requests Session 對象的一些優(yōu)勢, 尤其 Session 級別的狀態(tài),例如 cookie 就不會被應用到的請求上去。要獲取一個帶有狀態(tài)的 PreparedRequest, 請用 Session.prepare_request() 取代 Request.prepare() 的調(diào)用,如下所示:
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)
為了避免這種情況的發(fā)生可以通過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 環(huán)境變量定義可信任 CA 列表。
如果將 verify 設置為 False,Requests 也能忽略對 SSL 證書的驗證。
>>>requests.get('https://kennethreitz.org', verify=False)
#<Response [200]>
默認情況下, verify 是設置為 True 的。選項 verify 僅應用于主機證書。對于私有證書,也可以傳遞一個 CA_BUNDLE 文件的路徑給 verify。也可以設置 # REQUEST_CA_BUNDLE 環(huán)境變量。
客戶端證書
也可以指定一個本地證書用作客戶端證書,可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
>>> 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 必須是解密狀態(tài)。目前,Requests 不支持使用加密的 key。
響應體內(nèi)容工作流
默認情況下,當進行網(wǎng)絡請求后,響應體會立即被下載??梢酝ㄟ^ stream 參數(shù)覆蓋這個行為,推遲下載響應體直到訪問 Response.content 屬性:
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)
此時僅有響應頭被下載下來了,連接保持打開狀態(tài),因此允許我們根據(jù)條件獲取內(nèi)容:
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 無法將連接釋放回連接池,除非 消耗了所有的數(shù)據(jù),或者調(diào)用了 Response.close。 這樣會帶來連接效率低下的問題。如果發(fā)現(xiàn)在使用 stream=True 的同時還在部分讀取請求的 body(或者完全沒有讀取 body),那么就應該考慮使用 with 語句發(fā)送請求,這樣可以保證請求一定會被關閉:
with requests.get('http://httpbin.org/get', stream=True) as r:
# 在此處理響應。
保持活動狀態(tài)(持久連接)
好消息——歸功于 urllib3,同一會話內(nèi)的持久連接是完全自動處理的!同一會話內(nèi)你發(fā)出的任何請求都會自動復用恰當?shù)倪B接!
注意:只有所有的響應體數(shù)據(jù)被讀取完畢連接才會被釋放為連接池;所以確保將 stream 設置為 False 或讀取 Response 對象的 content 屬性。
流式上傳
Requests支持流式上傳,這允許你發(fā)送大的數(shù)據(jù)流或文件而無需先把它們讀入內(nèi)存。要使用流式上傳,僅需為你的請求體提供一個類文件對象即可:
with open('massive-body') as f:
requests.post('http://some.url/streamed', data=f)
強烈建議你用二進制模式(binary mode)打開文件。這是因為 requests 可能會為你提供 header 中的 Content-Length,在這種情況下該值會被設為文件的字節(jié)數(shù)。如果你用文本模式打開文件,就可能碰到錯誤。
塊編碼請求
對于出去和進來的請求,Requests 也支持分塊傳輸編碼。要發(fā)送一個塊編碼的請求,僅需為你的請求體提供一個生成器(或任意沒有具體長度的迭代器):
def gen():
yield 'hi'
yield 'there'
requests.post('http://some.url/chunked', data=gen())
對于分塊的編碼請求,我們最好使用 Response.iter_content() 對其數(shù)據(jù)進行迭代。在理想情況下,你的 request 會設置 stream=True,這樣你就可以通過調(diào)用 iter_content 并將分塊大小參數(shù)設為 None,從而進行分塊的迭代。如果你要設置分塊的最大體積,你可以把分塊大小參數(shù)設為任意整數(shù)。
POST 多個分塊編碼的文件
你可以在一個請求中發(fā)送多個文件。例如,假設你要上傳多個圖像文件到一個 HTML 表單,使用一個多文件 field 叫做 "images":
<input type="file" name="images" multiple="true" required="true"/>
要實現(xiàn),只要把文件設到一個元組的列表中,其中元組結構為 (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,在這種情況下該值會被設為文件的字節(jié)數(shù)。如果你用文本模式打開文件,就可能碰到錯誤。
事件掛鉤
Requests有一個鉤子系統(tǒng),你可以用來操控部分請求過程,或信號事件處理。
可用的鉤子:
response:
從一個請求產(chǎn)生的響應
你可以通過傳遞一個 {hook_name: callback_function} 字典給 hooks 請求參數(shù)為每個請求分配一個鉤子函數(shù):
hooks=dict(response=print_url)
callback_function 會接受一個數(shù)據(jù)塊作為它的第一個參數(shù)。
def print_url(r, *args, **kwargs):
print(r.url)
若執(zhí)行你的回調(diào)函數(shù)期間發(fā)生錯誤,系統(tǒng)會給出一個警告。
若回調(diào)函數(shù)返回一個值,默認以該值替換傳進來的數(shù)據(jù)。若函數(shù)未返回任何東西,也沒有什么其他的影響。
我們來在運行期間打印一些請求方法的參數(shù):
>>>> requests.get('http://httpbin.org', hooks=dict(response=print_url))
http://httpbin.org
<Response [200]>
自定義身份驗證
Requests 允許你使用自己指定的身份驗證機制。
任何傳遞給請求方法的 auth 參數(shù)的可調(diào)用對象,在請求發(fā)出之前都有機會修改請求。
自定義的身份驗證機制是作為 requests.auth.AuthBase 的子類來實現(xiàn)的,也非常容易定義。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來進行網(wǎng)絡請求:
>>>> 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 不保證重進入時的安全性。多次調(diào)用該方法 會導致部分收到的數(shù)據(jù)丟失。如果你要在多處調(diào)用它,就應該使用生成的迭代器對象:
lines = r.iter_lines()
# 保存第一行以供后面使用,或者直接跳過
first_line = next(lines)
for line in lines:
print(line)
代理
如果需要使用代理,你可以通過為任意請求方法提供 proxies 參數(shù)來配置單個請求:
mport requests
proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)
你也可以通過環(huán)境變量 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 協(xié)議的代理。這是一個可選功能,若要使用, 你需要安裝第三方庫。
pip install requests[socks]
安裝好依賴以后,使用 SOCKS 代理和使用 HTTP 代理一樣簡單:
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}
合規(guī)性
Requests 符合所有相關的規(guī)范和 RFC,這樣不會為用戶造成不必要的困難。但這種對規(guī)范的考慮導致一些行為對于不熟悉相關規(guī)范的人來說看似有點奇怪。
編碼方式
當你收到一個響應時,Requests 會猜測響應的編碼方式,用于在你調(diào)用 Response.text 方法時對響應進行解碼。Requests 首先在 HTTP 頭部檢測是否存在指定的編碼方式,如果不存在,則會使用charade 來嘗試猜測編碼方式。
只有當 HTTP 頭部不存在明確指定的字符集,并且 Content-Type 頭部字段包含 text 值之時, Requests 才不去猜測編碼方式。在這種情況下, RFC 2616 指定默認字符集必須是 ISO-8859-1 。Requests 遵從這一規(guī)范。如果你需要一種不同的編碼方式,你可以手動設置 Response.encoding 屬性,或使用原始的 Response.content。
HTTP動詞
Requests 提供了幾乎所有HTTP動詞的功能:GET、OPTIONS、HEAD、POST、PUT、PATCH、DELETE。以下內(nèi)容為使用 Requests 中的這些動詞以及 Github API 提供了詳細示例。
我將從最常使用的動詞 GET 開始。HTTP GET 是一個冪等方法,從給定的 URL 返回一個資源。因而,當你試圖從一個 web 位置獲取數(shù)據(jù)之時,你應該使用這個動詞。一個使用示例是嘗試從 Github 上獲取關于一個特定 commit 的信息。假設我們想獲取 Requests 的 commit a050faf 的信息。我們可以這樣去做:
>>>> import requests
>>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我們應該確認 GitHub 是否正確響應。如果正確響應,我們想弄清響應內(nèi)容是什么類型的。像這樣去做:
>>>> if (r.status_code == requests.codes.ok):
... print r.headers['content-type']
...
application/json; charset=utf-8
可見,GitHub 返回了 JSON 數(shù)據(jù),非常好,這樣就可以使用 r.json 方法把這個返回的數(shù)據(jù)解析成 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 提供方一樣,實際上并未實現(xiàn) OPTIONS 方法。這是一個惱人的疏忽,但沒關系,那我們可以使用枯燥的文檔。然而,如果 GitHub 正確實現(xiàn)了 OPTIONS,那么服務器應該在響應頭中返回允許用戶使用的 HTTP 方法,例如:
>>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print verbs.headers['allow']
GET,HEAD,POST,OPTIONS
轉而去查看文檔,我們看到對于提交信息,另一個允許的方法是 POST,它會創(chuàng)建一個新的提交。由于我們正在使用 Requests 代碼庫,我們應盡可能避免對它發(fā)送笨拙的 POST。作為替代,我們來玩玩 GitHub 的 Issue 特性。
本篇文檔是回應 Issue #482 而添加的。鑒于該問題已經(jīng)存在,我們就以它為例。先獲取它。
>>> 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
嗯,那看起來似乎是個愚蠢之處。我們發(fā)表個評論來告訴這個評論者他自己的愚蠢。那么,這個評論者是誰呢?
>>>> print comments[2][u'user'][u'login']
kennethreitz
好,我們來告訴這個叫 Kenneth 的家伙,這個例子應該放在快速上手指南中。根據(jù) 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
非常好?,F(xiàn)在,我們來折磨一下這個叫 Kenneth 的家伙,我決定要讓他急得團團轉,也不告訴他是我在搗蛋。這意味著我想刪除這條評論。GitHub 允許我們使用完全名副其實的 DELETE 方法來刪除評論。我們來清除該評論。
>>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
很好。不見了。最后一件我想知道的事情是我已經(jīng)使用了多少限額(ratelimit)。查查看,GitHub 在響應頭部發(fā)送這個信息,因此不必下載整個網(wǎng)頁,我將使用一個 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 一樣可以搞定它們。你可以使用內(nèi)建的 .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 的內(nèi)部采用了模塊化設計。部分原因是為了實現(xiàn)傳輸適配器(Transport Adapter),你可以看看關于它的最早描述。傳輸適配器提供了一個機制,讓你可以為 HTTP 服務定義交互方法。尤其是它允許你應用服務前的配置。
Requests 自帶了一個傳輸適配器,也就是 HTTPAdapter。 這個適配器使用了強大的 urllib3,為 Requests 提供了默認的 HTTP 和 HTTPS 交互。每當 Session 被初始化,就會有適配器附著在 Session 上,其中一個供 HTTP 使用,另一個供 HTTPS 使用。
Request 允許用戶創(chuàng)建和使用他們自己的傳輸適配器,實現(xiàn)他們需要的特殊功能。創(chuàng)建好以后,傳輸適配器可以被加載到一個會話對象上,附帶著一個說明,告訴會話適配器應該應用在哪個 web 服務上。
>>>> s = requests.Session()
>>> s.mount('http://www.github.com', MyAdapter())
這個 mount 調(diào)用會注冊一個傳輸適配器的特定實例到一個前綴上面。加載以后,任何使用該會話的 HTTP 請求,只要其 URL 是以給定的前綴開頭,該傳輸適配器就會被使用到。
傳輸適配器的眾多實現(xiàn)細節(jié)不在本文檔的覆蓋范圍內(nèi),不過你可以看看接下來這個簡單的 SSL 用例。更多的用法,你也許該考慮為 BaseAdapter 創(chuàng)建子類。
示例: 指定的 SSL 版本
Requests 開發(fā)團隊刻意指定了內(nèi)部庫(urllib3)的默認 SSL 版本。一般情況下這樣做沒有問題,不過是不是你可能會需要連接到一個服務節(jié)點,而該節(jié)點使用了和默認不同的 SSL 版本。
你可以使用傳輸適配器解決這個問題,通過利用 HTTPAdapter 現(xiàn)有的大部分實現(xiàn),再加上一個ssl_version 參數(shù)并將它傳遞到 urllib3 中。我們會創(chuàng)建一個傳輸適配器,用來告訴 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 屬性會阻塞,直到整個響應下載完成。如果你需要更多精細控制,該庫的數(shù)據(jù)流功能(見 流式請求) 允許你每次接受少量的一部分響應,不過這些調(diào)用依然是阻塞式的。
如果你對于阻塞式 IO 有所顧慮,還有很多項目可以供你使用,它們結合了 Requests 和 Python 的某個異步框架。典型的優(yōu)秀例子是 grequests 和 requests-futures。
Header 排序
在某些特殊情況下你也許需要按照次序來提供 header,如果你向 headers 關鍵字參數(shù)傳入一個OrderedDict,就可以向提供一個帶排序的 header。然而,Requests 使用的默認 header 的次序會被優(yōu)先選擇,這意味著如果你在 headers 關鍵字參數(shù)中覆蓋了默認 header,和關鍵字參數(shù)中別的 header 相比,它們也許看上去會是次序錯誤的。
如果這個對你來說是個問題,那么用戶應該考慮在 Session 對象上面設置默認 header,只要將 Session 設為一個定制的 OrderedDict 即可。這樣就會讓它成為優(yōu)選的次序。
超時(timeout)
為防止服務器不能及時響應,大部分發(fā)至外部服務器的請求都應該帶著 timeout 參數(shù)。在默認情況下,除非顯式指定了 timeout 值,requests 是不會自動進行超時處理的。如果沒有 timeout,你的代碼可能會掛起若干分鐘甚至更長時間。
連接超時指的是在你的客戶端實現(xiàn)到遠端機器端口的連接時(對應的是connect()_),Request 會等待的秒數(shù)。一個很好的實踐方法是把連接超時設為比 3 的倍數(shù)略大的一個數(shù)值,因為 TCP 數(shù)據(jù)包重傳窗口 (TCP packet retransmission window) 的默認大小是 3。
一旦你的客戶端連接到了服務器并且發(fā)送了 HTTP 請求,讀取超時指的就是客戶端等待服務器發(fā)送請求的時間。(特定地,它指的是客戶端要等待服務器發(fā)送字節(jié)之間的時間。在 99.9% 的情況下這指的是服務器發(fā)送第一個字節(jié)之前的時間)。
如果你制訂了一個單一的值作為 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)
作者:在努力中
鏈接:http://m.itdecent.cn/p/2065f0292de6
來源:簡書