Python介面測試實戰2 - 使用Python傳送請求
如有任何學習問題,可以新增作者微信:lockingfree
課程目錄
Python介面測試實戰3(下)- unittest測試框架
Python介面測試實戰4(下) - 框架完善:用例基類,用例標籤,重新執行上次失敗用例
Python介面測試實戰5(上) - Git及Jenkins持續整合
Python介面測試實戰5(下) - RESTful、Web Service及Mock Server更多學習資料請加QQ群: 822601020獲取
本節內容
- requests安裝
- requests使用
- JSON型別解析
- requests庫詳解
- 帶安全認證的請求
序言
上節課我們學習了介面測試的理論,抓包工具及使用Postman手工測試各種介面,這節課我們主要講解使用Python語言來發送介面請求,實現介面測試自動化。
傳送請求,我們這裡主要使用Python的一個第三方包(需要先安裝): requests 。
Python3自帶的http.client和urllib.request都能傳送http請求,不過相對來說使用較麻煩,第三方庫requests讓傳送請求更簡單,支援自動編碼解碼,會話保持,長連等
參考: requests官方文件
requests安裝
pip install requests pip3 install requests sudo python3 -m pip install requests
驗證是否安裝成功:
開啟命令列,輸入 python
,在python shell環境下輸入 import requests
沒有報錯即安裝成功
requests的使用
一個最簡單的GET請求
傳送一個請求分3步:
- 組裝請求: 請求可能包含url,params(url引數),data(請求資料),headers(請求頭),cookies等,最少必須有url
- 傳送請求,獲取響應:支援get,post等各種方法傳送,返回的是一個響應物件
- 解析響應: 輸出響應文字
開啟Pycharm,新建一個demo專案,專案下新建一個Python檔案,輸入以下內容:
# 匯入requests包 import requests # 1. 組裝請求 url = "http://httpbin.org/get"# 這裡只有url,字串格式 # 2. 傳送請求,獲取響應 res = requests.get(url) # res即返回的響應物件 # 3. 解析響應 print(res.text)# 輸出響應的文字
帶引數的GET請求
import requests url = "http://www.tuling123.com/openapi/api?key=ec961279f453459b9248f0aeb6600bbe&info=你好"# 引數可以寫到url裡 res = requests.get(url=url) # 第一個url指get方法的引數,第二個url指上一行我們定義的介面地址 print(res.text)
或
import requests url = "http://www.tuling123.com/openapi/api" params = {"key":"ec961279f453459b9248f0aeb6600bbe","info":"你好"} # 字典格式,單獨提出來,方便引數的新增修改等操作 res = requests.get(url=url, params=params) print(res.text)
傳統表單類POST請求(x-www-form-urlencoded)
import requests url = "http://httpbin.org/post" data = {"name": "hanzhichao", "age": 18} # Post請求傳送的資料,字典格式 res = requests.post(url=url, data=data) # 這裡使用post方法,引數和get方法一樣 print(res.text)
JSON型別的POST請求(application/json)
import requests url = "http://httpbin.org/post" data = '''{ "name": "hanzhichao", "age": 18 }''' # 多行文字, 字串格式,也可以單行(注意外層有引號,為字串) data = '{"name": "hanzhichao", "age": 18}' res = requests.post(url=url, data=data) #data支援字典或字串 print(res.text)
data引數支援字典格式也支援字串格式,如果是字典格式,requests方法會將其按照預設表單urlencoded格式轉換為字串,如果是字串則不轉化
如果data以字串格式傳輸需要遵循以下幾點:
- 必須是嚴格的JSON格式字串,裡面必須用雙引號,k-v之間必須有逗號,布林值必須是小寫的true/false等等
- 不能有中文,直接傳字串不會自動編碼
一般來說,建議將data宣告為字典格式(方便資料新增修改),然後再用json.dumps()方法把data轉換為合法的JSON字串格式
import requests import json # 使用到JSON中的方法,需要提前匯入 url = "http://httpbin.org/post" data = { "name": "hanzhichao", "age": 18 }# 字典格式,方便新增 headers = {"Content-Type":"application/json"} # 嚴格來說,我們需要在請求頭裡宣告我們傳送的格式 res = requests.post(url=url, data=json.dumps(data), headers=headers) #將字典格式的data變數轉換為合法的JSON字串傳給post的data引數 print(res.text)
或直接將字典格式的data資料賦給post方法的JSON引數(會自動將字典格式轉為合法的JSON文字並新增headers)
import requests url = "http://openapi.tuling123.com/openapi/api/v2" data = { "reqType":0, "perception": { "inputText": { "text": "附近的酒店" }, "inputImage": { "url": "imageUrl" }, "selfInfo": { "location": { "city": "北京", "province": "北京", "street": "資訊路" } } }, "userInfo": { "apiKey": "ec961279f453459b9248f0aeb6600bbe", "userId": "206379" } } res = requests.post(url=url, json=data) # JSON格式的請求,將資料賦給json引數 print(res.text)
練習:
http://www.tuling123.com/openapi/api?key=ec961279f453459b9248f0aeb6600bbe&info=你好 http://openapi.tuling123.com/openapi/api/v2
JSON型別解析
序列化和反序列化
程式中的物件,如Python中的字典、列表、函式、類等,都是存在記憶體中的,一旦斷電就會消失,不方便傳遞或儲存,所以我們需要將記憶體中的物件轉化為文字或者檔案格式,來滿足傳輸和持久化(儲存)需求
- 序列化: 記憶體物件 -> 文字/檔案
- 反序列化: 文字 -> 記憶體物件
物件在HTTP中的傳輸過程
HTTP協議是超文字傳輸協議,是通過文字或二進位制進行傳輸的,所以我們傳送的請求要轉化成文字進行傳輸,收到的響應也是文字格式,如果是JSON,一般還需要將文字格式重新轉化為物件
JSON物件(Python字典) -> 轉為文字請求 -> 傳送請求
-> 伺服器收到文字請求 -> 將文字請求轉化為物件,獲取其中的引數,處理業務
-> 返回文字格式的響應 -> 客戶端轉為物件格式來從響應中取值
JSON物件與Python字典的區別
JSON物件是javascript object即javascript中的物件,是一種通用的格式,格式嚴格,不支援備註。
JSON文字和JSON物件的區別:
- JSON文字是符合JSON格式的文字,實際上是一個字串
-
JSON物件是記憶體中一個物件,擁有屬性和方法,可以通過物件獲取其中的引數資訊
Python中我們一般提到JSON物件指的是字典
Python的字典的格式和JSON格式,稍有不同:
- 字典中的引號支援單引號和雙引號,JSON格式只支援雙引號
- 字典中的True/False首字母大寫,JSON格式為true/false
- 字典中的空值為None, JSON格式為null
JSON格式操作方法
- 序列化(字典 -> 文字/檔案控制代碼): json.dumps()/json.dump()
- 反序列化(文字/檔案控制代碼 -> 字典) : json.loads()/json.load()
import json # 需要匯入JSON包 data = {'name': '張三', 'password': '123456', "male": True, "money": None} # 字典格式 str_data = json.dumps(data) # 序列化,轉化為合法的JSON文字(方便HTTP傳輸) print(str_data)
輸出: {"name": "\u5f20\u4e09", "password": "123456", "male": true, "money": null}
json.dumps()支援將json文字格式化輸出
import requests import json res = requests.post("http://www.tuling123.com/openapi/api?key=ec961279f453459b9248f0aeb6600bbe&info=怎麼又是你") print(res.text) # 輸出為一行文字 res_dict = res.json() # 將響應轉為json物件(字典)等同於`json.loads(res.text)` print(json.dumps(res_dict, indent=2, sort_keys=True, ensure_ascii=False)) # 重新轉為文字
看一下輸出結果對比:
{"code":100000,"text":"我才要說怎麼又是你"}# res.text,有些介面中文會返回為\u.. { "code": 100000, "text": "我才要說怎麼又是你"# 樹狀格式,比較清晰,顯示中文 }
- indent: 縮排空格數,indent=0輸出為一行
- sork_keys=True: 將json結果的key按ascii碼排序
- ensure_ascii=Fasle: 不確保ascii碼,如果返回格式為utf-8包含中文,不轉化為\u...
反序列化
import json res_text = {"name": "\u5f20\u4e09", "password": "123456", "male": true, "money": null}# JSON文字格式的響應資訊 res_dict = json.loads(res_text) # 轉化為字典 print(res_dict['name'])# 方便獲取其中的引數值
輸出: 張三
檔案的序列化與反序列化
- 序列化:字典 -> 檔案控制代碼
import json res_dict = {'name': '張三', 'password': '123456', "male": True, "money": None} # 字典格式 f = open("demo1.json","w") json.dump(res_dict, f)
檢視同級目錄,增加了一個demo1.json檔案,內容為:
{"name": "\u5f20\u4e09", "password": "123456", "male": true, "money": null}
- 序列化: 檔案控制代碼 -> 字典
在專案中(和下面指令碼檔案同一路徑下)新建 demo2.json
檔案,內容如下,儲存
{ "name": "張三", "password": "123456", "male": true, "money": null }
新建Python檔案
import json f = open("demo.JSON","r", encoding="utf-8")# 檔案中有中文需要指定編碼 f_dict = json.load(f) # 反序列化將檔案控制代碼轉化為字典 print(f['name']) # 讀取其中引數 f.close()
什麼時候使用JSON物件(字典)什麼時候使用JSON文字?
一般在組裝data引數時,建議使用字典格式,傳送請求時用 json.dumps(data)
轉化為文字傳送,收到請求後使用 json.loads(res.text)
轉化為字典,方便我們獲取其中的引數資訊
練習:
- 解析以下json格式檔案,傳送請求並列印響應
注: method支援get和post,如果沒有method,有data預設發post請求,沒有data預設發get請求,type支援:form或json,沒有預設發form格式
demo1.json
{ "url": "http://www.tuling123.com/openapi/api", "method": "get", "params": { "key": "ec961279f453459b9248f0aeb6600bbe", "info": "你好" } }
demo2.json
{ "url": "http://openapi.tuling123.com/openapi/api/v2", "method": "post", "type": "json", "data": { "reqType": 0, "perception": { "inputText": { "text": "附近的酒店" }, "inputImage": { "url": "imageUrl" }, "selfInfo": { "location": { "city": "北京", "province": "北京", "street": "資訊路" } } }, "userInfo": { "apiKey": "ec961279f453459b9248f0aeb6600bbe", "userId": "206379" } } }
requests庫詳解
請求方法
- requests.get()
- requests.post()
- requests.put()
... - requests.session(): 用於保持會話(session)
除了requests.session()外,其他請求方法的引數都差不多,都包含url,params, data, headers, cookies, files, auth, timeout等等
請求引數
auth=(user,password)
響應解析
- res.status_code: 響應的HTTP狀態碼
- res.reason: 響應的狀態碼含義
- req.text:響應的文字格式,按req.encoding解碼
- req.content: 響應的二進位制格式
- req.encoding: 解碼格式,可以通過修改
req.encoding='utf-8'
來解決一部分中文亂碼問題 - req.apparent_encoding:真實編碼,由chardet庫提供的明顯編碼
- req.json(): (注意,有括號),響應的json物件(字典)格式,慎用!如果響應文字不是合法的json文字,或報錯
- req.headers: 響應頭
- req.cookies: 響應的cookieJar物件,可以通過
req.cookies.get(key)
來獲取響應cookies中某個key對應的值
...
示例:
import requests res = requests.get("https://www.baidu.com") print(res.status_code, res.reason) # 200 OK print(res.text) # 文字格式,有亂碼 print(res.content) # 二進位制格式 print(res.encoding) # 檢視解碼格式 ISO-8859-1 print(res.apparent_encoding) # utf-8 res.encoding='utf-8' # 手動設定解碼格式為utf-8 print(res.text) # 亂碼問題被解決 print(res.cookies.items()) # cookies中的所有的項 [('BDORZ', '27315')] print(res.cookies.get("BDORZ")) # 獲取cookies中BDORZ所對應的值 27315
詳情參考: Py.qi: python3之requests
帶安全認證的請求
需要登入的請求(Cookie/Session認證)
例如:
直接訪問: https://demo.fastadmin.net/admin/index/login.html頁面(頁面可以看做一個返回html程式碼的GET請求)會提示請登入後操作
登入頁面: https://demo.fastadmin.net/admin/index/login.html 使用者名稱/密碼:admin/123456(POST表單請求)

