凌晨三点那次掉坑:接口没挂,是我们这头先撑不住了
有个做公开数据采集的团队,任务每天凌晨两点准时跑,四个小时后收工。头一个月成功率还有 99%,第二个月掉到 92%,第三个月直接 86%,平均响应时间从 180ms 涨到 1.2 秒。团队第一反应是目标站点开始封了,抓了两天包才发现,对面一直很客气——是我们这边先把自己堵死了:每个请求都新建会话,没设超时,失败了原地立刻重试,代理那边好好的,客户端自己的连接数先爆了。
这类毛病在高频接口请求里太常见。挂个代理不难,难的是让几千上万次请求连着跑几个小时,还保持稳定和低延迟。下面按延迟、成功率、代码实现、选型四块拆,都是能直接照做的。
那 1 秒延迟,其实是分四段花掉的
把一次请求拆开,时间花在四个地方:解析域名、建立连接、代理出口转发、目标服务端排队。很多人一句"代理慢"就概括了,实际慢的往往不是代理本身,是前面两段被反复重做。
域名解析通常 10-80ms,但如果你每换一个 IP 就重新起一个会话,这几十毫秒就要重付一次,一千次请求就是几十秒。建连握手一次往返 30-120ms,取决于你到代理节点的物理距离。代理出口转发 20-200ms 不等,出口城市离目标越远,这一段拖得越明显。最后是服务端排队,50ms 到好几秒都有可能——并发一高,对面给你排进队列,这时候换多少 IP 都不顶用,得把并发压下来。
| 环节 | 常见耗时 | 出问题时你会看到什么 |
|---|---|---|
| DNS 解析 | 10–80ms | 换个 IP 就重建会话,域名反复解析 |
| TCP 建连握手 | 30–120ms | 连接池没用起来,每次请求都握手 |
| 代理出口转发 | 20–200ms | 出口城市和目标隔着大半个地图,绕路明显 |
| 服务端排队 | 50ms–数秒 | 并发一高就被限流,返回变慢但不报错 |
看完这张表你会发现,想压低延迟,先砍的是重复动作,再谈换节点。会话复用、连接池、就近出口,这三件事做完,多数团队的 P95 延迟能掉一半以上。
成功率这个数,不同团队算出来的根本不是一回事
有团队说自己成功率 99.7%,另一个团队用同样的代理说自己只有 94%,两边都没撒谎,是口径不一样。
严格一点的口径是:HTTP 状态码 200,并且响应体通过了业务校验(字段齐全、内容非空),才算一次有效请求。宽松一点的口径是:只要收到响应就算到达。中间还夹着一类被混进去的:429 限流和 5xx 服务端错误,很多人把它们一起算作失败,然后转头去换代理,其实换多少都没用。
建议至少拆成三个数看:到达率(收到响应的比例)、有效产出率(校验通过的比例)、异常分布(超时、429、5xx 各占多少)。这三个数摆在一起,问题出在哪一头,一眼就能看出来。超时占比高,先查连接和超时设置;429 多,先降并发;5xx 集中,多半是对面服务端的问题,等一等再上。
| 统计口径 | 怎么算 | 观察到的区间 |
|---|---|---|
| 到达率 | 收到任意响应即计数 | 通常高于有效产出率 3–8 个百分点 |
| 有效产出率 | 200 且响应体校验通过 | 良好水平在 95% 以上,随目标站点浮动 |
| 超时占比 | 连接或读取超时次数 / 总请求 | 超过 2% 就该查客户端设置 |
| 限流占比 | 429 与显式限流提示 / 总请求 | 大于 1% 说明并发超出对方承受度 |
代码里最容易踩的三个坑(附一段能直接用的写法)
第一个坑:不用连接池。每次请求都新建会话,等于每次都重新握手。改成复用一个 Session,把连接池开大,重复握手这部分开销基本就没了。
第二个坑:不设超时,或者只设一个数。很多人写 timeout=5,实际上该分开设:连接 3 秒、读取 8 秒,这样卡住的是哪一段你心里有数。没有超时的请求会一直挂着占住连接,几十个这样的请求就能把池子占满。
第三个坑:立刻原地重试。失败了马上再打一次,对面只会更烦你。重试要带退避间隔,而且次数别给太多,三次封顶。
import time
import requests
from requests.adapters import HTTPAdapter
PROXY = "http://USER:PASS@HOST:PORT"
PROXIES = {"http": PROXY, "https": PROXY}
session = requests.Session()
# 连接池开大,让连接尽量复用;重试自己控制,别让库偷偷重试
session.mount("https://", HTTPAdapter(pool_connections=64, pool_maxsize=128, max_retries=0))
def fetch(url, retry=3):
for i in range(retry):
try:
# (连接超时, 读取超时) 分开设
r = session.get(url, proxies=PROXIES, timeout=(3, 8))
if r.status_code == 200 and r.text:
return r.text
# 429 / 503 一般是对面在限流,退一步再试
time.sleep(1.5 * (i + 1))
except requests.exceptions.Timeout:
time.sleep(0.8 * (i + 1))
except requests.exceptions.RequestException:
time.sleep(0.5)
return None
这段代码没什么花活,但把它跑起来,那家团队的成功率从 86% 拉回了 98.5%,平均延迟回到 210ms 左右。改动的地方就三处:会话复用、超时分开设、重试带退避。
短效动态、长效静态、固定IP,什么活配什么IP
延迟和成功率调顺之后,接下来才是挑 IP。这里最忌讳一刀切,一种类型打天下。
公开数据采集这类分散的活,需要不断获取新的 IP 来分散节奏,短效动态更合适,成本也低;但如果你要在同一个会话里连续翻页、带着登录态往下走,动态 IP 每分钟换一次,登录态就断了,这时候该上长效静态,几小时到一天的存活时间够你走完一个完整流程;还有一些核心任务接的是白名单,出口必须长期不变,那就用固定IP代理,稳字当头。
| 类型 | 存活特点 | 适合的场景 | 要注意的地方 |
|---|---|---|---|
| 短效动态IP | 按次或按分钟更换 | 分散的公开数据采集、市场研究取样 | 别在需要连续会话的任务上用 |
| 长效静态IP | 数小时到一天 | 连续翻页、带登录态的接口轮询 | 并发别堆太高,容易触发限流 |
| 固定IP代理 | 长期保持不变 | 白名单对接、稳定性优先的核心任务 | 做好单点备份,别只留一条路 |
神龙HTTP这块的产品线正好覆盖了这三种:短效动态、长效静态、固定IP代理,一站式能配齐。底层是国内三大运营商正规授权、3000 万+ 的代理IP储备,支持 HTTP/HTTPS/SOCKS5,出口覆盖 300+ 城市级定位——这一点在高频请求里很实用,你可以按目标站点所在区域就近选出口,把前面说的"出口转发"那一段直接压短。官方给的可用率是 99.9%、IP 纯度 99.8%,配合低延迟高并发的提取方式,够撑住几个小时不间断的接口调用。
跑起来之后,怎么盯住那条曲线
任务上线不等于收工。高频请求的问题往往不是一上来就暴露,而是跑了两小时之后慢慢浮现:延迟一点点爬升,超时占比从 0.3% 涨到 2%,你不管它,第二天早上就是一片红。
比较省事的做法是给自己留三个观察点:每分钟的请求数和失败数、P95 延迟、以及最近十分钟的异常类型分布。这三个数不用很精确,但要有,能让你在问题扩大之前动手。另外建议把日志里每次请求的出口 IP、耗时、状态码都记下来,事后复盘时,是哪个城市的出口拖慢的、哪个时间段开始异常的,一看日志就清楚。
神龙HTTP 的个人中心里带了可视化的用量统计和实时监控,异常识别和使用趋势分析都能直接看,省了自己搭一套看板。真遇到摸不准的情况,技术团队 7×24 小时在线,把日志片段发过去,一般能很快定位到是出口侧还是客户端侧的问题。API 也兼容主流爬虫编程语言,文档和示例代码都有,接起来不费劲。
常见问题
Q:换了 IP 之后成功率反而更低,是什么原因?
先看异常类型。如果 429 和 5xx 变多了,说明问题不在 IP,而在你的请求节奏——出口换了,但并发和频率没变,对面照样限流。如果超时变多,检查一下新出口到你目标站点的距离,跨区域绕路会很拖时间。还有一种情况是换了 IP 但没换会话,旧连接还挂在那儿,新 IP 根本没生效,把 Session 重建一次再试。
Q:延迟多少算正常?
看你的目标和出口距离。同城或相邻区域,端到端 150-300ms 是常见区间;跨区域绕一下,到 400-600ms 也不奇怪。真正要盯的不是平均值,是 P95 和 P99——平均值 200ms 但 P99 有 3 秒,说明有一小撮请求在拖后腿,这部分才是影响任务能不能按时跑完的关键。
Q:短效动态和长效静态到底怎么选?
一句话:需要保持会话连贯性的活,用长效静态;只需要分散节奏、每个请求互不相关的活,用短效动态。拿不准就先用短效动态跑一轮,把日志记下来,看看失败请求是不是集中在"会话中断"这一类,是的话再换长效静态,成本可控。
Q:超时时间设多少合适?
连接和读取分开设,连接 3 秒、读取 8 秒是个不错的起点。目标站点响应本身偏慢的,读取可以放宽到 10-15 秒;反过来,如果对方是轻量接口,读取压到 5 秒反而能更快暴露问题。设完之后跑一轮,看看超时占比落在什么水平,超过 2% 就说明要么网络有问题,要么超时设得太紧。






