从这段代码开始,我的比价服务只活了40分钟
接旅游比价这个活时我报价很低,因为需求听着简单:同时拉几家OTA(在线旅行社,携程、飞猪、去哪儿这类)同一家酒店同一天的价格,拼成对比表。第一版代码长这样:
PROXY_LIST = open("free.txt").read().split()
def fetch(ota, city):
return requests.get(ota.url,
proxies={"https": random.choice(PROXY_LIST)},
timeout=5)
for ota in OTAS:
fetch(ota, city="杭州")
跑起来40分钟,三家OTA有两家开始返回403,剩下那家给我的价格全是别的城市。我第一反应是并发太高,把线程从50降到8,没用。真实原因是两件事叠在一起:免费代理的可用率不到10%,以及OTA按出口IP归属城市返回库存和价格——我用杭州出发的查询配了个郑州出口,价格表错得离谱。比价业务里数据错比数据少更致命,因为它会静默写进数据库,用户看到的是错的,你还不知道。
调度架构:把"能连上"和"能用"拆成两层
免费池的根本毛病是把两件事混为一谈:IP存活,和IP适合当前任务。我现在的代理IP池按四层组织,每层只干一件事。
- 提取层:按需拉取,不做本地常驻池。比价请求有明显波峰(早上8到10点),池子按分钟扩缩,常驻池只会积累脏IP。
- 健康校验层:不用"百度能打开就算活"这种通用探测,直接拿目标OTA的列表页打,3秒超时、连续2次失败标记不可用。能过通用探测、照样被目标站拦的IP,我碰到太多次了。
- 调度层:以"城市+OTA"为键做亲和,同城查询复用同地域出口,把出口地理漂移率压到 2% 以下。
- 熔断层:下一节单独讲。
这套结构下,单IP平均响应从免费池的 1840ms 降到 620ms;换成隧道代理那一档能压到 180ms(代理网关自身转发延迟在10ms以内,剩下的全是目标站排队)。顺手测出来的一个结论:池子规模对成功率的影响远小于校验频率,我把校验从5分钟缩到30秒后,成功率直接涨了11个百分点。
故障转移:熔断阈值定多少,是被投诉逼出来的
最初的熔断写得很粗暴——单IP失败一次就拉黑。结果早高峰一次网络抖动,整池被清空,业务方直接在群里@我。现在的参数是三档:单IP连续3次失败拉黑 300秒;同一城市可用IP少于10个时降级到邻省出口并打标记;整体成功率跌破 90% 触发告警而不是熔断,熔断交给人工确认。说实话这个90%是被投诉逼出来的数字,低于它意味着数据质量已经不可信了。
| 方案 | 平均响应 | 实测可用率 | 单位成本 | 切城市耗时 | 2小时被封比例 |
|---|
| 免费公开池 | 1840ms | 6.2% | 0元 | 不可控 | 91% |
| 动态短效代理 | 620ms | 96.4% | 0.0022元/IP起 | 约300ms | 7.8% |
| 隧道代理 | 180ms | 99.9% | 16元/天起 | 会话重建<100ms | 1.2% |
免费池那6.2%的可用率是我拿500个IP逐个打的实测值,不是估算。有意思的是这6.2%在凌晨能升到18%左右,白天抗不住——所以如果有人拿夜间数据跟你吹免费池,别信。
监控告警:指标要按城市和OTA两个维度拆
只盯总成功率会被平均数骗。我现在的看板拆成"城市×OTA"的矩阵,因为封禁往往是单站单地域触发的。告警规则三条:单城市成功率连续5分钟低于85%、单OTA的403比例超过20%、池内可用IP数低于阈值。第二条帮我抓到过一次OTA改风控规则,比业务方发现得早了半天。
成本上,每天16元的隧道代理跑我这个量级(日均20万请求)刚好够用,换成短效IP按量算是每天11元左右,但调度代码复杂度翻倍。我个人觉得,只要QPS稳定在上百,隧道那档更省心。边界也讲清楚:这套架构在日均几十万请求时够用,如果你要做到千万级,调度层得改成无状态的注册中心,本地亲和表会成为瓶颈。
最后补一句踩坑经验:地域校验别只看IP库的归属地,要用目标站返回的价格做交叉验证,我靠这个抓到过两次代理标称城市与实际出口不符的情况。现在这套调度跑的隧道这一档走的是 mayihttp.com,不是因为它最便宜,而是城市覆盖够散、切换够快,比价业务最怕的就是地域漂移。