干爬虫的都有过这种经历:IP换了,User-Agent改了,TLS指纹也折腾了,结果跑一会儿还是被封。
问题出在哪?很可能不是身份暴露了,而是你的请求节奏太像机器人了。
真人上网什么样?不会每隔3秒准时点一次链接,不会按顺序一页一页翻,更不会连续两小时保持同一个频率。但很多爬虫就是这么跑的——固定延时、固定顺序、固定并发。这种机械节拍,反爬系统一眼就能认出来。

反爬系统怎么看出你不是人?
现在稍微像样点的反爬系统,除了查IP、查指纹、查请求头,还会盯着你的行为时序。
说白了,它会把你的每一次请求都记上时间戳,然后看这些时间戳的分布。如果你每次请求间隔都稳定在2.8秒到3.2秒之间,标准差小得可怜,那基本就露馅了。真人不可能这么准。
真人操作的节奏是什么样的?不均匀、有停顿、有快有慢、偶尔中断。
看一篇长文章可能花30秒,扫一眼标题可能就2秒。有时候连着快速点好几个链接,有时候半天不动。甚至中途去接杯水,回来继续。这些“不规律”才是真实用户的标志。
有安全团队统计过,真实用户的页面停留时间是一条长尾分布——大部分页面停留短,但偶尔会有特别长的。而固定延时的爬虫,停留时间分布几乎就是一条竖线。
随机化该随机什么?
不是加个随机数就完事了。要随机,至少得覆盖三个层面。
第一,时间间隔。
别用固定延时,也别用均匀随机。均匀随机(比如 random.uniform(1, 3))虽然比固定延时好,但特征还是太“平”——每个区间出现概率一样,长期看还是不像人。
更自然的做法是用正态分布或者对数正态分布。大部分操作间隔集中在某个范围,偶尔冒出一个特别短或特别长的。比如 random.gauss(2, 0.5),均值2秒,标准差0.5秒,偶尔出现0.5秒或4秒,这才像人在操作。
第二,请求顺序。
很多爬虫是按顺序一页页爬的——第1页、第2页、第3页……这种线性顺序本身就是信号。
真人浏览路径往往是跳的。从首页点进第3页,返回,再点第7页,再返回首页,再去第2页。这种来回跳转比顺序翻页自然得多。
实现也简单:别按固定顺序遍历URL列表,每次从待抓取队列里随机抽一个。偶尔插几个“返回上一页”或者“回首页”的请求,模拟真实浏览路径。
第三,并发数。
固定并发数(比如始终保持10个线程)也是明显的机器特征。真人同一时间只能看一个页面,并发永远是1。
当然爬虫不可能真用1,那效率太低了。但可以动态调整:高峰时并发高一点,低谷时降下来,甚至偶尔完全暂停几秒到几十秒。这种“有呼吸感”的节奏,比恒定并发自然得多。
代码大概长这样
下面是个简单的Python示例,把上面三个层面揉进一个调度逻辑里:
import random
import time
from queue import Queue
class HumanLikeScheduler:
def __init__(self, url_queue):
self.queue = url_queue
self.last_request_time = time.time()
def get_next_url(self):
"""随机抽下一个URL,而不是按顺序取"""
if self.queue.empty():
return None
# 随机跳过几个,模拟用户跳跃行为
skip = random.randint(0, min(3, self.queue.qsize() - 1))
for _ in range(skip):
self.queue.put(self.queue.get())
return self.queue.get()
def wait_before_request(self):
"""基于正态分布的随机延时"""
# 基础延时:均值2秒,标准差0.8秒
delay = random.gauss(2, 0.8)
# 确保延时不为负,且不超过10秒
delay = max(0.3, min(delay, 10))
# 偶尔插入“长停顿”(模拟用户离开)
if random.random() < 0.05: # 5%概率
delay += random.uniform(5, 30)
time.sleep(delay)
self.last_request_time = time.time()
def should_pause(self):
"""判断是否需要整体暂停一会儿"""
if random.random() < 0.02:
pause_time = random.uniform(30, 120)
print(f"模拟用户离开,暂停 {pause_time:.0f} 秒")
time.sleep(pause_time)
return True
return False
# 使用示例
urls = Queue()
for i in range(100):
urls.put(f"https://example.com/page/{i}")
scheduler = HumanLikeScheduler(urls)
while not urls.empty():
url = scheduler.get_next_url()
if url is None:
break
scheduler.wait_before_request()
scheduler.should_pause()
print(f"请求: {url}")
# response = requests.get(url, proxies=...)核心思路就一个:让每个请求的时间间隔、顺序、节奏都带点不确定性,别按预设的固定规则跑。
别光顾着随机,频率得先降下来
还有一点别搞错了:随机化是为了“更像人”,不是为了“更快”。
如果你的基础请求频率本身就超出人类极限——比如每秒发10个请求——那再加随机化也没用。反爬系统一眼就知道这不是人。
正确的顺序是:先把整体频率降到人类合理范围(比如每分钟几个到几十个请求),然后再在这个基础上做随机化。 随机化是锦上添花,不是雪中送炭。
代理IP在这事里起什么作用?
请求模式随机化和代理IP,是互相配合的关系。
同一个IP做随机化请求,效果有限——反爬系统最终还是会把那个IP标成“行为异常”。但如果你把随机化的请求分散到大量不同IP上,每个IP只承担少量请求,那每个IP的行为都会显得很正常。
随机化负责“行为像人”,代理IP负责“身份分散”。两个一起用,才能把被风控的概率压下来。
总结
模拟人类操作节奏,核心就三点:时间间隔用非均匀分布、请求顺序要跳、并发数要动态变。
这些技巧不需要什么机器学习模型,几行代码就能有明显效果。关键是理解一个底层逻辑:反爬系统找的不是“错误”,而是“规律”。 你的请求越没规律,就越难被认成机器。
当然,随机化的同时别忘了控制整体频率。一个每分钟只发5个请求但节奏自然的爬虫,比一个每分钟发500个请求但“随机化”了的爬虫,活得久得多。
