现代网站已经学会识别自动化浏览器(Selenium、Puppeteer、Playwright)并阻止其请求。无头浏览器留下数十个数字痕迹,反机器人系统可以在毫秒内计算出自动化。在本指南中,我们将探讨所有无头浏览器的伪装方法,并提供 Python 和 JavaScript 的代码示例,以确保您的爬虫稳定运行而不被阻止。
本文旨在为从事网页抓取、测试自动化或从受保护网站收集数据的开发人员提供帮助。我们将讨论检测的技术细节和绕过保护的实际解决方案。
网站如何检测无头浏览器:主要方法
反机器人系统使用多层浏览器检查,分析数百个参数。无头浏览器在许多特征上与普通浏览器不同,这些特征无法通过简单地更改 User-Agent 来隐藏。了解检测方法是有效伪装的第一步。
自动化的 JavaScript 标记
最常见的方法是检查仅在自动化浏览器中出现的 JavaScript 属性:
navigator.webdriver— 在 Selenium 和 Puppeteer 中返回truewindow.chrome— 在无头 Chrome 中不存在navigator.plugins.length— 在无头模式下等于 0navigator.languages— 通常是空数组或仅包含 "en-US"navigator.permissions— API 在无头模式下的表现不同
分析 Chrome DevTools 协议
Puppeteer 和 Playwright 通过 Chrome DevTools 协议(CDP)控制浏览器。可以通过特殊的 JavaScript 检查来检测 CDP 连接,这些检查分析 window.cdc_ 对象或检查鼠标和键盘事件行为中的异常。
Canvas 和 WebGL 指纹
无头浏览器生成相同的 Canvas 和 WebGL 指纹,因为它们使用软件渲染而不是硬件渲染。反机器人系统创建一个不可见的 Canvas 元素,在其上绘制文本或图形并计算图像的哈希值。如果成千上万的用户具有相同的哈希值——这就是机器人的迹象。
行为分析
现代系统(DataDome、PerimeterX、Cloudflare Bot Management)分析鼠标移动、滚动速度、点击模式。无头浏览器瞬间执行操作,没有自然延迟,这暴露了自动化。此外,还分析事件:在普通浏览器中,点击之前总会发生 mousemove 事件,而机器人通常在没有先前鼠标移动的情况下点击。
重要: 现代反机器人系统使用机器学习同时分析数百个参数。仅伪装一个参数(例如 User-Agent)无法保护您免受阻止——需要综合方法。
删除 navigator.webdriver 和其他 JavaScript 标记
navigator.webdriver 属性是检测 Selenium 和其他 WebDriver 工具的最简单方法。在普通浏览器中,此属性返回 undefined,而在自动化浏览器中返回 true。可以通过在页面加载之前执行 JavaScript 代码来删除它。
Selenium(Python):通过 CDP 删除 webdriver
对于 Selenium,需要使用 Chrome DevTools 协议在加载任何页面之前执行 JavaScript:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(options=options)
# 通过 CDP 删除 navigator.webdriver
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
'''
})
driver.get('https://example.com')
选项 --disable-blink-features=AutomationControlled 禁用 Chrome 在自动化模式下添加的标志。这是基本保护,需与其他方法结合使用。
Puppeteer(Node.js):通过 Page.evaluateOnNewDocument 伪装
在 Puppeteer 中使用 page.evaluateOnNewDocument() 方法在页面加载之前执行代码:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const page = await browser.newPage();
// 删除 webdriver 和其他标记
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
// 添加 chrome 对象
window.chrome = {
runtime: {}
};
// 模拟插件
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
});
await page.goto('https://example.com');
})();
Playwright:内置伪装选项
Playwright 提供了更先进的伪装,但仍需额外设置:
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({
headless: true,
args: ['--disable-blink-features=AutomationControlled']
});
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
});
await page.goto('https://example.com');
})();
伪装 Chrome DevTools 协议
Puppeteer 和 Playwright 留下 CDP 连接的痕迹,可以通过分析 window 对象来检测。反机器人系统寻找带有前缀 cdc_、$cdc_ 或 __webdriver_ 的变量,这些变量是在通过 DevTools 协议连接时由 Chrome 创建的。
删除 CDP 变量
以下脚本从 window 对象中删除所有与自动化相关的变量:
await page.evaluateOnNewDocument(() => {
// 删除所有以 cdc_ 开头的变量
const cdcProps = Object.keys(window).filter(prop =>
prop.includes('cdc_') ||
prop.includes('$cdc_') ||
prop.includes('__webdriver_')
);
cdcProps.forEach(prop => {
delete window[prop];
});
// 重新定义 document.__proto__ 以隐藏 CDP
const originalQuery = document.querySelector;
document.querySelector = function(selector) {
if (selector.includes('cdc_')) return null;
return originalQuery.call(this, selector);
};
});
使用打补丁的 Chromium 版本
有些修改过的 Chromium 版本不会留下 CDP 痕迹。例如,puppeteer-extra 库与 puppeteer-extra-plugin-stealth 插件一起使用,自动应用数十个补丁以伪装 CDP。
设置 User-Agent 和 HTTP 头
无头浏览器使用过时或不真实的 User-Agent 字符串。例如,Puppeteer 默认在 User-Agent 中添加 "HeadlessChrome"。此外,还需要设置普通浏览器请求中存在的其他头。
适用的 User-Agent 伪装
使用真实浏览器的最新 User-Agent。以下是 2024 年的示例:
# Chrome 120 在 Windows 10 上
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
# Chrome 120 在 macOS 上
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
# Firefox 121 在 Windows 上
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
在 Selenium 中设置头
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument('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')
# 通过 CDP 设置其他头
driver.execute_cdp_cmd('Network.setUserAgentOverride', {
"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
"platform": 'Win32',
"acceptLanguage": 'en-US,en;q=0.9'
})
在 Puppeteer 中设置头
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-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Sec-Fetch-Dest': 'document'
});
Sec-Fetch-* 头是至关重要的——它们在 Chrome 76+ 中出现,缺少这些头会暴露旧版浏览器或机器人。
模拟 Canvas 和 WebGL 指纹
Canvas 和 WebGL 指纹识别是一种强大的检测方法,因为无头浏览器生成相同的指纹。反机器人系统创建一个不可见的 Canvas,绘制文本并计算像素的哈希值。如果成千上万的请求具有相同的哈希值——这就是机器人。
在 Canvas 中添加噪声
以下脚本在 Canvas 指纹中添加随机噪声,使每个请求都是唯一的:
await page.evaluateOnNewDocument(() => {
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) {
imageData.data[i] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 1] += Math.floor(Math.random() * 10) - 5;
imageData.data[i + 2] += Math.floor(Math.random() * 10) - 5;
}
context.putImageData(imageData, 0, 0);
};
HTMLCanvasElement.prototype.toDataURL = function() {
addNoise(this, this.getContext('2d'));
return originalToDataURL.apply(this, arguments);
};
});
模拟 WebGL 参数
WebGL 揭示了显卡和驱动程序的信息。在无头模式下,这些参数显示软件渲染:
await page.evaluateOnNewDocument(() => {
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// 模拟真实显卡
if (parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter.call(this, parameter);
};
});
参数 37445 是 UNMASKED_VENDOR_WEBGL,而 37446 是 UNMASKED_RENDERER_WEBGL。在无头模式下,它们返回 "Google SwiftShader",这暴露了自动化。
Selenium Stealth:Python 的现成解决方案
selenium-stealth 库自动应用数十个补丁以伪装 Selenium。这是 Python 开发者最简单的解决方案,无需手动设置每个参数。
安装和基本设置
pip install selenium-stealth
from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(options=options)
# 应用 stealth 补丁
stealth(driver,
languages=["en-US", "en"],
vendor="Google Inc.",
platform="Win32",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
driver.get("https://bot.sannysoft.com")
driver.save_screenshot("test.png")
该库自动删除 navigator.webdriver,添加 window.chrome,模拟插件,伪装 WebGL,并应用 20 多个补丁。这覆盖了 80% 的检测情况。
与代理的高级设置
为了实现全面的伪装,将 selenium-stealth 与 住宅代理 结合使用——它们提供真实家庭用户的 IP 地址,这对于绕过高级反机器人系统至关重要:
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "ip:port"
proxy.ssl_proxy = "ip:port"
capabilities = webdriver.DesiredCapabilities.CHROME
proxy.add_to_capabilities(capabilities)
driver = webdriver.Chrome(desired_capabilities=capabilities, options=options)
stealth(driver, languages=["en-US", "en"], vendor="Google Inc.", platform="Win32")
Puppeteer Extra Stealth 插件用于 Node.js
对于 Puppeteer,有一个名为 puppeteer-extra-plugin-stealth 的插件,它是 JavaScript 生态系统中最先进的无头浏览器伪装解决方案。它包含 23 个独立模块,每个模块伪装自动化的特定方面。
安装和基本使用
npm install puppeteer-extra puppeteer-extra-plugin-stealth
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
Stealth 插件伪装的内容
插件自动应用以下补丁:
- 删除
navigator.webdriver - 添加
window.chrome对象 - 模拟
navigator.permissionsAPI - 伪装
navigator.plugins和navigator.mimeTypes - 模拟 Canvas 和 WebGL 指纹
- 伪装 User-Agent 和语言
- 修复 iframe contentWindow 中的异常
- 模拟电池 API、媒体设备、WebRTC
与代理和其他参数的设置
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(StealthPlugin());
puppeteer.use(AdblockerPlugin({ blockTrackers: true }));
(async () => {
const browser = await puppeteer.launch({
headless: true,
args: [
'--proxy-server=http://your-proxy:port',
'--disable-web-security',
'--disable-features=IsolateOrigins,site-per-process'
]
});
const page = await browser.newPage();
// 代理认证
await page.authenticate({
username: 'your-username',
password: 'your-password'
});
await page.setViewport({ width: 1920, height: 1080 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
})();
Playwright:反检测设置
Playwright 的架构比 Puppeteer 更加完善,留下的自动化痕迹更少。然而,要绕过高级反机器人系统,需要额外的设置。Playwright 有一个 stealth 插件的移植——playwright-extra。
安装 playwright-extra
npm install playwright-extra playwright-extra-plugin-stealth playwright
const { chromium } = require('playwright-extra');
const stealth = require('playwright-extra-plugin-stealth');
chromium.use(stealth());
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York'
});
const page = await context.newPage();
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test.png' });
await browser.close();
})();
为最大伪装设置浏览器上下文
Playwright 允许创建具有独特设置的隔离浏览器上下文。这对于多账户或并行抓取非常有用:
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
viewport: { width: 1440, height: 900 },
locale: 'en-US',
timezoneId: 'America/Los_Angeles',
permissions: ['geolocation'],
geolocation: { latitude: 37.7749, longitude: -122.4194 },
colorScheme: 'light',
deviceScaleFactor: 2,
isMobile: false,
hasTouch: false,
// 为上下文设置代理
proxy: {
server: 'http://your-proxy:port',
username: 'user',
password: 'pass'
}
});
geolocation 和 timezoneId 参数必须与代理的 IP 地址相符,否则反机器人系统会发现不一致(例如,来自加利福尼亚的 IP,但时区为纽约)。
代理轮换以降低被阻止的风险
即使是完美伪装的无头浏览器,如果使用同一个 IP 地址进行数百个请求,也可能被阻止。现代反机器人系统分析来自单个 IP 的请求频率并阻止可疑活动。代理轮换是爬虫保护的必要元素。
爬虫的代理类型:比较
| 代理类型 | 速度 | 信任分数 | 更适合于 |
|---|---|---|---|
| 数据中心 | 非常高(50-200 毫秒) | 低 | 简单网站,大规模爬虫 |
| 住宅 | 中等(300-1000 毫秒) | 高 | 受保护的网站,社交媒体 |
| 移动 | 低(500-2000 毫秒) | 非常高 | 移动应用,Instagram,TikTok |
对于抓取受保护的网站(市场、社交媒体、广告平台),建议使用 住宅代理,因为它们拥有真实家庭用户的 IP 地址,不会被反机器人系统列入黑名单。
在 Puppeteer 中实现代理轮换
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
const proxyList = [
'http://user1:pass1@proxy1:port',
'http://user2:pass2@proxy2:port',
'http://user3:pass3@proxy3:port'
];
async function scrapeWithRotation(urls) {
for (let i = 0; i < urls.length; i++) {
const proxy = proxyList[i % proxyList.length];
const browser = await puppeteer.launch({
headless: true,
args: [`--proxy-server=${proxy}`]
});
const page = await browser.newPage();
try {
await page.goto(urls[i], { waitUntil: 'networkidle2' });
const data = await page.evaluate(() => document.body.innerText);
console.log(data);
} catch (error) {
console.error(`Error on ${urls[i]}:`, error);
} finally {
await browser.close();
}
// 请求之间的延迟
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
scrapeWithRotation([
'https://example1.com',
'https://example2.com',
'https://example3.com'
]);
通过基于会话的代理进行轮换
一些代理提供商(包括 ProxyCove)提供基于会话的轮换——每个请求自动获得新的 IP,而无需重启浏览器。这是通过代理的特殊 URL 格式实现的:
// 格式:username-session-RANDOM:password@gateway:port
const generateSessionProxy = () => {
const sessionId = Math.random().toString(36).substring(7);
return `http://username-session-${sessionId}:password@gateway.proxycove.com:12321`;
};
const browser = await puppeteer.launch({
args: [`--proxy-server=${generateSessionProxy()}`]
});
如何检查伪装质量:测试工具
在设置伪装后,需要检查您的无头浏览器模拟普通用户的效果如何。有几个专门的网站可以分析浏览器的数十个参数,并显示哪些自动化痕迹仍然存在。
主要测试工具
- bot.sannysoft.com — 检查 15 个以上的检测参数,包括 webdriver、Chrome 对象、插件、Canvas
- arh.antoinevastel.com/bots/areyouheadless — 专注于检测无头 Chrome
- pixelscan.net — 具有所有参数可视化的高级指纹分析
- abrahamjuliot.github.io/creepjs — 最详细的分析(200+ 参数),显示浏览器的信任级别
- iphey.com — 检查 IP 地址是否属于代理和 VPN
测试自动化
创建一个脚本以在每次更改设置后自动检查伪装:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
async function testStealth() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// 测试 1:Sannysoft
await page.goto('https://bot.sannysoft.com');
await page.screenshot({ path: 'test-sannysoft.png', fullPage: true });
// 测试 2:你是无头吗
await page.goto('https://arh.antoinevastel.com/bots/areyouheadless');
const headlessDetected = await page.evaluate(() => {
return document.body.innerText.includes('You are not Chrome headless');
});
console.log('无头检测到:', !headlessDetected);
// 测试 3:Webdriver 属性
const webdriverPresent = await page.evaluate(() => navigator.webdriver);
console.log('navigator.webdriver:', webdriverPresent);
// 测试 4:Chrome 对象
const chromePresent = await page.evaluate(() => !!window.chrome);
console.log('window.chrome 存在:', chromePresent);
await browser.close();
}
testStealth();
成功伪装的检查清单
如果您的无头浏览器正确伪装,则:
navigator.webdriver返回undefinedwindow.chrome存在并包含runtime对象navigator.plugins.length大于 0- WebGL 供应商和渲染器显示真实显卡,而不是 SwiftShader
- Canvas 指纹对每个会话都是唯一的
- User-Agent 与当前版本的 Chrome/Firefox 相符
- 代理的 IP 地址不在黑名单中(通过 iphey.com 检查)
- 时区和语言与 IP 地址的地理位置相符
结论
伪装无头浏览器是一项复杂的任务,需要关注数十个参数。现代反机器人系统使用机器学习并同时分析数百个浏览器特征,因此简单更改 User-Agent 已经不再有效。成功的爬虫需要结合多种保护方法。
有效伪装的主要元素:删除自动化的 JavaScript 标记(navigator.webdriver、CDP 变量)、模拟 Canvas 和 WebGL 指纹、设置现实的 HTTP 头以及使用高质量的代理。现成的解决方案(Python 的 selenium-stealth,Node.js 的 puppeteer-extra-plugin-stealth)覆盖了 80% 的检测情况,但要绕过高级保护仍需额外设置。
关键点是选择代理。即使是完美伪装的浏览器,如果使用黑名单中的 IP 地址或从同一 IP 发出过多请求,也会被阻止。对于抓取受保护的网站,建议使用 住宅代理 进行自动轮换——它们提供高信任分数和最低的被阻止风险,因为它们使用真实家庭用户的 IP 地址,而不是服务器地址。
定期通过专门的服务(bot.sannysoft.com、pixelscan.net)测试伪装质量,并根据反机器人系统的变化调整设置。爬虫是一场开发者与保护者之间的持久战争,因此今天有效的配置可能在几个月后需要更新。