requests响应验证:状态码检查与内容校验
1. 引言:为何响应验证至关重要?
在网络请求中,我们往往关注如何发送请求,却忽视了对响应的严格验证。你是否遇到过这些问题:API返回200状态码却返回错误信息?依赖第三方服务时因数据格式异常导致程序崩溃?爬虫因未验证状态码陷入无限重试?本文将系统讲解如何使用requests库进行全方位响应验证,包括状态码检查、内容校验、异常处理等核心技术,帮助你构建健壮的网络请求应用。
读完本文,你将掌握:
- 状态码检查的3种方法及适用场景
- JSON/XML/HTML等响应内容的结构化验证技巧
- 异常处理与重试策略的最佳实践
- 响应验证在自动化测试中的高级应用
- 构建可复用的响应验证框架
2. 状态码检查:HTTP响应的第一道防线
2.1 状态码基础与分类
HTTP状态码(HTTP Status Code)是服务器对客户端请求的响应状态标识,由3位数字组成。requests库通过response.status_code属性提供状态码访问,并在requests.status_codes模块中定义了友好的名称映射。
import requests
from requests import codes
response = requests.get("https://api.example.com/data")
print(response.status_code) # 输出状态码数字,如200
print(codes.ok) # 输出200,codes.ok是200的友好名称
print(codes.not_found) # 输出404
HTTP状态码可分为五大类:
| 类别 | 范围 | 含义 | 常见状态码 |
|---|---|---|---|
| 信息响应 | 100-199 | 请求已接收,继续处理 | 100(continue), 101(switching_protocols) |
| 成功响应 | 200-299 | 请求已成功处理 | 200(ok), 201(created), 204(no_content) |
| 重定向 | 300-399 | 需要进一步操作完成请求 | 301(moved_permanently), 302(found), 304(not_modified) |
| 客户端错误 | 400-499 | 请求包含语法错误或无法完成 | 400(bad_request), 401(unauthorized), 403(forbidden), 404(not_found) |
| 服务器错误 | 500-599 | 服务器在处理请求时发生错误 | 500(internal_server_error), 502(bad_gateway), 503(service_unavailable) |
2.2 状态码检查的三种方法
2.2.1 直接比较状态码
最直观的方法是直接比较status_code属性:
response = requests.get("https://api.example.com/data")
if response.status_code == 200:
print("请求成功")
elif response.status_code == 404:
print("资源不存在")
elif response.status_code == 500:
print("服务器错误")
else:
print(f"请求失败,状态码: {response.status_code}")
使用requests.codes模块可提高可读性:
if response.status_code == codes.ok: # 等同于response.status_code == 200
print("请求成功")
elif response.status_code == codes.not_found: # 等同于response.status_code == 404
print("资源不存在")
2.2.2 使用ok属性快速判断
response.ok属性是一个便捷的布尔值,当状态码在200-399范围内时返回True,否则返回False:
response = requests.get("https://api.example.com/data")
if response.ok:
print("请求成功或重定向")
# 进一步检查是否为成功响应
if 200 <= response.status_code < 300:
print("请求成功")
else:
print(f"重定向,状态码: {response.status_code}")
else:
print(f"请求失败,状态码: {response.status_code}")
2.2.3 使用raise_for_status()主动抛出异常
response.raise_for_status()方法会在状态码表示请求失败(4xx或5xx)时抛出HTTPError异常,便于异常处理流程:
try:
response = requests.get("https://api.example.com/data")
response.raise_for_status() # 状态码为4xx或5xx时抛出HTTPError
print("请求成功")
except requests.exceptions.HTTPError as e:
print(f"请求失败: {e}")
except requests.exceptions.RequestException as e:
print(f"请求发生异常: {e}")
raise_for_status()的实现原理可简化为:
def raise_for_status(self):
http_error_msg = ''
if 400 <= self.status_code < 500:
http_error_msg = f"{self.status_code} Client Error: {self.reason} for url: {self.url}"
elif 500 <= self.status_code < 600:
http_error_msg = f"{self.status_code} Server Error: {self.reason} for url: {self.url}"
if http_error_msg:
raise HTTPError(http_error_msg, response=self)
2.3 状态码检查的最佳实践
2.3.1 按场景选择合适的检查方式
| 检查方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 直接比较状态码 | 需要根据不同状态码执行不同逻辑 | 精确控制,可处理所有状态码 | 代码较长,可读性较差 |
ok属性 | 只需简单判断成功或失败 | 简洁,代码量少 | 无法区分具体状态码 |
raise_for_status() | 需要在失败时立即中断流程 | 符合异常处理最佳实践,代码清晰 | 需要配合try-except块使用 |
2.3.2 处理重定向状态码
requests默认会自动处理重定向(3xx状态码),可通过allow_redirects参数控制:
# 禁止自动重定向
response = requests.get("https://api.example.com/data", allow_redirects=False)
if response.is_redirect: # 判断是否为重定向
print(f"重定向到: {response.headers['Location']}")
# 手动处理重定向
redirect_response = requests.get(response.headers['Location'])
elif response.is_permanent_redirect: # 判断是否为永久重定向
print(f"永久重定向到: {response.headers['Location']}")
2.3.3 常见状态码处理策略
try:
response = requests.get("https://api.example.com/data")
response.raise_for_status()
# 处理成功响应
if response.status_code == codes.created: # 201
print("资源创建成功")
elif response.status_code == codes.no_content: # 204
print("请求成功,无返回内容")
else: # 200, 202等其他成功状态码
print("请求成功")
except requests.exceptions.HTTPError as e:
status_code = response.status_code
if status_code == codes.unauthorized: # 401
print("未授权,请登录")
# 处理登录逻辑
elif status_code == codes.forbidden: # 403
print("权限不足")
elif status_code == codes.not_found: # 404
print("资源不存在")
elif status_code == codes.too_many_requests: # 429
print("请求过于频繁,请稍后再试")
# 实现退避重试策略
elif status_code == codes.internal_server_error: # 500
print("服务器内部错误")
elif status_code == codes.service_unavailable: # 503
print("服务暂时不可用")
else:
print(f"请求失败: {e}")
3. 响应内容校验:确保数据可靠性
3.1 响应内容的基本属性
requests提供了多种属性和方法来获取和处理响应内容:
| 属性/方法 | 描述 |
|---|---|
response.text | 以字符串形式返回响应内容,自动根据HTTP头部猜测编码 |
response.content | 以字节形式返回响应内容,适用于二进制数据(如图像) |
response.json() | 将JSON格式的响应内容解析为Python字典/列表 |
response.encoding | 获取或设置响应内容的编码方式 |
response.apparent_encoding | 根据响应内容猜测的编码方式 |
3.2 JSON响应内容校验
3.2.1 基本JSON解析与校验
import json
try:
response = requests.get("https://api.example.com/users/1")
response.raise_for_status()
# 解析JSON
try:
data = response.json()
print("JSON解析成功")
# 基本结构校验
if not isinstance(data, dict):
raise ValueError("响应不是JSON对象")
# 字段校验
required_fields = ["id", "name", "email"]
for field in required_fields:
if field not in data:
raise ValueError(f"缺少必填字段: {field}")
# 类型校验
if not isinstance(data["id"], int):
raise TypeError("id必须是整数")
if not isinstance(data["name"], str):
raise TypeError("name必须是字符串")
if not isinstance(data["email"], str) or "@" not in data["email"]:
raise ValueError("email格式无效")
print("JSON内容校验通过")
print(f"用户信息: {data}")
except json.JSONDecodeError:
print("JSON解析失败")
except ValueError as e:
print(f"数据校验失败: {e}")
except TypeError as e:
print(f"数据类型错误: {e}")
except requests.exceptions.RequestException as e:
print(f"请求异常: {e}")
3.2.2 使用JSON Schema进行结构化校验
对于复杂的JSON结构,推荐使用JSON Schema进行校验:
from jsonschema import validate, ValidationError
# 定义JSON Schema
user_schema = {
"type": "object",
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1, "maxLength": 100},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0, "maximum": 150},
"is_active": {"type": "boolean"},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["id", "name", "email"],
"additionalProperties": False
}
try:
response = requests.get("https://api.example.com/users/1")
response.raise_for_status()
user_data = response.json()
# 使用JSON Schema校验
validate(instance=user_data, schema=user_schema)
print("JSON Schema校验通过")
except ValidationError as e:
print(f"JSON结构校验失败: {e.message}")
except Exception as e:
print(f"处理失败: {e}")
3.3 文本响应内容校验
对于HTML、XML等文本响应,可使用正则表达式或专用解析库进行校验:
import re
from bs4 import BeautifulSoup
# HTML内容校验示例
try:
response = requests.get("https://example.com")
response.raise_for_status()
# 检查页面标题
soup = BeautifulSoup(response.text, "html.parser")
title = soup.title.string
if not title:
raise ValueError("页面缺少标题")
if "Example" not in title:
raise ValueError(f"页面标题不符合预期: {title}")
# 使用正则表达式检查内容
if not re.search(r"Example Domain", response.text):
raise ValueError("页面内容不包含预期文本")
print("HTML内容校验通过")
except Exception as e:
print(f"HTML内容校验失败: {e}")
3.4 二进制响应内容校验
对于图片、文件等二进制内容,可通过文件大小、哈希值等方式进行校验:
import hashlib
def validate_file_hash(content, expected_hash):
"""验证文件内容的MD5哈希值"""
md5_hash = hashlib.md5(content).hexdigest()
return md5_hash == expected_hash
try:
response = requests.get("https://example.com/image.jpg", stream=True)
response.raise_for_status()
# 检查Content-Length
expected_size = int(response.headers.get("Content-Length", 0))
if expected_size > 0:
content = response.content
actual_size = len(content)
if actual_size != expected_size:
raise ValueError(f"文件大小不匹配: 预期{expected_size}字节,实际{actual_size}字节")
# 检查MD5哈希值(假设服务器提供了Content-MD5头部)
expected_md5 = response.headers.get("Content-MD5")
if expected_md5:
if not validate_file_hash(content, expected_md5):
raise ValueError("文件哈希值不匹配")
print("二进制文件校验通过")
except Exception as e:
print(f"二进制文件校验失败: {e}")
4. 异常处理与重试策略:构建健壮的请求逻辑
4.1 requests异常体系
requests定义了丰富的异常类,继承关系如下:
4.2 全面的异常处理示例
try:
response = requests.get(
"https://api.example.com/data",
timeout=10, # 设置超时时间
headers={"User-Agent": "MyApp/1.0"}
)
response.raise_for_status()
# 处理响应
data = response.json()
# ...
except requests.exceptions.HTTPError as e:
# 处理HTTP错误
print(f"HTTP错误: {e}")
except requests.exceptions.ConnectionError as e:
# 处理连接错误(DNS失败、拒绝连接等)
print(f"连接错误: {e}")
except requests.exceptions.Timeout as e:
# 处理超时错误
print(f"超时错误: {e}")
except requests.exceptions.TooManyRedirects as e:
# 处理重定向过多
print(f"重定向过多: {e}")
except requests.exceptions.JSONDecodeError as e:
# 处理JSON解析错误
print(f"JSON解析错误: {e}")
except requests.exceptions.RequestException as e:
# 处理其他所有requests异常
print(f"请求异常: {e}")
except Exception as e:
# 处理其他非requests异常
print(f"其他异常: {e}")
4.3 智能重试策略实现
结合tenacity库实现带退避策略的重试机制:
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
# 定义需要重试的异常类型
RETRY_EXCEPTIONS = (
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.HTTPError # 仅对5xx错误重试
)
def is_retryable_http_error(e):
"""判断HTTP错误是否可重试(5xx状态码)"""
return hasattr(e, 'response') and e.response is not None and 500 <= e.response.status_code < 600
@retry(
stop=stop_after_attempt(3), # 最多重试3次
wait=wait_exponential(multiplier=1, min=2, max=10), # 指数退避:2s, 4s, 8s...
retry=(
retry_if_exception_type((requests.exceptions.ConnectionError, requests.exceptions.Timeout)) |
retry_if_exception_type(requests.exceptions.HTTPError) & retry_if_exception(is_retryable_http_error)
),
before_sleep=lambda retry_state: print(f"重试 {retry_state.attempt_number}/3..."),
reraise=True # 最后一次失败后抛出异常
)
def fetch_data(url):
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
try:
data = fetch_data("https://api.example.com/data")
print("数据获取成功")
except Exception as e:
print(f"最终请求失败: {e}")
5. 响应验证在自动化测试中的应用
5.1 API测试中的响应验证框架
import requests
import pytest
from jsonschema import validate
class APITester:
def __init__(self, base_url):
self.base_url = base_url
def get(self, endpoint, schema=None, **kwargs):
url = f"{self.base_url}/{endpoint.lstrip('/')}"
response = requests.get(url, **kwargs)
# 验证状态码
assert response.status_code == 200, f"GET {endpoint} 状态码错误: {response.status_code}"
# 如果提供了schema,验证JSON结构
if schema:
try:
data = response.json()
validate(instance=data, schema=schema)
except Exception as e:
pytest.fail(f"GET {endpoint} JSON结构验证失败: {str(e)}")
return response
# 使用示例
tester = APITester("https://api.example.com")
user_schema = {
"type": "object",
"properties": {
"id": {"type": "integer"},
"name": {"type": "string"}
},
"required": ["id", "name"]
}
def test_user_endpoint():
response = tester.get("users/1", schema=user_schema)
data = response.json()
assert data["id"] == 1, "用户ID不匹配"
assert data["name"] == "John Doe", "用户名不匹配"
5.2 响应验证与测试覆盖率
6. 总结与最佳实践
6.1 响应验证清单
每次网络请求后,建议按以下清单进行验证:
-
状态码检查
- 使用
raise_for_status()主动抛出异常 - 对关键操作检查具体状态码(如201表示创建成功)
- 使用
-
响应头验证
- 检查
Content-Type确保响应格式正确 - 验证
Content-Length确保数据完整 - 检查
Last-Modified或ETag进行缓存验证
- 检查
-
响应内容验证
- 结构化数据(JSON/XML)使用Schema验证
- 文本内容使用关键词或正则表达式验证
- 二进制内容验证大小和哈希值
-
异常处理
- 捕获所有可能的请求异常
- 对可重试错误实现智能重试
- 记录详细的错误日志以便调试
6.2 性能与安全考量
- 性能优化:对于大型响应,使用流式读取(
stream=True)并分块验证 - 安全最佳实践:验证所有用户输入,对响应内容进行消毒处理,避免注入攻击
- 资源管理:使用
response.close()或上下文管理器确保连接正确关闭
# 使用上下文管理器自动管理连接
with requests.get("https://api.example.com/large-data", stream=True) as response:
response.raise_for_status()
# 分块处理大型响应
for chunk in response.iter_content(chunk_size=8192):
# 处理每个块
validate_chunk(chunk)
6.3 构建可复用的响应验证框架
将响应验证逻辑封装为可复用的工具类:
class ResponseValidator:
@staticmethod
def validate_status(response, expected_codes=None):
"""验证状态码"""
if expected_codes is None:
response.raise_for_status()
else:
if response.status_code not in expected_codes:
raise ValueError(f"状态码不符合预期: {response.status_code}, 预期: {expected_codes}")
return True
@staticmethod
def validate_json(response, schema=None):
"""验证JSON响应"""
try:
data = response.json()
if schema:
validate(instance=data, schema=schema)
return data
except Exception as e:
raise ValueError(f"JSON验证失败: {str(e)}")
@staticmethod
def validate_headers(response, required_headers=None):
"""验证响应头"""
if required_headers:
for header in required_headers:
if header not in response.headers:
raise ValueError(f"缺少必填响应头: {header}")
return True
@staticmethod
def validate_content(response, validator_func):
"""使用自定义函数验证内容"""
if not validator_func(response.content):
raise ValueError("内容验证失败")
return True
# 使用示例
validator = ResponseValidator()
try:
response = requests.get("https://api.example.com/data")
validator.validate_status(response, expected_codes=[200, 201])
validator.validate_headers(response, required_headers=["Content-Type"])
data = validator.validate_json(response, schema=user_schema)
validator.validate_content(response, lambda content: len(content) > 0)
print("所有验证通过")
except ValueError as e:
print(f"验证失败: {e}")
通过本文介绍的技术,你可以构建一个健壮、可靠的网络请求系统,有效处理各种异常情况,确保应用在复杂的网络环境中稳定运行。记住,响应验证不是可有可无的额外步骤,而是保障数据可靠性和系统稳定性的关键环节。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



