爬虫请求重试与故障恢复机制:从简单重试到熔断降级的工程实践
发布时间: 2026-06-01 10:27:22
阅读量: 20 人次
网络请求必然失败,重试策略决定采集的最终成功率
在爬虫系统中,网络请求失败是常态而非异常。代理IP失效、目标网站限流、网络抖动、服务器过载等原因都会导致请求失败。如果没有合理的重试与故障恢复机制,爬虫可能因为一次偶发错误就终止整个任务,或陷入无效重试的循环。本文从基础重试到高级熔断降级,系统讲解如何构建高可用的爬虫请求处理层。
一、哪些失败值得重试?—— 错误分类
不是所有错误都适合重试,盲目重试可能加剧服务器压力。需要根据错误类型区分处理:
• 可重试错误:超时(Timeout)、5xx服务端错误、429限流、连接断开(Connection Reset)。这些错误通常是临时的,重试有可能成功。
• 不应重试的错误:404(页面不存在)、403(禁止访问,需更换IP或检查权限)、400请求错误。重复重试只会浪费资源。
• 需要特殊处理的错误:代理认证失败(407)、IP被封。应先更换代理IP再重试。
• 可重试错误:超时(Timeout)、5xx服务端错误、429限流、连接断开(Connection Reset)。这些错误通常是临时的,重试有可能成功。
• 不应重试的错误:404(页面不存在)、403(禁止访问,需更换IP或检查权限)、400请求错误。重复重试只会浪费资源。
• 需要特殊处理的错误:代理认证失败(407)、IP被封。应先更换代理IP再重试。
二、基础重试:固定间隔与最大次数
最简单的实现
import time
import requests
def fetch_with_retry(url, max_retries=3):
for i in range(max_retries):
try:
response = requests.get(url, timeout=10)
if response.status_code == 200:
return response
elif response.status_code in [429, 500, 502, 503]:
time.sleep(2) # 固定等待
continue
except Exception:
time.sleep(2)
return None
缺点
固定间隔重试可能加剧服务器拥堵,且无法适应错误类型的不同恢复时间。
import time
import requests
def fetch_with_retry(url, max_retries=3):
for i in range(max_retries):
try:
response = requests.get(url, timeout=10)
if response.status_code == 200:
return response
elif response.status_code in [429, 500, 502, 503]:
time.sleep(2) # 固定等待
continue
except Exception:
time.sleep(2)
return None
缺点
固定间隔重试可能加剧服务器拥堵,且无法适应错误类型的不同恢复时间。
三、指数退避重试:智能等待,缓解服务器压力
指数退避(Exponential Backoff)是推荐的工业级重试策略,每次重试的等待时间呈指数增长,并加入随机抖动避免“惊群效应”。
import random
def exponential_backoff(attempt, base_delay=1, max_delay=60):
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, delay * 0.1) # 加10%随机抖动
time.sleep(delay)
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
if response.status_code == 200:
return response
if response.status_code in [429, 503]:
exponential_backoff(attempt)
continue
except requests.exceptions.Timeout:
exponential_backoff(attempt)
指数退避的优势在于:给服务器足够的恢复时间,同时避免多个客户端同时重试造成二次拥堵。对于需要频繁重试的场景,还可引入Jitter(随机抖动),将重试时间分散。
import random
def exponential_backoff(attempt, base_delay=1, max_delay=60):
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, delay * 0.1) # 加10%随机抖动
time.sleep(delay)
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
if response.status_code == 200:
return response
if response.status_code in [429, 503]:
exponential_backoff(attempt)
continue
except requests.exceptions.Timeout:
exponential_backoff(attempt)
指数退避的优势在于:给服务器足够的恢复时间,同时避免多个客户端同时重试造成二次拥堵。对于需要频繁重试的场景,还可引入Jitter(随机抖动),将重试时间分散。
四、重试与代理IP切换的联动
当使用代理IP时,很多失败是由于代理IP本身质量问题(慢、不稳定、被封)。最佳实践是在重试时同时切换代理IP。
def fetch_with_proxy_retry(url, proxy_pool):
for retry in range(3):
proxy = proxy_pool.get()
try:
response = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)
if response.status_code == 200:
return response
if response.status_code in [403, 429]:
proxy_pool.mark_bad(proxy) # 标记IP不可用
continue
except Exception:
proxy_pool.mark_bad(proxy)
return None
使用山水代理的隧道代理时,服务端已内置重试和IP切换逻辑,客户端只需简单调用,无需自行管理。
def fetch_with_proxy_retry(url, proxy_pool):
for retry in range(3):
proxy = proxy_pool.get()
try:
response = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=10)
if response.status_code == 200:
return response
if response.status_code in [403, 429]:
proxy_pool.mark_bad(proxy) # 标记IP不可用
continue
except Exception:
proxy_pool.mark_bad(proxy)
return None
使用山水代理的隧道代理时,服务端已内置重试和IP切换逻辑,客户端只需简单调用,无需自行管理。
五、熔断降级:防止雪崩的系统保护机制
当目标网站或代理服务商持续不可用时,无休止的重试会耗尽系统资源。熔断器(Circuit Breaker)可以在错误率达到阈值时,暂时停止请求,进入“熔断”状态,一段时间后再试探性恢复。
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = 0
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
def call(self, func):
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "HALF_OPEN"
else:
raise Exception("Circuit breaker open")
try:
result = func()
self.reset()
return result
except Exception:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "OPEN"
raise
熔断器可以在代理池大面积失效或目标网站完全不可达时,快速失败并抛出异常,让上层调度器暂停任务或切换到备用源。
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=30):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = 0
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
def call(self, func):
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "HALF_OPEN"
else:
raise Exception("Circuit breaker open")
try:
result = func()
self.reset()
return result
except Exception:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "OPEN"
raise
熔断器可以在代理池大面积失效或目标网站完全不可达时,快速失败并抛出异常,让上层调度器暂停任务或切换到备用源。
六、Scrapy中的重试与代理中间件集成
Scrapy框架内置了重试中间件(RetryMiddleware),可配置重试条件和次数。结合代理中间件,可实现重试时自动更换IP。
# settings.py
RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
# middlewares.py - 自定义代理中间件
class RetryProxyMiddleware:
def process_response(self, request, response, spider):
if response.status in [403, 429]:
return self.retry_with_new_proxy(request)
return response
def retry_with_new_proxy(self, request):
new_proxy = get_new_proxy()
request.meta['proxy'] = new_proxy
return request
# settings.py
RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
# middlewares.py - 自定义代理中间件
class RetryProxyMiddleware:
def process_response(self, request, response, spider):
if response.status in [403, 429]:
return self.retry_with_new_proxy(request)
return response
def retry_with_new_proxy(self, request):
new_proxy = get_new_proxy()
request.meta['proxy'] = new_proxy
return request
七、实战建议:构建健壮的爬虫请求层
1. 区分错误类型,配置差异化重试策略
超时和5xx错误使用指数退避,429则增加等待时间,代理认证失败立即切换IP。
2. 设置最大重试次数和总超时
避免无限重试,比如最多3次,或者每次任务总超时不超过2分钟。
3. 记录重试日志,分析失败原因
定期检查哪些URL频繁失败,可能是页面结构变化或代理IP质量问题,及时调整策略。
4. 使用专业代理服务降低重试次数
山水代理的代理IP质量高,可用率95%以上,且隧道代理内置自动重试,可大幅简化客户端逻辑。
超时和5xx错误使用指数退避,429则增加等待时间,代理认证失败立即切换IP。
2. 设置最大重试次数和总超时
避免无限重试,比如最多3次,或者每次任务总超时不超过2分钟。
3. 记录重试日志,分析失败原因
定期检查哪些URL频繁失败,可能是页面结构变化或代理IP质量问题,及时调整策略。
4. 使用专业代理服务降低重试次数
山水代理的代理IP质量高,可用率95%以上,且隧道代理内置自动重试,可大幅简化客户端逻辑。
总结
重试与故障恢复是爬虫工程中容易被忽视却至关重要的环节。从简单的固定间隔重试,到指数退避、代理联动,再到熔断降级,每一步都是提升采集稳定性的有效手段。结合高质量的代理服务(如山水代理),可以显著减少重试频率,提升整体效率。建议开发者根据业务的重要性和目标网站的反爬特点,选择合适的重试策略。


黑公网安备 23100002000084号