使用者:Xyy23330121/Python/HTTP 請求
本章節,我們將主要講解如何使用 Python 自動訪問網際網路內容。本章節默認讀者對 HTML 有基本的了解,知道如何使用 正則表達式 等方式,從 HTML 代碼中提取信息。
Python 內置的模塊 urllib 提供了對網際網路內容進行請求的方法。而第三方模塊 requests 提供了比 urllib 更好用的方法。本章講解的內容將基於 requests,而非 urllib。
安裝
編輯對於使用從 python.org 下載的 Windows 版本的 Python,我們可以從命令行程序中,輸入以下代碼以安裝 request。
python -m pip install requests
如果連結不暢,或者下載失敗。可以用以下代碼,改為從清華大學提供的託管伺服器中下載 requests。
python -m pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
基本請求
編輯訪問網際網路的基本方式是進行 HTTP 請求。如果讀者僅是訪問網頁,無需登錄網站帳號等,只要看本章節內容即可。
import requests
r = requests.get("https://zh.wikiversity.org")
requests.get() 等請求函數會進行一次請求,並返回請求得到的結果。
響應結果
編輯對於結果對象 r,r.content會包含所得到的響應內容。該響應內容是二進制的 bytes 對象。在實際使用時,並不會直接使用二進制的內容,而是通過 HTTP 頭中的信息,將二進制內容按指定的編碼解碼後、再進行處理。
requests 會按照 HTTP 頭中的信息自動設置編碼方式。讀者也可以自行設置編碼方式。
r = requests.get("https://www.baidu.com/s?wd=URL")
print(r.encoding) #输出自动的编码方式
r.encoding = "ascii" #将编码方式改为 ascii
響應結果文本
編輯類似 r.content,r.text會包含所得到的結果的文本形式。它等同於
r.content.decode(encoding = r.encoding, errors = "ignore")
如果請求的是一個 HTML 頁面,則返回的會是該 HTML 頁面的原始碼,比如:
r = requests.get("https://www.baidu.com/s?wd=URL")
print(r.text)
輸出為:
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
如果請求的並非 HTML 頁面,此方法也會原樣返回得到的內容。比如以下方式請求得到的是一個 json 文本序列。
s = requests.get("https://api.bilibili.com/x/web-show/page/header")
print(s.text)
輸出為:
{"code":-400,"message":"Key: 'ResourceID' Error:Field validation for 'ResourceID' failed on the 'min' tag","ttl":1}
json 響應結果
編輯使用 r.json() 即可將獲得的 json 結果轉換為對應的 Python 對象。該方法等同於:
import json
json.loads(r.text)
原始響應結果
編輯在極少數情況,讀者可能需要使用原始的響應結果。r.raw 會輸出原始的響應結果對象。如果要使用原始響應結果,在請求時,需要添加 stream 參數,比如:
r = requests.get("https://www.baidu.com/s?wd=URL", stream=True)
原始的響應結果對象是第三方庫 urllib3 中的一個 urllib3.response.HTTPResponse 對象。這裡不多贅述。
高級請求
編輯除了基本方式之外,HTTP 能傳輸更多信息。如果要實現更高級的功能,就要了解 HTTP 能傳輸的信息內容。本章將講解 HTTP 請求中包含的內容,以及如何用 requests 模塊中的方法來傳輸這些信息。
URL參數
編輯在 HTTP 標準中, URL 是可以包含參數的。比如:
r = requests.get("https://www.baidu.com/s?wd=URL")
除上述由讀者在 URL 中添加參數之外,requests 還可以將參數自動加入到 URL 裡面並進行請求:
payload = {"wd" : "URL"}
r = requests.get("https://www.baidu.com/s", params = payload)
print(r.url) #输出:https://www.baidu.com/s?wd=URL
請求的標頭
編輯HTTP 請求中,HTTP 標頭(HTTP header)會包含比如「發送請求的客戶端名稱」、「傳輸的編碼方式」等等。具體參見 HTTP 標頭。
以「客戶端名稱」為例,想要自定義發送的客戶端名稱,可以用如下方式:
head = {'user-agent': 'my-app/0.0.1'}
r = requests.get("https://www.baidu.com/", headers = head)
需要注意的是:
- 標頭字典中的所有值必須是字符串、bytes 或者 Unicode 信息。最好不要使用 Unicode 信息。
- 自定義標頭的優先級低於其它可確定的信息源,比如:
- (如果有)netrc 配置文件的內容
- (如果有)auth 參數的內容。
- (如果有)在 URL 中提供的代理憑據。標頭 Proxy-Authorization 將被 URL 中提供的代理憑據覆蓋。
- (可以確定信息長度時)標頭 Content-Length 會被修改為確定的信息長度。
- 其它類似的信息源。
cookies
編輯詳細使用方式參見 https://httpbin.org/#/。
cookie 是 HTTP 標頭中的一個欄位。
網頁常用 cookies 來記錄用戶的情況。瀏覽器在請求網頁時,也會發送上次瀏覽網頁時留下的 cookies,以實現自動登錄等功能。一個 cookie 會包含以下的內容:
- 鍵
- 值
- 域名
- 路徑
- 過期時間
在使用時,我們可以用字典,只傳遞「鍵」和「值」,讓其它內容自動填充。比如:
url = 'https://httpbin.org/cookies'
cookies = {'name': 'value'}
r = requests.get(url, cookies=cookies)
print(r.text)
也可以使用 requests.cookies.RequestsCookieJar 實例來傳遞更多內容。比如:
#创建存储 cookies 的对象。
jar = requests.cookies.RequestsCookieJar()
#添加两个 cookie
jar.set('name1', 'value1', domain='httpbin.org', path='/cookies')
jar.set('name2', 'value2', domain='httpbin.org', path='/')
#请求时传递 cookies。
url = 'https://httpbin.org/cookies'
r = requests.get(url, cookies=jar)
print(r.text)
對於需要登錄的網頁,讀者可以使用比如 EditThisCookie 之類的瀏覽器擴展程序來查看並複製自己瀏覽網頁產生的 cookies,將其詳細信息複製,並用 requests 模塊發送。此時請求的結果,等同於讀者登錄帳號之後的請求結果。
身份驗證信息
編輯HTTP 協議支持自定義的身份驗證信息。該信息的內容應當由伺服器和用戶自行約定。並會覆蓋位於標頭的 Authorization 信息。
該內容應當為一個多個不含空格的ASCII字符串組成的元組,可以使用以下方式插入該信息。
auth = ("Basic","QWxhZGRpbjpvcGVuIHNlc2FtZQ==")
url = 'https://httpbin.org/get'
r = requests.get(url, auth=auth)
響應延時
編輯在請求時,有時會需要一些時間才能正確接收到伺服器返回的信息;也可能請求丟失、伺服器未響應。設定響應延時可以解決這些問題。
響應延時是以秒為單位的浮點數或形如 (连接延时, 读取延时)
的浮點數元組。使用方式為:
url = 'https://httpbin.org/get'
r = requests.get(url, timeout=0.1)
https 重定向
編輯許多網頁都會把 http 的內容重定向到 https 來處理。雖然禁止重定向沒有什麼好處,我們是可以禁止這樣的重定向的。比如:
r = requests.get('http://github.com/', allow_redirects=False)
代理伺服器
編輯可以設置經由代理伺服器發送請求。方式如下:
proxies = {
'http': 'http://10.10.1.10:3128',
'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)
流
編輯如果在請求時設置了 stream 參數為 True,則返回的內容會是一個「二進制流」。
r = requests.get('https://github.com', stream=True)
if r.encoding is None:
r.encoding = 'utf-8'
for line in r.iter_lines(): #像二进制流一样处理返回的内容
line.decode(r.encoding)
證書
編輯在互相傳遞信息時,需要驗證雙方的身份。網際網路通過公私鑰加密建立了「證書」機制,通過證書可以驗證溝通雙方的身份。其具體內容可以參見 數字證書 。讀者在進行交流時,也需要驗證證書。
伺服器驗證
編輯在進行請求時,可以對伺服器返回的 TLS 證書進行驗證。
不進行驗證
編輯默認總會驗證伺服器返回的 TLS 證書。如果要不進行驗證,可以增加以下參數。
requests.get('http://example.org', verify=False)
要求證書路徑
編輯如果 verify 參數為一個字符串,則只有伺服器使用路徑包含 verify 的證書時,才會通過驗證。比如:
requests.get('https://github.com', verify='/path/to/certfile')
客戶端驗證
編輯可以為自己的客戶端設置證書。
如果給 cert 參數輸入字符串,則應當輸入指向 ssl 客戶端認證文件(.pem 文件)的路徑。如果輸入元組,則應當傳入形如 ("cert","key")
的元組。
更多請求
編輯上面章節提到的請求,均為「GET」請求,只用於讀取資料。如果要進行其它操作,則需要使用其它的請求。要了解所有的請求方式,參見 HTTP 請求方法。
requests 實現了以下幾種請求方式:
- GET,讀取資料。
- HEAD,獲得資料的元信息,而不獲取整個資料。
- OPTIONS,使伺服器傳回該資源支持的所有HTTP請求方法。
- POST,上傳新數據。
- PUT,上傳並修改。
- DELETE,刪除數據。
- PATCH,將局部更改應用到資源。
在 requests 模塊中,這些請求方式支持 requests.get() 函數中支持的一切參數。以下將主要講解它們在使用時與 GET 的區別。
requests.head
編輯requests.head() 函數向伺服器請求資料,但伺服器不會返回資料的正文部分。與 GET 不同,此函數默認禁用重定向。
r = requests.head('https://httpbin.org/get')
如果要啟用重定向,則需要:
r = requests.head('http://github.com/', allow_redirects=True)
requests.options
編輯OPTIONS 請求的方法如下:
r = requests.options('https://httpbin.org/get')
print(r.headers['allow']) #输出:OPTIONS, GET, HEAD
從以上輸出可以知道,該 URL 支持三種請求:OPTIONS、GET 和 HEAD。
requests.post
編輯POST 請求用於上傳信息。
form 信息的發送
編輯http 支持表單內容的發送。其基本使用方法如下:
payload = {'key': 'value'} #要上传的信息
r = requests.post('https://httpbin.org/post', data=payload)
print(r.text)
通過在 data 中傳入字典,可以將字典的信息以 form 的形式發送。
在 data 中,可以給同一個鍵設置多個值,此時可以用以下兩種方法:
payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
payload_dict = {'key1': ['value1', 'value2']}
r2 = requests.post('https://httpbin.org/post', data=payload_dict)
文件發送
編輯如果要發送文件,則可以使用以下方式:
url = 'https://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=files)
print(r.text)
其中,文件內容最好是二進制的,這樣有助於自動獲取請求中信息的大小。這樣發送的文件不包含文件名,也不包含處理方式。僅包含文件的內容。
如果要包含文件名、文件類型等信息,可以使用元組:
url = 'https://httpbin.org/post'
files = {'file': (
'report.xls',
open('report.xls', 'rb'),
'application/vnd.ms-excel',
{'Expires': '0'}
)}
r = requests.post(url, files=files)
print(r.text)
如果讀者希望,也可以直接將文本內容當成文件的內容並發送出去:
url = 'https://httpbin.org/post'
files = {'file': (
'report.csv',
'some,data,to,send\nanother,row,to,send\n'
)}
r = requests.post(url, files=files)
print(r.text)
流式發送
編輯對於較大的文件,可能無法在一個請求中完整發送。此時,需要使用流式發送:
with open('file_path', 'rb') as f:
requests.post("http://some.url/streamed", data = f)
這種流式發送,需要所請求的伺服器有對應的支持。
同時發送多個文件
編輯使用以下方法,可以同時發送多個文件:
url = 'https://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
在發送文件時,建議用二進制模式打開文件。
requests.put
編輯r = requests.put('https://httpbin.org/put', data={'key': 'value'})
requests.delete
編輯r = requests.delete('https://httpbin.org/delete')
requests.patch
編輯r = requests.patch('https://httpbin.org/put', data={'key': 'value'})
請求的鉤子
編輯requests 模塊的請求支持使用鉤子。目前支持的鉤子僅有一個:"response"
。這個鉤子會在請求得到響應時觸發,使用方法如下:
def print_url(r):
print(f"向 {r.url} 发出了请求")
r = requests.get('https://httpbin.org/', hooks={'response': print_url})
程序輸出為:
向 https://httpbin.org/ 发出了请求
在得到響應後,鉤子指定的函數會被傳入一個響應結果對象。
特別的,如果 "response"
鉤子所指定的函數返回值不為 None,則 requests.get()
函數會返回鉤子函數所返回的內容,比如以下的鉤子函數:
def record_hook(r, *args, **kwargs):
r.hook_called = True
return r
高級響應結果處理
編輯任何請求所得的回應,都是一個 request.Response 對象。本章節將講解該對象的方法。
響應結果的標頭
編輯讀者可以通過以下方式,讀取網站返回時使用的 標頭。
r = requests.get(url)
print(r.headers)
標頭會以字典形式返回。
響應結果的 cookies
編輯讀者可以通過以下方式,讀取網站返回的 cookies。
r = requests.get(url)
print(r.cookies)
此方法返回的是一個 requests.cookies.RequestsCookieJar 實例,該實例有以下方法:
響應延時
編輯r = requests.get(url)
print(r.elapsed)
響應延時是發送請求到收到響應之間的時間間隔,為一個 datetime.timedelta 對象。
重定向歷史記錄
編輯r = requests.get(url)
print(r.history)
在進行請求時,requests 模塊會自動進行重定向。通過這種方法可以獲得重定向的歷史記錄列表,列表的元素為每次重定向時的 request.Response 對象。
HTTP 狀態碼
編輯r = requests.get(url)
print(r.status_code) #状态码,比如 404、200 等
print(r.reason) #状态字符串,比如"Not Found"等
會話
編輯會話(Session)是一種高級對象。它會自動保存網站所發送的 cookie 並在下一次請求時向對應的網站提供。它同時還提供了連接池和連接配置。
創建 Session 和使用
編輯以下程序創建了一個 Session,並用該 Session 發送了一個 GET 請求:
import requests
s = requests.Session()
s.get('https://httpbin.org/get')
也可以用上下文管理器的方式來使用 Session:
import requests
with requests.Session() as s:
s.get('https://httpbin.org/get')
Session 支持上面列出的一切請求方式。使用 Session 發送請求時所使用的「方法名稱」和「方法參數」,和使用上面列出的函數來發送請求時的「函數名稱」和「函數參數」是一樣的。
Session 包含的設置
編輯對於經過以下代碼所創建的 Session:
import requests
session = requests.Session()
該對象在進行請求時,會自動在請求中輸入各種參數。以下是參數的列表:
屬性 | 說明 | 備註 |
---|---|---|
session.cookies | cookie | 一個 RequestsCookieJar 實例,和上面 #cookies 章節一致。 可以修改成其它的、cookielib.CookieJar 的子類型的實例。 |
session.auth | 身份驗證信息 | 和上面 #身份驗證信息 章節一致。 |
session.verify | 伺服器驗證 | 和上面 #伺服器驗證 章節一致。 |
session.cert | 客戶端驗證 | 和上面 #客戶端驗證 章節一致。 |
session.headers | 標頭信息 | 和上面 #請求的標頭 章節一致。 |
session.hooks | 鉤子 | 和上面 #請求的鉤子 章節一致。 |
session.proxies | 代理伺服器 | 和上面 #代理伺服器 章節一致。 |
session.stream | 流 | 和上面 #流 章節一致。 |
session.max_redirects | 最大重定向次數 | 可以設置重定向的最大次數,默認為 30。 |
session.params | 請求參數 | 在請求時會被自動加入的,所有參數的字典。 |
除上述屬性之外,requests.Sessions 還有一些其它的方法,詳情參見文檔。