課程目錄
Python接口測試實戰1(上)- 接口測試理論
Python接口測試實戰1(下)- 接口測試工具的使用
Python接口測試實戰2 - 使用Python發送請求
Python接口測試實戰3(上)- Python操作資料庫
Python接口測試實戰3(下)- unittest測試架構
Python接口測試實戰4(上) - 接口測試架構實戰
Python接口測試實戰4(下) - 架構完善:用例基類,用例标簽,重新運作上次失敗用例
Python接口測試實戰5(上) - Git及Jenkins持續內建
Python接口測試實戰5(下) - RESTful、Web Service及Mock Server
更多學習資料請加添加作者微信:superz-han 擷取
PDF下載下傳:連結:https://pan.baidu.com/s/1OwAa8nl1eeBj8fcrgd3sBA 密碼:e9d8
本節内容
- requests安裝
- requests使用
- JSON類型解析
- requests庫詳解
- 帶安全認證的請求
序言
上節課我們學習了接口測試的理論,抓包工具及使用Postman手工測試各種接口,這節課我們主要講解使用Python語言來發送接口請求,實作接口測試自動化。
發送請求,我們這裡主要使用Python的一個第三方包(需要先安裝):requests。
Python3自帶的http.client和urllib.request都能發送http請求,不過相對來說使用較麻煩,第三方庫requests讓發送請求更簡單,支援自動編碼解碼,會話保持,長連等
參考: requests官方文檔
- Windows: 打開cmd指令行,輸入
,等待安裝完成即可pip install requests
- Linux: (建議使用Python3),終端中輸入
pip3 install requests
- Mac: (建議使用Python3),
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)
練習:
- 利用圖靈聊天接口(GET)
,結合Python的input編寫一個機器人聊天室http://www.tuling123.com/openapi/api?key=ec961279f453459b9248f0aeb6600bbe&info=你好
- 利用圖靈查詢接口(POST)
,封裝一個實用的查詢方法,查詢你附近的美食等等http://openapi.tuling123.com/openapi/api/v2
序列化和反序列化
程式中的對象,如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.get()
- requests.post()
-
requests.put()
...
-
requests.session(): 用于保持會話(session)
除了requests.session()外,其他請求方法的參數都差不多,都包含url,params, data, headers, cookies, files, auth, timeout等等
請求參數
- url: 字元串格式,參數也可以直接寫到url中
- params:url參數,字典格式
- data: 請求資料,字典或字元串格式
- headers: 請求頭,字典格式
- cookies: 字典格式,可以通過攜帶cookies繞過登入
- files: 字典格式,用于混合表單(form-data)中上傳檔案
- auth: Basic Auth授權,數組格式
auth=(user,password)
- timeout: 逾時時間(防止請求一直沒有響應,最長等待時間),數字格式,機關為秒
響應解析
- 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/dashboard?ref=addtabs
頁面(頁面可以看做一個傳回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自動化所有領域技能
- 同步快速解決各種問題
- 配套實戰項目練習