做数据采集这行干了三四年,最头疼的事不是写解析逻辑,而是跑着跑着IP就废了。明明代码没改,昨天还好好的,今天一跑全是403或者验证码页面。后来我慢慢摸索出来,被封这件事,八成不是你的代码写得烂,而是你的请求模式太"整齐"了。 今天就把我在代理IP轮换和频率控制上踩过的坑、总结出来的经验,掰开了揉碎了讲一遍。
先搞清楚,对面到底是怎么"认"出你的
很多人一上来就想着"我多买几个IP不就行了",方向没错但太粗糙。你得先理解对方封你的逻辑。一个网站判断你是不是"机器",通常看这么几层:
第一层是IP本身。你用的IP是不是机房段?是不是短时间内被大量请求过?是不是同一个IP在几秒内访问了它家几十个不同页面?这些特征一出来,风控系统直接给你标记。
第二层是请求行为模式。正常人刷网页,看一篇文章要十几秒,中间还会划拉两下。你的爬虫呢?0.3秒一个请求,URL排列得跟教科书似的,User-Agent永远同一个,Cookie干干净净。这种"完美"本身就是最大的破绽。
第三层是时间维度的统计。就算你单个请求间隔拉到了2秒,但一天之内同一个IP(或者同一批IP)持续不断地打,总量一上来,照样触发阈值。所以光看单次间隔是不够的,得看一个时间窗口内的总请求量。
理解了这三层,你就知道光换IP是远远不够的,得把IP轮换策略、请求节奏、行为拟真这三件事配合起来做。
请求轮换:不是"随机"两个字就能打发的
我见过太多人写轮换逻辑,就一行 random.choice(ip_list),然后觉得万事大吉。问题在于,真正的轮换要考虑好几件事:
轮换粒度要匹配IP的"寿命"。 如果你用的是短效动态IP,比如有效期只有5分钟,那你在这5分钟里用这个IP打多少请求、打哪些页面,都得控制在合理范围内。别一个IP 5分钟里干完了别人一天的量。反过来,如果你用的是固定IP或者长效静态IP,存活时间更长,那你的请求预算可以稍微放宽,但也不能无脑打。
同一个目标域名,别用同一个IP反复打。 哪怕你每次请求之间隔了5秒,但连续20次都从同一个IP出去访问同一个站,风控那边一统计,"这个IP怎么一直在访问我",直接拉黑。正确做法是:对同一个目标,每请求N次(比如5到8次)就换一个IP,让流量看起来是"分散"的。
IP和"身份"要绑定一段时间。 你用一个IP访问了某网站,带了Cookie,下次再用另一个IP带着同样的Cookie去访问,这很可疑。所以换IP的时候,最好把对应的会话状态(Cookie、Token)也一起重置,模拟"换了一台电脑"的效果。
这里给一个我实际在用的轮换策略参考:
import time
import random
import requests
class ProxyRotator:
def __init__(self, proxy_pool, target_domain, max_per_ip=6, cooldown=30):
"""
proxy_pool: 从代理服务商API拉取到的IP列表
target_domain: 当前要采集的目标域名
max_per_ip: 每个IP对同一域名最多请求次数
cooldown: 一个IP用完后冷却多久再进池子
"""
self.pool = list(proxy_pool)
self.domain = target_domain
self.max_per_ip = max_per_ip
self.cooldown = cooldown
self.ip_usage = {} ip -> 已用次数
self.ip_cooldown_until = {} ip -> 冷却截止时间
def get_next_proxy(self):
now = time.time()
candidates = []
for ip in self.pool:
used = self.ip_usage.get(ip, 0)
cd_until = self.ip_cooldown_until.get(ip, 0)
if used < self.max_per_ip and now >= cd_until:
candidates.append(ip)
if not candidates:
全部在冷却,等最短冷却结束的那个
min_cd = min(self.ip_cooldown_until.values())
wait = max(0, min_cd - now) + random.uniform(1, 3)
time.sleep(wait)
return self.get_next_proxy()
ip = random.choice(candidates)
self.ip_usage[ip] = self.ip_usage.get(ip, 0) + 1
if self.ip_usage[ip] >= self.max_per_ip:
self.ip_cooldown_until[ip] = now + self.cooldown + random.uniform(0, 15)
return ip
def reset_session(self):
"""换IP时同步重置会话"""
self.session = requests.Session()
self.session.headers = {
"User-Agent": random.choice(self._ua_list()),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
}
def _ua_list(self):
return [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
]
def fetch(self, url):
proxy = self.get_next_proxy()
self.reset_session()
delay = random.uniform(2.5, 6.0) 每次请求前随机等待
time.sleep(delay)
proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
resp = self.session.get(url, proxies=proxies, timeout=15)
return resp
注意几个细节:每次请求前的等待时间是2.5到6秒的随机区间,不是固定值;每个IP对同一域名最多用6次就强制冷却;冷却时间也不是固定的30秒,而是30秒再加0到15秒的随机偏移。这些"不整齐"的地方,恰恰是让请求模式看起来像真人操作的关键。
频率控制:比换IP更要命的事
说句可能得罪人的话:80%的封IP事故,不是IP质量的问题,是你请求太猛了。 再好的代理IP,你一秒打十个请求,对面风控系统看你的流量曲线跟心电图拉了直线似的,不封你封谁?
频率控制我一般分三层来做:
第一层:单次请求间隔。 两次请求之间至少隔2到5秒,而且这个间隔本身要是随机的。别写死3秒,写 random.uniform(2, 5)。更进阶一点,可以模拟"人看内容"的节奏——请求完一个页面,根据页面大小或者内容量,动态调整下一次请求的等待时间。页面内容多,就多看几秒。
第二层:单IP的总请求预算。 一个IP在一个时间窗口内(比如10分钟),总共能发多少请求,你得设上限。短效动态IP寿命短,这个预算可以设高一点;固定IP寿命长,反而要更保守,因为对面有足够的时间去统计你这个IP的历史行为。我一般给固定IP设的预算是每10分钟不超过15到20个请求,看着少,但稳。
第三层:全局并发控制。 如果你同时跑多个任务、多个线程,一定要有一个全局的"信号量"或者令牌桶,控制同一时刻最多有几个请求在飞。我见过有人开了20个线程,每个线程各自控制间隔,结果20个线程同时发出去,瞬时并发拉满,IP秒封。全局并发我一般控制在3到5个,宁慢勿快。
这三层的关系可以这么理解:
| 控制层级 | 控制什么 | 推荐参数 | 为什么重要 |
|---|---|---|---|
| 单次间隔 | 两次请求之间的等待 | 2~6秒随机 | 模拟人类阅读节奏,避免"机器节拍" |
| 单IP预算 | 一个IP在时间窗口内的总请求数 | 短效IP:10min/20次;固定IP:10min/15次 | 防止单IP流量异常,触发IP级风控 |
| 全局并发 | 同一时刻在飞的请求总数 | 3~5个 | 防止多线程叠加导致瞬时流量尖峰 |
还有一个很多人忽略的点:别在整点或者固定时间点开始跑任务。 你每天上午10:00:00准时开始采集,连续跑一周,对面一看你的流量曲线,每天10点准时冒出一个尖峰,这太明显了。把启动时间随机化,比如10:00到10:20之间随机一个点开始,跑的过程中也穿插一些"休息",比如每采集50个页面就停30秒到1分钟。
代理IP怎么选:别只看"便宜"
讲完策略,说点实际的——IP本身的质量直接决定了你策略的上限。你策略写得再花哨,IP本身是机房段、是被人用烂过的脏IP,那对面一查IP信誉分,你策略白搭。
选代理IP我一般看这么几个硬指标:
IP来源和纯度。 这是最核心的。IP是运营商正规分配的住宅段还是机房段?有没有被其他用户大量使用过?纯度不够的IP,你拿到手可能已经被标记了。我后来一直用的是神龙HTTP,他们家是国内三大运营商正规授权的,IP纯度标称99.8%,实际用下来确实干净,很少出现"刚拿到的IP就被对面拒了"的情况。他们资源池有3000万+,覆盖300多个城市节点,你采集的时候如果需要指定某个省份或者城市的IP,直接通过API筛选就行,不用自己再折腾。
IP的时效和更新频率。 短效动态IP的话,3分钟、5分钟、10分钟、15分钟、30分钟这些档位,你根据自己采集的节奏来选。如果你单次任务跑的时间不长,5分钟或10分钟的短效IP就够了,用完即弃,对面根本来不及统计你的行为。神龙HTTP的短效动态IP池是每日更新去重的,3000万+资源池里每天都是新面孔,基本不用担心拿到一个"老面孔"IP。
延迟和稳定性。 这个特别容易被忽略。你IP再干净,延迟高到每次请求要等8秒、10秒,你的采集效率直接腰斩,而且高延迟下超时重试的概率变大,重试本身又会增加请求量,恶性循环。神龙HTTP这边延迟控制得不错,高并发提取也不卡顿,HTTP/HTTPS/SOCKS5协议都支持,你根据目标站的要求选就行。
API好不好用。 说句实在话,代理IP服务最终是要嵌到你的代码里的。如果API文档写得稀里糊涂,取个IP要调三个接口,那开发体验极差。神龙HTTP的API我比较喜欢的一点是,兼容主流爬虫语言,取IP、管理套餐、查看用量,几个接口就搞定了。而且他们个人中心有可视化的数据统计,你哪个IP用了多少次、成功率多少、什么时候开始异常,一目了然。之前我有个IP段突然成功率掉到70%,在后台趋势图里一眼就看到了,赶紧把那批IP从池子里踢掉,省得后面一直拿坏IP去撞。
如果你IP需求量不大,就一两个固定IP长期用,那可以看看他们的固定IP池,基于高性能云主机,ISP正式分配,存活时间长,稳定性很高,按个数包时计费,适合那种"我就需要一两个靠谱IP长期挂着"的场景。如果量大、需要频繁换,短效动态IP池更合适,包量包时都支持,灵活度比较高。
实战中几个容易踩的坑
坑一:只换IP不换"指纹"。 你IP换了,但TLS指纹、HTTP/2指纹、浏览器指纹全一样。现在不少站的风控已经不看IP了,直接看你的TLS握手特征。如果你的请求全部是同一个Python requests库发出来的,TLS指纹一模一样,换一百个IP也没用。解决办法:用不同的请求库或者给请求加上随机化的TLS参数,至少别所有请求都长一个样。
坑二:错误处理太粗暴。 遇到403或者验证码,你的代码直接换个IP重试,再403,再换,再403……十秒钟换了二十个IP,对面一看,"这帮IP怎么全在撞我的墙",连带把整个IP段都标记了。正确做法:遇到403,先停30秒到1分钟,检查是不是请求头有问题、是不是触发了频率限制,确认没问题再换IP重试,而且同一个错误连续出现3次以上,就暂停这个任务,别硬撞。
坑三:所有任务共用一个IP池。 你同时跑三个采集任务,A任务采新闻,B任务采商品,C任务采评论,三个任务从同一个池子里取IP。结果A任务把IP用完了,B任务拿到的IP已经被A任务"污染"了(对面已经知道这个IP在高频访问)。不同任务最好用不同的IP段,或者至少在轮换逻辑里做隔离。
坑四:忽略"静默期"。 一个IP被标记了,不是立刻就能恢复的。有些站的风控标记会持续几小时甚至一两天。你被403了,换个IP继续跑,过十分钟又拿回那个"被标记"的IP(因为短效IP池会循环),又403。所以你的IP管理里要有个"黑名单"机制,被标记的IP至少冷却几个小时再放回池子。
常见问题
Q:我用的代理IP明明都是新的,为什么还是频繁被封?
大概率不是IP的问题,是请求模式的问题。你检查一下:是不是所有请求的间隔都是固定值?是不是User-Agent从来没换过?是不是同一个IP在短时间内访问了太多不同页面?IP再新,行为模式太"整齐",风控系统一样能识别。把间隔随机化、UA轮换、单IP请求量控制住,你会发现封的频率明显下降。另外也确认一下你的IP是不是真的"干净",有些低价IP池里的IP虽然标着"新",但实际是回收的旧IP,信誉分已经很低了。这种情况下换一家IP服务商比调代码更有效,像神龙HTTP这种运营商正规授权、每日去重更新的池子,IP的"出身"就比较干净。
Q:短效动态IP和固定IP,我该怎么选?
看你的场景。如果你的采集任务跑的时间不长(比如一次跑个十几分钟到半小时),或者你需要频繁更换IP来降低单IP的暴露度,短效动态IP更合适,用完就扔,对面来不及建立你的行为画像。如果你的任务需要长期稳定运行(比如持续几天的监控采集),或者目标站对IP变化很敏感(频繁换IP反而触发风控),那固定IP或者长效静态IP更稳。神龙HTTP这两类都有,短效的有3到30分钟多个档位可选,固定IP按个数包时计费,你根据实际用量来搭配就行,不用全买一种。
Q:请求频率到底设多少才安全?有没有一个"标准值"?
没有标准值,这取决于目标站的风控严格程度。但有一个通用的原则:你的请求速率不应该超过一个正常用户手动操作时能达到的速率。 你拿手机或者浏览器手动刷那个网站,一分钟能看几个页面?大概5到8个。你的爬虫如果一分钟发30个请求,那就是正常用户的4到6倍,太显眼了。我一般把单IP的请求速率控制在每分钟不超过8到10个请求,多线程的话全局并发压在3到5个,基本不会触发频率类风控。不同站差异很大,有些小站风控很松,你快一点也没事;有些大站风控很严,你慢一点也可能被盯上。最稳妥的做法是:先慢着跑,观察一段时间,确认稳定后再逐步提速,找到那个"不触发风控的最大速率"。
Q:我的代理IP用了一段时间后成功率明显下降,怎么排查?
先别急着换IP,按这个顺序排查:第一,看是不是你的请求模式变了(比如最近加了新任务、改了并发数),导致整体请求量上去了;第二,看是不是目标站升级了风控策略,之前能过的现在不行了,这种情况下所有IP都会受影响,不是IP本身的问题;第三,看IP池里是不是混入了一批质量差的IP,有些IP池是动态更新的,新补进来的IP质量可能参差不齐。神龙HTTP后台有可视化的使用统计和趋势图,你打开一看,哪个时间段的成功率开始掉的、掉的是哪批IP,基本就能定位是IP的问题还是策略的问题。如果是IP的问题,联系他们技术支持,7×24小时在线,让他们帮你排查或者替换那批有问题的IP段。
最后说一句掏心窝的话:代理IP这个东西,策略比资源重要,节奏比速度重要。 你花大价钱买了一堆优质IP,但请求模式跟机关枪似的,该封还是封。反过来,IP质量中等偏上,但你的轮换策略、频率控制、行为拟真都做到位了,跑个把月稳稳当当的,大有人在。把精力多花在"怎么让请求看起来像人"上,比单纯堆IP数量有效得多。


