在扩展爬虫时的经典错误是“凭感觉”购买代理:买了100个IP,启动爬虫,半小时后就被封了。问题不在于地址数量,而在于没有人计算每个IP的实际负载。我们将分析基于RPS(每秒请求数)、延迟和目标网站限制的代理池计算公式——附有具体数字和Python代码。
为什么按IP数量计算代理池是错误的
新手爬虫的典型思路是:“需要收集50,000个商品卡片,所以我们买500个代理并分散负载”。这个逻辑看似合理,但没有考虑到最重要的一点——像Wildberries和Ozon这样的网站并不是根据请求的绝对数量来封禁,而是根据单个IP在单位时间内的请求强度。也就是说,500个代理,每个每秒发出20个请求,会立即被封禁——反爬虫系统会识别出类似DDoS的模式。
另一方面,如果你有50个代理,但每个每5-10秒发出1个请求,模拟人类行为,你可以稳定地爬取数周而不会被封禁。IP数量并不是稳定性的原因,而是正确计算负载的结果。因此,公式应该从“买多少个IP”出发,而不是“需要达到多少RPS,以及每个IP的安全负载是多少”。
还有一个细节:不同类型的代理在单个IP上的“承受能力”不同。数据中心代理在高请求频率下更容易被封禁,因为它们的子网容易被识别为托管服务。住宅和移动IP看起来像普通用户,可以允许稍高的请求频率而不被封禁——但这并不意味着可以完全忽视限制。
代理池计算的基本公式
计算代理池中所需代理数量的公式如下:
N = (RPS_target × Delay_per_ip) / Concurrency_per_ip
其中:
- N — 代理池中所需的代理数量;
- RPS_target — 目标爬取速度(整个系统的每秒请求数);
- Delay_per_ip — 单个IP之间的最小安全间隔(以秒为单位);
- Concurrency_per_ip — 每个IP允许的并行线程数(通常为1,住宅代理最多为2)。
逻辑很简单:如果你想保持每秒10个请求的总数,而单个IP之间的安全间隔为8秒,那么一个IP在8秒内只能发出1个请求,即0.125 RPS。为了获得10 RPS的总数,需要10 / 0.125 = 80个代理。这就是替代“以防万一买500个IP”的计算。
如何计算爬取的目标RPS
在计算代理池之前,需要确定RPS_target——你实际需要的每秒请求数,以便在合理的时间内收集数据。这里的公式是反向的:
RPS_target = Total_requests / Time_budget_seconds
例如:需要在8小时内从Wildberries收集100,000个商品卡片(28,800秒)。如果每个卡片需要1个请求,RPS_target = 100,000 / 28,800 ≈ 3.47请求每秒。这并没有看起来那么多——许多人高估了所需的速度,购买了过量的代理。
如果任务包含多种类型的请求(例如,首先获取类别列表,然后是卡片,然后是评论),请分别计算每个阶段的RPS——它们可以并行进行,使用不同的代理池,整体负载将分散到不同的端点。
每个IP的限制:每分钟安全请求数
Delay_per_ip是公式中最重要的参数,需要根据每个网站的经验来确定。基于市场爬虫实践的一般参考:
| 平台 | 单个IP之间的安全间隔 | 单个IP的最大请求数/分钟 |
|---|---|---|
| Wildberries(商品API) | 4-6秒 | 10-15 |
| Ozon(商品页面) | 5-8秒 | 8-12 |
| Avito(广告) | 6-10秒 | 6-10 |
| Yandex.Market | 5-7秒 | 8-12 |
这些数字是起始点,而不是教条。开始时使用保守的值(暂停的上限),监控429和403错误的百分比,如果封禁没有增加,则逐渐减少暂停时间。突然增加RPS而不进行逐步测试是导致整个代理池在一天内被封禁的最常见方式。
实际计算:Wildberries、Ozon、Avito
我们将分析三个实际场景,并根据公式进行完整计算。
场景1:监控Wildberries的价格。 需要每2小时更新20,000个商品的价格。RPS_target = 20,000 / (2 × 3600) ≈ 2.78 RPS。对于每个IP暂停5秒且并发=1:N = (2.78 × 5) / 1 ≈ 14个代理。为了防止部分IP被封,建议购买1.5-2倍的代理池,即21-28个代理。
场景2:一次性收集Ozon的目录。 500,000个卡片在24小时内。RPS_target = 500,000 / 86,400 ≈ 5.79 RPS。暂停6秒:N = (5.79 × 6) / 1 ≈ 35个代理。加上安全系数——50-60个代理。
场景3:实时监控Avito的竞争对手。 5,000个广告,每15分钟更新一次。RPS_target = 5000 / 900 ≈ 5.56 RPS。暂停8秒:N = (5.56 × 8) / 1 ≈ 45个代理。这里需要注意,Avito会积极封禁数据中心的子网,因此对于这样的任务,建议直接使用住宅或移动IP。
公式中的住宅、移动和数据中心代理
代理类型直接影响Delay_per_ip,从而影响公式中的最终N。数据中心代理更便宜且速度更快,但在请求之间需要更长的暂停时间,并且在高频率下更容易被封禁——实际上,对于同样的5 RPS,你可能需要2-3倍于住宅代理的数据中心IP。
| 代理类型 | 平均Delay_per_ip | 何时使用 |
|---|---|---|
| 数据中心代理 | 8-15秒 | 开放API,无严格反爬虫的网站 |
| 住宅代理 | 4-8秒 | Wildberries、Ozon、Avito和其他带反爬虫的网站 |
| 移动代理 | 3-6秒 | 最激进的保护,社交网络,移动API |
对于像Wildberries和Ozon这样的市场爬取,通常最佳选择是住宅代理——它们在价格和稳定性之间取得了平衡,允许在不急剧增加封禁的情况下保持较短的暂停时间。数据中心代理只应在没有激进反爬虫的网站或一次性任务中考虑,且RPS不高。
Python中的代理池实现:带轮换的代码
以下是一个简化的代理池实现,控制每个IP的请求频率。逻辑是:每个代理存储最后使用的时间,调度程序只选择那些自上次请求以来经过足够时间的IP。
import time
import random
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class ProxyNode:
address: str
delay_per_ip: float # 安全间隔(秒)
last_used: float = field(default=0.0)
class ProxyPool:
def __init__(self, proxies: List[str], delay_per_ip: float):
self.nodes = [ProxyNode(address=p, delay_per_ip=delay_per_ip) for p in proxies]
def get_available_proxy(self) -> Optional[ProxyNode]:
now = time.time()
available = [
node for node in self.nodes
if now - node.last_used >= node.delay_per_ip
]
if not available:
return None
# 从可用的中随机选择,以避免队列模式
node = random.choice(available)
node.last_used = now
return node
def size(self) -> int:
return len(self.nodes)
def calculate_pool_size(rps_target: float, delay_per_ip: float, concurrency: int = 1) -> int:
"""公式:N = (RPS_target * Delay_per_ip) / Concurrency_per_ip"""
return max(1, int((rps_target * delay_per_ip) / concurrency))
# 示例用法
rps_target = 5.79 # 从任务量和时间计算得出
delay_per_ip = 6.0 # Ozon的安全间隔
n_proxies = calculate_pool_size(rps_target, delay_per_ip)
print(f"代理池中需要的代理数量:{n_proxies}") # ~35,安全系数50-60
在实际爬虫中,这个逻辑需要补充任务队列(例如,通过asyncio或multiprocessing),以便工作线程在没有可用代理时等待,而不是因缺少代理而失败。还应在收到429/403时添加指数退避——这会在检测到封禁迹象时自动增加特定IP的暂停时间。
监控和动态调整代理池
静态的公式计算是起点,而不是最终解决方案。在实际使用中,需要监控三个关键指标:
- 成功率 — 成功请求(状态码200)占总请求的百分比。低于90-95%时,意味着需要增加Delay_per_ip或在池中添加代理;
- 封禁率 — 在过去一小时内显示封禁迹象的代理比例(验证码、403、重定向到验证表单);
- 实际RPS — 实际请求处理速度,可能由于超时和重试而与计算值不同。
实际规则是:如果封禁率在一小时内超过5-7%,请将Delay_per_ip增加20-30%,并重新计算N。如果成功率在几个小时内稳定在98%以上,可以逐渐减少暂停时间并缩小代理池——这将直接节省代理预算而不影响稳定性。
一个好的做法是为每个IP单独记录日志:最后一次成功请求的时间、连续错误的数量、平均响应时间。这可以自动将“损坏”的代理排除在轮换之外30-60分钟,而不是继续通过它们发送请求并在每一步都收到验证码。
计算代理池时的常见错误
即使知道公式,在计算细节上也容易出错。以下是典型错误的清单:
- 忽视封禁的安全系数。 即使在理想计算下,5-10%的代理也会因封禁或网络问题而暂时不可用。始终在基本N上添加1.3-2倍的系数;
- 对所有端点使用相同的Delay_per_ip。 类别页面和商品页面可能有不同的限制——请分别计算;
- 未经过测试的Concurrency大于1。 从一个IP发出的并行请求会显著增加封禁风险,尤其是在住宅代理上——从Concurrency = 1开始;
- 缺乏User-Agent和头部的轮换。 即使计算出的代理池也无法拯救,如果所有请求都来自相同的浏览器指纹;
- 固定的暂停时间没有抖动。 请求之间严格相同的间隔(例如,正好5.0秒)是反爬虫容易识别的模式。添加±20-30%的随机偏差。
结论
公式N = (RPS_target × Delay_per_ip) / Concurrency_per_ip将代理池的计算从猜测转变为具有具体数字的工程任务。首先根据数据量和时间确定目标爬取速度,然后经验性地找到特定平台的安全间隔,最后计算所需的IP数量——并增加30-100%的安全系数以防封禁和故障。
这种方法节省了预算:与其“以防万一”购买过量的IP,不如支付确切所需的代理量,以达到目标RPS而不冒被封禁的风险。对于市场爬取和其他受保护的网站,建议从住宅代理开始——它们在与Wildberries、Ozon和Avito的反爬虫系统打交道时提供了最佳的稳定性和成本平衡。