Requests從入門到進階


特點

  • Keep-Alive & 連接池
  • 國際化域名和 URL
  • 帶持久 Cookie 的會話
  • 瀏覽器式的SSL認證
  • 自動內容解碼
  • 基本/摘要式的身份認證
  • 優雅的key/value Cookie
  • 自動解壓
  • Unicode 響應體
  • HTTP(S) 代理支持
  • 文件分塊上傳
  • 流下載
  • 連接超時
  • 分塊請求
  • 支持 .netrc

缺點:

  • 同步阻塞模式,不支持異步和協程
  • 尚不支持HTTP2.0

官方文檔:https://requests.readthedocs.io/zh_CN/latest/

安裝

通過pip命令安裝即可:pip install requests

發送請求

發送GET請求

使用requests發送請求,只要使用request.get(url)方法填入對應的接口地址即可,支持攜帶URL參數。調用方法返回響應對象,可以通過響應對象的status_code、text、headers等屬性,來獲取狀態碼、響應文本和響應頭等數據,示例如下。

import requests
res = requests.get('https://httpbin.org/get?name=臨淵&age=18')
print('狀態碼', res.status_code)
print('響應文本', res.text)
print('響應頭', res.headers)

URL只支持ASCII(美國標准碼),在實際的傳輸過程中,中文及一些特殊字符需要經過urlencode(URL編碼)。如上例中的接口地址會被編碼成:

https://httpbin.org/get?name=%E4%B8%B4%E6%B8%8A&age=18

requests在發送請求時會自動進行編碼,運行后顯示如下。

狀態碼 200
響應文本 {
  "args": {
    "age": "18", 
    "name": "\u4e34\u6e0a"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.18.4"
  }, 
  "origin": "111.194.126.253, 111.194.126.253", 
  "url": "https://httpbin.org/get?name=\u4e34\u6e0a&age=18"
}

