49.[Python]使用requests包進行HTTP互動方法詳解
簡介
Python的HTTP包有urllib、urllib2、httplib等,但是都需要了解較多的HTTP原理才能編碼,藉助requests包可以在較高的抽象層次上完成HTTP互動過程的開發。安裝requests使用pip install requests
命令,requests包內嵌了urllib3,自動支援HTTP長連線、連線池等功能。
使用方法
requests支援HTTP的HEAD、GET、POST、PUT、OPTIONS、DELETE、PATCH等請求:
r = requests.options('http://localhost:5000/')
print "Options:" , r.headers
r = requests.post('http://localhost:5000/', data={'name': 'mars'})
print "Post:", r.content
r = requests.put('http://localhost:5000/', data={'name': 'loo'})
print "Put:", r.content
r = requests.get('http://localhost:5000/')
print "Get:", r.content
r = requests.delete('http://localhost:5000/')
print "Delete:", r.content
r = requests.get('http://localhost:5000/' )
print "Get:", r.content
變數r
是一個requests.models.Response
型別的響應物件,通過r
可以得到HTTP響應的所有資訊。
傳遞QUERY引數
在URI的query部分傳遞引數,可以直接按照標準放在URL字串中(允許為同一個key賦值多個value):
r = requests.get('http://localhost:5000/?xx=bb&xx=cc')
也可以放在請求的params
引數中:
params = {
'xx': ['bb', 'cc'],
'yy': None,
}
r = requests.get('http://localhost:5000/' , params=params)
print "Request URL:", r.url
使用字典做引數時,對同一個key的多個value要放在列表中,如果某個key對應的值為None,則其不會放在query中。
定製請求頭
為HTTP請求報文的頭部新增內容,可以請求時為headers
引數賦值:
r = requests.get('http://localhost:5000/', headers={"mars": "loo"})
填寫cookie
cookie可以以字典的形式賦值給cookies
引數:
cookies = {
"name": "mars"
}
r = requests.post('http://localhost:5000/', cookies=cookies)
通過RequestsCookieJar
物件可以設定cookie的域、path等資訊:
jar = requests.cookies.RequestsCookieJar()
jar.set('username', 'mars', domain='httpbin.org', path='/cookies')
jar.set('password', 'loo', domain='httpbin.org', path='/else')
r = requests.get('http://httpbin.org/cookies', cookies=jar)
print r.text
如果是在localhost做實驗,
domain
引數需要賦值為空字串''
。
http://httpbin.org/cookies提供的服務是:如果請求包含cookie的話,會在響應體中迴應cookie內容,所以上述程式碼返回:
{
"cookies": {
"username": "mars"
}
}
因為password
在/else
這個path,所以通過/cookies
無法訪問key為password
的cookie項。
填充請求體
如果採用application/x-www-form-urlencoded格式傳送HTTP請求,可以將請求內容放在data
引數中。如果採用application/json格式請求,可以將內容(dict型別)放在json
引數中,或者將字典轉化為JSON字串之後傳給data
引數,同時指定content-type為application/json:
import json
d = {
"mars": "loo"
}
r = requests.post('http://localhost:5000/', data = json.dumps(d),
headers={"content-type": "application/json"})
如果需要上傳檔案,直接將檔案以'rb'
模式開啟放入字典(必須使用'rb'
模式,requests才能自動推算出正確的content-length),然後傳入files
引數,請求型別會自動轉換為multipart/form-data
:
files = {
'image': open('sample.jpg', 'rb'),
}
r = requests.post('http://localhost:5000/', files=files)
需要上傳的檔案大小超過記憶體時,可以將檔案的讀取放在上下文管理器中,比如:
with open('verybig.zip', 'rb') as f:
requests.post('http://localhost:5000/', data=f)
處理響應物件
從為QUERY傳遞引數的例子中可以看到,使用響應物件的url
屬性可以訪問請求的URL。status_code
屬性可以獲取響應狀態碼。raise_for_status
方法,當狀態碼為4XX或5XX時,丟擲對應的客戶端或服務端異常,如果是2XX或3XX錯誤,返回None。比如:
r = requests.get('http://localhost:5000/')
print r.raise_for_status()
輸出可能是None
,或者是:
Traceback (most recent call last):
File "a.py", line 38, in <module>
print r.raise_for_status()
File "/Users/linglingguo/Envs/learnselenium/lib/python2.7/site-packages/requests/models.py", line 862, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: FORBIDDEN for url: http://localhost:5000/
encoding
屬性獲取響應編碼,text
屬性會嘗試按照encoding
屬性自動將響應內容轉碼後返回,如果encoding
屬性為None,requests會根據chardet
猜測正確的編碼。針對響應內容是二進位制檔案(如圖片)的場景,content
屬性獲取響應的原始內容(以位元組為單位),比如:
from PIL import Image
from io import BytesIO
r = requests.post('http://localhost:5000/picture')
i = Image.open(BytesIO(r.content))
i.save('sample.jpg', 'jpeg')
如果響應內容的大小超過了機器記憶體,需要分段讀取響應內容,可以在請求時使用stream=True
然後呼叫響應物件的iter_content
方法:
r = requests.post('http://localhost:5000/', stream=True)
with open('download.zip', 'wb') as f:
for chunk in r.iter_content(chunk_size=10*1024*1024):
if chunk:
f.write(chunk)
針對application/json
格式的響應內容,requests內建了json
方法將結果轉換為字典後返回:
r = requests.post('http://localhost:5000/', data={'name': 'mars'})
print "JSON:", r.json()
如果響應內容不能轉換為字典,丟擲異常:ValueError: No JSON object could be decoded
。
通過headers
屬性可以訪問響應的頭部。
重定向與訪問歷史
如果請求過程發生了重定向,requests預設返回最後一個成功的響應,如果要獲取中間重定向過程的響應,可以訪問history
屬性(按照訪問先後順序的響應物件列表),比如:
r = requests.get("http://localhost:5000")
print r.history
for re in r.history:
print re.status_code, re.headers
上述程式碼輸出為:
[<Response [302]>, <Response [301]>]
302 {'Date': 'Wed, 28 Sep 2016 07:58:28 GMT', 'Content-Length': '211', 'Content-Type': 'text/html; charset=utf-8', 'Location': 'http://localhost:5000/a', 'Server': 'Werkzeug/0.11.10 Python/2.7.10'}
301 {'Date': 'Wed, 28 Sep 2016 07:58:28 GMT', 'Content-Length': '215', 'Content-Type': 'text/html; charset=utf-8', 'Location': 'http://localhost:5000/404', 'Server': 'Werkzeug/0.11.10 Python/2.7.10'}
可以發現請求先被重定向到http://localhost:5000/a
,最後被重定向到http://localhost:5000/404
拿到了結果。如果想禁用requests預設的處理轉發的行為,可以使用allow_redirect=False
,比如:
r = requests.get("http://localhost:5000", allow_redirects=False)
print r.status_code, r.headers
for re in r.history:
print re.status_code, re.headers
上述程式碼輸出為:
302 {'Date': 'Wed, 28 Sep 2016 08:04:04 GMT', 'Content-Length': '211', 'Content-Type': 'text/html; charset=utf-8', 'Location': 'http://localhost:5000/a', 'Server': 'Werkzeug/0.11.10 Python/2.7.10'}
超時
requests預設不設定超時,一直等待服務端響應。requests中將超時分為兩個部分:連線超時和響應讀取超時,分別表示Socket建立TCP連結超時和TCP連結建立以後,客戶端讀取服務端響應超時。如果設定timeout
引數為一個數值,則連線超時和響應讀取超時設定為同樣的值,如果timeout
引數是一個包含兩個數值的元組,則分別代表連線超時和響應讀取超時。如果過了超時時間設定以後,未成功建立TCP連結或者未成功讀取服務端響應,會拋異常,比如響應讀取超時的異常如下(以設定3秒超時為例):
requests.exceptions.ReadTimeout: HTTPConnectionPool(host='localhost', port=5000): Read timed out. (read timeout=3)
Session物件
這裡的Session是指一系列有相關意義的HTTP請求/響應的集合。使用requests.Session物件可以在多個HTTP請求之間保持變數、共用cookie、保持長連線從而提高效能(由urllib3實現)等。在請求間自動保持cookie的例子:
s = requests.Session()
r = s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
print "1 - Headers:", r.headers
print "1 - Content:", r.content
r = s.get('http://httpbin.org/cookies')
print "2 - Text:", r.text
print "2 - Headers:", r.request.headers
上述程式碼輸出為:
1 - Headers: {'Content-Length': '56', 'Server': 'nginx', 'Connection': 'keep-alive', 'Access-Control-Allow-Credentials': 'true', 'Date': 'Fri, 30 Sep 2016 07:35:18 GMT', 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'}
1 - Content: {
"cookies": {
"sessioncookie": "123456789"
}
}
2 - Text: {
"cookies": {
"sessioncookie": "123456789"
}
}
2 - Headers: {'Connection': 'keep-alive', 'Cookie': 'sessioncookie=123456789', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'python-requests/2.11.1'}
通過為Session物件賦值可以在請求間提供預設值,HTTP VERB方法呼叫中,對於新增的值會追加,已有的值會覆蓋:
s = requests.Session()
s.headers.update({'1': '2'})
r = s.get('http://localhost:5000/headers', headers={'3': '4', '1': 'mars'})
伺服器收到的header中'3'->'4'
,'1'->'mars'
。但是對於這種在呼叫時新增的方法,不會在請求間保持,比如:
s = requests.Session()
r = s.get('http://httpbin.org/cookies', cookies={'mars': 'loo'})
print(r.text)
r = s.get('http://httpbin.org/cookies')
print(r.text)
程式碼輸出為:
{
"cookies": {
"mars": "loo"
}
}
{
"cookies": {}
}
可以把Session物件放在上下文管理器中,這樣發生異常時可以自動銷燬會話,從而釋放連線池中的連線,提高程式效能:
with requests.Session() as s:
r = s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
根據響應獲取請求
使用響應物件的request
屬性可以訪問響應對應的請求物件,比如:
r = requests.get("http://localhost:5000", headers={"Perm": "Authroized"})
print "Request headers:", r.request.headers
上述程式碼輸出可能為:
Request headers: {'Perm': 'Authroized', 'Connection': 'keep-alive', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'python-requests/2.11.1'}
其實通過響應物件的request
屬性得到的是一個PreparedRequest物件,可以修改其某些屬性後通過Session物件的send
方法重發修改後的請求:
from requests import Session
r = requests.get('http://www.baidu.com')
print r.status_code, r.content
s = Session()
r2 = s.send(r.request)
print r2.status_code, r2.content
也可以先構造Request物件,然後呼叫其prepare
方法返回一個PreparedRequest物件,再將該物件傳給Session物件的send
方法:
s = requests.Session()
s.cookies.update({'mars': 'loo'})
req = requests.Request('GET', 'http://httpbin.org/cookies')
prepared = req.prepare()
r = s.send(prepared)
print r.text
上述程式碼的輸出為:
{
"cookies": {}
}
說明通過Request物件的prepare方法生成的PreparedRequest物件不會讀取Session物件層次上的預設值(如cookie的設定)。Session物件的prepare_request
方法也可以接受一個Request物件作為引數,返回PreparedRequest物件,但是會讀取Session物件層次上的預設值(如cookie的設定):
s = requests.Session()
s.cookies.update({'mars': 'loo'})
req = requests.Request('GET', 'http://httpbin.org/cookies')
prepared = s.prepare_request(req)
r = s.send(prepared)
print r.text
上述程式碼的輸出為:
{
"cookies": {
"mars": "loo"
}
}
SSL認證
requests預設內建了Mozilla公佈的受信CA,requests預設會對伺服器端SSL證書進行認證,如果證書非法會報如下錯誤:
requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)
如果要禁用SSL的驗證,可以請求時設定verify
引數為False:
r = requests.get('https://xxx.com', verify=False)
依賴requests版本預設的CA的話,只有更新requests包的時候才會更新受信CA。新版本的requests會嘗試使用certifi(如果使用了certifi的話,建議經常升級certifi)。
如果服務端的SSL/TLS版本與requests預設的不一致,可以藉助HTTPAdapter物件更改Session物件使用的協議版本:
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager
import ssl
class MyAdapter(HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(num_pools=connections,
maxsize=maxsize,
block=block,
ssl_version=ssl.PROTOCOL_TLSv1)
s = requests.Session()
# 掛載Adapter(https請求底層採用TLSv1協議)
s.mount('https://', MyAdapter())
s.get('https://xxx.com')
HTTP認證
關於HTTP認證可以閱讀我的上一篇部落格。
基本認證
import requests
from requests.auth import HTTPBasicAuth
r = requests.get('http://localhost:5000/', auth=HTTPBasicAuth('mars', 'loo'))
print r.status_code, r.content
摘要認證
from requests.auth import HTTPDigestAuth
r = requests.get('http://localhost:5000/', auth=HTTPDigestAuth('mars', 'loo'))
print "1 - Response Header:", r.headers
print "1 - Request Header:", r.request.headers
上述程式碼的輸出樣例為:
1 - Response Header: {'Date': 'Thu, 29 Sep 2016 15:05:08 GMT', 'Content-Length': '12', 'Content-Type': 'text/html; charset=utf-8', 'Server': 'Werkzeug/0.11.10 Python/2.7.10'}
1 - Request Header: {'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'python-requests/2.11.1', 'Connection': 'keep-alive', 'Cookie': 'session=.eJyrVkosLcmIz8vPS05VsqpWUkhSslKKrHI18HdJr_B396z0c_fK8atyNAXSWb7hrgZRWckVfu6BQDm37MiQUFulWh2IEfkFiYWlSGaEeBr7hkeaRlbl5PplBWX7GXka-Rn5Gvrm-gLNCMr0dckGyrsaRob4As2oBQDP-yuE.Cs6_JA.kHRL3DmOS5wJavbd34C8cY7Fvl8', 'Authorization': 'Digest username="mars", realm="Authentication Required", nonce="c148818b24be7094bc1a4f714d18ada5", uri="/", response="ed0fd2e1bf8bbe2c3bccc52c60b63a11", opaque="a271f9c9f64d7b67c52c4f4b0971a5a3"'}
代理場景
HTTP/HTTPS代理
請求時可以通過proxies
引數設定代理,代理中也可以設定認證方式,比如:
proxies = {
"https": "https://username:[email protected]",
"http://stackoverflow.com": "http://username:[email protected]"
}
r = requests.get('https://www.google.com', proxies=proxies)
print r.status_code
r = requests.get('http://stackoverflow.com', proxies=proxies)
print r.status_code
SOCKS代理
SOCKS代理從傳輸層轉發代理報文,並不關注應用層採用的是什麼協議,速度一般比HTTP/HTTPS代理速度快。如果代理使用了SOCKS協議,則需要安裝相應的包支援:pip install requests[socks]
,然後在代理的URL中按照SOCKS協議書寫代理的URL即可。
如果覺得我的文章對您有幫助,歡迎關注我(CSDN:Mars Loo的部落格)或者為這篇文章點贊,謝謝