返回博客

如何在爬虫中绕过无头浏览器检测:Selenium和Puppeteer的伪装技巧

了解如何在解析时绕过对无头浏览器的保护:Selenium、Puppeteer 和 Playwright 的伪装技术,附带代码示例和代理设置。

📅2026年1月10日
```html

现代网站已经学会识别自动化浏览器(Selenium、Puppeteer、Playwright)并阻止其请求。无头浏览器留下数十个数字痕迹,反机器人系统可以在毫秒内计算出自动化。在本指南中,我们将探讨所有无头浏览器的伪装方法,并提供 Python 和 JavaScript 的代码示例,以确保您的爬虫稳定运行而不被阻止。

本文旨在为从事网页抓取、测试自动化或从受保护网站收集数据的开发人员提供帮助。我们将讨论检测的技术细节和绕过保护的实际解决方案。

网站如何检测无头浏览器:主要方法

反机器人系统使用多层浏览器检查,分析数百个参数。无头浏览器在许多特征上与普通浏览器不同,这些特征无法通过简单地更改 User-Agent 来隐藏。了解检测方法是有效伪装的第一步。

自动化的 JavaScript 标记

最常见的方法是检查仅在自动化浏览器中出现的 JavaScript 属性:

  • navigator.webdriver — 在 Selenium 和 Puppeteer 中返回 true
  • window.chrome — 在无头 Chrome 中不存在
  • navigator.plugins.length — 在无头模式下等于 0
  • navigator.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.permissions API
  • 伪装 navigator.pluginsnavigator.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'
    }
});

geolocationtimezoneId 参数必须与代理的 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 返回 undefined
  • window.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)测试伪装质量,并根据反机器人系统的变化调整设置。爬虫是一场开发者与保护者之间的持久战争,因此今天有效的配置可能在几个月后需要更新。

```