使用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
相关产品推荐
相关产品推荐

