经常在知乎看到有刚学完 Python 基础的小白提问:
“为什么我刚写好的爬虫,才跑了十几页就被封了?是我代码写得太烂,还是运气不好?”
点开大图一看,代码基本上长这样:
+ 效率问题 :如果数据量是 10 万条,0.1 秒的固定间隔意味着你要跑接近 3 个小时。
+ 特征暴露 : 固定间隔比随机间隔更容易被算法识别。 谁家好人能做到连续 3 小时、每隔精准的 100 毫秒点击一次网页?
这等于在脸上写着:“我是爬虫,快来封我”。 更高级的反爬(如 Cloudflare、Akamai)不仅看 UA,还会校验:
爬虫和反爬是一场没有终点的攻防战。别指望有一段代码能一劳永逸地解决所有网站,培养
从防守方视角看问题
的思维,比你收藏 100 个“高效爬虫模板”管用得多。
点开大图一看,代码基本上长这样:
import requests
for url in urls:
response = requests.get(url)
print(response.text)
实话说,不是你运气不好,是你特么在用高射炮炸网站的服务器。
很多人以为爬虫是一门“只要拿到 HTML 就万事大吉”的技术,却忽略了防守方(运维和反爬工程师)手里的武器。作为一名写了多年爬虫的硬核谢绝剧本的程序员,我今天不和你聊什么复杂的逆向、高深混淆,就聊聊最底层的 5个反爬与抗反爬逻辑 。 读懂这 5 条,你的爬虫生存率至少提升 10 倍。一、 请求频率失控 —— 你不是在爬数据,你是在做 访问攻击
1. 现场还原
前 10 个请求顺风顺水,第 11 个请求突然抛出: 429 Too Many Requests 或者更恶心一点:网站返回 200,但里面的数据全是假的(蜜罐数据)。2. 底层逻辑:Rate Limit(速率限制)
稍微懂点运维的都知道,Nginx 里面有一行经典的配置:limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
这意味着:
单个 IP 每秒最多允许 10 个请求。
你的循环在好几百多核的机器上狂飙,一秒砸过去 100 个请求,运维不封你封谁?你这不叫采集,你这叫无照驾驶的分布式拒绝服务攻击。
3. 破局
很多新手会说:“那我加个 time.sleep(0.1) 不就行了?” 太天真了。+ 效率问题 :如果数据量是 10 万条,0.1 秒的固定间隔意味着你要跑接近 3 个小时。
+ 特征暴露 : 固定间隔比随机间隔更容易被算法识别。 谁家好人能做到连续 3 小时、每隔精准的 100 毫秒点击一次网页?
正确姿势:引入随机波动,模拟人类阅读停顿。
import random
import time
import requests
def safe_request(url):
# 随机间隔 0.5 到 2 秒,打破固定的频率特征
time.sleep(random.uniform(0.5, 2.0))
return requests.get(url)
二、 IP 信誉透支 —— 一个 IP 薅到死,等于自投罗网
1. 现象
你控制了频率,爬虫跑了 1 个小时,起初很正常。渐渐地,超时越来越多,最后连首页都打不开了。换个手机热点,又正常了。2. 底层逻辑:IP 信誉评分(IP Reputation)
现在的反爬系统不仅仅看你“当下”这一秒发了多少请求,它们有一个全天候的 风控画像 。- 这个 IP 是否频繁访问敏感 API?
- 这个 IP 的访问链路是否只去详情页,从不去首页?
- 这个 IP 是否触发过滑块验证码?
3. 破局
单一 IP 访问量大是原罪。唯一的解法是: IP 轮换(Proxy Rotation) 。 但是,市面上几块钱一万个的免费代理就别碰了,99% 都是早就死透的或者本身就是钓鱼蜜罐。 我们需要搭建一个代理轮换机制,并且必须配合 有效性检测 :import requests
# 基础代理池轮换示例
proxy_list = [
{
"http": "http://user1:pass1@101.xxx.xxx.xxx:8080"},
{
"http": "http://user2:pass2@102.xxx.xxx.xxx:8080"},
]
def check_and_get(url, proxies):
try:
# 使用 httpbin 测试 IP 是否穿透成功
r = requests.get(url, proxies=proxies, timeout=5)
if r.status_code == 200:
return r.text
except Exception as e:
print(f"当前代理失效,准备切换...")
return None
三、 请求特征暴露 —— 你的 User-Agent 正在大声出卖你
1. 现象
频率也控了,代理也加了,结果刚跑 5 分钟,依然秒封。2. 底层逻辑:HTTP 指纹与特征识别
别总盯着你的 Python 逻辑看,看看你发出去的 HTTP 请求头。如果你用 requests 且不带 headers ,对方服务器的日志里会赫然写着: User-Agent: python-requests/2.31.0这等于在脸上写着:“我是爬虫,快来封我”。 更高级的反爬(如 Cloudflare、Akamai)不仅看 UA,还会校验:
- Accept-Language(浏览器语言偏好)
- Accept-Encoding(压缩算法,现代浏览器基本都支持 br)
- TLS 指纹(JA3/JA4):看你握手阶段的加密套件像不像 Python。
3. 破局
不要只寒酸地改一个 User-Agent,要伪装成一整套 高拟真浏览器请求头集群 ,并进行随机轮换:import random
import requests
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]
def get_random_headers():
return {
"User-Agent": random.choice(USER_AGENTS),
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Referer": "https://www.google.com/", # 模拟搜索引擎跳转来源
}
_注:如果是极其严苛的 TLS 级别指纹校验,建议直接出门左转用 curl_cffi 库代替 requests。_curl_cffirequests
四、 行为模式异常 —— 你的爬虫“不像个正常人”
1. 现象
请求头完美,IP 天天换,频率也拉低了。但只要数据量一上万,风控系统还是能把你揪出来。2. 底层逻辑:漏斗模型与访问路径(Behavior Analysis)
正常人类看知乎是怎样的? 输入 zhihu.com -> 看看推荐页 -> 点击某一个感兴趣的回答 -> 停留 30 秒 -> 偶尔点进答主主页 。 你的爬虫是怎样的? 直接请求 zhihu.com/question/123456/answer/78910 ,然后 不知疲倦地连续请求 5000 个详情页 。 反爬系统在后台计算你的 访问量/会话数(PV/Session)比例 。当一个 Session 只有深度详情页的请求,且漏斗模型完全是直线的,机器学习算法会一秒判定你不是人。3. 破局
模拟真实跳转链(Referer)与分层爬取。 别一上来就舔详情页,先去首页晃一圈,拿到 Session,再顺着列表页点进去。
def realistic_crawl(session, target_url):
# 1. 假装访问首页,建立 Session 信任
session.get("https://example.com/")
time.sleep(random.uniform(1.0, 2.5))
# 2. 访问列表页
list_response = session.get("https://example.com/list")
time.sleep(random.uniform(2.0, 4.0))
# 3. 带着列表页的上下文,去请求详情页
session.headers.update({
"Referer": "https://example.com/list"})
detail_response = session.get(target_url)
return detail_response
五、 缺乏高匿名保护 —— 真实 IP 暴露等于裸奔
1. 核心痛点
很多同学按照上面的教程做了前 4 步,觉得自己已经是架构师了。结果一查后台,发现所有的请求还是通过家里的宽带或者公司机房的固定 IP 发出去的。 结果就是: 你家宽带的 IP 信誉彻底废了。 在现代大厂的盾面前,没有代理保护的采集无异于裸奔。我们需要一个能提供 海量高匿名 IP、超低延迟、并且支持毫秒级轮换 的“防弹衣”。2. 工业级破局:高匿名隧道代理
在商业高频多线程采集场景下,如果我们自己去维护一个几万大小的代理池,光是验证 IP 有效性、处理超时重试的代码就能写死你。 这时候通常会直接接入成熟的工业级方案 —— 比如 爬虫代理 。它利用 隧道代理技术 ,在服务端帮我们自动实现毫秒级 IP 切换,网络延迟能压到 100ms 左右。 针对高频多线程采集,可以直接用下面的动态转发标准模式,在代码里只需要配置一个固定的隧道入口,每次请求出去,爬虫代理会自动帮你变幻出一个全新的真实出口 IP:import threading
import requests
# 亿牛云动态隧道代理配置
PROXY_DYNAMIC = "http://ip.16yun.cn:8080" # 隧道服务器地址
PROXY_USER = "YOUR_YINIUYUN_USER" # 你的用户名
PROXY_PASS = "YOUR_YINIUYUN_PASSWORD" # 你的密码
def fetch_data(url, thread_id):
# 每次请求,爬虫代理服务端会自动分配不同的高匿名出口 IP
proxies = {
"http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_DYNAMIC}",
"https": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_DYNAMIC}",
}
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code == 200:
print(f"[线程 {thread_id}] 成功穿透!当前返回状态: {response.status_code}")
except Exception as e:
print(f"[线程 {thread_id}] 发生异常: {e}")
# 模拟 5 个线程并发请求,测试多线程并发切换 IP 的稳定性
threads = []
target_url = "https://httpbin.org/ip" # 用于验证出口 IP 的测试地址
for i in range(5):
t = threading.Thread(target=fetch_data, args=(target_url, i))
threads.append(t)
t.start()
for t in threads:
t.join()
总结:给新手的打怪通关指南
这五个逻辑从来不是孤立的,它们是层层递进的防御矩阵: $$\text{控制频率} \rightarrow \text{高匿名代理保护} \rightarrow \text{伪装请求特征} \rightarrow \text{模拟人类行为}$$| 维度 | 解决什么问题 | 核心手段 | 优先级 |
|---|---|---|---|
| 请求频率 | 避免触发 Rate Limit 阈值 | random.uniform 随机延时 | ⭐⭐⭐ |
| 代理保护 | 隐藏真实身份,防止宽带/机房被封 | 接入高匿名隧道代理 | ⭐⭐⭐⭐⭐ |
| 请求特征 | 避免被识别出是 Python/脚本 | 模拟完整 Header、处理 TLS 指纹 | ⭐⭐⭐⭐ |
| 行为模式 | 破坏反爬系统的机器学习画像 | 遵循“首页->列表->详情”访问路径 | ⭐⭐⭐ |