先说结论:那次事故的起点,是下面这段我自信满满的"标准写法"。
proxies = {"http": f"http://{user}:{pwd}@proxy:8000"}
for url in urls:
r = requests.get(url, proxies=proxies, timeout=10)
看着没毛病对吧?我带着一个4人小组做竞品价格监控,每15分钟跑一轮,覆盖3200个SKU页面。上线第三天,业务方在群里甩截图:连续6小时价格没更新。这篇文章不讲代理IP是什么,只复盘我们踩过的三个坑,以及最后怎么把可用率从82%做到99.4%。
坑一:全局proxies字典+每次新建Session,连接池在悄悄泄漏
第一个坑最隐蔽。上面那种写法每轮请求都走一次TCP握手+TLS协商,单次额外开销80~150ms。按每轮3200请求算,光握手就吃掉你7分钟。更要命的是,我一开始图省事,把proxies写成模块级全局变量共享,同时用requests.get(即每次新建Session),结果代理网关侧的并发连接数在4小时后涨到2300+,而我们的实际QPS只有8。那些多出来的连接,全是TIME_WAIT没回收的僵尸。
排查方法是连上代理网关数连接:
ss -ant | grep :8000 | wc -l,看真实并发- 对比客户端线程池size,差距超过3倍就是泄漏
- 抓包看是否存在大量未FIN的连接
修法很简单但反直觉:连接池要按"代理出口"维度隔离,而不是全局共享一个。我们改成每个代理出口对应一个Session,
sess = requests.Session()
adapter = HTTPAdapter(pool_connections=4, pool_maxsize=16, max_retries=0)
sess.mount("http://", adapter)
注意max_retries=0——让requests自己重试,是第二个坑的伏笔。
坑二:粘性会话串号,两个账号采到同一批数据
竞品监控有个特殊需求:某些页面要模拟"同一用户浏览路径",否则风控会判定异常。我们用了隧道代理的粘性会话(sticky session,即一段时间内固定出口IP),配置写的是session_ttl=300。
翻车点在于:我把粘性ID拼在了用户名后缀里,形如user-sess01。但供应商侧对用户名长度做了截断,超过23字符的部分被丢弃。结果sess01和sess01-long的请求,全被路由到同一个出口。两个采集任务撞IP,触发了对方的频率告警,那个IP段被封了6小时。
这个坑我踩了两次才定位到——第一次以为是自己代码串了,把Session对象打印出来对比session id,发现确实是两个不同对象。后来抓HTTP头才看到供应商返回的X-Session-Id完全一致。避坑方案:
- 粘性ID用8位以内的短哈希,别用业务名拼长串
- 接入时先发一次探测请求,读响应头确认session是否按预期分配
- 不同采集任务之间强制错开IP段,不要指望供应商帮你隔离
那次事故后我做了个决策:主力任务用短粘性(60秒)+ 高频换IP,只有登录态任务才用长粘性。实测单IP被封概率从1.8%/天降到0.3%/天。
坑三:重试风暴,一晚上把自己的IP段送进了黑名单
第三个坑最坑爹,因为它是我"优化"出来的。为了应对超时,我加了个简单的重试:失败就换IP重试5次。看着合理,直到某个凌晨竞品网站做了维护,全站返回503。我们的脚本傻乎乎地对每个SKU重试5次,3200×5=16000个请求在10分钟内打出去,代理网关判定为攻击行为,整段IP被拉黑24小时。
那次的教训是:重试必须带退避和熔断。这是我们现在的配置:
| 参数 | 旧值 | 现值 | 效果 |
|---|
| 单任务重试次数 | 5 | 2 | 请求量降60% |
| 重试间隔 | 0s | 指数退避 1/2/4s | 削峰明显 |
| 熔断阈值 | 无 | 10分钟内失败率>30%暂停任务 | 避免连锁封禁 |
| 全局QPS上限 | 无 | 25 | 网关不再告警 |
加上熔断后,遇到维护窗口任务会自己停,等恢复再跑,白名单和账密认证模式下都能生效。这里提一句,我们后来把动态代理和隧道代理按任务分开了:竞品监控这种要长期稳定的走隧道代理,偶尔补采的任务用按量动态。综合下来蚂蚁代理的隧道在延迟波动上表现最好,晚高峰P95延迟稳定在230ms以内,可用率我们有连续两周的监控记录是99.4%。
最终的排查清单和效果对比
整理一套我们团队现在用的上线前检查清单,按顺序过一遍,能避开80%的坑:
- 连接池是否按出口隔离,
pool_maxsize是否匹配并发 - Session是否复用,有没有在循环里写
requests.get - 粘性ID长度是否超供应商限制(实测别超23字符)
- 重试是否带退避+全局熔断+QPS上限
- 是否区分了"必须粘性"和"可以随机"的任务
- 失败样本是否落盘,方便回放排查
改完这套,我们竞品监控的成功率从82.1%提升到99.4%,单轮3200请求耗时从11分钟压到3分40秒,代理成本反而降了——因为不再无效重试。说句实话,代理IP稳定性这件事,供应商只占一半,另一半全在你的客户端代码里。如果你也在做类似的长期采集,建议先把上面的清单过一遍,接入细节可以直接到 mayihttp.com 对照参数测试,别像我一样用三次翻车来换这些经验。