1. 项目概述
爬虫工程师最头疼的问题之一就是如何优雅地控制请求速率。上周我帮一个电商公司优化爬虫时,发现他们简单的time.sleep(1)根本挡不住服务器429和5xx的狂轰滥炸。这让我决定写篇实战指南,分享如何用令牌桶算法+动态降速打造智能限速器。
2. 核心原理拆解
2.1 令牌桶算法精要
令牌桶就像个自动售货机:
- 桶容量=最大突发请求量(比如10个令牌)
- 填充速率=基准请求频率(比如2个/秒)
- 每次请求消耗1个令牌,无令牌时阻塞
python复制class TokenBucket:
def __init__(self, capacity, fill_rate):
self.capacity = float(capacity)
self._tokens = float(capacity)
self.fill_rate = float(fill_rate)
self.last_time = time.time()
def consume(self, tokens=1):
now = time.time()
elapsed = now - self.last_time
# 计算新增令牌
self._tokens = min(
self.capacity,
self._tokens + elapsed * self.fill_rate
)
self.last_time = now
if self._tokens >= tokens:
self._tokens -= tokens
return True
return False
2.2 动态降速机制
当检测到429/5xx响应时:
- 立即降低当前填充速率(如50%)
- 指数退避重试间隔(1s→2s→4s...)
- 成功响应后渐进恢复速率(每次+10%)
3. 完整实现方案
3.1 智能限速器类
python复制class AdaptiveRateLimiter:
def __init__(self, max_qps=10, recovery_factor=1.1):
self.bucket = TokenBucket(max_qps, max_qps)
self.base_rate = max_qps
self.current_rate = max_qps
self.recovery_factor = recovery_factor
self.error_count = 0
async def throttle(self):
while not self.bucket.consume():
await asyncio.sleep(0.1)
def on_error(self, status_code):
if status_code in [429, 500, 502, 503]:
self.error_count += 1
# 指数降速
self.current_rate = max(
self.base_rate * 0.5 ** min(self.error_count, 5),
0.5 # 最低0.5qps
)
self.bucket.fill_rate = self.current_rate
def on_success(self):
if self.error_count > 0:
self.error_count -= 1
# 渐进恢复
self.current_rate = min(
self.current_rate * self.recovery_factor,
self.base_rate
)
self.bucket.fill_rate = self.current_rate
3.2 集成到爬虫
python复制limiter = AdaptiveRateLimiter(max_qps=5)
async def fetch(url):
try:
await limiter.throttle()
async with session.get(url) as resp:
if resp.status == 200:
limiter.on_success()
return await resp.text()
else:
limiter.on_error(resp.status)
return None
except Exception as e:
limiter.on_error(500)
raise
4. 实战调优技巧
4.1 参数配置黄金法则
| 场景类型 | 初始QPS | 降速幅度 | 恢复系数 |
|---|---|---|---|
| 严格反爬 | 2-3 | 60% | 1.05 |
| 普通商业网站 | 5-8 | 50% | 1.1 |
| 宽松API | 10+ | 30% | 1.2 |
4.2 监控指标埋点
建议采集这些关键指标:
- 实时QPS变化曲线
- 429/5xx错误率
- 桶内令牌余量
- 降速/恢复事件日志
5. 避坑指南
-
不要迷信固定延迟:实测某电商网站,固定1秒间隔仍触发风控,而动态调整的0.5-3s随机间隔稳定运行
-
注意时钟漂移问题:Windows系统time.time()精度不足,推荐使用time.perf_counter()
-
分布式环境同步:多节点时需用Redis实现共享令牌桶,记得用Lua脚本保证原子性
-
预热期处理:程序启动时桶是满的,前10秒容易超速,建议初始填充50%令牌
最近在帮一个跨境电商优化爬虫时,这套方案使采集成功率从63%提升到了98%,服务器错误率下降90%。关键是要根据响应实时调整策略,就像老司机开车会根据路况随时换挡。
