隐藏 Selenium 和 Puppeteer 的完整指南
现代反机器人保护系统通过数十个特征轻松识别自动化浏览器:从 JavaScript 变量到 WebDriver 的行为特征。网站使用 Cloudflare、DataDome、PerimeterX 和自有解决方案,阻止高达 90% 的来自标准配置的 Selenium 和 Puppeteer 的请求。
在本指南中,我们将探讨所有自动化伪装的方法:从基本设置到高级绕过检测技术。您将获得适用于 Python 和 Node.js 的现成解决方案和代码示例,这些方法可对抗大多数保护系统。
网站如何检测自动化
反机器人保护系统同时分析浏览器的多个参数。即使您隐藏了一个特征,其他特征也会暴露出自动化。了解所有检测方法是有效伪装的关键。
WebDriver 指示器
最简单的检测方法是检查仅在自动化浏览器中存在的 JavaScript 变量:
// 这些变量暴露了 Selenium/Puppeteer
navigator.webdriver === true
window.navigator.webdriver === true
document.$cdc_ // ChromeDriver 特有变量
window.document.documentElement.getAttribute("webdriver")
navigator.plugins.length === 0 // 自动化浏览器没有插件
navigator.languages === "" // 语言列表为空
Cloudflare 和类似系统首先检查这些属性。如果其中任何一个返回正值,请求将被阻止。
浏览器指纹识别
先进的系统根据数十个参数创建浏览器的唯一指纹:
- Canvas 指纹识别 — 渲染隐藏图像并分析像素数据
- WebGL 指纹识别 — 图形渲染器和显卡参数
- 音频上下文 — 音频处理的独特特征
- 字体指纹识别 — 系统中安装的字体列表
- 屏幕分辨率 — 屏幕分辨率、颜色深度、可用区域
- 时区和语言 — 时区、浏览器语言、系统区域设置
自动化浏览器通常具有这些参数的非典型组合。例如,无头 Chrome 没有插件,但支持 WebGL — 这种组合在真实用户中极为罕见。
行为分析
现代系统跟踪行为模式:
- 鼠标移动 — 机器人以直线移动光标或根本不移动
- 动作速度 — 立即填写表单,点击速度不人性化
- 滚动模式 — 突然跳跃而不是平滑滚动
- 键盘事件 — 按键之间没有自然延迟
- 请求频率 — 动作之间的间隔过于规律
重要: DataDome 和 PerimeterX 使用机器学习分析行为。它们在数百万个会话上进行训练,即使在技术参数伪装得当的情况下,也能识别出机器人,如果行为看起来不自然。
Selenium 的基本伪装
Selenium WebDriver 在标准配置下留下许多痕迹。让我们逐步配置以最小化检测,以 Python 和 ChromeDriver 为例。
关闭 webdriver 标志
第一步是隐藏变量 navigator.webdriver。这可以通过 Chrome DevTools Protocol 实现:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
# 配置 Chrome 选项
chrome_options = Options()
# 关闭自动化标志
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
# 创建驱动
driver = webdriver.Chrome(options=chrome_options)
# 通过 CDP 删除 webdriver
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
})
driver.get('https://example.com')
配置 User-Agent 和其他头部
无头浏览器通常使用过时或特定的 User-Agent 字符串。需要设置一个真实浏览器的最新 User-Agent:
# Windows 上最新的 Chrome User-Agent
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
chrome_options.add_argument(f'user-agent={user_agent}')
# 伪装的其他参数
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-gpu')
# 窗口大小与真实用户相同
chrome_options.add_argument('--window-size=1920,1080')
chrome_options.add_argument('--start-maximized')
添加插件和语言
自动化浏览器没有插件,且通常显示空的语言列表。通过 CDP 修复此问题:
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en', 'ru']
});
Object.defineProperty(navigator, 'plugins', {
get: () => [
{
0: {type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "可移植文档格式"},
description: "可移植文档格式",
filename: "internal-pdf-viewer",
length: 1,
name: "Chrome PDF 插件"
},
{
0: {type: "application/pdf", suffixes: "pdf", description: ""},
description: "",
filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
length: 1,
name: "Chrome PDF 查看器"
}
]
});
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32'
});
'''
})
Selenium 完整配置示例
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import random
def create_stealth_driver():
chrome_options = Options()
# 基本伪装设置
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
# User-Agent
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
chrome_options.add_argument(f'user-agent={user_agent}')
# 窗口大小
chrome_options.add_argument('--window-size=1920,1080')
chrome_options.add_argument('--start-maximized')
driver = webdriver.Chrome(options=chrome_options)
# 通过 CDP 伪装脚本
stealth_script = '''
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
Object.defineProperty(navigator, 'platform', {get: () => 'Win32'});
window.chrome = {
runtime: {}
};
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
'''
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': stealth_script
})
return driver
# 使用
driver = create_stealth_driver()
driver.get('https://bot.sannysoft.com/') # 检测网站
配置 Puppeteer 绕过检测
Puppeteer 面临与 Selenium 相同的检测问题。然而,对于 Node.js,有一个现成的库 puppeteer-extra-plugin-stealth,可以自动化大多数伪装设置。
安装 puppeteer-extra
npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth
使用 stealth 插件的基本配置
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
// 启用伪装插件
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: 'new', // 新的无头模式 Chrome
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu',
'--window-size=1920,1080',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process'
]
});
const page = await browser.newPage();
// 设置视口
await page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 1
});
// 设置 User-Agent
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
// 附加头部
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
});
await page.goto('https://bot.sannysoft.com/');
// 截图以进行检查
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
无插件的手动设置
如果您想要完全控制或无法使用第三方库,可以手动配置伪装:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
// 重定义 webdriver 和其他属性
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en']
});
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
// 伪装 Chrome 无头
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32'
});
window.chrome = {
runtime: {}
};
// 重定义 permissions
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
});
await page.goto('https://example.com');
await browser.close();
})();
绕过 Cloudflare 的设置
Cloudflare 使用先进的检测方法。要绕过它,您需要添加随机延迟和模拟行为:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function bypassCloudflare(url) {
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-web-security'
]
});
const page = await browser.newPage();
// 随机 User-Agent
const userAgents = [
'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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
];
await page.setUserAgent(userAgents[Math.floor(Math.random() * userAgents.length)]);
// 访问页面
await page.goto(url, { waitUntil: 'networkidle2' });
// 等待 Cloudflare 检查(通常 5-10 秒)
await page.waitForTimeout(8000);
// 随机鼠标移动
await page.mouse.move(100, 100);
await page.mouse.move(200, 200);
const content = await page.content();
await browser.close();
return content;
}
对抗 JavaScript 指纹识别
JavaScript 指纹识别是基于多个参数创建浏览器的唯一指纹。即使您隐藏了 webdriver,系统仍会分析数百个其他属性以识别自动化。
指纹识别的主要向量
反机器人保护系统检查以下参数:
| 参数 | 检查内容 | 检测风险 |
|---|---|---|
| navigator.webdriver | 自动化标志的存在 | 关键 |
| navigator.plugins | 插件的数量和类型 | 高 |
| window.chrome | Chrome API 的存在 | 中 |
| navigator.permissions | 浏览器权限 API | 中 |
| screen.colorDepth | 屏幕颜色深度 | 低 |
| navigator.hardwareConcurrency | CPU 核心数量 | 低 |
综合伪装脚本
以下脚本重定义了大多数问题属性。通过 CDP(Selenium)或 evaluateOnNewDocument(Puppeteer)注入:
const stealthScript = `
// 删除 webdriver
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// 添加 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)
);
// 伪装插件
Object.defineProperty(navigator, 'plugins', {
get: () => {
return [
{
0: {type: "application/x-google-chrome-pdf", suffixes: "pdf"},
description: "可移植文档格式",
filename: "internal-pdf-viewer",
length: 1,
name: "Chrome PDF 插件"
},
{
0: {type: "application/pdf", suffixes: "pdf"},
description: "可移植文档格式",
filename: "internal-pdf-viewer",
length: 1,
name: "Chrome PDF 查看器"
},
{
0: {type: "application/x-nacl"},
description: "本机客户端可执行文件",
filename: "internal-nacl-plugin",
length: 2,
name: "本机客户端"
}
];
}
});
// 语言
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en']
});
// 平台
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32'
});
// 供应商
Object.defineProperty(navigator, 'vendor', {
get: () => 'Google Inc.'
});
// 删除 Selenium 的痕迹
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
// 电池 API(无头模式下缺失)
if (!navigator.getBattery) {
navigator.getBattery = () => Promise.resolve({
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1
});
}
`;
删除 WebDriver 属性
ChromeDriver 和其他 WebDriver 实现将特定变量添加到全局作用域。这些变量以前缀 cdc_ 开头,容易被保护系统发现。
检测 cdc 变量
可以通过简单的脚本检查这些变量的存在:
// 查找所有 cdc 变量
for (let key in window) {
if (key.includes('cdc_')) {
console.log('检测到 WebDriver 变量:', key);
}
}
// 典型的 ChromeDriver 变量:
// cdc_adoQpoasnfa76pfcZLmcfl_Array
// cdc_adoQpoasnfa76pfcZLmcfl_Promise
// cdc_adoQpoasnfa76pfcZLmcfl_Symbol
// $cdc_asdjflasutopfhvcZLmcfl_
方法 1:通过 CDP 删除
最可靠的方法是在页面加载之前通过 Chrome DevTools Protocol 删除变量:
from selenium import webdriver
driver = webdriver.Chrome()
# 删除所有 cdc 变量
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
// 删除已知的 cdc 变量
const cdcProps = [
'cdc_adoQpoasnfa76pfcZLmcfl_Array',
'cdc_adoQpoasnfa76pfcZLmcfl_Promise',
'cdc_adoQpoasnfa76pfcZLmcfl_Symbol',
'$cdc_asdjflasutopfhvcZLmcfl_'
];
cdcProps.forEach(prop => {
delete window[prop];
});
// 删除所有包含 'cdc_' 的变量
Object.keys(window).forEach(key => {
if (key.includes('cdc_') || key.includes('$cdc_')) {
delete window[key];
}
});
'''
})
方法 2:修改 ChromeDriver
更激进的方法是修改 ChromeDriver 的二进制文件,将字符串 cdc_ 替换为其他字符序列。这可以防止这些变量的创建:
import re
def patch_chromedriver(driver_path):
"""
修补 ChromeDriver,将 'cdc_' 替换为随机字符串
"""
with open(driver_path, 'rb') as f:
content = f.read()
# 将所有 'cdc_' 替换为 'dog_'(或任何其他相同长度的字符串)
patched = content.replace(b'cdc_', b'dog_')
with open(driver_path, 'wb') as f:
f.write(patched)
print(f'ChromeDriver 已修补: {driver_path}')
# 使用
patch_chromedriver('/path/to/chromedriver')
注意: 修改 ChromeDriver 的二进制文件可能会导致其无法正常工作。在修补之前请务必备份。此方法并不适用于所有版本的 ChromeDriver。
方法 3:使用 undetected-chromedriver
undetected-chromedriver 库在启动时会自动修补 ChromeDriver:
pip install undetected-chromedriver
import undetected_chromedriver as uc
# 创建自动修补的驱动
driver = uc.Chrome()
driver.get('https://nowsecure.nl/') # 检测网站
input('按 Enter 键关闭...')
driver.quit()
伪装 Canvas、WebGL 和 Audio API
Canvas、WebGL 和音频指纹识别是基于图形渲染和声音处理特征创建唯一指纹的方法。每个浏览器、操作系统和硬件的组合都会产生独特的结果。
Canvas 指纹识别
系统在 Canvas 上绘制隐藏图像并分析生成的像素。无头浏览器由于缺乏 GPU 加速,通常会产生非典型结果。
// 典型的 Canvas 指纹识别代码
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '14px Arial';
ctx.fillText('Browser fingerprint', 2, 2);
const fingerprint = canvas.toDataURL();
为了保护,可以向 Canvas API 添加随机噪声:
const canvasNoiseScript = `
// 向 Canvas 添加随机噪声
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// 添加噪声的函数
const addNoise = (canvas, context) => {
const imageData = originalGetImageData.call(context, 0, 0, canvas.width, canvas.height);
for (let i = 0; i < imageData.data.length; i += 4) {
// 向 RGB 添加最小噪声(肉眼不可见)
imageData.data[i] += Math.floor(Math.random() * 3) - 1;
imageData.data[i + 1] += Math.floor(Math.random() * 3) - 1;
imageData.data[i + 2] += Math.floor(Math.random() * 3) - 1;
}
context.putImageData(imageData, 0, 0);
};
// 重定义 toDataURL
HTMLCanvasElement.prototype.toDataURL = function() {
if (this.width > 0 && this.height > 0) {
const context = this.getContext('2d');
addNoise(this, context);
}
return originalToDataURL.apply(this, arguments);
};
`;
WebGL 指纹识别
WebGL 提供有关显卡和驱动程序的信息。无头浏览器通常显示 SwiftShader(软件渲染器)而不是真实 GPU:
const webglMaskScript = `
// 伪装 WebGL 参数
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// UNMASKED_VENDOR_WEBGL
if (parameter === 37445) {
return 'Intel Inc.';
}
// UNMASKED_RENDERER_WEBGL
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter.call(this, parameter);
};
// 也适用于 WebGL2
if (typeof WebGL2RenderingContext !== 'undefined') {
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter2.call(this, parameter);
};
}
`;
音频上下文指纹识别
音频 API 也提供唯一的指纹。向音频处理添加噪声:
const audioMaskScript = `
// 向音频上下文添加噪声
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
const originalCreateAnalyser = AudioContext.prototype.createAnalyser;
AudioContext.prototype.createAnalyser = function() {
const analyser = originalCreateAnalyser.call(this);
const originalGetFloatFrequencyData = analyser.getFloatFrequencyData;
analyser.getFloatFrequencyData = function(array) {
originalGetFloatFrequencyData.call(this, array);
// 添加最小噪声
for (let i = 0; i < array.length; i++) {
array[i] += Math.random() * 0.0001;
}
};
return analyser;
};
}
`;
模拟人类行为
即使在技术伪装完美的情况下,机器人也会通过行为暴露自己。机器学习系统分析鼠标移动模式、动作速度和事件顺序。
动作之间的随机延迟
切勿使用固定延迟。真实用户会有不同持续时间的暂停:
import random
import time
def human_delay(min_seconds=1, max_seconds=3):
"""模拟人类的随机延迟"""
delay = random.uniform(min_seconds, max_seconds)
time.sleep(delay)
# 使用
driver.get('https://example.com')
human_delay(2, 4) # 暂停 2-4 秒
element = driver.find_element(By.ID, 'search')
human_delay(0.5, 1.5) # 输入前的短暂停顿
element.send_keys('search query')
human_delay(1, 2)
平滑的鼠标移动
机器人以直线移动鼠标或瞬移光标。真实用户会创建加速和减速的曲线轨迹:
// Puppeteer: 平滑的鼠标移动
async function humanMouseMove(page, targetX, targetY) {
const steps = 25; // 中间点的数量
const currentPos = await page.evaluate(() => ({
x: window.mouseX || 0,
y: window.mouseY || 0
}));
for (let i = 0; i <= steps; i++) {
const t = i / steps;
// 使用缓动以实现平滑
const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
const x = currentPos.x + (targetX - currentPos.x) * ease;
const y = currentPos.y + (targetY - currentPos.y) * ease;
await page.mouse.move(x, y);
await page.waitForTimeout(Math.random() * 10 + 5);
}
// 保存位置
await page.evaluate((x, y) => {
window.mouseX = x;
window.mouseY = y;
}, targetX, targetY);
}
// 使用
await humanMouseMove(page, 500, 300);
await page.mouse.click(500, 300);
自然滚动
真实用户平滑地滚动,并在阅读内容时暂停:
async function humanScroll(page) {
const scrollHeight = await page.evaluate(() => document.body.scrollHeight);
const viewportHeight = await page.evaluate(() => window.innerHeight);
let currentPosition = 0;
while (currentPosition < scrollHeight - viewportHeight) {
// 随机滚动步长(200-500px)
const scrollStep = Math.floor(Math.random() * 300) + 200;
currentPosition += scrollStep;
// 平滑滚动
await page.evaluate((pos) => {
window.scrollTo({
top: pos,
behavior: 'smooth'
});
}, currentPosition);
// 暂停以“阅读”内容(1-3 秒)
await page.waitForTimeout(Math.random() * 2000 + 1000);
}
}
// 使用
await page.goto('https://example.com');
await humanScroll(page);
自然输入文本
人们以不同的速度打字,犯错并纠正:
async function humanTypeText(page, selector, text) {
await page.click(selector);
for (let char of text) {
// 按键之间的随机延迟(50-200ms)
const delay = Math.random() * 150 + 50;
await page.waitForTimeout(delay);
// 5% 的机会出现错字
if (Math.random() < 0.05) {
// 输入一个随机字符
const wrongChar = String.fromCharCode(97 + Math.floor(Math.random() * 26));
await page.keyboard.type(wrongChar);
await page.waitForTimeout(100 + Math.random() * 100);
// 删除(Backspace)
await page.keyboard.press('Backspace');
await page.waitForTimeout(50 + Math.random() * 50);
}
await page.keyboard.type(char);
}
}
// 使用
await humanTypeText(page, '#search-input', 'example search query');
集成代理以实现完全匿名
浏览器的伪装是无用的,如果所有请求都来自同一个 IP 地址。反机器人保护系统跟踪每个 IP 的请求数量,并阻止可疑活动。代理是任何严肃自动化的必要组成部分。
选择代理类型
不同的任务需要不同类型的代理:
| 代理类型 | 优点 | 应用 |
|---|---|---|
| 类型 1 | 优点 1 | 应用 1 |
| 类型 2 | 优点 2 | 应用 2 |
| 类型 3 | 优点 3 | 应用 3 |