響應頭 {'Access-Control-Allow-Credentials': 'true', 'Access-Control-Allow-Origin': '*', 'Content-Encoding': 'gzip', 'Content-Type': 'application/json', 'Date': 'Mon, 20 Jan 2020 02:33:47 GMT', 'Referrer-Policy': 'no-referrer-when-downgrade', 'Server': 'nginx', 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Content-Length': '222', 'Connection': 'keep-alive'}

使用Params

Params又叫Query Params,即URL參數,如?name=臨淵&age=18。如果參數很多,直接寫到URL中會比較長,不方便查看和修改。URL參數由多組鍵值對組成。可以通過字典傳給requests請求放到的params參數,即request.get(url, params={}),示例如下。

import requests
res = requests.get('https://httpbin.org/get', params={'name': '臨淵', 'age': '18'})
print('響應文本轉為字典', res.json())

由於參數可能較多,一般我們可以使用變量,先把url及參數等數據組裝好,然后在傳入請求方法中。

res.json()方法實際上是使用了json.loads(res.text)將響應文本嘗試以JSON格式轉為字典。由於該方法存在異常(比如正常情況下返回JSON格式,500報錯時則會返回非JSON格式的報錯信息),建議使用try...except處理,修改如下。

import requests
url = 'https://httpbin.org/get'
url_params = {'name': '臨淵', 'age': '18'}
res = requests.get(url, params=url_params)
try:
    print('響應文本轉為字典', res.json())
except:
    print('響應文本', res.text)

url_params是自定義的變量名,一般筆者習慣使用params作為變量名,來表示和請求方法參數params的對應關系。即

params = {'name': '臨淵', 'age': '18'}
res = requests.get(url, params=params)

運行結果如下。

響應文本轉為字典 {'args': {'age': '18', 'name': '臨淵'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.4'}, 'origin': '111.194.126.253, 111.194.126.253', 'url': 'https://httpbin.org/get?name=臨淵&age=18'}

使用請求頭

請求頭是鏈接和請求數據的一些輔助說明信息,常見的請求頭有:

  • Accept:客戶端能接受的內容類型
  • Accept-Charset:瀏覽器可以接受的字符編碼
  • Accept-Encoding:瀏覽器可以支持的壓縮編碼類型
  • Accept-Languge:瀏覽器可以接受的語言
  • Referer:連接來路
  • User-Agent:發送請求的客戶端信息
  • Connection:連接類型(Keepalive保持連接/Close關閉連接)
  • X-Requested-With:XMLHttpRequest(是Ajax異步請求)
  • Cookie:服務器標記信息
  • Cache-Control:緩存機制(no-cache無緩存或max-age=緩存保存時間)
  • Expries:緩存過期時間
  • Content-Type:內容類型(MIME類型)
  • Content-Length:數據長度

請求頭項一般不區分大小寫。Cookie是請求頭的一項(注意為單數形式,不帶s)。因此在請求一些需要登錄狀態的接口時可以手動抓取到Cookie,放到請求頭中使用,示例如下。
(1)手動登錄后,通過Chrome開發者工具抓取請求正常訪問時的請求頭信息。

請求頭中一般cookie用於驗證登錄,referer用於防止盜鏈,user-agent用於反爬。

(2)組裝字典格式的請求頭並使用
請求頭一般有一組組鍵值對組成,我們同樣使用Python中的的字典格式,構造出請求頭數據,並傳遞給請求方法的headers參數即可。

import requests
url = 'https://www.jianshu.com/shakespeare/v2/notes/9d3f991c901a/book'
headers = {
    'user-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
    'referer': 'https://www.jianshu.com/p/9d3f991c901a',
    'cookie': '__yadk_uid=ratDswBmN3Kzid42v2gKV2q8veUvOsEd; read_mode=day; default_font=font2; locale=zh-CN; remember_user_token=W1s3NTc1NzIxXSwiJDJhJDExJFRVVTNvMlV6NjJaVTlXZjF0YWFuZi4iLCIxNTc5NTczNDg1Ljk2MzgyODYiXQ%3D%3D--feb4c1d88427a88d7321791daf2d76f7b11ed4b3; _m7e_session_core=d0065296a1834086d0279a548d932927; Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1579573487,1579587460,1579591333,1579591335; Hm_lpvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1579601795; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%221697595f5777a9-0a3caa4a8cc382-36667905-1024000-1697595f578430%22%2C%22%24device_id%22%3A%221697595f5777a9-0a3caa4a8cc382-36667905-1024000-1697595f578430%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_utm_source%22%3A%22weixin-friends%22%2C%22%24latest_utm_medium%22%3A%22reader_share%22%2C%22%24latest_utm_campaign%22%3A%22hugo%22%2C%22%24latest_utm_content%22%3A%22note%22%7D%2C%22first_id%22%3A%22%22%7D'
}
res = requests.get(url, headers=headers)
print(res.text)

headers=headders第一個headers是請求方法的固定參數,第二個headers是我們自定義的字典變量(變量也可以使用其他名稱),執行后打印信息如下。

{"notebook_id":26739010,"notebook_name":"Python接口測試","liked_by_user":false}

注:本例中請求頭實際並沒有登錄限制,只需要在請求頭添加了user-agent即可正常使用。

使用Cookies

Cookies可以作為一個整體的字符串放到請求頭的Cookie字段中,當Cookies很多並且需要組裝時,使用字符串會比較長並難以維護。此時可以將Cookies拆開成一組組鍵值對,構造為字典格式的數據,傳遞給請求方法的cookies參數,示例如下。

import requests
url = 'https://www.jianshu.com/shakespeare/v2/notes/9d3f991c901a/book'
headers = {
    'user-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36',
    'referer': 'https://www.jianshu.com/p/9d3f991c901a',
}
cookies = {'Hm_lpvt_0c0e9d9b1e7d617b3e6842e85b9fb068': '1579601795',
 'Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068': '1579573487,1579587460,1579591333,1579591335',
 '__yadk_uid': 'ratDswBmN3Kzid42v2gKV2q8veUvOsEd',
 '_m7e_session_core': 'd0065296a1834086d0279a548d932927',
 'default_font': 'font2',
 'locale': 'zh-CN',
 'read_mode': 'day',
 'remember_user_token': 'W1s3NTc1NzIxXSwiJDJhJDExJFRVVTNvMlV6NjJaVTlXZjF0YWFuZi4iLCIxNTc5NTczNDg1Ljk2MzgyODYiXQ%3D%3D--feb4c1d88427a88d7321791daf2d76f7b11ed4b3',
 'sensorsdata2015jssdkcross': '%7B%22distinct_id%22%3A%221697595f5777a9-0a3caa4a8cc382-36667905-1024000-1697595f578430%22%2C%22%24device_id%22%3A%221697595f5777a9-0a3caa4a8cc382-36667905-1024000-1697595f578430%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_utm_source%22%3A%22weixin-friends%22%2C%22%24latest_utm_medium%22%3A%22reader_share%22%2C%22%24latest_utm_campaign%22%3A%22hugo%22%2C%22%24latest_utm_content%22%3A%22note%22%7D%2C%22first_id%22%3A%22%22%7D'
}

res = requests.get(url, headers=headers, cookies=cookies)
print(res.text)

注:Cookies中不能擁有非ASCII字符,中文應進行URL編碼后使用。

同名參數處理:
假設url中具有同名參數,如name=臨淵,age=18,age=30。由於字典中不能存在同名的鍵,我們可以使用嵌套列表實現。示例如下。
params = [('name','臨淵'), ('age', '18'), ('age', '30')]
請求方法中的其他參數,如data、headers等,如果存在同名變量也可以這樣處理。

發送POST請求

POST方法和GET方法本質上一樣的,都是HTTP請求的一種請求動作。只是通常情況下GET請求不使用請求體數據,而POST使用。既然POST方法會發送請求體數據,就會涉及到數據類型的問題。客戶端和服務端商量好,才能正常的解析和通訊。這種數據類型又稱為媒體類型,標准稱法為MIME(Multipurpose Internet Mail Extensions)類型,即多用途互聯網郵件擴展類型。數據類型的聲明,一般放在請求頭(請求輔助信息)的Content-Type字段中,常見的有以下幾種格式。

  • application/x-www-form-url-encoded:表單URL編碼格式
  • multipart/form-data:復合表單格式(支持文件上傳,文件二進制
  • application/json:JSON格式
  • application/xml:XML格式

不同數據類型的請求,數據組裝方式也不同,至於什么時候用表單,什么時候用JSON格式要看接口文檔或問開發小哥哥,接口在編寫時便已確定好了需要使用的的數據(媒體)類型。

發送POST請求使用requests的post方法即可,格式如下。

res = requests.post(url,data={}, json={}, files={})

data、json、files都是可選參數(一般同時只用其中一個)。分別用來將數據按不同格式編碼發送。

  • data參數接受字典時將數據按普通表單(application/x-www-form-url-encoded)格式發送。
  • json參數存在時將字典格式的請求數據按JSON格式(application/json)發送
  • files參數將字典格式的請求數據(可以包含打開的文件)按混合表單(multipart/form-data)格式發送。

同時使用三者之一時,會自動在請求頭中添加對應的內容類型聲明Content-Type:...

當data參數接受字符串格式的參數是按Raw原始格式發送,不進行編碼和添加請求頭。當data參數接受文件對象時按binary二進制格式發送。

發送FORM表單格式數據

Form表單指網頁中包含輸入框、選擇框、按鈕等組成的一組用戶填寫及選擇的數據。如登錄、注冊表單。表單是最常用的一種請求數據類型,對應的請求頭媒體類型聲明:Content-Type:application/x-www-form-urlencoded

之所以稱為urlencoded,是因為,請求體數據,實際會按url編碼格式發送,如name=臨淵,password=123456實際上會編碼為
name=%E4%B8%B4%E6%B8%8A&password=123456作為請求體數據,后台傳輸。

表單類型的參數同樣是由多組鍵值對組成,我們同樣適用字典格式構造請求體數據並傳遞給請求方法的data參數即可,示例如下。

import requests
url = 'https://httpbin.org/post'
data = {'name': '臨淵', 'password': '123456'}
res = requests.post(url, data=data)
print(res.text)

發送POST請求只要使用requests.post()方法即可,方法中的data=data,第一個data是請求方法的一個固定的關鍵字參數,后面的data是上面我自定義的變量,即{'name': '臨淵', 'password': '123456'},使用其他變量名可以。打印結果如下。

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "\u4e34\u6e0a", 
    "password": "123456"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "39", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.18.4"
  }, 
  "json": null, 
  "origin": "111.194.126.253, 111.194.126.253", 
  "url": "https://httpbin.org/post"
}

發送時,請求頭中會自動添加"Content-Type": "application/x-www-form-urlencoded"
對於JSON格式的響應數據,我們可以使用res.json()轉為字典格式並通過字典取值提取響應字段的變量進行斷言。假設我們要斷言響應結果的url為"https://httpbin.org/post",form不為空且name和password是我們傳的值,示例如下。

res_dict = res.json()
form = res_dict.get('form') 
assert "https://httpbin.org/post" == res_dict.get('url')
assert form and "臨淵" == form.get('name') and '123456' == form.get('password')

再次運行,結果和上次一致。沒有報錯即為assert斷言通過,斷言失敗時會報AssertionError。

發送JSON格式數據

JSON格式是一種通用的數據格式,在Python中JSON實際為“符合JSON語法格式的字符串”,本質是str類型。JSON格式和Python的字典一一對應,略有不同,如JSON中的true/false/null對應字典中的True/False/None。我們同樣可以使用字典來構造JSON請求的數據,然后傳遞夠請求方法的json參數即可,示例如下。

import requests
url = 'https://httpbin.org/post'
json_data = {'name': '臨淵', 'age': 18, 'on_site': True, 'favorite': None}
res = requests.post(url, json=json_data)
print(res.text)

URL參數和FORM變動格式中的數字實際都是轉為字符串格式去發送的,而JSON中可以區分數字格式和字符串格式。如{"age": 18}{"age":"18"}有可能是不一樣的。響應文本打印結果如下。

{
  "args": {}, 
  "data": "{\"name\": \"\\u4e34\\u6e0a\", \"age\": 18, \"on_site\": true, \"favorite\": null}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "70", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.18.4"
  }, 
  "json": {
    "age": 18, 
    "favorite": null, 
    "name": "\u4e34\u6e0a", 
    "on_site": true
  }, 
  "origin": "111.194.126.253, 111.194.126.253", 
  "url": "https://httpbin.org/post"
}

發送時,請求頭中會自動添加"Content-Type": "application/json"
細心的同學會發現,FORM表單格式發送的數據會出現在響應的form字段中,JSON格式的卻出現在data字段中。這是因為JSON和XML等格式一樣屬於Raw(原始格式),即原樣發送。但是在實際發送時仍要確保請求數據都轉為ASCII(美國標准碼)來傳輸。因此中文參數“臨淵”在傳輸是會按utf-8編碼轉換為“\u4e34\u6e0a”。由於JSON格式中只能使用雙引號,響應中data參數是一個JSON格式的字符串,需要使用轉義字符“\”。

發送XML格式的數據

上例提到XML和JSON都屬於Raw格式的數據,XML和JSON在Python中實際都是不同格式的文本字符串。我們將字符串傳遞給請求方法的data參數即可原樣發送,即data參數有以下3重作用:

  1. data = {} 或 [(,), (,)]:接受一個字典或嵌套列表格式的數據,會按表單Url編碼格式
  2. data = '':接受一個字符串或bytes二進制字符串,會原樣發送(需要手動添加請求頭,如果存在中文需要手動編碼)
  3. data = open('...', 'rb'):接受一個文件對象,按binary格式流式上傳。

發送XML格式的數據只要將XML格式的多行字符串傳遞給請求方法的data參數即可,示例如下。

import requests
url = 'https://httpbin.org/post'
xml_data = '''
<xml>
    <name>臨淵</name>
    <age>12</name>
</xml>
'''
headers = {'Content-Type': 'application/xml'}

res = requests.post(url, data=xml_data.encode('utf-8'), headers=headers)
print(res.text)

由於xml_data數據中存在非ASCII碼,需要將數據按utf-8格式編碼為bytes二進制字符串發送。由於使用Raw格式發送數據時不會自動添加請求頭,因此一般要手動在請求頭中添加內容類型聲明,並將構造的字典類型的請求頭變量,傳遞給請求方法的關鍵字參數headers。響應結果如下。

{
  "args": {}, 
  "data": "\n<xml>\n    <name>\u4e34\u6e0a</name>\n    <age>12</name>\n</xml>\n", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "57", 
    "Content-Type": "application/xml", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.18.4"
  }, 
  "json": null, 
  "origin": "111.194.126.253, 111.194.126.253", 
  "url": "https://httpbin.org/post"
}

