You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用aiohttp_retry的RetryClient处理JSON解码错误时重试失效求助

问题分析

你的重试机制未生效的核心原因是:

  • RetryClient 默认仅对请求层面的异常(如连接失败、非成功状态码)触发重试
  • 你捕获了JSON解码异常(ContentTypeError)并直接返回错误字典,RetryClient 会认为该请求已成功完成,不会触发重试

你遇到的报错 Attempt to decode JSON with unexpected mimetype: text/html; charset=utf-8 对应 aiohttp.ContentTypeError,这是响应解析阶段的异常,默认不在RetryClient的重试触发范围内,且你的代码逻辑阻断了异常向上传递。

解决方案

1. 配置重试异常类型,包含解析异常

修改ExponentialRetry的retry_exception_types参数,将ContentTypeError加入重试触发的异常列表。

2. 不要阻断解析异常,让RetryClient捕获处理

移除异常块中直接返回错误字典的逻辑,捕获异常后重新抛出(或不捕获,让RetryClient自动处理),确保异常能传递给重试机制。

修改后的完整代码

import asyncio
import logging
from aiohttp import TCPConnector, ContentTypeError, ClientError
from aiohttp_retry import RetryClient, ExponentialRetry

logger = logging.getLogger(__name__)
API_HEADER = {}  # 替换为你的实际请求头

async def get_parcel_details(client, sem, url):
    async with sem, client.get(url) as resp:
        # 依赖RetryClient的raise_for_status=True处理非成功状态码,无需手动判断
        try:
            parcel_details = await resp.json(encoding='UTF-8', content_type='application/json')
            return parcel_details
        except ContentTypeError as e:
            logger.error(f"URL {url} 解析JSON失败: {str(e)}")
            raise  # 抛出异常,触发RetryClient重试

async def async_main(APNs: list):
    connector = TCPConnector(ssl=False, limit=15, limit_per_host=10, enable_cleanup_closed=True)
    # 配置重试选项,添加ContentTypeError到重试异常类型
    retry_options = ExponentialRetry(
        attempts=3,
        retry_exception_types=(ClientError, ContentTypeError)
    )
    async with RetryClient(
        headers=API_HEADER,
        connector=connector,
        raise_for_status=True,
        retry_options=retry_options
    ) as retry_client:
        sem = asyncio.Semaphore(20)
        tasks = [
            asyncio.create_task(get_parcel_details(retry_client, sem, f'https://api_endpoint/parcel/{apn}'))
            for apn in APNs
        ]
        # 注意:return_exceptions=True会捕获所有异常,包括重试后的最终失败,需后续处理
        parcels = await asyncio.gather(*tasks, return_exceptions=True)
        # 可选:过滤或处理最终仍失败的任务
        processed_parcels = []
        for idx, result in enumerate(parcels):
            if isinstance(result, Exception):
                logger.error(f"APN {APNs[idx]} 最终请求失败: {str(result)}")
                processed_parcels.append({'Owner': 'ERROR', 'Rental': False})
            else:
                processed_parcels.append(result)
        return processed_parcels
关键说明
  • 异常传递:只有当请求过程中抛出的异常属于retry_exception_types列表时,RetryClient才会自动重试该请求。解析时抛出ContentTypeError后重新抛出,才能让重试机制感知到。
  • 避免手动重试:不要在异常块中手动调用get,这会和RetryClient的内置重试逻辑冲突,导致请求重复或混乱。
  • return_exceptions=True:asyncio.gather的这个参数会捕获所有任务的异常,避免单个任务失败导致整个协程崩溃,但后续需要手动处理这些异常结果。

内容的提问来源于stack exchange,提问作者TASC Solutions

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:55:10