跳到主要内容

1 篇博文 含有标签「puppeteer」

查看所有标签

Puppeteer 被反爬检测拦截?从 Chrome CDP 到 Electron 的替代方案

· 阅读需 7 分钟

在为客户构建数据采集工具时遇到此问题,记录从 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"

三个场景的踩坑说明了一个事实:依赖外部 Chrome 进程做 Cookie 提取,在 WSL2 + 反爬检测 + 进程管理的组合下太脆弱。最终方案是用 Electron 的 BrowserWindow 替代外部 Chrome

为什么 Electron 有效

  1. 不触发反爬检测:Electron 内置的 Chromium 与 Chrome 共享渲染引擎,反爬系统不将其标记为自动化环境
  2. 无外部 Chrome 依赖:不需要管理 Chrome 进程、CDP 端口、profile 目录
  3. 无 WSL2 网络问题:Electron 直接运行在目标 OS 上,不存在跨系统网络隔离
  4. 无单实例冲突: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 端口。


需要数据采集或自动化工具开发?

联系我们