Raw格式的數據都會記錄在該接口響應數據的data字段中。

Raw格式的請求(Text、JavaScript、JSON、XML、HTML等)都可以按這種方式發送。JSON請求自然也可以按原始方式發送,示例如下。

import requests
url = 'https://httpbin.org/post'
json_data_str = '''
{
    "name": "臨淵", 
    "age": 18, 
    "on_site": true, 
    "favorite": null
}
'''
headers = {'Content-Type': 'application/json'}
res = requests.post(url, data=json_data_str.encode('utf-8'), headers=headers)
print(res.text)

注意以上的json_data_str須是符合JSON格式的字符串,包括必須使用雙引號,應該使用小寫的true,無值應該是null,由於字符串中存在中文,同樣要手動進行encode編碼,同時要手動添加請求頭指定內容類型。

為方便構造請求數據,也可以先構造一個字典格式的請求數據,再使用json.dumps(),將字典格式的數據轉為JSON字符串發送,示例如下。

import requests
import json

url = 'https://httpbin.org/post'
json_data = {
    'name': '臨淵', 
    'age': 18, 
    'on_site': True, 
    'favorite': None
}
headers = {'Content-Type': 'application/json'}
res = requests.post(url, data=json.dumps(json_data), headers=headers)
print(res.text)

注意以上json_data是字典格式的變量,因此要使用TrueNone。在將字典轉為JSON字符串時,需要首先導入json庫。json.dumps()將字典格式的json_data轉換為JSON字符串,並通過默認的ensure_ascii=True參數將中文轉換為\u形式的ASCII字符(如“臨淵”會轉換為“\u4e34\u6e0a”),因此不再需要進行編碼后發送。

