失败任务应使用 requests + urllib3.Retry 实现指数退避重试,设置 total、status_forcelist 和 backoff_factor;任务状态需持久化至 Redis 或原子写入 JSONL 文件;异步爬虫中用 asyncio.timeout 包裹请求并 create_task 提交重试;超限失败任务应入死信队列并记录原因。
requests + 指数退避最靠谱直接硬循环重试会触发反爬限流,requests 默认不带重试逻辑,必须手动加。核心是控制重试次数、间隔和异常类型,别一遇到 ConnectionError 或 Timeout 就狂试。
urllib3.Retry 配合 requests.adapters.HTTPAdapter,比自己写 while 循环更可控backoff_factor(比如 0.3),它会让第 n 次重试等待 backoff_factor * (2^(n-1)) 秒,避免雪崩404 或 403 重试——那是业务状态,不是临时故障from requests.adapters import HTTPAdapter from urllib3.util.retry import Retrysession = requests.Session() retry_strategy = Retry( total=3, status_forcelist=[429, 500, 502, 503, 504], backoff_factor=0.3, ) adapter = HTTPAdapter(max_retries=retry_strategy) session.mount("http://", adapter) session.mount("https://", adapter)
queue
程序崩溃或重启后,内存里的待爬队列就丢了。得把「已取未完成」「失败待重试」的任务落地,否则回收机制形同虚设。
Redis 是首选:用 LPUSH/RPOP 做可靠队列,配合 BRPOP 阻塞读取,天然支持多进程/多机消费flock 加锁pickle 序列化任务对象——跨 Python 版本或代码变更后很可能反序列化失败asyncio 爬虫里怎么安全回收失败请求?小心协程泄漏异步环境下,没处理完的 async with session.get(...) 可能卡住整个事件循环,而且 try/except 不捕获所有异常类型。
asyncio.timeout() 包裹请求,防止某个 URL 死等(比如 DNS 卡住)await asyncio.sleep() 然后重试——这会阻塞当前协程;改用 asyncio.create_task() 提交重试任务,并加个最大重试计数限制asyncio.TimeoutError、aiohttp.ClientError、UnicodeDecodeError(响应编码异常)import asyncio import aiohttpasync def fetch_with_retry(session, url, max_retries=2): for i in range(max_retries + 1): try: async with asyncio.timeout(10): async with session.get(url) as resp: return await resp.text() except (aiohttp.ClientError, asyncio.TimeoutError, UnicodeDecodeError) as e: if i == max_retries: raise e await asyncio.sleep(0.5 * (2 ** i))
# 指数退避 return None
不是所有失败都要无限重试。持续失败的任务可能是目标页面已下线、参数非法、或风控永久封禁,继续重试只会浪费资源、暴露 IP。
redis list 名为 dead_tasks),供人工排查或定时归档X-RateLimit-Remaining 等),别只记个 "failed"
真正难的不是写重试逻辑,而是判断什么时候该停——这需要结合日志、监控和业务语义,纯技术方案兜不住所有情况。