上一篇:8.Urllib庫基本使用
下一篇:10.正則表達式基礎(chǔ)
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關(guān)鍵字參數(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
響應(yīng)內(nèi)容
我們能讀取服務(wù)器響應(yīng)的內(nèi)容矮慕。再次以 GitHub 時間線為例:
Requests 會自動解碼來自服務(wù)器的內(nèi)容恳啥。大多數(shù) unicode 字符集都能被無縫地解碼亏拉。
response = requests.get("https://api.github.com/events")
print(response) # <Response [200]>
# print(response.text) # Json格式
請求發(fā)出后仔沿,Requests 會基于 HTTP 頭部對響應(yīng)的編碼作出有根據(jù)的推測坐桩。當訪問 r.text 之時,Requests 會使用其推測的文本編碼封锉∶圊危可以找出 Requests 使用了什么編碼,并且能夠使用 r.encoding 屬性來改變它:
print(response.encoding) # utf-8
#使用 r.content 來找到編碼成福,然后設(shè)置 r.encoding 為相應(yīng)的編碼
print(response.encoding) # ISO-8859-1
#改變編碼
response.encoding = 'ISO-8859-1'
#二進制響應(yīng)內(nèi)容
#以字節(jié)的方式訪問請求響應(yīng)體碾局,對于非文本請求:
print(type(response.content))
print(type(response.text))
response.text返回的是Unicode型的數(shù)據(jù)。---文本
response.content返回的是bytes型也就是二進制的數(shù)據(jù)奴艾。-----圖片等
但是兩者打印輸出是一樣的
Json響應(yīng)內(nèi)容
Requests 中有一個內(nèi)置的 JSON 解碼器净当,處理 JSON 數(shù)據(jù)
response = requests.get(url)
# print(response.json()) #json數(shù)據(jù)
# json成功調(diào)用并不意外者響應(yīng)成功,有的服務(wù)器會在失敗的響應(yīng)中包含一個 JSON 對象(比如 HTTP 500 的錯誤細節(jié)需要status_code判斷
print(response.status_code) #200
print(response.raise_for_status()) #none
原始響應(yīng)
暫未看懂蕴潦,先略過
定制請求頭
如果想為請求添加 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ù)可以通過字典構(gòu)造成湖笨,這樣對于發(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)
響應(yīng)
可以通過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ù)類似辫呻,也是構(gòu)造一個字典然后通過files參數(shù)傳遞
import requests
url = 'http://httpbin.org/post'
files = {"files":open('test.py', 'rb')}
response = requests.post(url,files = files)
print(response.text)
可以顯式地設(shè)置文件名,文件類型和請求頭:
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
如果某個響應(yīng)中包含一些 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到服務(wù)器放闺,可以使用 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 會自動處理所有重定向匾寝。
- 可以使用響應(yīng)對象的 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è)定的秒數(shù)時間之后停止等待響應(yīng)杨幼。基本上所有的生產(chǎn)代碼都應(yīng)該使用這一參數(shù)聂渊。如果不使用推汽,的程序可能會永遠失去響應(yīng):
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 僅對連接過程有效,與響應(yīng)體的下載無關(guān)歧沪。 timeout 并不是整個下載響應(yīng)的時間限制歹撒,而是如果服務(wù)器在 timeout 秒內(nèi)沒有應(yīng)答,將會引發(fā)一個異常(更精確地說诊胞,是在 timeout 秒內(nèi)沒有從基礎(chǔ)套接字上接收到任何字節(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)絡(luò)問題(如:DNS 查詢失敗暖夭、拒絕連接等)時锹杈,Requests 會拋出一個 ConnectionError 異常。
- 如果 HTTP 請求返回了不成功的狀態(tài)碼迈着, Response.raise_for_status() 會拋出一個 HTTPError 異常竭望。
- 若請求超時,則拋出一個 Timeout 異常裕菠。
- 若請求超過了設(shè)定的最大重定向次數(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è)置會話層數(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ū)塊退出后會話能被關(guān)閉,即使發(fā)生了異常也一樣
從字典參數(shù)中移除一個值有時會想省略字典參數(shù)中一些會話層的鍵厚者。要做到這一點躁劣,只需簡單地在方法層參數(shù)中將那個鍵的值設(shè)置為 None ,那個鍵就會被自動省略掉库菲。
請求與響應(yīng)對象
任何時候進行了類似 requests.get() 的調(diào)用账忘,都在做兩件主要的事情。其一熙宇,在構(gòu)建一個 Request 對象鳖擒, 該對象將被發(fā)送到某個服務(wù)器請求或查詢一些資源。其二烫止,一旦 requests 得到一個從服務(wù)器返回的響應(yīng)就會產(chǎn)生一個 Response 對象蒋荚。該響應(yīng)對象包含服務(wù)器返回的所有信息,也包含原來創(chuàng)建的 Request 對象馆蠕。如下是一個簡單的請求期升,從 Wikipedia 的服務(wù)器得到一些非常重要的信息:
# response = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
如果想訪問服務(wù)器返回給我們的響應(yīng)頭部信息惊奇,可以這樣做:
print(response.headers)
然而,如果想得到發(fā)送到服務(wù)器的請求的頭部播赁,我們可以簡單地訪問該請求颂郎,然后是該請求的頭部:
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 就不會被應(yīng)用到的請求上去耿戚。要獲取一個帶有狀態(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 設(shè)為文件夾路徑呜呐,文件夾必須通過 OpenSSL 提供的 c_rehash 工具處理。
還可以通過 REQUESTS_CA_BUNDLE 環(huán)境變量定義可信任 CA 列表悍募。
如果將 verify 設(shè)置為 False蘑辑,Requests 也能忽略對 SSL 證書的驗證。
>>>requests.get('https://kennethreitz.org', verify=False)
#<Response [200]>
默認情況下坠宴, verify 是設(shè)置為 True 的洋魂。選項 verify 僅應(yīng)用于主機證書。對于私有證書喜鼓,也可以傳遞一個 CA_BUNDLE 文件的路徑給 verify副砍。也可以設(shè)置 # 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隅忿。
響應(yīng)體內(nèi)容工作流
默認情況下心剥,當進行網(wǎng)絡(luò)請求后启搂,響應(yīng)體會立即被下載×跆眨可以通過 stream 參數(shù)覆蓋這個行為胳赌,推遲下載響應(yīng)體直到訪問 Response.content 屬性:
tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'
r = requests.get(tarball_url, stream=True)
此時僅有響應(yīng)頭被下載下來了,連接保持打開狀態(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 讀取未解碼的相應(yīng)體。
如果在請求中把 stream 設(shè)為 True纷责,Requests 無法將連接釋放回連接池捍掺,除非 消耗了所有的數(shù)據(jù),或者調(diào)用了 Response.close再膳。 這樣會帶來連接效率低下的問題挺勿。如果發(fā)現(xiàn)在使用 stream=True 的同時還在部分讀取請求的 body(或者完全沒有讀取 body),那么就應(yīng)該考慮使用 with 語句發(fā)送請求喂柒,這樣可以保證請求一定會被關(guān)閉:
with requests.get('http://httpbin.org/get', stream=True) as r:
# 在此處理響應(yīng)不瓶。
保持活動狀態(tài)(持久連接)
好消息——歸功于 urllib3,同一會話內(nèi)的持久連接是完全自動處理的灾杰!同一會話內(nèi)你發(fā)出的任何請求都會自動復用恰當?shù)倪B接蚊丐!
注意:只有所有的響應(yīng)體數(shù)據(jù)被讀取完畢連接才會被釋放為連接池;所以確保將 stream
設(shè)置為 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
,在這種情況下該值會被設(shè)為文件的字節(jié)數(shù)栏渺。如果你用文本模式打開文件呛梆,就可能碰到錯誤。
塊編碼請求
對于出去和進來的請求迈嘹,Requests 也支持分塊傳輸編碼削彬。要發(fā)送一個塊編碼的請求,僅需為你的請求體提供一個生成器(或任意沒有具體長度的迭代器):
def gen():
yield 'hi'
yield 'there'
requests.post('http://some.url/chunked', data=gen())
對于分塊的編碼請求秀仲,我們最好使用 Response.iter_content()
對其數(shù)據(jù)進行迭代融痛。在理想情況下,你的 request 會設(shè)置 stream=True
神僵,這樣你就可以通過調(diào)用 iter_content
并將分塊大小參數(shù)設(shè)為 None
雁刷,從而進行分塊的迭代。如果你要設(shè)置分塊的最大體積保礼,你可以把分塊大小參數(shù)設(shè)為任意整數(shù)沛励。
POST 多個分塊編碼的文件
你可以在一個請求中發(fā)送多個文件责语。例如,假設(shè)你要上傳多個圖像文件到一個 HTML 表單目派,使用一個多文件 field 叫做 "images":
<input type="file" name="images" multiple="true" required="true"/>
要實現(xiàn)坤候,只要把文件設(shè)到一個元組的列表中,其中元組結(jié)構(gòu)為 (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
白筹,在這種情況下該值會被設(shè)為文件的字節(jié)數(shù)。如果你用文本模式打開文件谅摄,就可能碰到錯誤徒河。
事件掛鉤
Requests有一個鉤子系統(tǒng),你可以用來操控部分請求過程送漠,或信號事件處理顽照。
可用的鉤子:
response
:
從一個請求產(chǎn)生的響應(yīng)
你可以通過傳遞一個 {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
抢野。
假設(shè)我們有一個web服務(wù)拷淘,僅在 X-Pizza
頭被設(shè)置為一個密碼值的情況下才會有響應(yīng)。雖然這不太可能指孤,但就以它為例好了启涯。
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)絡(luò)請求:
>>>> requests.get('http://pizzabin.org/admin', auth=PizzaAuth('kenneth'))
<Response [200]>
流式請求
使用 Response.iter_lines()
你可以很方便地對流式 API (例如 Twitter 的流式 API ) 進行迭代。簡單地設(shè)置 stream
為 True
便可以使用 iter_lines
對相應(yīng)進行迭代:
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()
中時恃轩,你需要提供一個回退編碼方式结洼,以防服務(wù)器沒有提供默認回退編碼,從而導致錯誤:
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)用它,就應(yīng)該使用生成的迭代器對象:
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/",
}
要為某個特定的連接方式或者主機設(shè)置代理摊溶,使用 <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ān)的規(guī)范和 RFC,這樣不會為用戶造成不必要的困難哄啄。但這種對規(guī)范的考慮導致一些行為對于不熟悉相關(guān)規(guī)范的人來說看似有點奇怪雅任。
編碼方式
當你收到一個響應(yīng)時,Requests 會猜測響應(yīng)的編碼方式咨跌,用于在你調(diào)用 Response.text
方法時對響應(yīng)進行解碼沪么。Requests 首先在 HTTP 頭部檢測是否存在指定的編碼方式,如果不存在,則會使用charade 來嘗試猜測編碼方式也殖。
只有當 HTTP 頭部不存在明確指定的字符集臀蛛,并且 Content-Type
頭部字段包含 text
值之時, Requests 才不去猜測編碼方式殉摔。在這種情況下, RFC 2616 指定默認字符集必須是 ISO-8859-1
记焊。Requests 遵從這一規(guī)范逸月。如果你需要一種不同的編碼方式,你可以手動設(shè)置 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ù)之時淘讥,你應(yīng)該使用這個動詞。一個使用示例是嘗試從 Github 上獲取關(guān)于一個特定 commit 的信息堤如。假設(shè)我們想獲取 Requests 的 commit a050faf
的信息蒲列。我們可以這樣去做:
>>>> import requests
>>> r = requests.get('https://api.github.com/repos/requests/requests/git/commits/a050faf084662f3a352dd1a941f2c7c9f886d4ad')
我們應(yīng)該確認 GitHub 是否正確響應(yīng)。如果正確響應(yīng)搀罢,我們想弄清響應(yīng)內(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 方法宙攻。這是一個惱人的疏忽,但沒關(guān)系介褥,那我們可以使用枯燥的文檔座掘。然而,如果 GitHub 正確實現(xiàn)了 OPTIONS柔滔,那么服務(wù)器應(yīng)該在響應(yīng)頭中返回允許用戶使用的 HTTP 方法溢陪,例如:
>>>> verbs = requests.options('http://a-good-website.com/api/cats')
>>> print verbs.headers['allow']
GET,HEAD,POST,OPTIONS
轉(zhuǎn)而去查看文檔,我們看到對于提交信息睛廊,另一個允許的方法是 POST形真,它會創(chuàng)建一個新的提交。由于我們正在使用 Requests 代碼庫超全,我們應(yīng)盡可能避免對它發(fā)送笨拙的 POST咆霜。作為替代邓馒,我們來玩玩 GitHub 的 Issue 特性。
本篇文檔是回應(yīng) 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 的家伙跌穗,這個例子應(yīng)該放在快速上手指南中。根據(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
額砌庄,這有點古怪哈羹唠。可能我們需要驗證身份娄昆。那就有點糾結(jié)了佩微,對吧?不對萌焰。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
非常好。現(xiàn)在废膘,我們來折磨一下這個叫 Kenneth 的家伙竹海,我決定要讓他急得團團轉(zhuǎn),也不告訴他是我在搗蛋殖卑。這意味著我想刪除這條評論站削。GitHub 允許我們使用完全名副其實的 DELETE 方法來刪除評論。我們來清除該評論孵稽。
>>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
很好许起。不見了。最后一件我想知道的事情是我已經(jīng)使用了多少限額(ratelimit)菩鲜。查查看园细,GitHub 在響應(yīng)頭部發(fā)送這個信息,因此不必下載整個網(wǎng)頁接校,我將使用一個 HEAD 請求來獲取響應(yīng)頭猛频。
>>>> r = requests.head(url=url, auth=auth)
>>> print r.headers
...
'x-ratelimit-remaining': '4995'
'x-ratelimit-limit': '5000'
...
很好。是時候?qū)憘€ Python 程序以各種刺激的方式濫用 GitHub 的 API蛛勉,還可以使用 4995 次呢鹿寻。
定制動詞
有時候你會碰到一些服務(wù)器,處于某些原因诽凌,它們允許或者要求用戶使用上述 HTTP 動詞之外的定制動詞毡熏。比如說 WEBDAV 服務(wù)器會要求你使用 MKCOL 方法。別擔心侣诵,Requests 一樣可以搞定它們痢法。你可以使用內(nèi)建的 .request
方法,例如:
>>>> r = requests.request('MKCOL', url, data=data)
>>> r.status_code
200 # Assuming your call was correct
這樣你就可以使用服務(wù)器要求的任意方法動詞了杜顺。
響應(yīng)頭鏈接字段
許多 HTTP API 都有響應(yīng)頭鏈接字段的特性财搁,它們使得 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 會自動解析這些響應(yīng)頭鏈接字段尖奔,并使得它們非常易于使用:
>>>> 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)部采用了模塊化設(shè)計洗鸵。部分原因是為了實現(xiàn)傳輸適配器(Transport Adapter)越锈,你可以看看關(guān)于它的最早描述。傳輸適配器提供了一個機制膘滨,讓你可以為 HTTP 服務(wù)定義交互方法。尤其是它允許你應(yīng)用服務(wù)前的配置稀拐。
Requests 自帶了一個傳輸適配器火邓,也就是 HTTPAdapter
。 這個適配器使用了強大的 urllib3,為 Requests 提供了默認的 HTTP 和 HTTPS 交互铲咨。每當 Session
被初始化躲胳,就會有適配器附著在 Session
上,其中一個供 HTTP 使用纤勒,另一個供 HTTPS 使用坯苹。
Request 允許用戶創(chuàng)建和使用他們自己的傳輸適配器,實現(xiàn)他們需要的特殊功能摇天。創(chuàng)建好以后粹湃,傳輸適配器可以被加載到一個會話對象上,附帶著一個說明泉坐,告訴會話適配器應(yīng)該應(yīng)用在哪個 web 服務(wù)上为鳄。
>>>> 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 版本。一般情況下這樣做沒有問題滑凉,不過是不是你可能會需要連接到一個服務(wù)節(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
屬性會阻塞粗合,直到整個響應(yīng)下載完成萍嬉。如果你需要更多精細控制,該庫的數(shù)據(jù)流功能(見 流式請求) 允許你每次接受少量的一部分響應(yīng)隙疚,不過這些調(diào)用依然是阻塞式的壤追。
如果你對于阻塞式 IO 有所顧慮,還有很多項目可以供你使用供屉,它們結(jié)合了 Requests 和 Python 的某個異步框架行冰。典型的優(yōu)秀例子是 grequests 和 requests-futures溺蕉。
Header 排序
在某些特殊情況下你也許需要按照次序來提供 header,如果你向 headers
關(guān)鍵字參數(shù)傳入一個OrderedDict
悼做,就可以向提供一個帶排序的 header疯特。然而,Requests 使用的默認 header 的次序會被優(yōu)先選擇肛走,這意味著如果你在 headers
關(guān)鍵字參數(shù)中覆蓋了默認 header漓雅,和關(guān)鍵字參數(shù)中別的 header 相比,它們也許看上去會是次序錯誤的朽色。
如果這個對你來說是個問題邻吞,那么用戶應(yīng)該考慮在 Session
對象上面設(shè)置默認 header,只要將 Session
設(shè)為一個定制的 OrderedDict
即可纵搁。這樣就會讓它成為優(yōu)選的次序吃衅。
超時(timeout)
為防止服務(wù)器不能及時響應(yīng),大部分發(fā)至外部服務(wù)器的請求都應(yīng)該帶著 timeout 參數(shù)腾誉。在默認情況下徘层,除非顯式指定了 timeout 值,requests 是不會自動進行超時處理的利职。如果沒有 timeout趣效,你的代碼可能會掛起若干分鐘甚至更長時間。
連接超時指的是在你的客戶端實現(xiàn)到遠端機器端口的連接時(對應(yīng)的是connect()
_)猪贪,Request 會等待的秒數(shù)跷敬。一個很好的實踐方法是把連接超時設(shè)為比 3 的倍數(shù)略大的一個數(shù)值,因為 TCP 數(shù)據(jù)包重傳窗口 (TCP packet retransmission window) 的默認大小是 3热押。
一旦你的客戶端連接到了服務(wù)器并且發(fā)送了 HTTP 請求西傀,讀取超時指的就是客戶端等待服務(wù)器發(fā)送請求的時間。(特定地桶癣,它指的是客戶端要等待服務(wù)器發(fā)送字節(jié)之間的時間拥褂。在 99.9% 的情況下這指的是服務(wù)器發(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))
如果遠端服務(wù)器很慢间雀,你可以讓 Request 永遠等待悔详,傳入一個 None 作為 timeout 值,然后就沖咖啡去吧惹挟。
r = requests.get('https://github.com', timeout=None)
有些地方不是太懂茄螃,先記錄。
上一篇:8.Urllib庫基本使用
下一篇:10.正則表達式基礎(chǔ)