晚上十点,抓取任务跑到第 8000 条突然不动了,日志里刷了一屏 502。打开代理后台一看,IP 明明还在有效期内。这时候大多数人的第一反应是"这家代理不行,换一家",其实八成的问题出在自己的判断逻辑上——目标站点的拒绝、连接没有复用、池子里没及时剔除坏 IP,这三件事被混成了同一件。
下面按我平时排查的顺序走一遍,从状态码开始,到连接复用,再到池内替换。每一段都能直接照着改代码。
一、先看状态码,它能告诉你八成的答案
别急着换 IP,先把最近 100 条失败请求的响应码拉出来,按类型归个堆。同样是失败,方向完全不一样:代理侧的问题换 IP 就能好,目标站点侧的信号换十个 IP 也没用,反而越换越糟。
| 状态码 / 现象 | 大概率是谁的问题 | 建议动作 |
|---|---|---|
| 407 | 代理认证失败 | 核对账号密码、白名单、协议头是否正确 |
| 502 / 503 / 504 | 代理网关异常 | 该 IP 记为失败,进入冷却队列后替换 |
| 连接超时、Connection reset | 链路不通 | 剔除并补位,不必反复重试 |
| 403 | 目标站点侧拦截 | 先降并发、换 UA,观察是否恢复 |
| 429 | 请求太密被限流 | 加延迟、拉长重试间隔,别加 IP |
| 200 但页面是验证码 | 目标站点侧软拦截 | 按内容判定失败,该 IP 冷却一段时间 |
值得单独说一句:200 不等于成功。很多爬虫只判断 status_code,结果把验证码页、空白页、"访问过于频繁"的提示页全都当成了正常数据写进库,等发现的时候已经脏了几万条。写个简单的判定函数,成本很低:
def looks_blocked(text):
signals = ["验证码", "访问过于频繁", "请稍后再试", "Access Denied"]
if len(text) < 500: # 正常详情页很少有这么短的
return True
return any(s in text for s in signals)
还有一个常用办法:分离变量。拿同一个 URL,分别用直连、代理 A、代理 B 各请求一次。直连也失败,那就是目标站点今天状态不对;只有代理 A 失败,锁定 IP;三个都失败,看看是不是账号或者协议选错了(HTTP 代理去访问 HTTPS 站点就是典型坑)。三步下来,问题基本就落在某一格了。
二、连接复用:IP 没坏,也可能被你用"坏"了
有些任务表面上像是 IP 失效,实际上是每次请求都重新建连接。一次完整请求要经历目标站握手、代理隧道建立、传输、断开,四次 RTT 里代理隧道至少占一半。并发一高,代理网关那边看到的连接数直接翻几倍,触发限流、掉包、超时,表现和 IP 失效一模一样。
解决办法不复杂,用 Session 把连接留住就行:
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Connection": "keep-alive",
})
adapter = requests.adapters.HTTPAdapter(
pool_connections=20, # 保留多少个主机连接池
pool_maxsize=50, # 单个池子最多复用多少连接
max_retries=0 # 重试自己写,别交给它静默重试
)
session.mount("http://", adapter)
session.mount("https://", adapter)
resp = session.get(url, proxies={"http": proxy, "https": proxy}, timeout=(5, 15))
几个容易忽略的细节:
超时一定要写。为什么不写 timeout 会挂死?因为默认是无限等待,一条卡住的连接能把整个线程池占满,半天没人释放。(5, 15) 的意思是连接 5 秒、读取 15 秒,按目标站点的实际响应时间再调。
复用要分层,不要一路粘到底。好消息是连接复用省资源,坏消息是同一个 IP 被反复用,目标站点更容易把它标记成异常。合理的做法是:同一批任务、同一个站点复用连接,一旦这批请求连续失败两次,就主动断开重建连接,顺带换一个新 IP 继续。
短效 IP 和长连接要搭配着来。如果你的 IP 是几分钟一换的短效类型,那连接池大小别开太大,池子里的连接还没热身 IP 就过期了,反而增加握手次数。这种场景更适合把池子压小、单连接多跑几条请求。
三、池内替换:把"换 IP"做成一条流水线
最原始的做法是遇到失败就重新提取一个 IP,听起来没问题,实际跑起来会因为一个坏 IP 反复触发提取,把额度耗光。成熟的写法是给每个 IP 建档,用状态驱动,而不是用异常驱动。
核心就四步:探活、标记、冷却、补位。
import time
cooldown_until = {} # ip -> 解冻时间戳
fail_count = {} # ip -> 连续失败次数
def mark_fail(ip):
n = fail_count.get(ip, 0) + 1
fail_count[ip] = n
# 30 秒起步,翻倍,最多冻 5 分钟
cooldown_until[ip] = time.time() + min(300, 30 * (2 ** (n - 1)))
def available(pool):
now = time.time()
return [ip for ip in pool if cooldown_until.get(ip, 0) < now]
几个实践里踩出来的经验:
探活别用目标站点。拿一个轻量的、稳定的公开接口去测连通性就够了,用目标站点探活等于白白多打一次请求,还容易被当成异常流量。
冷却时长按失败次数递增。第一次失败冻 30 秒,第二次 60 秒,第三次 120 秒。这样偶发性的网络抖动不会误杀好 IP,而真正坏掉的 IP 也不会反复回来捣乱。
替换要限量。同一条任务的重试次数设个上限,3 次足够。到上限还没成功,就把它扔进待复查队列,别让一个死链把整个池子拖垮。
按城市和运营商分组。同一个站点在不同地区的响应差异很大,如果你的池子里 IP 是乱的,失败了也不知道为什么。分组之后,某个分组整体失败就能立刻定位到是线路问题而不是 IP 个体问题。神龙HTTP 提供 300+ 城市级精准定位,配合分组调度会顺手很多。
四、底子够厚,排查才有意义
上面三步做完,你会发现一个问题:排查能解决"怎么用",但解决不了"够不够用"。池子里只有几十个 IP,一冷却就空了,任何策略都救不回来。这也是为什么规模上来之后,代理资源本身的稳定性比代码技巧更重要。
拿神龙HTTP 来说,3000 万+正规授权代理 IP 储备、三大运营商正规授权、可用率 99.9%,这些数字背后的意义是:你标记掉一批坏 IP,池子里还有足够的替补,冷却策略不会因为"无 IP 可用"而失效。协议上 HTTP / HTTPS / SOCKS5 都支持,短效动态、长效静态、固定 IP 按任务类型挑就行,比如需要登录态保持的任务用长效静态更省心。
另外两个用得上、但经常被忽略的地方:API 兼容主流爬虫编程语言,文档里有示例代码,接入基本是复制粘贴的量级;个人中心有可视化用量统计和实时监控,能看趋势也能识别异常。我一般会用它来对账——后台显示的成功率和我日志里统计的对不上,那多半是我的判定逻辑漏了内容层面的失败,比如前面说的验证码页被当成 200 收下了。
五、一套可以直接照搬的检查清单
把上面这些串起来,出问题的时候按这个顺序过一遍,通常 20 分钟内能定位:
① 拉最近 100 条失败的响应码,按代理侧 / 站点侧归类,占比超过 60% 的那一类就是主因;
② 用直连 + 两个代理做对照测试,确认是 IP 个体问题还是线路整体问题;
③ 检查是否所有请求都新建了连接,Session 和连接池有没有真的挂上去;
④ 检查 timeout 是否配置,重试是否设了上限;
⑤ 看冷却队列和池子剩余可用量,可用数低于总池 30% 就该考虑扩容了;
⑥ 抽查 20 条 200 响应,确认内容里没有验证码和拦截提示。
大多数"代理 IP 失效"其实是这六步里的某一步没做,而不是代理真的不行。
常见问题
Q:一直返回 407,是不是 IP 不能用了?
不是,407 是认证类错误,和 IP 好坏没关系。先核对账号密码有没有多余空格、白名单里是否加上了当前出口 IP、协议类型是否选对(用 HTTP 代理去请求 HTTPS 地址就会出问题)。三项都对了还报 407,直接联系技术支持,别在代码里反复重试。
Q:状态码 200,但抓回来的内容是空的或者一堆乱码,怎么判?
先看编码,很多站点返回 gzip 但响应头没写清楚,需要手动解压;编码排除后,就按内容判断——长度异常短、命中验证码关键词、页面结构里找不到目标字段,这三种都算失败。建议在入库前加一道内容校验,比事后清洗省力得多。
Q:短效 IP 几分钟就换,连接复用还有意义吗?
有,但用法不同。IP 生命周期短的时候,把连接池压小、让一条连接多跑几条请求更划算,避免连接刚建好 IP 就过期。反过来,如果任务需要保持登录态,就别用短效 IP,选长效静态那种能稳定挂一段时间的类型。
Q:池子开多大比较合适?
先算并发:目标站点能接受的最大请求速率 ÷ 单 IP 的安全速率,就是你在跑的同一时刻需要的 IP 数量,再乘 2 到 3 倍作为替补余量。经验值是冷却队列长期占总量 20% 以上,就说明池子偏小或者 IP 质量有问题,该调策略或者扩资源了。
Q:为什么换了新 IP 还是被拦?
大概率不是 IP 的问题,而是请求特征太明显——固定的 UA、固定的请求间隔、固定的访问顺序。把 UA 换掉、间隔加随机抖动、控制单 IP 的请求频率,通常比继续换 IP 有效。





