Python接口测试实战2 - 使用Python发送请求

课程目录#

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 官方文档

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 步:

  1. 组装请求: 请求可能包含 url,params(url 参数),data(请求数据),headers(请求头),cookies 等,最少必须有 url
  2. 发送请求,获取响应:支持 get,post 等各种方法发送,返回的是一个响应对象
  3. 解析响应: 输出响应文本

打开 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)

练习:

  1. 利用图灵聊天接口(GET) http://www.tuling123.com/openapi/api?key=ec961279f453459b9248f0aeb6600bbe&info=你好,结合 Python 的 input 编写一个机器人聊天室
  2. 利用图灵查询接口(POST)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']) # 方便获取其中的参数值

输出:张三

文件的序列化与反序列化

  1. 序列化:字典 -> 文件句柄
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}
  1. 序列化: 文件句柄 -> 字典

在项目中(和下面脚本文件同一路径下)新建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)转化为字典,方便我们获取其中的参数信息
练习:

  1. 解析以下 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 等等

请求参数#

  • 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 表单请求)
未登录访问接口
  1. 使用会话保持
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() 每次都会建立一个新会话)

  1. 抓取 cookies
    1. 使用 Chrome 浏览器访问 https://demo.fastadmin.net/admin/index/login.html,登录
    2. 打开开发者工具刷新当前页面(https://demo.fastadmin.net/admin/index/login.html)
    3. Network 面包查看当前请求,将 cookie 后面的值复制出来,组装成字典格式
抓取cookies
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 发送请求(同样要注意登录过期时间)
    练习
  1. 抓包并用脚本发一条微博或一篇博客

appid 或 token 方式#

  • appid: 系统为合法用户赋予的访问 id,固定的字符串,一般经过加密以确保 HTTP 传输中的安全
  • token: 即令牌,固定或需要动态申请(有一定有效期),一般由用户信息及申请时间计算加密而成,用于验证接口访问的权限

token 与 session 的区别

创建应用 获取token 通用文字接口
  • 从网络上找一张带文字的图片,右键,复制图片地址(注意不支持 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": "莲师言:你若心中有我,自然会再见。"
    }
  ]
}

练习:

  1. 自己注册任意一个开发者平台(微信开发者平台,百度开发者平台,饿了么开发者平台),创建应用,根据相应的授权方式获取 token,并使用 token 正常访问一个接口

开放协议授权#

reqeusts 支持 Basic Auth(基本授权)和 Digist Auth(摘要授权)

Oauth1.0 Oauth2.0 参考: requests 官方文档
Basic Auth

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

  1. 高效学习,快速掌握 Python 自动化所有领域技能
  2. 同步快速解决各种问题
  3. 配套实战项目练习