發送Multipart/form-data請求(文件上傳)

網頁上的表單有兩種,一種是不包含文件上傳,所有用戶輸入或選擇的數據都可以使用字符串格式表示,這種稱為普通表單或純文本表單,對應MIME類型為application/x-www-form-urlencoded

另一種即包括普通輸入框等,也包含一個或多個文件上傳框。普通輸入框中的變量值可以已字符串格式編碼,而上傳的文件(如圖片文件)則不一定能直接轉為字符串,要使用二進制格式。因此要使用多部分的混合格式,筆者稱之為混合表單,對應MIME類型為multipart/form-data。在表單中,每個需要上傳的文件和普通輸入框一樣對應一個指定的變量。因此同樣可以使用字典格式組裝混合表單的請求數據傳遞給請求方法的files參數即可,示例如下。

import requests
url = 'https://httpbin.org/post'
multi_form_data = {
    'name': '臨淵',
    'age': '18',  # 不能使用int類型
    'avatar': open('/Users/apple/Pictures/robot.png', 'rb'),
    'avatar2': open('/Users/apple/Pictures/robot.jpg', 'rb'),
}

res = requests.post(url, files=multi_form_data)
print(res.text)

表單數據中的數字要使用字符串格式的數字,文件要以rb二進制格式打開傳輸,支持多個變量以及多個文件。

