使用者: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() 等請求函數會進行一次請求,並返回請求得到的結果。

響應結果

編輯

對於結果對象 rr.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

編輯
關於 httpbin.org
httpbin.org 是一個公益性的網站。向 httpbin.org 域名下的對應 URL 發送請求時。請求中的所有信息會在返回信息的正文中出現。善用這一工具有助於讀者了解如何發送請求。
詳細使用方式參見 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 還有一些其它的方法,詳情參見文檔。