Obscura CDP 兼容——Puppeteer/Playwright 零改动迁移
Obscura 通过 CDP 协议兼容 Puppeteer 和 Playwright。核心 Domain 实现、生命周期映射、已知差异与迁移清单。
为什么 CDP 兼容是核心能力
Chrome DevTools Protocol(CDP)是 Puppeteer 和 Playwright 与浏览器通信的底层协议。如果你已经有用 Puppeteer 或 Playwright 编写的爬虫和自动化脚本,把它们从 Headless Chrome 切换到 Obscura,只需要改一行连接代码。
Obscura 实现了 CDP 的核心 Domain,覆盖了日常浏览器自动化所需的绝大多数功能:页面导航、DOM 查询、JS 执行、网络与 Cookie 管理、请求拦截、输入事件等。
启动 CDP 服务
# 基础模式
obscura serve --port 9222
# 启用反检测
obscura serve --port 9222 --stealth
# Docker 部署
docker run -d --name obscura -p 127.0.0.1:9222:9222 h4ckf0r0day/obscura服务默认绑定 127.0.0.1(安全原因)。Docker 中需要设置为 0.0.0.0 才能从容器外部连接。
Puppeteer 接入
使用 puppeteer-core(不是 puppeteer,后者内置 Chrome):
npm install puppeteer-coreimport puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser',
});
const page = await browser.newPage();
await page.goto('https://httpbin.org/anything');
// 提取 HN 首页标题列表
const stories = await page.evaluate(() =>
Array.from(document.querySelectorAll('.titleline > a'))
.map(a => ({ title: a.textContent, url: a.href }))
);
console.log(stories);
// 请求拦截
await page.setRequestInterception(true);
page.on('request', req => {
if (['image', 'media', 'font'].includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
await browser.disconnect();Playwright 接入
使用 playwright-core:
npm install playwright-coreimport { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP('ws://127.0.0.1:9222');
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://httpbin.org/anything');
console.log(await page.title());
// 表单填写与提交
await page.goto('https://httpbin.org/post');
await page.fill('#username', 'admin');
await page.fill('#password', 'admin');
await page.click('form input[type="submit"]');
await browser.close();注意:Playwright 用 connectOverCDP,直接传 WebSocket 地址字符串。不要用 connect,后者走的是 Playwright 私有协议,Obscura 不实现。
Obscura 会处理 POST 请求、跟随 302 重定向、保持 Cookie 一致性。
CDP Domain 覆盖
Obscura 实现了自动化场景常用的 CDP Domain。下表列出主要 Domain 及其代表性方法,便于对照你现有脚本依赖的能力是否被覆盖:
| Domain | 代表性方法 |
|---|---|
| Target | createTarget、closeTarget、attachToTarget、createBrowserContext、disposeBrowserContext |
| Page | navigate、getFrameTree、addScriptToEvaluateOnNewDocument、lifecycleEvent |
| DOM | getDocument、querySelector、querySelectorAll、getOuterHTML、resolveNode |
| DOMSnapshot | captureSnapshot |
| Runtime | evaluate、callFunctionOn、getProperties、addBinding |
| Network | enable、setCookie、getCookies、setExtraHTTPHeaders、setUserAgentOverride |
| Fetch | enable、continueRequest、fulfillRequest、failRequest |
| Input | dispatchMouseEvent、dispatchKeyEvent |
| Storage | getCookies、setCookies、deleteCookies |
| Accessibility | getFullAXTree |
| Browser | getVersion、getWindowBounds、setWindowBounds |
需要说明两点:一是 DOMSnapshot 主要服务于 browser-use 这类 DOM-agent 框架的感知能力;二是页面转 Markdown 走的是 CLI 的 --dump markdown 和 MCP 的 browser_markdown 工具,不是某个 CDP 自定义 Domain。如果你依赖的某个 Domain 方法不在表里,先在 obscura serve --verbose 下跑一次你的脚本,看日志里是否有 unknown method 报错,再决定是否需要自行扩展。
生命周期事件映射
Puppeteer 和 Playwright 的 goto 依赖 CDP 生命周期事件来判断页面何时加载完成。Obscura 的生命周期状态机如下:
init → commit → domcontentloaded → load → networkidle2 → networkidle0各阶段说明:
init:开始导航commit:服务器返回首字节,导航已提交domcontentloaded:DOM 解析完成load:所有同步资源加载完成networkidle2:500ms 内活跃连接不超过 2 个networkidle0:500ms 内没有活跃连接
waitUntil 参数映射关系:
| Puppeteer/Playwright | Obscura |
|---|---|
load(默认) | load |
domcontentloaded | domcontentloaded |
networkidle0 | networkidle0 |
networkidle | networkidle0 |
实际使用经验:通过 CDP 驱动时默认 waitUntil 是 domcontentloaded(而不是 CLI 的 load),这是为了匹配 Puppeteer/Playwright 客户端的默认预期。如果你的页面靠 XHR 异步填充内容,显式指定 waitUntil: 'networkidle0' 更稳妥。
Docker 模式下的 CDP 连接
Docker 中运行 Obscura:
# 启动容器,暴露 CDP 端口
docker run -d --name obscura \
-p 127.0.0.1:9222:9222 \
h4ckf0r0day/obscura
# 从宿主机连接
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser',
});从另一个容器连接时,需要确保网络互通:
docker network create obscura-net
docker run -d --name obscura --network obscura-net h4ckf0r0day/obscura
docker run --rm --network obscura-net my-app已知差异与避坑指南
1. 共享 V8 Isolate
所有 Page 共享一个 V8 Isolate,通过 tokio::sync::Mutex 串行化访问。这意味着:
Target.createTarget可以并发调用(立即返回)- 实际的
Page.navigate会在后台异步执行 - 大量并发页面可能导致 V8 争用
建议控制并发页面数,或使用 --workers N 多进程模式。
2. canAccessOpener 字段
TargetInfo 负载中包含 canAccessOpener 字段。严格的 CDP 客户端(如 chromiumoxide)依赖这个字段,缺失会导致解析失败。Obscura 在每个 TargetInfo 负载里都会带上它。
3. Chrome DevTools 面板
Obscura 不提供 Performance、Memory 等 DevTools 面板。调试依赖日志和 CDP 命令响应。
4. setRequestInterception
Puppeteer 的 setRequestInterception(true) 和 Playwright 的 page.route() 均正常工作,底层通过 Fetch.enable 实现。
5. 文件 URL
默认禁止通过 CDP 访问 file:// URL。需要显式开启 --allow-file-access。
迁移清单
从 Headless Chrome 迁移到 Obscura 的检查清单:
- 将
puppeteer替换为puppeteer-core - 启动
obscura serve替代chrome-launcher或puppeteer.launch() - 使用
puppeteer.connect()替代puppeteer.launch() - 确认不依赖 Chrome DevTools 面板功能
- 测试请求拦截、Cookie 操作、evaluate 等核心路径
- 如果使用 Stealth,确认
--stealth在 serve 参数中
总结
Obscura 通过 CDP 协议提供了与 Headless Chrome 高度兼容的接口。对于大多数爬虫和自动化场景,只需改一行连接代码即可完成迁移。如果你遇到兼容性问题,Obscura 的扩展机制允许添加新的 CDP Domain 和 Web API。
需要企业代理方案?
我们可根据目标站点、并发规模与稳定性目标提供定制方案。