- 使用會話保持
import requests s = requests.session() # 新建一個會話 s.post(url="https://demo.fastadmin.net/admin/index/login.html",data={"username":"admin","password":"123456"}) # 傳送登入請求 res = s.get("https://demo.fastadmin.net/admin/dashboard?ref=addtabs") # 使用同一個會話傳送get請求,可以保持登入狀態 print(res.text)
如果不使用session()而單獨發一個post登入請求一個get請求是否可以呢?你可以自己試一下(requests.get()或post()每次都會建立一個新會話)
- 抓取cookies
- 使用Chrome瀏覽器訪問https://demo.fastadmin.net/admin/index/login.html,登入
- 開啟開發者工具重新整理當前頁面( https://demo.fastadmin.net/admin/index/login.html )
- Network麵包檢視當前請求,將cookie後面的值複製出來,組裝成字典格式
import requests url = "https://demo.fastadmin.net/admin/dashboard?ref=addtabs" cookies = {"PHPSESSID":"9bf6b19ddb09938cf73d55a094b36726"} res = requests.get(url=url, cookies=cookies) # 攜帶cookies傳送請求 print(res.text)
兩種方式的對比
- 使用session方式:每次都要傳送兩次請求,效率較低
- 使用攜帶cookies方式:需要手動抓包,提取組裝,cookies中是session有一定有效期,過期之後要重新抓取和更換cookies
- 如果很多或所有請求都需要登入,可以發一次請求,保持該session為全域性變數,其他介面都使用該session傳送請求(同樣要注意登入過期時間)
練習
- 抓包並用指令碼發一條微博或一篇部落格
appid或token方式
- appid: 系統為合法使用者賦予的訪問id,固定的字串,一般經過加密以確保HTTP傳輸中的安全
- token: 即令牌,固定或需要動態申請(有一定有效期),一般由使用者資訊及申請時間計算加密而成,用於驗證介面訪問的許可權
token與session的區別
- session是存在伺服器的,服務端通過驗證客戶端的請求所攜帶的session值在服務會話中是否存在,來驗證使用者是否合法
- token: 是按一定演算法加密計算出來的,服務端通過解密客戶端所攜帶的token值來驗證使用者是否合法
示例: - 訪問百度AI開發者平臺: http://ai.baidu.com/,註冊並登入,成為開發者,選擇文字識別
- 文字識別開發者文件: http://ai.baidu.com/docs#/OCR-API/top
- 根據文件新建應用,檢視自己的App Key和Secret Key
- 參考文件: http://ai.baidu.com/docs#/Auth/top,使用App Key和Secret Key獲取token
- 參考通用文字介面文件: http://ai.baidu.com/docs#/OCR-API/top
- 從網路上找一張帶文字的圖片,右鍵,複製圖片地址(注意不支援https地址的圖片)
import requests import json app_key = 'kPoFYw85FXsnojsy5bB9hu6x' secret_key = 'l7SuGBkDQHkjiTPU3m6NaNddD6SCvDMC' img_url = '//upload-images.jianshu.io/upload_images/7575721-40c847532432e852.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240' # 獲取token get_token_url = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={}&client_secret={}'.format(app_key,secret_key) token = requests.get(url=get_token_url).json().get("access_token")# 從獲取token介面的響應中取得token值 # 識別圖片文字 orc_url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token={}'.format(token) data = {"url": img_url} res = requests.post(url=orc_url, data=data) print(json.dumps(res.json(), indent=2, ensure_ascii=False)) # 格式化輸出
顯示結果:
{ "log_id": 4745549456768330559, "words_result_num": 6, "words_result": [ { "words": "我又問:那麼何時,你帶我回去?" }, { "words": "蓮師言:你是你,我是我。你若不願流連凡塵,自會回去。" }, { "words": "我問蓮師:我從哪裡來,要到哪裡去?" }, { "words": "蓮師言:世間種種變相,皆有起源。來與去皆是命中定數,不可參度。" }, { "words": "我再問:我是否還會再見到你?" }, { "words": "蓮師言:你若心中有我,自然會再見。" } ] }
練習:
- 自己註冊任意一個開發者平臺(微信開發者平臺,百度開發者平臺,餓了麼開發者平臺),建立應用,根據相應的授權方式獲取token,並使用token正常訪問一個介面
開放協議授權
reqeusts支援Basic Auth(基本授權)和Digist Auth(摘要授權)
Oauth1.0 Oauth2.0 參考: requests官方文件

import requests import json # 基本授權可以直接在請求方法中使用`auth = (user,password)` res = requests.get("https://api.github.com/user", auth=("hanzhichao", "hanzhichao123")) print(json.dumps(res.json(), indent=2, ensure_ascii=False))# 格式化輸出
數字簽名
無論是cookie/session還是appid/token方式,只用來驗證請求者身份而不驗證引數,因此無法防止請求引數被抓包攔截後篡改(仍攜帶合法的cookie或token)
數字簽名(sign或sig)是用來對原始引數整體進行加密後生成的一個字串,請求時引數和簽名一期傳送,伺服器收到請求後對引數再次計算簽名核對和所攜帶的簽名是否一致。
例如: 原始簽名{}
此為北京龍騰育才 Python高階自動化(介面測試部分)授課筆記
想要參加現場(北京)/網路課程的可以聯絡作者微信:lockingfree
- 高效學習,快速掌握Python自動化所有領域技能
- 同步快速解決各種問題
- 配套實戰專案練習