Obscura CDP 兼容——Puppeteer/Playwright 零改动迁移

Obscura 通过 CDP 协议兼容 Puppeteer 和 Playwright。核心 Domain 实现、生命周期映射、已知差异与迁移清单。

亿牛云技术团队2026年7月2日4 分钟阅读

为什么 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-core
import 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-core
import { 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代表性方法
TargetcreateTarget、closeTarget、attachToTarget、createBrowserContext、disposeBrowserContext
Pagenavigate、getFrameTree、addScriptToEvaluateOnNewDocument、lifecycleEvent
DOMgetDocument、querySelector、querySelectorAll、getOuterHTML、resolveNode
DOMSnapshotcaptureSnapshot
Runtimeevaluate、callFunctionOn、getProperties、addBinding
Networkenable、setCookie、getCookies、setExtraHTTPHeaders、setUserAgentOverride
Fetchenable、continueRequest、fulfillRequest、failRequest
InputdispatchMouseEvent、dispatchKeyEvent
StoragegetCookies、setCookies、deleteCookies
AccessibilitygetFullAXTree
BrowsergetVersion、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/PlaywrightObscura
load(默认)load
domcontentloadeddomcontentloaded
networkidle0networkidle0
networkidlenetworkidle0

实际使用经验:通过 CDP 驱动时默认 waitUntildomcontentloaded(而不是 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 的检查清单:

  1. puppeteer 替换为 puppeteer-core
  2. 启动 obscura serve 替代 chrome-launcherpuppeteer.launch()
  3. 使用 puppeteer.connect() 替代 puppeteer.launch()
  4. 确认不依赖 Chrome DevTools 面板功能
  5. 测试请求拦截、Cookie 操作、evaluate 等核心路径
  6. 如果使用 Stealth,确认 --stealth 在 serve 参数中

总结

Obscura 通过 CDP 协议提供了与 Headless Chrome 高度兼容的接口。对于大多数爬虫和自动化场景,只需改一行连接代码即可完成迁移。如果你遇到兼容性问题,Obscura 的扩展机制允许添加新的 CDP Domain 和 Web API。

需要企业代理方案?

我们可根据目标站点、并发规模与稳定性目标提供定制方案。