一、反爬与反反爬的博弈
做爬虫这几年,最大的感受就是:爬虫和反爬是一场永无止境的博弈。你写了一个能用的爬虫,过两天就不能用了;你升级了反爬策略,对方又找到了新的突破口。
这篇文章把我遇到的常见反爬机制整理出来,不是为了教你怎么攻破别人的网站,而是帮你理解这些机制的原理,在合法合规的前提下完成数据采集任务。
- 本文仅供学习交流,请勿用于非法用途
- 采集数据前请阅读网站的 robots.txt 和服务条款
- 控制请求频率,不要对目标服务器造成过大压力
- 尊重数据版权,不要用于商业盈利
二、User-Agent 检测
这是最简单也是最基础的反爬手段。服务器通过检查 User-Agent 来判断请求是否来自真实浏览器。
2.1 反爬原理
很多爬虫框架默认的 User-Agent 是这样的:
python-requests/2.28.1
Python-urllib/3.9
服务器一看就知道这是程序发的请求,直接返回 403 或者跳转到验证码页面。
2.2 应对方案:使用真实浏览器的 User-Agent
import random
# 准备一个 User-Agent 池
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',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
]
headers = {
'User-Agent': random.choice(user_agents)
}
2.3 进阶方案:随机轮换
class UARotator:
def __init__(self):
self.ua_list = [
# Chrome on Windows
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
# Chrome on Mac
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
# Firefox on Windows
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
# Safari on Mac
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15',
# Edge on Windows
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
]
def get_ua(self):
return random.choice(self.ua_list)
# 使用
ua_rotator = UARotator()
headers = {'User-Agent': ua_rotator.get_ua()}
三、IP 频率限制
这是最常见的反爬手段。服务器会统计每个 IP 的请求频率,如果超过阈值就封禁。
3.1 反爬原理
服务器通常有以下几种限制策略:
- 固定时间窗口:每分钟最多 60 次请求
- 滑动时间窗口:任意 60 秒内最多 60 次请求
- 令牌桶:每秒生成固定数量的令牌,请求消耗令牌
3.2 应对方案:控制请求频率
import time
import random
def fetch_with_delay(url, min_delay=1, max_delay=3):
"""带随机延时的请求"""
response = requests.get(url)
# 随机延时 1-3 秒
time.sleep(random.uniform(min_delay, max_delay))
return response
# 批量采集
for i in range(100):
response = fetch_with_delay(f'https://api.example.com/page/{i}')
print(f"第 {i+1} 页采集完成")
3.3 应对方案:使用代理 IP
import random
# 代理池
proxy_pool = [
{'http': 'http://proxy1.example.com:8080', 'https': 'http://proxy1.example.com:8080'},
{'http': 'http://proxy2.example.com:8080', 'https': 'http://proxy2.example.com:8080'},
{'http': 'http://proxy3.example.com:8080', 'https': 'http://proxy3.example.com:8080'},
]
def get_proxy():
return random.choice(proxy_pool)
# 使用代理发送请求
proxy = get_proxy()
response = requests.get('https://api.example.com', proxies=proxy)
3.4 代理类型对比
| 代理类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 免费代理 | 免费 | 不稳定、速度慢、存活时间短 | 测试、学习 |
| 共享代理 | 价格便宜 | 多人共用,可能被封 | 小规模采集 |
| 独享代理 | 稳定、速度快 | 价格较高 | 商业项目 |
| 住宅代理 | 模拟真实用户、难被封 | 价格最贵 | 高难度反爬 |
四、Cookie 验证
很多网站会在第一次访问时设置 Cookie,后续请求必须带上这个 Cookie 才能正常访问。
4.1 反爬原理
典型的流程是:
- 用户第一次访问,服务器返回设置 Cookie 的响应
- 浏览器自动保存 Cookie,下次请求带上
- 服务器验证 Cookie 的合法性
- 如果 Cookie 缺失或无效,返回错误页面
4.2 应对方案:使用 Session
import requests
session = requests.Session()
# 第一次请求:获取 Cookie
session.get('https://example.com')
# 后续请求自动带上 Cookie
response = session.get('https://example.com/data')
print(response.text)
4.3 应对方案:手动设置 Cookie
# 从浏览器复制 Cookie
cookies = {
'session_id': 'abc123',
'user_id': '456',
'csrf_token': 'xyz789'
}
response = requests.get('https://example.com', cookies=cookies)
五、验证码对抗
验证码是最让人头疼的反爬手段。不过验证码也分很多种,难度各不相同。
5.1 验证码类型
| 类型 | 特点 | 破解难度 |
|---|---|---|
| 图形验证码 | 数字、字母组合 | ⭐⭐ 可用 OCR |
| 滑块验证码 | 拖动滑块到正确位置 | ⭐⭐⭐ 需要图像识别 |
| 点选验证码 | 点击指定文字或物体 | ⭐⭐⭐⭐ 较难 |
| reCAPTCHA | Google 验证 | ⭐⭐⭐⭐⭐ 极难 |
5.2 图形验证码识别
from PIL import Image
import pytesseract
def recognize_captcha(image_path):
"""识别图形验证码"""
# 打开图片
image = Image.open(image_path)
# 转为灰度图
image = image.convert('L')
# 二值化处理
threshold = 128
table = [0 if i < threshold else 1 for i in range(256)]
image = image.point(table, '1')
# OCR 识别
text = pytesseract.image_to_string(image)
return text.strip()
# 使用
result = recognize_captcha('captcha.png')
print(f"识别结果: {result}")
注意:验证码识别的准确率通常不是 100%,建议结合重试机制使用。对于高难度的验证码,建议使用第三方打码平台。
六、JavaScript 动态渲染
现在很多网站用 Vue、React 这些前端框架,页面内容是 JavaScript 动态加载的。直接用 requests 拿到的 HTML 是空的,看不到实际数据。
6.1 反爬原理
典型的单页应用(SPA)流程:
- 浏览器请求 HTML,得到一个几乎为空的页面框架
- 浏览器执行 JavaScript,发起 AJAX 请求获取数据
- JavaScript 将数据渲染到页面上
6.2 应对方案:分析数据接口
最直接的方法是找到 JavaScript 调用的数据接口,直接请求接口:
# 1. 在浏览器开发者工具中找到数据接口
# 2. 复制 curl 命令
# 3. 用 curl 转 Python 工具转换
# 4. 直接请求接口
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'application/json',
}
# 直接请求数据接口
response = requests.get('https://api.example.com/data', headers=headers)
data = response.json()
print(data)
6.3 应对方案:使用 Selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 配置 Chrome
chrome_options = Options()
chrome_options.add_argument('--headless') # 无头模式
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
# 启动浏览器
driver = webdriver.Chrome(options=chrome_options)
# 访问页面
driver.get('https://example.com')
# 等待页面加载完成
import time
time.sleep(3)
# 获取渲染后的页面内容
html = driver.page_source
print(html)
# 关闭浏览器
driver.quit()
七、参数签名验证
这是目前最流行的反爬手段之一。服务器要求请求参数必须带有签名,签名由特定算法生成,防止参数被篡改。
7.1 反爬原理
典型的签名流程:
- 客户端收集所有请求参数
- 按照特定规则拼接成字符串
- 使用密钥和加密算法生成签名
- 将签名附加到请求中
- 服务器用同样的方式验证签名
7.2 应对方案:逆向分析
参考本站的两篇实战文章:
- 51job 数据采集实战 - HmacSHA256 签名逆向
- MytokenCap 数据采集实战 - MD5 签名逆向
7.3 简单示例
import hashlib
import time
def generate_sign(params, secret_key):
"""生成参数签名"""
# 1. 按键排序
sorted_params = sorted(params.items())
# 2. 拼接成字符串
param_str = '&'.join([f'{k}={v}' for k, v in sorted_params])
# 3. 拼接密钥
sign_str = f'{param_str}&key={secret_key}'
# 4. MD5 加密
sign = hashlib.md5(sign_str.encode()).hexdigest()
return sign
# 使用
params = {
'page': '1',
'size': '20',
'timestamp': str(int(time.time()))
}
secret_key = 'your_secret_key'
sign = generate_sign(params, secret_key)
params['sign'] = sign
response = requests.get('https://api.example.com', params=params)
八、行为检测
高级反爬系统会分析用户的行为模式,比如鼠标轨迹、点击位置、页面停留时间等。
8.1 检测维度
- 请求间隔:人类操作有随机性,程序请求通常很规律
- 访问路径:人类会浏览多个页面,程序直奔目标
- 鼠标轨迹:人类移动鼠标有曲线,程序是直线
- 页面停留:人类会花时间阅读,程序秒开秒关
8.2 应对方案:模拟人类行为
import time
import random
def human_like_delay():
"""模拟人类的随机延时"""
# 正态分布,平均 3 秒,标准差 1 秒
delay = random.gauss(3, 1)
delay = max(1, delay) # 最少 1 秒
time.sleep(delay)
def random_scroll():
"""模拟随机滚动"""
scroll_amount = random.randint(100, 500)
# 在 Selenium 中使用
# driver.execute_script(f'window.scrollBy(0, {scroll_amount})')
def random_click():
"""模拟随机点击"""
# 随机选择页面元素点击
pass
# 采集流程
for url in url_list:
# 随机延时
human_like_delay()
# 访问页面
response = requests.get(url)
# 模拟滚动
random_scroll()
# 再延时
human_like_delay()
九、总结
反爬机制越来越复杂,但核心思路是不变的:让服务器认为你是一个正常的人类用户。
| 反爬机制 | 核心思路 | 应对策略 |
|---|---|---|
| User-Agent | 识别爬虫特征 | 使用真实浏览器的 UA,定期更新 |
| IP 限制 | 限制请求频率 | 控制延时、使用代理池 |
| Cookie | 验证会话状态 | 使用 Session、手动设置 Cookie |
| 验证码 | 区分人机 | OCR 识别、打码平台、避免触发 |
| JS 渲染 | 隐藏数据接口 | 分析接口、使用 Selenium |
| 参数签名 | 防止参数篡改 | 逆向分析、还原算法 |
| 行为检测 | 分析操作模式 | 模拟人类行为、随机化操作 |
最后再说一次:技术是中立的,关键在于怎么用。遵守规则、控制频率、尊重版权,才能长久地做下去。