先说个真事:三秒钟被拦下来的那一批请求
前阵子帮一个朋友看他写的采集脚本。跑起来不到三分钟,成功率从 95% 掉到 7%。他第一反应是"这家代理不行",换了一家,还是一样。我让他把日志打出来,问题一眼就看明白了——同一个出口 IP,3 秒内打了 40 次列表页,请求头里 User-Agent 还老老实实写着 python-requests/2.31,Cookie 是空的,Referer 也没带。
这不是代理的问题,是这套请求在目标站点眼里长得太"假"了。
站点风控从来不是只盯着一个 IP 看。它看的是一整串信号拼起来的画像:这个 IP 从哪来、多久发一次请求、请求头长什么样、访问路径是不是一条直线、有没有加载页面里的图片和脚本、这个 IP 最近在别人手里干过什么。任何一环不像真人,就给你记一分;分数攒够了,403 或者一个验证码页面就来了。
| 站点看到的东西 | 像正常用户的样子 | 像脚本的样子 |
|---|---|---|
| 同一 IP 的请求间隔 | 几秒到几十秒,抖动明显 | 毫秒级均匀,像节拍器 |
| 请求头 | UA、语言、编码、Referer 齐备 | 只有 Host 和 UA,其他全空 |
| 访问路径 | 首页 → 列表 → 详情,还会往回跳 | 直接从详情页一路直推 |
| IP 历史 | 干净、独享、地区稳定 | 被几十个脚本轮着用过的机房段 |
四个最常见的触发点,一个比一个隐蔽
第一个:频率。这个最好理解,也最容易踩。目标是列表页,一个正常用户翻页也就一分钟两三次;你一分钟 200 次,等于自己报警。很多站点不是硬性限流,而是阈值加评分——超过自然访问量的大约 10 倍就开始记,超过 30 倍基本当场拦。
第二个:指纹重复。换个 IP,但请求头一个字没改,Cookie 也没有,浏览器指纹参数一模一样。风控系统不傻,它会把"请求特征"和"IP"分开算分。你这批请求换了 100 个 IP 但特征是同一套,那 100 个 IP 在它那儿会被串成一伙。
第三个:IP 本身的信誉。这是很多人忽略的。同一段机房 IP 如果被大量人拿去跑过各种任务,早就进了公开的灰名单。你拿到手的时候它是"新"的,但在站点那边是"老熟人"。这也是为什么免费 IP 或者来路不明的 IP 池,用起来十有八九被秒拦。
第四个:行为不像人。只抓详情页不抓列表页、不请求 CSS 和图片、Referer 永远是空的、访问顺序完全固定、每次都在同一个时间点开始跑。单看每条都不致命,叠在一起就是标准脚本模板。
降低封禁率的配置做法,可以直接抄
先解决"人味儿"的问题。请求头别偷懒,把真实浏览器那一套补齐,然后用 Session 保持 Cookie 连贯——同一批任务里,Cookie 突然断掉本身就是一个异常信号。
import requests
HEADERS = {
"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Referer": "<列表页地址>",
}
session = requests.Session()
session.headers.update(HEADERS)
resp = session.get(url, proxies={"http": proxy, "https": proxy}, timeout=8)
接着控并发。别一上来就 50 个线程,那是给自己挖坑。建议从 5 个线程起步,跑 200 个请求看一眼 403 和 429 的比例,低于 1% 就加一档,超过 2% 立刻降回上一档。这个"爬坡"过程跑一次,你就有自己的一套安全值了。
第三步是换 IP 的时机。别等被封了才换,要按请求数换。一个 IP 连续跑 10 到 30 个请求就换掉,具体数字看你打的页面轻重。换的时候也别整批一起换,让不同线程的 IP 错开,不然出口 IP 在同一秒集体变更,这个模式本身就很显眼。
最后是失败后的处理,这块最容易被写成死循环。碰到 403/429,不要原地重试,先等,再换 IP,等待时间按指数翻倍,加一点随机抖动。
import time, random
def fetch(url, get_proxy, rotate):
for attempt in range(4):
proxy = get_proxy()
try:
r = session.get(url, proxies={"http": proxy, "https": proxy}, timeout=8)
if r.status_code == 200:
return r.text
if r.status_code in (403, 429):
rotate() # 丢掉这个IP,换新的
time.sleep(2 ** attempt + random.uniform(0.3, 1.2))
continue
except requests.RequestException:
rotate()
time.sleep(1 + random.uniform(0, 0.8))
return None
换 IP 的节奏:什么时候换、换多快、换哪个地区
这里有个反直觉的点:换得越勤,不一定越安全。如果你每个请求都换一个新 IP,那这个站点会突然看到一大批来自不同地区、每个只访问一次、行为完全一致的请求——这比同一个 IP 慢慢跑还要可疑。
比较稳的做法是"分组 + 会话保持"。把任务按页面类型分组,同一组里的请求尽量落在同一个 IP 上跑完一小段(比如 10-20 个),再整体换。涉及登录态或者需要连续翻页的场景,前后几个请求必须走同一个出口 IP,不然上一页的 Cookie 到下一页就作废了。
还有地区。很多站点会看 IP 归属地和账号/内容的地域是否对得上。你要抓的是某个城市的本地信息,就用那个城市的 IP,别让一个南方城市的页面被北方 IP 反复访问。神龙HTTP 这边支持 300+ 城市级精准定位,提取时把地区参数带上就行,比全国 IP 乱窜自然得多。
| 参数 | 保守配置 | 激进配置 | 说明 |
|---|---|---|---|
| 并发线程 | 5 | 20 | 先保守跑一轮,看 403 率再决定 |
| 单 IP 请求数 | 10 | 30 | 页面越重、越敏感,数字越小 |
| 请求间隔 | 1-3 秒随机 | 0.2-0.6 秒随机 | 一定要有抖动,别用固定 sleep |
| 重试次数 | 3 次 | 5 次 | 每次重试都换 IP 并延长等待 |
工具这块怎么配:动态 IP 和监控面板要一起用
配置思路定了,剩下的就是选对 IP 的来源。做公开数据采集这类任务,短效动态 IP 通常更合适——一批 IP 用完就丢,不长期占用,被标记的概率天然更低。需要长期保持同一个出口的(比如要维持登录状态、做持续性监测),那就用长效静态 IP 或固定 IP 代理,别拿动态 IP 硬撑,反而会因为频繁切换把自己搞挂。
接入上没什么门槛,神龙HTTP 的 API 兼容主流爬虫编程语言,文档和示例代码都给好了,支持 HTTP/HTTPS/SOCKS5,把提取链接往上面那段 get_proxy() 里一填就能跑。更实用的是个人中心那套可视化用量统计和实时监控——封禁率突然抬头的时候,你能第一时间看出是并发打高了还是某个地区的 IP 出了问题,而不是等到跑完几万条才发现全是空数据。
另外提一句底子的事:IP 池的纯净度直接决定你的起点。3000万+正规授权代理 IP 储备、IP 纯度 99.8%、可用率 99.9%,这些数字的意义在于,你不需要花一半时间去筛掉那些一看就不能用的 IP。真正把精力放在请求策略上,才是降低封禁率的大头。
常见问题
Q:IP 换了新的,为什么还是被封?
大概率不是 IP 的问题。先回头检查三件事:请求头是不是还是老一套、并发是不是没降、访问路径是不是还是直推详情页。风控是给"请求整体"打分,只换 IP 不换特征,等于换了件外套但脸没变。
Q:短效动态 IP 和长效静态 IP 到底怎么选?
看你要不要"连续性"。一次性抓取、批量翻页、公开数据采集这类,用短效动态 IP,按请求数轮换,成本低也不容易被标记。需要长时间保持同一个出口、维持登录状态的,用长效静态或固定 IP 代理。两种都备着,按任务切换,比死磕一种强。
Q:单 IP 跑多少个请求换一次比较合适?
没有标准答案,但有个试出来的办法:从 20 个开始,看 403 和 429 的比例。低于 1% 可以往上加,超过 2% 就减半。轻量页面(纯 HTML 列表)放宽一点,带接口请求、带搜索参数的页面收得紧一点,10 个以内更保险。
Q:被封之后能不能马上换个 IP 继续打同一个页面?
不建议。刚被拦的那几分钟,站点对这个页面的整体请求都很敏感,你换 IP 立刻冲上去,新 IP 也会被一起记上。正确做法是先退避十几秒到几分钟,同时把这批请求的间隔拉长、并发降下来,再慢慢恢复。硬顶只会把更多 IP 拖进灰名单。






