Python总结----Requests库深度理解(四)
第一章:GET()请求
代码示例:
1 import requests 2 from requests.exceptions import RequestException 3 4 def get_request(url, params=None, headers=None): 5 try: 6 response = requests.get( 7 url, 8 params=params, 9 headers=headers, 10 timeout=5 # 设置超时时间 11 ) 12 13 # 抛出HTTP错误(如404, 500等) 14 response.raise_for_status() 15 16 # 尝试解析JSON 17 try: 18 data = response.json() 19 return {"status": "success", "data": data} 20 except ValueError: 21 return {"status": "success", "data": response.text} 22 23 except RequestException as e: 24 return {"status": "error", "message": str(e)} 25 26 # 使用示例 27 result = get_request( 28 url="https://api.example.com/data", 29 params={"id": 123}, 30 headers={"Accept": "application/json"} 31 ) 32 33 if result["status"] == "success": 34 print("获取的数据:", result["data"]) 35 else: 36 print("请求出错:", result["message"])
response.raise_for_status():是 requests 库中的一个重要方法,它的作用是检查 HTTP 请求是否成功,如果请求返回了错误状态码(即非 2xx 的状态码,如 404、500 等),它会抛出一个 HTTPError 异常。如果响应状态码是 200、201、204 等(即 2xx 成功状态码),raise_for_status() 不会执行任何操作,程序继续执行。如果状态码是 4xx(客户端错误,如 404 Not Found)或 5xx(服务器错误,如 500 Internal Server Error),它会抛出 requests.exceptions.HTTPError 异常。
response.json():将 HTTP 响应体(response.text)解析为 Python 字典或列表(取决于 JSON 数据结构)。如果响应内容不是有效的 JSON,会抛出 ValueError 异常。response.text:返回响应体的原始字符串(如 '{"name": "Alice", "age": 25}')。response.json() 内部调用 json.loads(response.text),将字符串解析为 Python 对象
第二章:POST()请求
HTTP规定POST提交的数据必须放在消息主体中,但是协议并没有规定必须使用何种编码方式。服务端通常是根据请求头中的Content-Type字段来获知请求中的消息主体是用何种方式进行编码。再对消息主体进行解析。常见的几种编码方式:
- application/x-www-form-urlencoded:以Form表单形式提交数据,提交的数据按照key1=1&key2=2的方式进行编码
- application/json:以JSON字符串方式提交数据;
- text/xml:以XML文档字符串方式提交数据
- multipart/form-data:一般用来上传文件
application/json(JSON 格式)代码示例:
1 import requests 2 import json 3 4 url = "https://api.example.com/post" 5 data = {"name": "Alice", "age": 25} # Python 字典 6 headers = {"Content-Type": "application/json"} 7 8 # 方法1:直接传递 dict,requests 会自动序列化为 JSON 9 response = requests.post(url, json=data, headers=headers) 10 11 # 方法2:手动序列化(效果相同) 12 response = requests.post( 13 url, 14 data=json.dumps(data), # 手动转为 JSON 字符串 15 headers=headers 16 ) 17 18 print(response.json()) # 解析响应 JSON
text/xml(XML 格式)代码示例:
1 import requests 2 3 url = "https://api.example.com/xml" 4 xml_data = """ 5 <user> 6 <name>Alice</name> 7 <age>25</age> 8 </user> 9 """ 10 headers = {"Content-Type": "text/xml"} 11 12 response = requests.post(url, data=xml_data, headers=headers) 13 print(response.text) # 响应可能是 XML 或 JSON
multipart/form-data(表单上传文件)代码示例:
1 # 纯上传文件 2 import requests 3 4 url = "https://api.example.com/upload" 5 files = {"file": open("example.txt", "rb")} # 以二进制模式打开文件 6 7 response = requests.post(url, files=files) 8 print(response.json())
1 # 表单提交+文件 2 import requests 3 4 url = "https://api.example.com/submit" 5 data = {"username": "alice"} 6 files = {"avatar": open("avatar.jpg", "rb")} 7 8 response = requests.post(url, data=data, files=files) 9 print(response.json())
1 # 上传多个文件 2 files = [ 3 ('files', ('file1.txt', open('file1.txt', 'rb'))), 4 ('files', ('file2.jpg', open('file2.jpg', 'rb'), 'image/jpeg')) 5 ] 6 response = requests.post(url, files=files)
换种方法实现文件上传
1 import requests 2 3 url = "http://httpbin.org/post" # 测试用的上传接口 4 file_path = "example.txt" # 本地文件路径 5 6 # 方法1:直接打开文件 7 with open(file_path, 'rb') as f: 8 response = requests.post(url, files={'file': f}) 9 10 # 方法2:指定文件名和内容(无需本地文件) 11 response = requests.post(url, files={'file': ('custom_name.txt', b'file content')}) 12 13 print(response.json()) # 查看服务器返回的响应
application/x-www-form-urlencoded(默认表单格式)代码示例:
1 import requests 2 3 url = "https://api.example.com/form" 4 data = {"name": "Alice", "age": 25} 5 headers = {"Content-Type": "application/x-www-form-urlencoded"} 6 7 response = requests.post(url, data=data) # 默认就是这种格式,可省略 headers 8 print(response.json())
总结:
| Content-Type | 用途 | requests 参数 |
| application/json | JSON 数据 | json=data 或 data=json.dumps(data) |
| text/xml | XML数据 | data=xml_string |
| multipart/form-data | 文件上传 |
files={"file": open("file.txt", "rb")} |
| application/x-www-form-urlencoded | 表单数据 | data={"key": "value"}(默认) |
| text/plain | 纯文本 | data="raw text" |
第三章:处理TOKEN
登录接口请求后提Token,随后在下个请求中使用Token值,示例代码如下:
1 import requests 2 import pytest 3 import os 4 5 class ApiClient: 6 """API 客户端封装类""" 7 BASE_URL = "http://www.example.com" 8 LOGIN_URL = f"{BASE_URL}/login" 9 TASKS_URL = f"{BASE_URL}/api/tasks" 10 TOKEN_FILE = "token.md" 11 12 @classmethod 13 def login(cls, username: str = "LZL", password: str = "123") -> str: 14 """登录并获取 Token""" 15 response = requests.post( 16 url=cls.LOGIN_URL, 17 json={"username": username, "password": password}, 18 headers={"Content-Type": "application/json"} 19 ) 20 response.raise_for_status() 21 token = response.json().get('token') 22 if not token: 23 raise ValueError("Token not found in response!") 24 return token 25 26 @classmethod 27 def save_token(cls, token: str): 28 """将 Token 保存到文件""" 29 with open(cls.TOKEN_FILE, 'w') as f: 30 f.write(token) 31 32 @classmethod 33 def load_token(cls) -> str: 34 """从文件加载 Token""" 35 if not os.path.exists(cls.TOKEN_FILE): 36 raise FileNotFoundError(f"Token file '{cls.TOKEN_FILE}' not found!") 37 with open(cls.TOKEN_FILE, 'r') as f: 38 return f.read().strip() 39 40 @classmethod 41 def get_tasks(cls, token: str) -> dict: 42 """获取任务列表""" 43 headers = {"Authorization": f"Bearer {token}"} 44 response = requests.get(url=cls.TASKS_URL, headers=headers) 45 response.raise_for_status() 46 return response.json() 47 48 # -------------------- 测试代码 -------------------- 49 50 @pytest.fixture(scope="session") 51 def token(): 52 """会话级 Fixture:优先从文件加载 Token,不存在则登录获取""" 53 try: 54 token = ApiClient.load_token() 55 except FileNotFoundError: 56 token = ApiClient.login() 57 ApiClient.save_token(token) 58 return token 59 60 def test_get_tasks(token): 61 """测试获取任务列表接口""" 62 tasks_data = ApiClient.get_tasks(token) 63 assert 'tasks' in tasks_data # 假设返回数据包含 tasks 字段 64 65 def test_token_file_persistence(token): 66 """测试 Token 文件读写一致性""" 67 # 重新加载 Token 并验证与当前 Token 一致 68 loaded_token = ApiClient.load_token() 69 assert loaded_token == token
第四章:接口测试框架涉及和开发
框架涉及如下:
新建项目名称为:testAPI,其下有6个文件夹分别是:Config、Data、Log、Reports、TestCases、Utils和__init__.py文件、runMain.py文件。
其中:
Config文件下有:__init__.py、config.ini、HTMLTestRunner.py、readConfig.py
Data文件下有:__init__.py、data.xlsx、taskID、Token_md
Log文件下有:__init__.py、log.txt
Reports下有:2025-06-26 13_12_11result.html、__init__.py
TestCases下有:__init__.py、test_tasks.py
Utils下有:__init__.py、excels.py、page.py
在testAPI/Utils中的page.py文件下编写page.py文件重构requests请求,代码如下:
1 import requests 2 from typing import Optional, Dict, Any, Union 3 import logging 4 5 class HttpClient: 6 """ 7 Requests 二次封装类,支持 GET/POST 请求,具备以下功能: 8 - 统一超时设置 9 - 自动异常处理 10 - 请求日志记录 11 - 响应内容自动解析(JSON/文本) 12 - 自定义请求头 13 """ 14 15 DEFAULT_TIMEOUT = 10 # 默认超时时间(秒) 16 DEFAULT_HEADERS = { 17 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", 18 "Content-Type": "application/json" 19 } 20 21 def __init__(self, base_url: Optional[str] = None): 22 self.base_url = base_url.rstrip("/") if base_url else None 23 self.session = requests.Session() # 使用 Session 保持连接 24 25 def _request( 26 self, 27 method: str, 28 url: str, 29 params: Optional[Dict] = None, 30 data: Optional[Union[Dict, str]] = None, 31 json: Optional[Dict] = None, 32 headers: Optional[Dict] = None, 33 timeout: Optional[Union[int, tuple]] = None, 34 **kwargs 35 ) -> Any: 36 """ 37 核心请求方法(私有) 38 :param method: HTTP 方法(GET/POST/PUT等) 39 :param url: 请求路径(自动拼接 base_url) 40 :param params: URL 查询参数 41 :param data: 表单数据 42 :param json: JSON 数据 43 :param headers: 请求头 44 :param timeout: 超时时间(秒) 45 :return: 解析后的响应内容(JSON 或文本) 46 """ 47 # 拼接完整 URL 48 full_url = f"{self.base_url}/{url.lstrip('/')}" if self.base_url else url 49 50 # 合并请求头(默认头 + 自定义头) 51 merged_headers = {**self.DEFAULT_HEADERS, **(headers or {})} 52 53 # 统一超时设置 54 timeout = timeout or self.DEFAULT_TIMEOUT 55 56 try: 57 response = self.session.request( 58 method=method, 59 url=full_url, 60 params=params, 61 data=data, 62 json=json, 63 headers=merged_headers, 64 timeout=timeout, 65 **kwargs 66 ) 67 response.raise_for_status() # 自动检查 HTTP 状态码 68 69 # 尝试解析 JSON,失败则返回文本 70 try: 71 return response.json() 72 except ValueError: 73 return response.text 74 75 except requests.exceptions.RequestException as e: 76 logging.error(f"请求失败: {method} {full_url} - {str(e)}") 77 raise # 重新抛出异常,由调用方处理 78 79 def get( 80 self, 81 url: str, 82 params: Optional[Dict] = None, 83 headers: Optional[Dict] = None, 84 timeout: Optional[Union[int, tuple]] = None, 85 **kwargs 86 ) -> Any: 87 """ 88 GET 请求封装 89 :param url: 请求路径 90 :param params: URL 查询参数 91 :param headers: 请求头 92 :param timeout: 超时时间 93 :return: 响应内容 94 """ 95 return self._request( 96 method="GET", 97 url=url, 98 params=params, 99 headers=headers, 100 timeout=timeout, 101 **kwargs 102 ) 103 104 def post( 105 self, 106 url: str, 107 data: Optional[Union[Dict, str]] = None, 108 json: Optional[Dict] = None, 109 headers: Optional[Dict] = None, 110 timeout: Optional[Union[int, tuple]] = None, 111 **kwargs 112 ) -> Any: 113 """ 114 POST 请求封装 115 :param url: 请求路径 116 :param data: 表单数据 117 :param json: JSON 数据 118 :param headers: 请求头 119 :param timeout: 超时时间 120 :return: 响应内容 121 """ 122 return self._request( 123 method="POST", 124 url=url, 125 data=data, 126 json=json, 127 headers=headers, 128 timeout=timeout, 129 **kwargs 130 )
备注:
1、{**dict1, **dict2}: Python 的字典解包语法,将多个字典合并为一个新字典。
2、timeout = timeout or self.DEFAULT_TIMEOUT:or 的短路行为:如果 timeout 有值(非 None、非 0、非空),则直接使用 timeout。如果 timeout 是 None 或其他假值(如 0),则使用 self.DEFAULT_TIMEOUT。
浙公网安备 33010602011771号