requests响应验证:状态码检查与内容校验

requests响应验证:状态码检查与内容校验

【免费下载链接】requests A simple, yet elegant, HTTP library. 【免费下载链接】requests 项目地址: https://gitcode.com/GitHub_Trending/re/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定义了丰富的异常类,继承关系如下:

mermaid

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 响应验证与测试覆盖率

mermaid

6. 总结与最佳实践

6.1 响应验证清单

每次网络请求后,建议按以下清单进行验证:

  1. 状态码检查

    • 使用raise_for_status()主动抛出异常
    • 对关键操作检查具体状态码(如201表示创建成功)
  2. 响应头验证

    • 检查Content-Type确保响应格式正确
    • 验证Content-Length确保数据完整
    • 检查Last-ModifiedETag进行缓存验证
  3. 响应内容验证

    • 结构化数据(JSON/XML)使用Schema验证
    • 文本内容使用关键词或正则表达式验证
    • 二进制内容验证大小和哈希值
  4. 异常处理

    • 捕获所有可能的请求异常
    • 对可重试错误实现智能重试
    • 记录详细的错误日志以便调试

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}")

通过本文介绍的技术,你可以构建一个健壮、可靠的网络请求系统,有效处理各种异常情况,确保应用在复杂的网络环境中稳定运行。记住,响应验证不是可有可无的额外步骤,而是保障数据可靠性和系统稳定性的关键环节。

【免费下载链接】requests A simple, yet elegant, HTTP library. 【免费下载链接】requests 项目地址: https://gitcode.com/GitHub_Trending/re/requests

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值