反反爬技术汇总
最后更新:2026-03-31 标准 requests/httpx 的 TLS Client Hello 与浏览器不同,部分服务(如 Cloudflare)会识别。 先识别反爬类型再选方案:IP 频率限制 → 代理池;设备指纹检测 → 浏览器模拟;签名验证 → JS 逆向;验证码 → 打码平台或 AI 识别;行为分析 → 无头浏览器 + 鼠标轨迹模拟。混合使用多种策略事半功倍。 代理 IP 质量决定成功率:数据中心 IP 被大量标记,住宅代理(Residential Proxy)最接近真实用户;移动代理(Mobile Proxy)绕过能力最强但最贵。按目
官方文档:https://playwright.dev/python/docs/intro
适用场景:系统性绕过 Web 反爬机制,实现稳定数据采集
最后更新:2026-03-31
1. 反爬机制概览
| 反爬类型 | 检测维度 | 常见表现 |
|---|---|---|
| IP 封禁 | 请求频率、IP 特征 | 429/403 响应、验证码 |
| UA 检测 | User-Agent 字符串 | 请求直接拒绝 |
| JS 指纹 | 浏览器环境特征 | 返回空数据或重定向 |
| 验证码 | 行为挑战 | 滑块、文字点击、reCAPTCHA |
| 签名校验 | 参数完整性 | 缺少 sign/token 返回错误 |
| 动态 JS | 加密逻辑更新 | 定期换算法/混淆 |
| TLS 指纹 | TLS Client Hello 特征 | 非浏览器指纹被拒绝 |
| 行为分析 | 鼠标轨迹、点击间隔 | 机器行为触发封禁 |
| Cookie 校验 | Cookie 中含加密值 | 无 Cookie 或 Cookie 过期返回异常 |
| 账号风控 | 账号行为异常 | 账号封禁、降速 |
2. IP 反封禁
代理池策略
import random
import httpx
PROXIES = [
"http://user:pass@proxy1:8080",
"http://user:pass@proxy2:8080",
"socks5://proxy3:1080",
]
def get_random_proxy():
return random.choice(PROXIES)
async def fetch(url: str) -> dict:
async with httpx.AsyncClient(proxy=get_random_proxy()) as client:
resp = await client.get(url)
return resp.json()
请求频率控制
import asyncio
import random
async def fetch_with_delay(url: str, min_delay: float = 1.0, max_delay: float = 3.0):
"""随机延迟,模拟人工浏览节奏"""
await asyncio.sleep(random.uniform(min_delay, max_delay))
async with httpx.AsyncClient() as client:
return await client.get(url)
代理质量检测
async def check_proxy(proxy: str, timeout: float = 5.0) -> bool:
try:
async with httpx.AsyncClient(proxy=proxy, timeout=timeout) as client:
resp = await client.get("https://httpbin.org/ip")
return resp.status_code == 200
except Exception:
return False
3. 浏览器指纹对抗
WebDriver 特征清除
# Playwright
await page.add_init_script("""
// 清除 webdriver 标记
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
// 伪造插件列表
Object.defineProperty(navigator, 'plugins', {
get: () => [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
{ name: 'Native Client', filename: 'internal-nacl-plugin' },
],
});
// 伪造语言
Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] });
// 伪造 chrome 对象
window.chrome = { runtime: {}, loadTimes: function(){}, csi: function(){}, app: {} };
// 修复 permissions 查询
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: originalQuery(parameters)
);
""")
Canvas 指纹对抗
# 在 Canvas 绘制时加入随机噪点,使指纹不固定
await page.add_init_script("""
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
const imageData = originalGetImageData.call(this, x, y, w, h);
for (let i = 0; i < 5; i++) {
const idx = Math.floor(Math.random() * imageData.data.length / 4) * 4;
imageData.data[idx] ^= 1;
}
return imageData;
};
""")
WebGL 指纹对抗
await page.add_init_script("""
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) return 'Intel Inc.'; // VENDOR
if (parameter === 37446) return 'Intel Iris OpenGL Engine'; // RENDERER
return getParameter.call(this, parameter);
};
""")
Audio 指纹对抗
await page.add_init_script("""
const originalGetChannelData = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function(channel) {
const array = originalGetChannelData.call(this, channel);
for (let i = 0; i < array.length; i += 100) {
array[i] += Math.random() * 1e-7;
}
return array;
};
""")
4. TLS 指纹对抗
标准 requests/httpx 的 TLS Client Hello 与浏览器不同,部分服务(如 Cloudflare)会识别。
使用 curl_cffi(推荐)
# pip install curl_cffi
from curl_cffi.requests import AsyncSession
async with AsyncSession(impersonate="chrome120") as session:
resp = await session.get("https://example.com/api/data")
print(resp.json())
# 支持的浏览器指纹
# chrome99, chrome100, ..., chrome120
# firefox91, firefox95, ...
# safari15_3, safari15_5, ...
# edge99, edge101
使用 tls-client(Python 绑定)
# pip install tls-client
import tls_client
session = tls_client.Session(
client_identifier="chrome_120",
random_tls_extension_order=True
)
resp = session.get("https://example.com/api")
print(resp.json())
5. 验证码处理
滑块验证码(基于轨迹模拟)
import asyncio
import random
import math
async def slide_captcha(page, slider_selector: str, track_distance: int):
"""模拟人工滑动轨迹"""
slider = await page.query_selector(slider_selector)
box = await slider.bounding_box()
start_x = box["x"] + box["width"] / 2
start_y = box["y"] + box["height"] / 2
# 生成带缓动的轨迹点
tracks = generate_track(track_distance)
await page.mouse.move(start_x, start_y)
await page.mouse.down()
await asyncio.sleep(random.uniform(0.1, 0.3))
current_x = start_x
for move_x, delay in tracks:
current_x += move_x
jitter_y = start_y + random.uniform(-2, 2)
await page.mouse.move(current_x, jitter_y)
await asyncio.sleep(delay)
await page.mouse.up()
def generate_track(distance: int) -> list[tuple[float, float]]:
"""生成类人工的加速-减速轨迹"""
tracks = []
current = 0
# 加速阶段(前 60%)
mid = distance * 0.6
while current < mid:
move = random.uniform(3, 8)
current += move
tracks.append((move, random.uniform(0.01, 0.02)))
# 减速阶段(后 40%)
while current < distance:
move = random.uniform(1, 3)
current += move
if current > distance:
move -= current - distance
current = distance
tracks.append((move, random.uniform(0.02, 0.05)))
return tracks
图片验证码(OCR)
# pip install ddddocr
import ddddocr
ocr = ddddocr.DdddOcr()
async def solve_captcha(page, captcha_selector: str) -> str:
# 截取验证码图片
captcha_element = page.locator(captcha_selector)
img_bytes = await captcha_element.screenshot()
# OCR 识别
result = ocr.classification(img_bytes)
return result
第三方打码平台
import httpx
async def solve_with_2captcha(image_base64: str, api_key: str) -> str:
"""使用 2captcha 服务"""
async with httpx.AsyncClient() as client:
# 提交任务
resp = await client.post("https://2captcha.com/in.php", data={
"key": api_key,
"method": "base64",
"body": image_base64,
})
task_id = resp.text.split("|")[1]
# 轮询结果
for _ in range(20):
await asyncio.sleep(5)
resp = await client.get(f"https://2captcha.com/res.php?key={api_key}&action=get&id={task_id}")
if resp.text.startswith("OK"):
return resp.text.split("|")[1]
raise Exception("验证码识别超时")
6. Cookie 反爬对抗
Cookie 自动维护
# 使用 httpx 的 CookieJar 自动管理 Cookie
async with httpx.AsyncClient(follow_redirects=True) as client:
# 首次访问触发 Cookie 生成(如 __cfduid、acw_tc 等)
await client.get("https://example.com")
# 后续请求自动携带 Cookie
resp = await client.get("https://example.com/api/data")
浏览器 Cookie 导出给 requests
from DrissionPage import ChromiumPage
page = ChromiumPage()
page.get("https://example.com/login")
# ... 完成登录 ...
# 导出 cookies 给 requests/httpx 使用
cookies = {c["name"]: c["value"] for c in page.cookies()}
async with httpx.AsyncClient(cookies=cookies) as client:
resp = await client.get("https://example.com/api/protected")
7. 反调试绕过
无限 debugger 绕过
// Hook Function 构造器,阻止 setInterval/setTimeout 中的 debugger
(function() {
var _Function = Function;
Function = function(...args) {
var fn = _Function(...args);
var body = fn.toString();
if (body.includes('debugger')) {
return function() {};
}
return fn;
};
Function.prototype = _Function.prototype;
})();
// 或直接覆盖 setInterval
var _setInterval = setInterval;
setInterval = function(fn, delay) {
if (fn.toString().includes('debugger')) {
return;
}
return _setInterval(fn, delay);
};
时间检测绕过
// 部分反调试通过计时判断是否有人工调试(调试时计时器走慢)
// 覆盖 Date.now 和 performance.now
var startTime = Date.now();
Date.now = function() { return startTime; };
performance.now = function() { return 0; };
8. 行为模拟
随机化操作时序
async def human_like_click(page, selector: str):
"""人工化点击:移动到元素附近 → 等待 → 点击"""
element = page.locator(selector)
box = await element.bounding_box()
# 随机点击元素内部位置(非正中心)
x = box["x"] + box["width"] * random.uniform(0.3, 0.7)
y = box["y"] + box["height"] * random.uniform(0.3, 0.7)
# 先移动到元素附近
await page.mouse.move(x + random.uniform(-10, 10), y + random.uniform(-10, 10))
await asyncio.sleep(random.uniform(0.1, 0.3))
await page.mouse.move(x, y)
await asyncio.sleep(random.uniform(0.05, 0.15))
await page.mouse.click(x, y)
随机滚动模拟浏览
async def simulate_reading(page, duration: float = 5.0):
"""模拟用户阅读页面"""
elapsed = 0.0
while elapsed < duration:
scroll_amount = random.randint(100, 400)
await page.evaluate(f"window.scrollBy(0, {scroll_amount})")
delay = random.uniform(0.5, 2.0)
await asyncio.sleep(delay)
elapsed += delay
9. 常用工具汇总
| 工具 | 用途 | 安装 |
|---|---|---|
curl_cffi |
TLS 指纹伪造 | pip install curl_cffi |
playwright-stealth |
Playwright 反检测 | pip install playwright-stealth |
ddddocr |
验证码 OCR | pip install ddddocr |
mitmproxy |
流量分析与修改 | pip install mitmproxy |
Frida |
动态插桩 | pip install frida-tools |
2captcha |
打码平台 | API 服务 |
anticaptcha |
打码平台 | API 服务 |
| 隧道代理 | IP 自动轮换 | 商业服务 |
10. 最佳实践
分层对抗策略
简单目标 → httpx / requests + 代理池
↓(有 JS 渲染)
DrissionPage / Playwright(浏览器模式)
↓(有强指纹检测)
curl_cffi + playwright-stealth
↓(有验证码)
ddddocr / 打码平台
↓(有 native 加密)
Frida 逆向 → unidbg 批量调用
↓(极难)
手动模拟 / RPC 远程调用
规避风险的基本原则
- 使用真实 User-Agent,定期更新
- 设置随机请求间隔(1~5 秒)
- 每次请求随机化 Headers 顺序
- 使用住宅代理(Residential Proxy)代替数据中心代理
- 模拟正常浏览行为(访问首页 → 列表页 → 详情页)
- Cookie 来自真实浏览器,而非手动构造
最佳实践
先识别反爬类型再选方案:IP 频率限制 → 代理池;设备指纹检测 → 浏览器模拟;签名验证 → JS 逆向;验证码 → 打码平台或 AI 识别;行为分析 → 无头浏览器 + 鼠标轨迹模拟。混合使用多种策略事半功倍。
代理 IP 质量决定成功率:数据中心 IP 被大量标记,住宅代理(Residential Proxy)最接近真实用户;移动代理(Mobile Proxy)绕过能力最强但最贵。按目标站点防御等级选择,不要盲目用最贵的。
Cookie 池维护比签名逆向更稳定:部分站点签名算法复杂(多版本混淆+WASM),维护成本高;若 Cookie 有效期长(几天到几周),维护一个大 Cookie 池(Playwright 自动登录+定期刷新)比破解签名更划算。
Playwright 的 stealth 模式用于绕过 headless 检测:playwright-extra + puppeteer-extra-plugin-stealth 修补了 navigator.webdriver、chrome.runtime 等常见 headless 检测点,比手动 patch 更完整。
建立完整指纹档案:User-Agent、Accept-Language、Canvas 指纹、WebGL 指纹、屏幕分辨率、时区要相互一致,各指纹组合构成设备档案,前后不一致会触发风控。
常见陷阱
陷阱:换了 IP 后仍然被封
现象: 切换代理 IP 后请求仍然报 403,且频率不高。
原因: 被封的不是 IP,而是设备指纹(Canvas/WebGL 指纹、TLS 指纹、浏览器特征);只换 IP 不换指纹,风控系统仍能识别同一设备。
解决: 同时更换 IP 和 User-Agent,清除/随机化 Cookie,使用不同浏览器配置(Profile)隔离指纹;严格场景用真实不同设备或浏览器实例。
陷阱:模拟点击后验证码仍弹出
现象: Playwright 模拟了鼠标移动和点击,但目标站仍然触发滑块验证。
原因: 行为分析不只看鼠标事件,还看事件时间间隔、触发顺序、Scroll 行为等综合指标;程序生成的事件在统计上和真人有差异。
解决: 在鼠标轨迹上叠加随机抖动(Bézier 曲线路径 + 随机速度变化);在提交前加入随机等待时间;考虑对接打码平台专门处理验证码。
陷阱:设置了随机延迟仍触发频率限制
现象: 每次请求间隔 1~3 秒,但几百次请求后 IP 仍然被限速。
原因: 频率限制基于滑动时间窗口(如每分钟不超过 30 次),偶尔连续快速请求(抖动低延时端)可能超出限制;或站点用分布式 Redis 统计跨 IP 的账号频率。
解决: 用令牌桶算法控制实际速率;对同一账号的请求做更严格的间隔控制,不只控制 IP 维度的频率。