文件類型的數據avatar可以只穿一個打開的文件對象open('/Users/apple/Pictures/robot.png', 'rb'),也可以傳遞三個參數:要保存的文件名,打開的文件及文件MIME類型,即

'avatar': ('robot.png', open('/Users/apple/Pictures/robot.png', 'rb'), 'image/png'),

比如有些接口上傳Excel文件時必須聲明文件名和MIME類型,如:

res = request.post(url, files={'upload_file': 
        ('data.xlsx', 
        open('data.xlsx', 'rb'),
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
    })

MIME類型參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types

發送Binary格式數據(單文件/流式上傳)

import requests
url = 'https://httpbin.org/post'

res = requests.post(url, data=open('/Users/apple/Pictures/robot.jpg', 'rb'))
print(res.text)

JSON與字典的相互轉換

JSON(JavaScript Object Notation),即JavaScript對象標記。 是一種通用的輕量級的數據交換格式。在Python中,JSON本質上是符合JSON格式的字符串(str類型),即JSON字符串。

JSON字符串中支持Object對象、Array數組、String字符串、Number數字、true/false布爾值、null空值6中數據類型,並支持層次嵌套。Python中的字典和JSON字符串中描述的數據類型一一對應,對應關系如下表所示。

JSON字符串 Python
Object {...} 字典 {...}
Array [...] 列表 [...]
String "..." 字符串 '...' 或 "..."
Number 1.5或3 浮點型或整型 1.5或3
true或false True或False
null None

注意,JSON格式較為嚴格,和Python字典格式略有不同:

  • 字典中的引號支持單引號和雙引號,JSON格式只支持雙引號
  • 字典中的True/False首字母大寫,JSON格式為true/false
  • 字典中的空值為None, JSON格式為null
  • 字典中可以使用#好注釋,JSON中不允許使用任何形式的注釋
  • 字典列表最后一項后可以有逗號,JSON數組最后一項后不可以有逗號

作為一種標准格式的字符串,JSON方便在不同系統中進行數據交換,方便進進行傳輸和存儲,卻不方便從整段字符串中提取響應的字段對應的值。

而字典作為內存中的一種數據結構,可以很方便的對其中的數據進行提取或添加等操作。因此我們常常需要在JSON字符串和字典之間相互轉換。

在接口請求中常用的轉換如下。

  • (1)使用字典格式構造請求數據
  • (2)轉為JSON字符串發送請求
  • (3)服務端解析處理
  • (4)返回JSON字符串格式的響應數據
  • (5)轉為字典格式提取相應的字段並斷言

Python自帶的json庫提供JSON字符或JSON文件對象和字典之間的相互轉換,主要方法如下:

  • json.loads(JSON字符串)/json.load(JSON文件對象):JSON字符串/文件轉字典
  • json.dumps(字典)、json.dump(字典,文件對象):字典轉JSON字符串/文件

使用示例如下。

import json
data = {
    'name': '臨淵', 
    'age': 18, 
    'on_site': True, 
    'favorite': None
}
# dict -->  JSON
data_str = json.dumps(data)
print('字典轉JSON字符串', type(data_str), data_str)
# JSON --> dict
data_dict = json.loads(data_str)
print('JSON字符串轉回字典', data_dict)
# dict --> JSON文件
with open('data.json', 'w', encoding='utf-8') as f:
    json.dump(data, f)

# JSON文件 --> dict
with open('data.json', 'r', encoding='utf-8') as f:
    data_dict = json.load(f)
print('JSON文件轉回字典', data_dict)

運行結果如下:

字典轉JSON字符串 <class 'str'> {"name": "\u4e34\u6e0a", "age": 18, "on_site": true, "favorite": null}
JSON字符串轉回字典 {'name': '臨淵', 'age': 18, 'on_site': True, 'favorite': None}
JSON文件轉回字典 {'name': '臨淵', 'age': 18, 'on_site': True, 'favorite': None}

生成的data.json文件內容如下:

{"name": "\u4e34\u6e0a", "age": 18, "on_site": true, "favorite": null}

在使用json.dumps()將字典轉為JSON字符串時,默認為確保ASCII碼已方便HTTP傳輸會將中文進行轉換,同時默認使用單行格式。如果想要更清晰的查看JSON字符串結果,可以使用ensure_ascii=False不進行轉換,使用indent=2空2格縮進顯示,sort_keys=True按key排序輸出,示例如下。

import json

data = {
    'name': '臨淵', 
    'age': 18, 
    'on_site': True, 
    'favorite': None
}
data_str = json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True)
print(data_str)

