跑了一整晚的采集脚本,早上打开日志,成功率从98%掉到31%,错误行清一色403和429。很多人的第一反应是"IP不能用了",于是把提取量翻三倍,结果第二天封得更狠,成本还翻了一倍。
代理IP被封,很少是单一原因。它更像是一串连锁反应:你的请求节奏太机械,被识别出来了;换IP的时候身份信息没跟着换,露出了破绽;再或者,你手里的池子本身就混着一堆别人用剩下的脏IP。这三个层面,任何一个出问题,封禁都会找上门。
下面把七个最常见的诱因拆开讲,每条都配一个能立刻上手的排查动作。
诱因一:请求节奏太"机械"
先看最容易被忽略的一条——频率。人手动点一个网页,两次操作之间隔三五百毫秒甚至好几秒;而脚本一旦跑起来,一秒钟几十个请求,间隔精确到毫秒,这种"整齐"本身就是最明显的特征。
我见过一个典型情况:同一个IP,每秒稳定发出20次请求,间隔误差不到5毫秒。跑了六分钟,接口开始返回429,再跑十分钟,直接返回403。把频率降到每秒2次,并且在每次请求前加一个随机等待(0.3到1.5秒),同一个池子当天连续跑了7个小时,成功率一直在96%以上。
操作建议:单个IP的并发控制在1到3之间,请求间隔加随机抖动,而不是写死 sleep(1)。如果业务确实需要高吞吐,靠加IP数量来解决,而不是靠单个IP硬顶。
诱因二:请求头是"一个模子"刻出来的
换了IP,但请求头一个字节都没变,这是第二个大坑。比如你的 User-Agent 写的是某个老版本浏览器,Accept-Language 永远是 zh-CN,屏幕分辨率、字体列表、编码支持这些一个不填——这种"干净得不像人"的请求,在多IP轮换之后反而更扎眼。
更糟的情况是只换了UA,其他没换。今天挂着一个 Windows 的UA,配的却是 Accept-Language: en-US,而IP落在国内某个城市。这种自相矛盾的组合,只要对方做一次交叉比对,就能把整批请求串起来。
操作建议:把请求头当成一整套"身份",UA、Accept、Accept-Language、Referer 要成组出现,并且和IP所在地区大致对应。别每次随机拼凑,而是准备5到10套固定组合,随机选一套用。宽度和高度这种看似无关的字段,也尽量保持一致。
诱因三:Cookie和会话没有跟着IP走
第三个诱因特别隐蔽:IP换了,但本地那份 Cookie 还留着上一次的 session id。对服务器来说,画面是这样的——一个全新的IP,第一次访问就带着一个已经被标记过的会话标识,中间没有任何浏览路径。这种"凭空出现的熟人",比完全陌生还可疑。
还有一个变体:登录态在多个IP之间来回跳。十分钟内,同一个账号从三个不同城市的IP登录,正常情况下根本不会发生。
操作建议:把IP和会话绑定成一组。换IP就换一份干净的 Cookie,或者干脆不带登录态。如果业务必须保持登录,那这个IP就要尽量长期固定,别用一次就扔。
诱因四:TLS握手指纹太统一
这条属于稍微进阶一点的坑,但排查起来不难。很多在用的语言库,底层的加密握手方式是完全固定的。也就是说,你换了三千个IP,但那三千个IP的握手特征一模一样——在对方眼里,这就是同一个人换了三千张脸。
常见的表现是:请求头伪装得挺好,浏览器指纹也做了处理,可还是被封。这时候去看看HTTP/2的连接参数、加密套件顺序这些底层细节,往往能找到答案。
操作建议:如果你发现请求头已经做得比较细了还是被封,优先怀疑这一层。解决办法是让请求工具更接近真实浏览器,或者使用能提供真实浏览器握手特征的工具链,而不是自己凭空模拟。
诱因五:池子里的IP重复率太高
说完"怎么用",再说"用什么"。有些便宜池子看着数字很大,实际去重之后少得可怜。我做过一次抽样:从一个宣称百万级的池子里随机提1000个IP,去重后只剩640个,重复率超过三成。这意味着你以为是"轮换",其实同一批IP被你和别人反复用,对方早就把它们标记成高风险了。
还有一类是"脏出天际"的IP:之前被大量滥用过,一上手就被拦。这种IP就算你请求写得再漂亮,第一秒就注定失败,跟技术无关。
操作建议:定期抽样去重,看重复率和成功率。如果一个池子的整体成功率长期低于90%,别在原池子里继续调参数了,问题在货源本身。
诱因六:IP类型和业务场景不匹配
机房IP和住宅IP,行为特征差别很大。用机房IP去做需要"像普通用户"的场景,往往一上来就被打了低分;反过来,用住宅IP去跑纯静态、无需登录的公开数据采集,成本白白翻了好几倍。
这不是"哪种更好"的问题,而是"哪种更合适"。同一个项目里,不同页面需要的IP类型可能都不一样。
诱因七:失败重试没有退避,越试越死
最后一条,也是最容易被写错的地方。请求失败了,脚本立刻用同一个IP再试一次、再试一次,两秒钟内打了八次。原本只是被限速的IP,这下直接被拉黑。
正确的做法是:失败就退避,指数增长加随机抖动;连续失败超过两三次,直接放弃这个IP,换一个新的。别跟一个IP死磕。
import random, time, requests
def fetch(url, proxy_pool, ua_list):
for attempt in range(4):
proxy = random.choice(proxy_pool) # 每轮换一个新的IP
headers = {
"User-Agent": random.choice(ua_list),
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept": "text/html,application/xhtml+xml",
}
try:
r = requests.get(url, headers=headers,
proxies={"http": proxy, "https": proxy},
timeout=8)
if r.status_code == 200:
return r.text
if r.status_code in (403, 429):
# 指数退避 + 随机抖动,不要立刻重试
time.sleep(2 ** attempt + random.uniform(0, 1.5))
except requests.RequestException:
time.sleep(1 + random.random())
return None # 这个URL本次放弃,交给下一轮任务
一张表把症状和原因对上号
| 你看到的现象 | 最可能的原因 | 先动手改什么 |
|---|---|---|
| 跑几分钟后开始大量429 | 请求频率过高,没有抖动 | 降并发,加随机间隔 |
| 换IP后第一请求就被403 | 请求头组合自相矛盾 | 整套请求头成组切换 |
| 偶尔封、偶尔不封,规律难找 | IP重复率高,池子里有脏IP | 抽样去重,换干净货源 |
| 伪装做得很细还是被封 | TLS握手特征过于统一 | 换底层请求工具链 |
| 同一个账号登录就异常 | 会话在多地区IP间跳 | IP与会话绑定 |
池子这一层,怎么选才省心
前面六条是"写法",最后这条是"货源"。写法可以慢慢调,货源不行,调多久都是白费。判断一个池子好不好,其实就三个数:可用率、IP纯度、重复率。
以神龙HTTP为例,它公开的几个指标正好对应了上面说的痛点:3000万+正规授权代理IP储备,可用率99.9%,IP纯度99.8%,背后是国内三大运营商正规授权的资源。纯度这个指标很多人不看,但它直接决定你会不会一上手就用到一个已经被打上标签的IP。另外它有300+城市级精准定位,需要某个城市或某个地区的IP时,粒度是够的。
接入层面也不用折腾太久,API 兼容主流的爬虫编程语言,官方有文档和示例代码,短效动态、长效静态、固定IP三条产品线按场景选就行。跑起来之后,个人中心里的可视化用量统计和实时监控挺实用——哪个时段成功率掉下去了、哪批IP异常多,看一眼就能定位,不用靠猜。技术团队是7×24小时在线,遇到成片失败的时候能省不少时间。
常见问题
Q:换IP之后还是被封,是不是代理质量不行?
先别急着换供应商。按顺序排查:单IP并发是否超过3、请求间隔有没有随机抖动、请求头是不是整套一起换的、Cookie有没有跟着换。这四项都确认没问题了,再抽样看池子的重复率和成功率。很多时候封禁是写法导致,换池子只能缓解一两天。
Q:动态短效和长效静态,采集时怎么选?
看业务需不需要"身份连续"。纯公开数据、无需登录、页面之间没有关联的,用短效动态,用完就换,成本低;需要保持登录态、需要连续翻页、或者对方会校验会话一致性的,用长效静态或固定IP,一个IP用久一点反而更安全。
Q:成功率多少算正常?
稳定在95%以上算健康。如果长期在85%到90%之间晃,说明某个环节有磨损,值得花时间排查;低于80%就别再调参数了,先确认池子的可用率和纯度,问题大概率在货源。
Q:加了退避之后速度变慢了,能接受吗?
短期看是慢了,但整体反而更快。因为无效重试少了,被整段封禁的情况也少了。想补回吞吐,靠增加IP数量横向扩展,比死磕单个IP划算得多——一个被封的IP会连着拖累它所在的那一批请求。






