在Python爬虫开发过程中,网络不稳定、目标服务器临时限流、请求超时等问题十分常见,单次请求失败后如果直接终止程序,会浪费已经完成的爬取进度。通过编写装饰器实现自动重试,可以在捕获到指定异常时自动重新发起请求,有效提升爬虫的健壮性。

自动重试装饰器的实现思路
装饰器的核心逻辑是包裹原有的请求函数,在函数执行抛出异常时,判断异常类型是否符合重试条件,如果符合则等待指定时间后重新调用函数,直到达到最大重试次数或者请求成功。需要支持配置的参数包括最大重试次数、重试间隔时间、需要捕获的异常类型,这样装饰器的通用性会更强。
基础版重试装饰器实现
以下是一个支持配置重试次数和间隔的基础装饰器,默认捕获请求相关的常见异常:
import time
from functools import wraps
import requests
def retry_request(max_retries=3, delay=1, exceptions=(requests.RequestException,)):
"""
请求自动重试装饰器
:param max_retries: 最大重试次数,默认为3次
:param delay: 每次重试间隔时间,单位为秒,默认为1秒
:param exceptions: 需要捕获的异常类型元组,默认为requests库的常见异常
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_retries + 1):
try:
return func(*args, **kwargs)
except exceptions as e:
if i == max_retries:
# 达到最大重试次数,抛出异常
raise e
print(f"请求失败,异常信息:{e},{delay}秒后第{i+1}次重试")
time.sleep(delay)
return wrapper
return decorator
装饰器的使用方式
将装饰器添加到爬虫的请求函数上方即可生效,示例如下:
@retry_request(max_retries=5, delay=2)
def fetch_page(url):
"""爬取指定url的页面内容"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 如果响应状态码不是200,抛出RequestException
return response.text
if __name__ == "__main__":
try:
content = fetch_page("http://ipipp.com/test_page")
print("页面爬取成功,内容长度:", len(content))
except Exception as e:
print("所有重试均失败,最终异常:", e)
进阶优化方向
- 可以增加重试间隔的退避策略,比如每次重试间隔时间是上一次的2倍,避免短时间内频繁请求给目标服务器造成压力
- 可以支持自定义判断逻辑,比如不仅根据异常重试,还可以根据响应内容中的错误标识决定是否重试
- 如果需要支持异步爬虫,可以编写适配asyncio的重试装饰器,逻辑和同步版本类似,只是等待需要使用asyncio.sleep
注意事项
使用自动重试功能时,要注意目标网站的robots协议,不要设置过于频繁的重试导致被目标服务器封禁IP。另外重试次数也不是越多越好,过多的重试会延长爬虫的整体运行时间,建议根据实际场景合理设置参数。
如果请求是写操作类的接口,要谨慎使用自动重试,避免重复提交数据造成业务异常,这类场景可以在装饰器中增加只重试读请求的配置选项。