Python脚本爬虫超时如何重试

wen 实用脚本 2

Python脚本爬虫超时如何重试:从原理到最佳实践(附完整代码)

目录导读

  1. 爬虫超时问题概述:为什么超时会发生?对数据采集的影响。
  2. 超时重试的核心原理:指数退避、最大重试次数、异常捕获机制。
  3. 代码实现方案:基于 requests 库 + retry 装饰器 + 自定义重试逻辑。
  4. 高级技巧:结合urllib3重试适配器、异步框架(如aiohttp)的重试策略。
  5. 常见问题与答案(FAQ):针对超时重试的6个高频疑问。
  6. 最佳实践清单:避免无限重试、日志记录、小文件与大数据场景区分。

爬虫超时问题概述

在日常爬虫开发中,【超时】是仅次于IP封禁的第二大痛点,原因包括:

Python脚本爬虫超时如何重试

  • 目标服务器响应慢(高并发、资源受限);
  • 网络波动(丢包、延迟高);
  • 反爬机制(故意延迟返回或断开连接);

如果不处理超时,脚本会挂死在该请求上,导致整个采集流程中断。正确的做法是:捕获超时异常 → 等待 → 自动重试


超时重试的核心原理

1 基本三要素

  • 异常检测:捕获 requests.exceptions.Timeout(或 ConnectionError 等);
  • 重试间隔:固定间隔 vs 指数退避(Exponential Backoff);
  • 最大重试次数:防止无限制重试浪费资源(如设为3次)。

2 指数退避公式

等待时间 = 基础间隔 × (2^当前重试次数) + 随机抖动

基础间隔=1秒,重试第1次等待1~2秒,第2次等待2~4秒……这样能缓解目标服务器压力。


代码实现方案(重点)

1 方法一:使用retry装饰器(推荐)

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_session_with_retries(retries=3, backoff_factor=0.5, status_forcelist=(500, 502, 504)):
    session = requests.Session()
    retry_strategy = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=status_forcelist,
        raise_on_status=False
    )
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session
# 使用
session = create_session_with_retries()
try:
    response = session.get('https://example.com/api', timeout=5)
    print(response.text)
except Exception as e:
    print(f"最终检测失败: {e}")

说明backoff_factor 控制等待时间(公式:{backoff_factor} * (2 ** (重试次数 - 1))),0.5即第一次等待0.5秒,第二次1秒,第三次2秒。

2 方法二:手动循环重试(更灵活)

import time
import requests
from requests.exceptions import Timeout, ConnectionError
def fetch_with_retry(url, max_retries=3, timeout=5, base_delay=1):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, timeout=timeout)
            response.raise_for_status()  # 抛出HTTP错误
            return response
        except (Timeout, ConnectionError) as e:
            if attempt == max_retries -1:
                raise  # 最后一次失败则向上抛
            delay = base_delay * (2 ** attempt) + (1 * attempt)  # 指数退避+随机
            print(f"第{attempt+1}次失败,等待{delay:.1f}秒重试...")
            time.sleep(delay)
    return None
# 使用
result = fetch_with_retry('https://slow-website.com/data')

高级技巧:异步爬虫的重试

如果你使用 aiohttp 进行异步爬虫,以下方案同样可以处理超时重试:

import asyncio
import aiohttp
async def fetch_with_retry_async(session, url, max_retries=3, timeout=10):
    for i in range(max_retries):
        try:
            async with session.get(url, timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
                return await resp.text()
        except (asyncio.TimeoutError, aiohttp.ClientError) as e:
            if i == max_retries -1:
                raise
            wait = (2 ** i) * 1.5
            print(f"异步重试第{i+1}次,等待{wait}秒")
            await asyncio.sleep(wait)

常见问题与答案(FAQ)

Q1:为什么要使用指数退避而不是固定间隔?

A:固定间隔容易造成“惊群效应”——所有重试同时发起,进一步压垮服务器,指数退避让后续重试间隔逐渐增大,给服务器恢复时间,也降低自身被封概率。

Q2:超时重试是否会无限循环?

A:不会,必须设置 max_retries 上限(通常3~5次),并在最后一次失败时抛出异常或记录日志,避免死循环。

Q3:requests.Session 和普通 requests.get 哪个适合重试?

ASession 自动复用TCP连接,配合 HTTPAdapter 可以实现全局重试策略,而单次 get 很难优雅控制重试中间状态,推荐使用 Session + 适配器。

Q4:重试时是否需要更换User-Agent?

A:建议在每次重试前随机更换User-Agent和Referer,因为连续相同特征的重试容易被识别为爬虫工具行为,增加被封风险。

Q5:如何处理HTTP 429(Too Many Requests)?

A:在 status_forcelist 中加入429,并配合 Retry-After 头部里的等待时间动态调整间隔,如果未返回该头部,则使用指数退避。

Q6:重试了3次还超时,如何记录日志?

A:建议使用 logging 模块记录每次重试的URL、失败原因、耗时、重试次数,输出到文件。

logging.warning(f"URL {url} 第{n}次超时,当前等待 {delay}s")

最佳实践清单

原则 具体做法
有限重试 设置 max_retries = 3~5次,超过则放弃该URL并记录
异常分类 对超时、连接错误、HTTP错误(500/502/503)分别处理
动态间隔 使用指数退避+随机抖动(random.uniform(0,1)*base
会话复用 requests.Session 管理连接池,避免每次新建连接
日志输出 必须记录失败详情,方便复盘优化
避免重试幂等性问题 如果是POST请求(如提交数据),重试前要确认接口支持幂等

Python爬虫超时重试不是简单循环发包,而是一个需要平衡成功率与服务器负载的系统策略,最佳方案是结合requestsRetry适配器 + 指数退避 + 最大次数限制 + 日志记录,对于快节奏生产环境,还可考虑使用tenacity库简化重试装饰器逻辑(@retry(stop=stop_after_attempt(3), wait=wait_exponential())),但本质核心与本文一致。

延伸思考:当目标网站使用Cloudflare或Akamai等反爬防御时,超时重试可能无效,此时需结合IP代理池、浏览器仿真(Selenium/Playwright)等更高阶技术,但这已超出本文讨论范围——超时重试是你的代码主动防御第一步,先用好它,再求突破。

本文提供的代码片段将基础间隔设为1秒,实际生产中可根据延迟测试结果调整为0.3~0.5秒,若无特殊说明,所有重试策略都应配合isinstance检查错误类型,避免误吞信号打断异常。

抱歉,评论功能暂时关闭!