輸出格式如下:

{
  "age": 18,
  "favorite": null,
  "name": "臨淵",
  "on_site": true
}

通用的請求方法

PUT/DELETE等請求方法使用requests對應的方法即可。

  • requests.get(url, **kwargs):發送GET請求
  • requests.post(url, **kwargs):發送POST請求
  • requests.put(url, **kwargs):發送PUT請求
  • requests.delete(url, **kwargs):發送DELETE請求
  • requests.head(url, **kwargs):發送head請求
  • erquests.options(url, **kwargs):發送options請求

這些請求方法的參數和用法一致,必選參數為url,其他參數為可選參數,常用參數如下。

  • url: 字符串格式,參數也可以直接寫到url中
  • params:url參數,字典格式
  • data: 請求數據,字典或字符串格式
  • headers: 請求頭,字典格式
  • cookies: 字典格式,可以通過攜帶cookies繞過登錄
  • files: 字典格式,用於混合表單(form-data)中上傳文件
  • auth: Basic Auth授權,數組格式 auth=(user,password)
  • timeout: 超時時間(防止請求一直沒有響應,最長等待時間),數字格式,單位為秒

這些方法都源於一個通用的請求方法requests.request(method, url, **kwargs)。這個通用的方法通過必選參數method來指定使用的請求動作。字符串格式,不區分大小寫,即requests.get(url)相當於requests.request('get', url)。

因此我們可以用同樣結構的的數據來組裝任何的HTTP請求,示例如下。

import requests
res = request.request(
    method='post',   # 也可以只寫'post',
    url='https://httpbin.org/post',  # 也可以只寫'https://httpbin.org/post',
    headers={}, 
    data={'name': '臨淵', 'password': '123456'}
)
print(res.text)

請求中也可以根據需求添加其他參數,這一組組鍵值對參數可以使用一個統一的字典來表示,即:

req = {
    'method': 'post',
    'url': 'https://httpbin.org/post', 
    'headers: {}, 
    'data': {'name': '臨淵', 'password': '123456'}
    }

然后通過**req字典解包,可以將一個字典參數req重新還原為其中的4組參數,因此上例子可以改為。

import requests
req = {
    'method': 'post',
    'url': 'https://httpbin.org/post', 
    'headers: {}, 
    'data': {'name': '臨淵', 'password': '123456'}
    }
res = request.request(**req)
print(res.text)

這樣做的好處是可以將任何類型的HTTP請求數據配置到數據文件中(如JSON或Yaml文件),然后將數據轉為字典直接發送。示例如下。
data.json文件內容:

[
    {
        "method": "get",
        "url": "https://httpbin.org/get"
    },
    {
        "method": "post",
        "url": "https://httpbin.org/post", 
        "headers": {}, 
        "data": {"name": "臨淵", "password": "123456"}
    }
]

發送請求腳本如下:

import requests
import json
with open('data.json', encoding='utf-8') as f:
    datas = json.load(f)  # 將JSON文件轉為字典

for req in datas:
    res = requests.request(**req)
    print(res.text)

SSL證書驗證

requests在請求HTTPS接口時,默認驗證SSL證書,請求方法中默認參數為verify=True,如果想要關閉證書驗證,可以設置為False,示例如下。

requests.get('https://www.baidu.com', verify=False)

不自動重定向

