如果代理费用增长速度超过收集的数据量——问题几乎总是出在重试逻辑上。 解析器默默地重复失败的请求多次,浪费流量在超时和验证码上, 而开发者甚至在日志中看不到这些开销。我们来分析如何计算实际损失并减少它们 而不降低数据质量。
为什么重试逻辑消耗流量
大多数解析器都是用天真的重试逻辑编写的:如果请求失败——就重试,最多3-5次。 问题在于,每个重试请求不仅是一个新的HTTP请求,而且是一个完整的周期:TCP握手、 TLS协商、整个页面的加载(即使只需要一个数据块),有时还会重新加载图像或JS文件, 如果解析器使用无头浏览器而不是简单的HTTP客户端。
在使用住宅代理时, 重试的成本尤其高,因为流量是按量计费,而不是按请求数量计费。一次失败的请求 到Wildberries的产品页面,包含图片和脚本,可能会消耗300-500 KB。如果解析器在超时 时进行3次重试,您将连续为同一个失败请求支付四次——而且没有保证第四次尝试会成功。
第二个原因是针对无法通过重试修复的错误进行重试。如果网站因检测到机器人而返回403, 使用相同的指纹和相同的cookie会话进行重试几乎肯定会得到相同的响应。解析器在 无法成功的尝试上浪费流量,直到IP地址或浏览器指纹发生变化。
实际消耗多少流量用于重试
为了理解问题的规模,我们以一个简单的例子为例。解析器从市场收集商品卡片, 平均响应大小为250 KB(HTML + JSON API + 部分静态内容)。在没有阻塞的稳定工作中, 失败请求的比例保持在5-8%。但是在通过廉价数据中心代理进行激进解析时, 这个比例可能会上升到25-35%,因为目标提供商很快识别出模式并开始返回验证码或 临时IP禁令。
让我们用数字来计算。假设需要收集100,000个商品卡片:
| 失败请求的比例 | 每个请求的重试次数(平均) | 最终流量 | 超支 |
|---|---|---|---|
| 5% | 0.15 | 28.75 GB | +15% |
| 15% | 0.45 | 36.25 GB | +45% |
| 30% | 0.90 | 47.5 GB | +90% |
如上所示,在30%的失败比例和“重试最多3次”的重试策略下,实际流量几乎是理论最小值 25 GB的两倍。这些额外的20+ GB是代理预算的直接损失,如果重新审视重试逻辑,可以减少这些损失。
解析器重试逻辑的常见错误
在修复重试逻辑之前,值得识别90%自定义解析器中出现的典型反模式:
- 不区分错误代码的重试。 在任何异常情况下启动重试——403、429、500、超时、连接中断——尽管它们的处理策略应该不同。
- 固定的重试间隔。 例如,无论是第一次尝试还是第五次尝试,间隔2秒——这对网站来说要么过于激进,要么对大流量来说过于缓慢。
- 使用相同的IP和会话进行重试。 如果网站因指纹阻止请求,使用相同参数进行重试不会改变结果,但会浪费流量。
- 没有尝试的上限。 一些解析器在“死”URL上循环,进行数十次重试才放弃。
- 没有区分临时和永久错误。 404(页面不存在)和503(服务器暂时不可用)需要不同的逻辑——但通常被相同处理。
带有Python代码的指数退避
一个简单但有效的解决方案是带有抖动(随机波动)的指数延迟,它减少了无意义的重试次数,并将负载分散到时间上。与固定的尝试间隔相比,延迟呈指数增长,这给网站“冷却”的时间,解析器也不必在几乎肯定会失败的请求上浪费流量。
import time
import random
import requests
def fetch_with_backoff(url, proxies, max_retries=4, base_delay=1.0):
retryable_codes = {429, 500, 502, 503, 504}
non_retryable_codes = {404, 410}
for attempt in range(max_retries + 1):
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code == 200:
return response
if response.status_code in non_retryable_codes:
# 没有意义的重试——页面物理上不存在
return None
if response.status_code not in retryable_codes:
return None
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
pass # 临时网络错误——可以重试
if attempt == max_retries:
return None
# 带有抖动的指数延迟
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
return None
这段代码的关键思想是将错误分为三类:无法通过重试修复的(404、410),可以延迟重试的(429、500-504、超时),以及其他所有被视为失败而不需要额外尝试的。这种分类本身就可以将多余的流量减少20-30%,与天真的“重试所有”相比。
建议: 在处理时添加Retry-After头部——
许多网站会提示您在重试前等待多少秒。忽略这个头部是导致额外封禁和流量的常见原因。
断路器:何时应该停止
指数退避在单个请求级别上有效,但无法保护您免受整个域名或特定代理节点在连续数百个URL中暂时不可用的情况。在这里,需要使用断路器模式——“自动开关”,它监控最近一段时间的错误比例,并在超过阈值时暂时停止尝试,而不是继续撞击关闭的门。
class CircuitBreaker:
def __init__(self, failure_threshold=0.5, window_size=50, cooldown=60):
self.failure_threshold = failure_threshold
self.window_size = window_size
self.cooldown = cooldown
self.results = []
self.open_until = 0
def is_open(self):
return time.time() < self.open_until
def record(self, success: bool):
self.results.append(success)
if len(self.results) > self.window_size:
self.results.pop(0)
if len(self.results) == self.window_size:
failure_rate = 1 - sum(self.results) / self.window_size
if failure_rate > self.failure_threshold:
self.open_until = time.time() + self.cooldown
self.results.clear()
逻辑很简单:如果最近50个请求中超过一半失败,解析器将在60秒内暂停对该域名或代理的尝试。在此期间,可以更换IP、降低请求速度或切换到其他代理池。这在处理临时封禁IP范围的目标网站时尤其重要——继续撞击关闭的门意味着只是白白浪费流量。
重试时的智能代理轮换
对于减少不必要的重试,最有效的措施之一是不要从已经被拒绝的IP重试请求。逻辑很简单:如果错误与IP封禁有关(403、429、重定向到验证码),在重试前更换代理会显著提高成功的机会并减少尝试次数。
对于像Wildberries、Ozon或Avito这样的市场解析,使用的组合效果很好:普通请求通过数据中心代理——它们更快且更便宜, 一旦封禁检测器连续触发几次,解析器就切换到住宅代理,这些代理更少受到反机器人系统的过滤。这样的混合方法减少了整体流量消耗,因为昂贵的住宅IP仅在真正需要时使用,而不是连续用于所有请求。
| 错误类型 | 重试策略 | 需要更换IP吗? |
|---|---|---|
| 连接超时 | 退避,1-2次重试 | 否 |
| 403 / 验证码 | 立即轮换 | 是,必须 |
| 429(速率限制) | 根据Retry-After进行退避 | 建议 |
| 500-503 | 退避,2-3次重试 | 否 |
| 404 / 410 | 不重试 | — |
对于模拟移动流量的解析器(例如,从市场或社交网络的移动版本应用程序中收集数据),使用移动代理是有意义的—— 它们更少引起保护系统的怀疑,因为运营商同时向成千上万的真实用户分配相同的IP,针对特定网站的封禁变得不切实际。
重试指标监控
没有指标,重试逻辑的优化变成了猜测。每次请求时应记录的最小指标集:
- 重试率——需要至少一次重试的请求比例。
- 重试后的成功率——重试中最终成功的比例(如果这个指标很低,重试只是在消耗流量)。
- 成功结果的流量——传输数据的总量除以成功收集的记录数。这是关键的效率指标。
- 按代码分布的错误——帮助了解流量的主要泄漏发生在哪里:超时、403、429还是其他。
- 特定代理节点的重试率——如果一个IP的重试率为80%,而其他为10%,问题是局部的,可以通过更换特定节点而不是整个逻辑来解决。
即使是简单的Google Sheets表格或包含这五个指标的CSV日志,每小时更新一次, 也能提供足够的数据来发现异常并及时调整策略——例如,降低对特定网站部分的请求频率或增加住宅IP在池中的比例。
流量优化检查清单
- 将错误代码分为可重试和不可重试——不要重试404/410。
- 实施带有抖动的指数退避,而不是固定延迟。
- 尊重
Retry-After头部,如果网站发送了它。 - 在403和怀疑检测到机器人时,在重试前更换IP。
- 对尝试次数设置严格限制(通常3-4次就足够)。
- 对高失败率的域名和代理节点实施断路器。
- 记录重试率和成功结果的流量——没有指标就无法优化。
- 分离代理池:稳定区域使用廉价数据中心,问题区域使用住宅或移动IP。
结论
重试逻辑不是解析器的小细节,而是影响数据收集成本的主要因素之一。 天真的“重试所有”策略可能会使实际流量比理论最小值增加40-90%,而且大多数重试最终以与第一次尝试相同的失败告终。通过按类型划分错误、指数退避、断路器和智能IP轮换,可以在不降低收集数据完整性的情况下大幅减少这些损失。
如果您的解析器与积极检测机器人的网站(市场、社交网络、广告平台)合作—— 根据任务组合几种类型的代理是值得的。对于基本操作,数据中心代理就足够了, 而在需要最大抗封禁的地方——使用住宅代理,这些代理具有普通用户的真实IP地址。这样的混合方法结合合理的重试逻辑,可以在收集相同数据量的情况下显著降低流量消耗。