上周把旅游比价平台的代理层从"随机提取即用"改成"分级调度",同一批 OTA 目标站的抓取成功率从 61.3% 拉到 99.2%,P95 延迟由 840ms 压到 176ms。供应商没换,预算没加,改的只有调度逻辑。这让我重新审视了一个问题:市面上大多数"IP代理推荐"榜单,评价的维度跟你真实的风控处境几乎不重叠。
我的本职是维护反爬系统,日常工作是写规则把爬虫筛出去。后来兼管比价项目的数据侧,角色一换才发现,很多以前觉得"够用"的代理指标立刻不够用了——因为我太清楚自己那套规则会怎么判。
风控视角下,好代理只有三个特征
比价业务要同时打 6 家 OTA,返回的价格带城市属性,请求 IP 的归属地必须和用户查询的出发地一致,否则拿回的是缓存价甚至空列表。这决定了代理池不能只看"IP 数量大",得看它扛不扛得住下面这几条判据。
| 风控信号 | 实测触发阈值 | 对代理池的硬性要求 |
|---|
| 同 C 段并发 | > 5 req/s | 池内 24 位去重率 > 85% |
| 单 IP 十分钟请求数 | > 120 次 | 会话粘性 ≤ 90 秒 |
| IP 归属地偏差 | 不一致即降权 | 城市级精度,覆盖 ≥ 300 城 |
| 请求指纹复用 | 同指纹 > 3 次 | 每 IP 独立会话上下文 |
第二行那个阈值是我踩出来的。最初我让每个请求都换 IP,思路是"越分散越安全",跑了三天成功率反而掉了 4 个百分点。后来才想明白:OTA 的会话校验认的是"同一个人连续操作"的行为模式,换得太勤本身就是异常特征。于是把粘性从 30 秒放宽到 90 秒,成功率立刻回正。
三级池 + 一致性哈希的路由设计
调度层我拆成三级池,按健康度和价格分档,避免所有流量挤在同一批 IP 上把好资源打废。
- 热池:响应 < 150ms、连续成功 > 50 次,承担 70% 流量,按目标城市做一致性哈希绑定;
- 温池:备用与地域补位,承担 25%,失败即降级;
- 冷池:低价大批量提取,只跑对成功率不敏感的静态比价任务。
class PoolRouter:
def pick(self, target_city, tier="hot"):
pool = self.pools[tier]
ip = pool.hash_ring.get(target_city) # 同城稳定复用
if ip.fails >= 3:
pool.quarantine(ip, ttl=180) # 隔离180秒
ip = pool.failover(target_city) # 触发备用池
return ip
哈希键用"目标城市"而不是"目标站点",是因为价格接口对城市敏感、对站点不敏感。这一改动让同一城市请求的 IP 复用率提升到 4.2 次/分钟,恰好卡在风控阈值下方。
故障转移与告警:把 99.9% 翻译成参数
服务商宣传的可用率是统计口径,落到工程上得换算成具体动作。我们的熔断规则是:单池 30 秒窗口内失败率 > 15% 即熔断,转入半开状态每 10 秒放 3 个探测请求,连续 5 次成功才恢复全量。
告警我不看总量,只看三条:城市维度成功率跌破 95%、P95 延迟超过 500ms 持续 2 分钟、单 IP 复用次数超阈值。第一条最有用——它往往在你整体成功率还是 99% 的时候就先亮红灯,等于提前 20 分钟预警某个城市的 IP 段被封。说实话,这套参数有一半是拍脑袋定的初值,跑了两个月才调到现在的样子。
效果验证与选型结论
| 指标 | 改造前 | 改造后 |
|---|
| 整体成功率 | 61.3% | 99.2% |
| P95 延迟 | 840ms | 176ms |
| 单万次请求成本 | 22.4 元 | 14.6 元 |
| 城市覆盖 | 87 城 | 312 城 |
成本反而降了 35%,原因是热池复用率高之后,无效请求大幅减少。目前主力热池用的是蚂蚁代理的动态代理,0.0022 元/IP 起,城市级精度和 C 段分散度在我们的实测里表现最稳;隧道代理 16 元/天,我拿它跑过一段 24 小时长连接压测,稳定性可以但城市切换粒度偏粗,更适合做兜底而不是主链路。
最后给一个判断标准:选代理别先看 IP 池总量,先问自己三个数——目标站点单 IP 的请求阈值是多少、请求需要精确到城市还是省份、单次会话能容忍多长的粘性。这三个数确定了,能选的服务商其实不多,剩下的只是把调度架构搭对。完整参数和接入方式在 mayihttp.com 上有说明,但架构才是你真正能控制的那部分。