當遇到重定向接口,requests默認跟隨重定向,返回所重定向接口的響應對象(<Response [200]>),對於一些單點登錄后轉向的接口,有時我們需要獲取原接口響應中的token信息,則需要使用allow_redirects=False關閉自動重定向,使用方法如下。

import requests
res = requests.get('https://httpbin.org/status/302')
print(res) 
res = requests.get('https://httpbin.org/status/302', allow_redirects=False)
print(res)

第一個自動跟隨重定向,返回<Response [200]>,關閉重定向后返回<Response [302]>。

代理設置

requests支持使用代理,對於HTTP和HTTPS分別使用不同的代理,使用方式如下。

import requests
proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080",
}
requests.get("http://example.org", proxies=proxies)

超時設置

requests支持對請求設置超時時間,以防止請求長時間無響應而阻塞,設置方法如下。

import requests
requests.get('https://github.com', timeout=5)  # 設置整體的超時時間5s
requests.get('https://github.com', timeout=(3, 2))  # 分別設置連接和下載響應內容的超時時間3s,2s。

如果在超時時間內未完成響應,則拋出TimeoutError

授權設置(身份認證)

授權是請求身份驗證的一些開放協議標准,授權協議很多,包括Basic Auth基礎授權,Digist Auth摘要授權,Oauth等。

Basic Auth

Basic Auth基礎授權使用用戶名和密碼來驗證身份,在requests中使用方法如下。

import requests
requests.get('https://api.github.com/user', auth=('githab賬號', '密碼'))

OAuth2.0

需要使用requests-oauthlib,參考鏈接:https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html

會話保持及默認配置

會話Session一般指客戶端和服務端的一次連接交互過程。在使用requests.get()等方法時,每次會建立一個新的會話與服務器進行連接。這樣不便於保持會話(如登錄)狀態,如果想要保持會話狀態,可以使用同一個會話對象來請求所有接口,示例如下。

import requests
s = requests.Session()  # 新建一個會話對象
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')  # 使用該會話對象請求
r = s.get("http://httpbin.org/cookies")  # 使用同樣的會話對象請求
print(r.text)

會話對象還可以用來設置默認的請求頭等HTTP配置,示例如下。

s = requests.Session()
s.auth = ('user', 'pass')  # 在會話中設置默認授權
s.headers.update({'x-test': 'true'})  # 在會話中設置默認請求頭
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'}) # 默認請求頭也會被發送

預制請求Preprared-request

假設有多個請求需要先准備好,再逐個發送,可以使用requests.Request()對象的prepare()方法生成預制請求對象,然后使用會話發送即可,示例如下。

import requests
s = request.Session()
req1 = requests.Request('GET', 'https://httpbin.org/get').prepare()
req2 = requests.Request('POST', 'https://httpbin.org/post', data={'a':1}).prepare()
s.send(req1)  # 發送預制請求
s.send(req2, headers={'x-test': 'true'})  # 支持添加額外項

使用適配器

適配器用於對匹配到的指定形式的請求做特殊處理,可以直接使用requests.adapters中的HTTPAdapter給定響應參數,也可以繼承HTTPAdapter或BaseAdapter自定義處理方式,示例如下。

import requests
s = requests.Session()
a = requests.adapters.HTTPAdapter(max_retries=3)  # 設置最大重試3次
s.mount('http://', a)  # 對該會話所有http://開頭的請求使用

詳細可參考API:HTTPAdapterBaseAdapter

並發請求

requests本身並不支持異步。想要並發請求常用的有多線程多進程或gevent方式。

多線程
直接使用threading的Thread對象即可,通過target指定要運行的方法,示例如下。

import requests
from threading import Thread

def print_res(res, *args, **kwargs):
    print(res.text)

s = requests.Session()
s.hooks={'response': print_res}

t1 = Thread(target=s.get, args=('https://httpbin.org/get',)) # 指定線程運行方法
t2 = Thread(target=s.post, args=('https://httpbin.org/post',), kwargs={'data': {'a': 1}})
t1.start() # 啟動線程
t2.start()
t1.join()  # 連接主線程
t2.join()  

默認線程運行無法獲取target函數的運行結果,這里給會話添加了默認hooks方法,來打印響應文本(也可以通過自定義請求方法來實現)。

如果想獲取線程結果,需要繼承Thread並編寫自己的線程處理類,示例如下。

import requests
from threading import Thread

class MyThread(Thread):
    def __init__(self, func, *args, **kwargs):  # 改變線程的使用方式,可以直接傳遞函數方法和函數參數
        super(MyThread, self).__init__()
        self.func = func
        self.args = args
        self.kwargs = kwargs
        self.result = None

    def run(self):
        self.result = self.func(*self.args, **self.kwargs)  # 為線程添加屬性result存儲運行結果

