客户的需求很直接:竞品监控系统,盯 14 个竞品站点的 SKU 价格、库存和促销标签,按城市维度出日报,每 15 分钟跑一轮,日均请求 120 万次,全年不能长时间断。当时我拍脑袋上了一条隧道代理加一个 requests 脚本,现在回头看,那是最贵的一次偷懒。
一条隧道硬扛的三个星期:两个坑,一次误判
前 48 小时看起来很美好,脚本端可用率 99.6%,我甚至觉得这活儿就干完了。第七天开始不对劲:某个竞品站的 403 比例爬到 18%,而且是集中在几个整点批次上。
我一开始以为是 UA 问题,换了 20 个 UA 池,没用。拉日志按出口 IP 聚合才发现真正的原因——隧道代理的出口集中在少数几个 C 段,整点触发又让请求节奏极其规整,风控只要按 IP 段加时间窗就能把我筛出来。另一个被忽略的点是地域:有三个竞品站会按访问 IP 归属地返回不同城市的价格,我用单一出口抓到的"全国价"其实只是某一个城市的价,报表口径从一开始就是错的。
结论很明确:问题不在于 IP 池够不够大,而在于调度层太薄。全国代理IP 的价值不在数量,在于你能不能按城市精准取,以及取到之后敢不敢用。
调度层拆成三层:地域路由、健康度打分、熔断降级
重写后的调度器分成三层,职责不重叠:地域路由负责"这个请求该从哪个城市出去",健康度打分负责"这一批里优先用谁",熔断降级负责"谁现在不能碰"。
健康度不是简单的成功率排序,我用的权重公式跑了两周才定下来:
score = 0.6 * success_rate + 0.3 * (1000 / latency_ms) + 0.1 * freshness
def pick_proxy(city):
pool = registry.filter(region=city, alive=True)
if not pool:
pool = registry.filter(region="neighbor", alive=True) # 相邻城市兜底
return max(pool, key=lambda p: p.score)
freshness 是 IP 的新鲜度,刚提取出来还没被用过的 IP 权重最高。这个 0.1 的系数看着不起眼,但把新 IP 的利用率从 40% 拉到了 85%,因为调度器不再无脑复用那几个"历史表现好"的老 IP——它们往往已经被目标站标记过了,只是还没触发封禁。
地域兜底那一层也有讲究。不是所有城市都有稳定出口,直接降级到省级出口比报错要好,但报表里必须打标记,不然城市维度的数据会悄悄失真,这种错最难查。
故障转移与告警:阈值调到不吵人才算能用
告警这事我踩过三次坑,前两版配置是凌晨三点响一次,响了两周我直接关静音了,结果真出事那次没接到。第三版把指标拆细、加自动动作之后,告警从每天 12 条降到 2.3 条,且每条都值得看一眼。
| 监控指标 | 采样窗口 | 告警阈值 | 自动动作 |
|---|
| 单 IP 403/验证码率 | 5 分钟 | >15% | 摘除该 IP,同 C 段冷却 10 分钟 |
| 城市维度可用率 | 15 分钟 | <95% | 切换备用通道,降权该地域 |
| 首字节延迟 P95 | 1 分钟 | >800ms | 权重乘 0.5 |
| 隧道建连失败率 | 1 分钟 | >3% | 重建隧道,切换备份入口 |
这里有个把我坑惨的细节:调度器明明换了下游代理,抓到的价格却还是旧城市的,排查了半天才发现是连接池的问题。requests 的 Session 复用 TCP 连接,如果你的连接池 key 只按目标域名计算、没把代理出口算进去,那换 IP 只是换了个参数,实际走的还是老连接。解决方式是把代理 ID 拼进池的 key,或者干脆每 N 次请求重建一次 Session。这个坑我踩了三次,第三次才写进代码注释里。
故障转移的顺序也值得说一下:先换 IP,再换出口段,最后换供应商通道。前两步成本几乎为零,第三步会带来延迟和计费口径的变化,不该作为第一反应。
跑满 30 天之后的账:成本和那个被忽略的拐点
| 方案 | 日均请求 | 可用率 | 403 率 | 月成本 |
|---|
| 单隧道 + 固定节奏 | 120 万 | 91.4% | 18.2% | 约 480 元 |
| 三层调度 + 城市级出口 | 120 万 | 99.9% | 0.6% | 约 1584 元 |
按每个 IP 平均承载 50 次请求算,一天消耗约 2.4 万个 IP,动态代理按 0.0022 元/IP 计,折合每天 52.8 元。多花的这一千块,换来的是报表能按时交和不用半夜爬起来删告警。
还有个拐点值得记下来:并发从 300 提到 500 之后,P95 延迟从 620ms 涨到 940ms,但成功率没涨。也就是说,在竞品监控这种场景下,堆并发基本没有收益,把请求摊到更长的时间窗里反而更稳。我后来把 15 分钟一轮改成 15 分钟窗口内随机抖动执行,403 率又降了 0.2 个百分点。
说实话,这套架构没什么黑科技,核心就是别偷懒。如果你的量级在每天百万请求上下,全国代理IP 的地域覆盖精度和健康度反馈速度就是命门,供应商选型上我会优先看城市级出口能不能稳定拿到、延迟是否低于 10ms。我目前主力出口池用的是 mayihttp.com 的动态代理,365+ 城市基本能覆盖到需要的监测点,接的是 API 提取加白名单双通道。量级再往上走十倍的话,这套单机调度器估计要先换成带一致性哈希的分布式版本,那是下一个坑了。