Puppeteer 被反爬检测拦截?从 Chrome CDP 到 Electron 的替代方案
在为客户构建数据采集工具时遇到此问题,记录从 Puppeteer 到 Electron 的完整排查过程。
TL;DR
Puppeteer stealth plugin 无法绕过高级验证码反爬系统。切换到 Chrome CDP 远程调试方案后,又遇到 WSL2 网络隔离和 Chrome 单实例限制两个坑。最终用 Electron BrowserWindow 加载目标网站,用户手动登录后通过 session.cookies.get() 自动提取 Cookie,彻底解决了反爬检测和跨平台问题。
场景一:Puppeteer 被 Anti-Bot 拦截
问题现象
使用 puppeteer-extra + puppeteer-extra-plugin-stealth 自动登录目标网站,浏览器启动后立即触发验证码拦截。即使通过了验证码,后续页面也会再次检测到自动化环境并强制退出。
import puppeteer from 'puppeteer-extra';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-infobars',
],
});
const page = await browser.newPage();
await page.goto('https://target-site.com/login');
// 验证码系统检测到自动化环境,页面被拦截
根因
高级反爬验证码系统不只检查 navigator.webdriver 等基础指纹。它通过多个维度判断自动化环境:Chromium 编译特征、Canvas/WebGL 渲染差异、鼠标轨迹模式、甚至 DevTools Protocol 调用栈。stealth plugin 能修复已知的指纹泄露点,但无法消除 Puppeteer Chromium 与正常 Chrome 的底层差异。
经过 8 轮排查(移除超时、监听断开事件、排查环境差异、stealth plugin 补全等),确认无论怎么配置都无法绕过。
解法
放弃 Puppeteer 自动登录,改用 Chrome DevTools Protocol (CDP) 连接用户真实浏览器提取 Cookie。
场景二:WSL2 连不上 Windows Chrome CDP
问题现象
在 WSL2 中运行 Node.js 脚本,通过 chrome-remote-interface 连接 Windows Chrome 的调试端口,连接超时:
import CDP from 'chrome-remote-interface';
const client = await CDP({
host: 'localhost',
port: 9222,
});
// Error: connect ECONNREFUSED 127.0.0.1:9222
Windows 端启动 Chrome 的命令:
chrome.exe --remote-debugging-port=9222
在 Windows PowerShell 中 curl localhost:9222/json 正常返回,但 WSL2 内无法连接。
根因
Chrome --remote-debugging-port=9222 默认绑定 127.0.0.1,即 Windows 的本地回环地址。WSL2 和 Windows 有各自独立的网络栈——WSL2 内的 localhost 指向 Linux 的回环地址,不是 Windows 的。所以从 WSL2 访问 localhost:9222 实际访问的是 Linux 的 9222 端口,而非 Windows Chrome。
解法
启动 Chrome 时加 --remote-debugging-address=0.0.0.0,让 CDP 监听所有网卡:
chrome.exe --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0
或者用 Windows netsh 配置端口转发:
netsh interface portproxy add v4tov4 listenport=9222 listenaddress=0.0.0.0 connectport=9222 connectaddress=127.0.0.1
注意事项
--remote-debugging-address=0.0.0.0 会将 CDP 端口暴露给局域网,存在安全风险。仅在内网开发环境使用,生产环境务必配合防火墙规则限制访问来源。
场景三:Chrome 忽略 --remote-debugging-port
问题现象
Chrome 已经在运行,带 --remote-debugging-port=9222 参数重新启动,参数被静默忽略。Chrome 只是在已有窗口中打开新标签页,CDP 端口没有开启:
# Chrome 已在运行
chrome.exe --remote-debugging-port=9222
# 没有报错,但 9222 端口并未监听
根因
Chrome 设计为单实例应用。检测到已有 Chrome 进程时,新启动的 Chrome 会将启动参数中的 URL 转发给已有进程,然后自行退出。--remote-debugging-port 等参数只在进程首次创建时生效,已有进程不会动态加载。
解法
先关闭所有 Chrome 进程,再带参数重启:
# Windows
taskkill /F /IM chrome.exe
chrome.exe --remote-debugging-port=9222
# macOS
pkill -f "Google Chrome"
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
也可以用 --user-data-dir 指定独立配置目录,避免与日常使用的 Chrome 冲突:
chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\chrome-debug-profile"
最终方案:Electron BrowserWindow 提取 Cookie
三个场景的踩坑说明了一个事实:依赖外部 Chrome 进程做 Cookie 提取,在 WSL2 + 反爬检测 + 进程管理的组合下太脆弱。最终方案是用 Electron 的 BrowserWindow 替代外部 Chrome。
为什么 Electron 有效
- 不触发反爬检测:Electron 内置的 Chromium 与 Chrome 共享渲染引擎,反爬系统不将其标记为自动化环境
- 无外部 Chrome 依赖:不需要管理 Chrome 进程、CDP 端口、profile 目录
- 无 WSL2 网络问题:Electron 直接运行在目标 OS 上,不存在跨系统网络隔离
- 无单实例冲突:Electron 创建独立的 BrowserWindow,不与用户日常 Chrome 冲突
完整实现
打开登录窗口并轮询 Cookie:
import { BrowserWindow } from 'electron';
let cookieWindow = null;
function openLoginWindow() {
cookieWindow = new BrowserWindow({
width: 1000,
height: 700,
title: '登录目标平台',
webPreferences: {
// 关键:使用独立 session,不影响主窗口
partition: 'cookie-login',
contextIsolation: true,
nodeIntegration: false,
},
});
cookieWindow.loadURL('https://target-site.com/login');
// 轮询检测目标 Cookie
const interval = setInterval(async () => {
const cookies = await cookieWindow.webContents.session.cookies.get({
domain: '.target-site.com',
});
const sessionCookie = cookies.find((c) => c.name === 'session_token');
if (sessionCookie) {
clearInterval(interval);
// 拼接完整 Cookie 字符串
const cookieStr = cookies
.map((c) => c.name + '=' + c.value)
.join('; ');
// 写入环境变量
process.env.SESSION_COOKIE = cookieStr;
updateEnvFile('SESSION_COOKIE', cookieStr);
cookieWindow.close();
}
}, 2000);
cookieWindow.on('closed', () => {
clearInterval(interval);
cookieWindow = null;
});
}
preload 脚本暴露 IPC 接口:
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
extractCookie: () => ipcRenderer.invoke('extract-cookie'),
onCookieExtracted: (callback) =>
ipcRenderer.on('cookie-extracted', (_, data) => callback(data)),
});
渲染进程调用:
// 检测是否在 Electron 环境
if (window.electronAPI) {
document.getElementById('extractBtn').addEventListener('click', () => {
window.electronAPI.extractCookie();
});
window.electronAPI.onCookieExtracted((data) => {
console.log('Cookie 已提取:', data);
});
}
注意事项
partition: 'cookie-login'创建隔离 session,登录窗口的 Cookie 不会污染主窗口。如果需要共享登录状态,去掉 partition 参数或使用相同 partition 名- 轮询间隔 2 秒是平衡体验和性能的经验值,不要用
while+await替代setInterval,那会阻塞渲染进程 session.cookies.get()只能获取当前 session 的 Cookie,无法跨 partition 读取
常见问题
Puppeteer stealth plugin 仍然被反爬检测到怎么办?
stealth plugin 只能绕过基础指纹检测(如 navigator.webdriver),高级反爬系统通过浏览器行为和底层 API 特征识别自动化环境。检测维度包括 Chromium 编译特征、Canvas 渲染差异、鼠标轨迹模式等,stealth plugin 无法完全覆盖。改用 Electron BrowserWindow 加载目标网站,用户手动登录后通过 session.cookies.get() 提取 Cookie 即可。
Chrome --remote-debugging-port 为什么不生效?
Chrome 采用单进程架构,已有 Chrome 进程运行时 --remote-debugging-port 参数会被静默忽略。新启动的 Chrome 只会在现有实例中打开新标签页,调试端口不会开启。需要先用 taskkill /F /IM chrome.exe(Windows)或 pkill -f "Google Chrome"(macOS)关闭所有 Chrome 进程,再带参数重新启动。也可以用 --user-data-dir 指定独立配置目录避免冲突。
WSL2 怎么连接 Windows 上 Chrome 的调试端口?
Chrome 的 --remote-debugging-port 默认绑定 Windows 的 127.0.0.1,而 WSL2 有独立的网络栈,localhost 指向 Linux 回环地址而非 Windows。两种解法:一是启动 Chrome 时加 --remote-debugging-address=0.0.0.0 让 CDP 监听所有网卡;二是在 Windows 上用 netsh interface portproxy 配置端口转发,将 WSL2 的请求转发到 Windows 的 CDP 端口。
需要数据采集或自动化工具开发?
联系我们