t1 = MyThread(requests.get, 'https://httpbin.org/get') 
t2 = MyThread(requests.post, 'https://httpbin.org/post', data={'a':1})
t1.start()
t2.start()
t1.join()
t2.join()
print(t1.result)  # 響應對象
print(t2.result.text)  # 響應對象的text屬性即響應文本

使用gevent

pip install gevent

示例如下。

from gevent import monkey;monkey.patch_all()  # 要放import requests上面
import requests
import gevent

g1 = gevent.spawn(requests.get, 'https://httpbin.org/get')
g2 = gevent.spawn(requests.post, 'https://httpbin.org/post', data={'a': 1})

gevent.joinall([g1, g2])

print(g1.value)  # 響應對象
print(g2.value)

使用grequests

pip install grequests

grequests封裝了gevent和requests方法,用起來更簡單,示例如下。

import grequests

req_list = [
  grequests.get('https://httpbin.org/get', ),  
  grequests.post('https://httpbin.org/post', data={'a': 1})
]
res_list = grequests.map(req_list)
print(res_list)

響應處理

res是請求返回的響應對象(變量名隨意)。res.text會自動將二進制格式的響應數據,使用默認編碼轉為文本(字符串)格式。

res響應對象包含各種響應的信息,常用的如下。

  • res.content:二進制響應數據
  • res.text:將二進制響應數據按默認編碼轉為文本(字符串格式)
  • res.json():將JSON格式響應文本(res.text)按轉為字典(!!!非JSON格式響應文本,使用此方法會報JSONDecoderError)
  • res.status_code:狀態碼
  • res.reason:狀態碼說明
  • res.headers:響應頭
  • res.cookies:響應Cookies(響應Cookies中有時候不能包含所有響應頭的Set-Cookies內容,可以通過解析響應頭獲取)
  • res.encoding:當前解碼格式,可以通過修改req.encoding來解決一部分亂碼問題
  • res.apparent_encoding:明顯編碼,使用chardet庫對響應數據分析出的編碼格式

亂碼處理

res.encoding解碼格式和res.apparent_encoding明顯編碼格式不一致時,便可能出現亂碼,如請求百度首頁,打印res.text會發現有亂碼,重新設置res.encoding為明顯編碼的格式,再次打印res.text便可以修復亂碼。示例如下。

import requests
res = requests.get('https://www.baidu.com/')
print(res.text)  # 有亂碼
print('解碼格式', res.encoding)  # 解碼格式 ISO-8859-1
print('明顯編碼', res.apparent_encoding)  # 明顯編碼 utf-8

res.encoding = res.apparent_encoding # 修改解碼格式
print(res.text)  # 亂碼解決

文件下載(流式下載)

對應資源類接口(如圖片鏈接),想要保持文件,可以直接使用res.content按二進制保存文件即可,示例如下。

import requests
res = requests.get('https://upload.jianshu.io/users/upload_avatars/7575721/5339c9d6-be6b-47cf-87cc-c0517467c6bc.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240')

with open('avatar.png', 'wb') as f:
    f.write(res.content)

對應較大的文件,可是使用流式下載,示例如下。

import requests
res = requests.get('https://upload.jianshu.io/users/upload_avatars/7575721/5339c9d6-be6b-47cf-87cc-c0517467c6bc.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240')

with open('avatar.png', 'wb') as f:
    for data in res.iter_content(128):  # 按128字節分塊保存
        f.write(data)
with open('avatar.png', 'wb') as f:
    for data in res.iter_content(128):  # 按128字節分塊保存
        f.write(data)

Hooks使用

Hooks即鈎子方法,用於在某個框架固定的某個流程執行是捎帶執行(鈎上)某個自定義的方法。
requests庫只支持一個response的鈎子,即在響應返回時可以捎帶執行我們自定義的某些方法。可以用於打印一些信息,做一些響應檢查或想響應對象中添加額外的信息,示例如下。

import requests
url = 'https://httpbin.org/post'

def verify_res(res, *args, **kwargs):
    print('url', res.url)
    res.status='PASS' if res.status_code == 200 else 'FAIL'

res = requests.get(url, data=data, hooks={'response': verify_res})
print(res.text)
print(res.status)

執行結果如下。

https://httpbin.org/post
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>

FAIL

verfiy_res是我們自定義的方法,第一個參數為響應對象,后面kwargs里是請求的一些配置。鈎子方法不能返回響應對象以外的有意義值,否則會破壞后面對響應對象的處理。

由於該接口只支持post請求,使用get請求時響應狀態碼為405(請求方法不被允許),因此響應對象被添加的status的值為FAIL。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM