如何使用 AdsCrawl 配合 Puppeteer:云浏览器指南

通过 CDP 将 Puppeteer 连接到 AdsCrawl 的云端 Chromium。包含设置、完整的 Node.js 示例、代理路由、会话清理以及常见陷阱。

Anonymous13 分钟阅读

如何使用 AdsCrawl 配合 Puppeteer:云浏览器指南

Puppeteer 是 Chromium 的可靠驱动,但在本地运行意味着要打包浏览器二进制文件、照看内存,还得指望你的 IP 能通过第一次机器人检查。AdsCrawl 颠覆了这种模式:它运行真实的云端 Chromium 实例,并通过 Chrome DevTools 协议(CDP)暴露出来,因此你现有的 Puppeteer 脚本可以连接到远程浏览器,而不是启动一个。本指南将带你走完整个工作流程——会话创建、端点发现、puppeteer.connect、代理路由和清理——以及那些让人栽跟头的失败模式。

为什么将 AdsCrawl 与 Puppeteer 配对使用

First viewport screenshot of Puppeteer

First viewport screenshot of Puppeteer.

Changelog | Puppeteer 的首屏截图。

Puppeteer 为你提供细粒度的页面控制:导航、evaluate、选择器、截图、网络拦截。它开箱即用所不具备的是浏览器身份、代理轮换,或能超越单机扩展的基础设施。

AdsCrawl 正好填补了这一空白。它是一个托管的云浏览器和浏览器自动化 API,专为需要抓取、测试或监控那些抵抗普通 HTTP 请求的动态网站的开发者而构建。对于 Puppeteer 工作流程,相关的部分包括:

  • 远程 CDP 会话——创建一个会话,获取 WebSocket 调试器端点,将 Puppeteer 连接到它。
  • 代理路由——通过住宅代理(100 多个轮换选项)路由浏览器,或自带代理,使请求来自目标期望的地区。
  • 有状态浏览器配置文件——通过单独的 Cloud Browser API,设置和 Cookie 在任务之间持久保存。
  • 结构化输出替代方案——如果你不需要脚本化交互,POST /html 返回渲染后的 HTML 或 Markdown,而 POST /screenshot 捕获 PNG,这通常比驱动完整会话更便宜。

分工很清晰:AdsCrawl 拥有浏览器、网络身份和代理层;Puppeteer 拥有脚本逻辑。

这两个产品如何配合

层级 负责方
浏览器进程、Chromium 版本、机器资源 AdsCrawl
IP 身份、代理轮换、地理路由 AdsCrawl
会话生命周期和并发限制 AdsCrawl
导航、点击、表单填写、evaluate Puppeteer
数据解析和下游管道 你的应用程序

一个重要限制:此集成针对基于 CDP 的 Chromium。它不提供 Firefox 或 WebDriver BiDi 会话。如果你的脚本依赖这些,那么这不适合。

先决条件

在编写任何代码之前,请准备好以下事项:

  1. 支持 fetch 的 Node.js(Node 18+ 是安全基线)。
  2. puppeteer-core,而不是完整的 puppeteer。你不需要本地 Chromium 下载,因为浏览器在远程运行:
npm install puppeteer-core
  1. 一个 AdsCrawl API 密钥,作为服务器端环境变量存储。切勿将其发送到浏览器或提交到仓库:
export ADSCRAWL_API_KEY="your_key_here"
  1. 了解你的计划限制。 AdsCrawl 定价基于积分:免费层提供 500 个一次性积分,Hobby 每月 9 美元,Starter 每月 49 美元,Pro 每月 199 美元。不同计划在每月积分、浏览器并发数、Cloud Browser 配置文件限制和 API 密钥数量上有所不同——在 Puppeteer 循环中,并发数会最先让你头疼。

分步指南:将 Puppeteer 连接到 AdsCrawl 云浏览器

First viewport screenshot of Changelog | Puppeteer

First viewport screenshot of Changelog | Puppeteer.

Puppeteer 的首屏截图。

步骤 1 — 创建 CDP 会话

向 /cdp/sessions 发送 POST 请求。响应包含 sessionId 和 cdpBaseUrl。保留两者;你需要第一个来清理,第二个来找到浏览器。

const base = "https://api.adscrawl.net";
const headers = {
  "x-api-key": process.env.ADSCRAWL_API_KEY,
  "content-type": "application/json",
};

const response = await fetch(base + "/cdp/sessions", {
  method: "POST",
  headers,
  body: JSON.stringify({ idleTimeoutMs: 600000 }),
});
if (!response.ok) throw new Error(await response.text());
const session = await response.json();

步骤 2 — 发现 WebSocket 调试器端点

会话的 cdpBaseUrl 是一个基础地址,而不是端点本身。将 /json/version 附加到它后面,并从响应中读取 webSocketDebuggerUrl。保留 URL 中嵌入的令牌——去掉它会破坏连接。

const discovery = new URL(session.cdpBaseUrl);
discovery.pathname += "/json/version";
const version = await fetch(discovery);
if (!version.ok) throw new Error(await version.text());
const { webSocketDebuggerUrl } = await version.json();

步骤 3 — 使用 puppeteer.connect 连接

这一行取代了 puppeteer.launch。你不是在启动浏览器;你是在加入一个已经在运行的浏览器。

const browser = await puppeteer.connect({
  browserWSEndpoint: webSocketDebuggerUrl,
});

步骤 4 — 运行页面操作

从这里开始,API 就是你熟悉的 Puppeteer。导航、求值、提取、截图。

const page = await browser.newPage();
await page.goto("https://www.adscrawl.net", { waitUntil: "domcontentloaded" });
console.log(await page.title());

步骤 5 — 断开连接并删除会话

这是人们跳过的步骤,而且会让他们花钱。browser.disconnect() 只会分离你的客户端;它不会结束远程会话或其生命周期。你必须显式调用 DELETE /cdp/sessions/:sessionId。

await browser.disconnect();

const stopped = await fetch(base + "/cdp/sessions/" + session.sessionId, {
  method: "DELETE",
  headers,
});
if (!stopped.ok) throw new Error(await stopped.text());

完整脚本

将整个内容包裹在嵌套的 try/finally 块中,这样脚本中途失败仍会释放会话:

import puppeteer from "puppeteer-core";

const base = "https://api.adscrawl.net";
const headers = {
  "x-api-key": process.env.ADSCRAWL_API_KEY,
  "content-type": "application/json",
};

const response = await fetch(base + "/cdp/sessions", {
  method: "POST",
  headers,
  body: JSON.stringify({ idleTimeoutMs: 600000 }),
});
if (!response.ok) throw new Error(await response.text());
const session = await response.json();

try {
  const discovery = new URL(session.cdpBaseUrl);
  discovery.pathname += "/json/version";
  const version = await fetch(discovery);
  if (!version.ok) throw new Error(await version.text());
  const { webSocketDebuggerUrl } = await version.json();

  const browser = await puppeteer.connect({
    browserWSEndpoint: webSocketDebuggerUrl,
  });

  try {
    const page = await browser.newPage();
    await page.goto("https://www.adscrawl.net");
    console.log(await page.title());
  } finally {
    await browser.disconnect();
  }
} finally {
  const stopped = await fetch(base + "/cdp/sessions/" + session.sessionId, {
    method: "DELETE",
    headers,
  });
  if (!stopped.ok) throw new Error(await stopped.text());
}

将其保存为 .mjs 文件,并在 shell 中已导出 API 密钥的情况下运行它。

实际示例:抓取 JavaScript 渲染的产品页面

First viewport screenshot of What is Puppeteer? | Puppeteer

First viewport screenshot of What is Puppeteer? | Puppeteer.

What is Puppeteer? | Puppeteer 的首屏截图。

当目标页面仅在客户端 JavaScript 运行后才渲染时,上述模式才真正有用。典型流程:

  1. 创建一个会话,代理路由到商店服务的地区。
  2. 连接 Puppeteer 并使用 waitUntil: "networkidle2" 或显式选择器等待进行导航。
  3. 使用 page.evaluate 从渲染后的 DOM 中提取结构化字段。
  4. 在 finally 中断开连接并删除会话。
await page.goto(productUrl, { waitUntil: "networkidle2" });
await page.waitForSelector("[data-price]");

const product = await page.evaluate(() => ({
  title: document.querySelector("h1")?.innerText.trim(),
  price: document.querySelector("[data-price]")?.innerText.trim(),
  inStock: !!document.querySelector("[data-stock='available']"),
}));

console.log(product);

如果你发现自己反复编写相同的提取逻辑,请退一步检查你是否真的需要 Puppeteer。AdsCrawl 的 POST /spa-extract 端点接受 CSS 选择器和网络 JSON 路径的模板,并直接返回结构化字段,在 missingFields 中报告任何找不到的内容。对于大规模的只读提取,这通常比维护浏览器脚本更便宜、更简单。

何时使用 Puppeteer 而非更简单的 HTTP API

First viewport screenshot of Changelog | Puppeteer

First viewport screenshot of Changelog | Puppeteer.

Changelog | Puppeteer 的首屏截图。

AdsCrawl 暴露了多种工作流程,选择错误的工作流程是浪费精力的最常见来源:

  • POST /html——从单个页面返回渲染后的 HTML 或 Markdown。最适合无需交互的内容提取。
  • POST /screenshot——PNG 输出,使用 fullPage 或 selector 捕获单个元素。最适合视觉监控和证据捕获。
  • POST /spa-extract——通过模板从动态页面获取结构化字段。最适合可重复的数据管道。
  • 远程 CDP 会话——完整的脚本化控制。当你需要点击、表单填写、多步导航或流程中的条件逻辑时最适合。
  • Cloud Browser——持久配置文件加实时查看器。当你需要在多次运行之间保持登录状态时最适合。

当任务真正具有交互性时选择 Puppeteer。如果单个 POST /html 调用就能返回你需要的内容,那就用它——代码更少、失败点更少,而且无需清理会话。

常见陷阱及如何避免

使用 puppeteer.launch 而不是 puppeteer.connect。 launch 在你自己的环境中启动浏览器进程。对于远程会话,你始终需要 connect。

假设 disconnect 会结束会话。 它不会。远程浏览器会继续运行并消耗资源,直到你 DELETE 它。

将会话 URL 泄露到日志中。 会话 URL 包含访问令牌。将它们保留在服务器端,远离公共日志、错误跟踪器和 CI 输出。

忽略延迟。 远程执行会增加网络往返。在超时中为此留出预算,而不是假设本地机器的速度。

忘记并发限制。 如果你收到 HTTP 429,说明你超出了计划的浏览器并发数。降低并行度并使用退避重试。如果收到 HTTP 402,请检查仪表板中的积分余额。

重用已删除的会话。 你不能。为每个任务创建一个新的临时会话。如果你需要跨任务的连续性,那正是 Cloud Browser 配置文件 API 的用途。

假设仅本地功能会延续。 扩展、本地文件访问和特定浏览器版本是迁移现有脚本前值得重新检查的假设。

调试失败的连接

当脚本挂起或抛出异常时,按顺序检查链条:

  1. POST /cdp/sessions 是否返回了 sessionId 和 cdpBaseUrl?如果没有,请阅读错误正文——它通常会指出问题所在。
  2. /json/version 获取是否成功?这里的失败通常意味着在重建 URL 时令牌被丢弃了。
  3. puppeteer.connect 是否解析成功?如果没有,请确认你传递的是 browserWSEndpoint 而不是原始基础 URL。
  4. page.goto 是否超时?检查代理路由,然后检查等待条件——一个永远不会出现的选择器会一直挂起直到超时触发。

对于在其他基础设施旁大规模运行此功能的团队,在做出承诺之前,值得比较浏览器 API 在 CDP 控制、代理处理和定价方面的表现——请参阅我们的 AdsCrawl 与 Steel 对比 和 2026 浏览器 API 指南 以获取并排分析。

相关阅读

来源与进一步阅读

常见问题

使用 AdsCrawl 时,我应该用 puppeteer.launch 还是 puppeteer.connect?

puppeteer.connect。浏览器已经在 AdsCrawl 的云中运行;你是在连接它,而不是启动它。launch 会在你自己的环境中启动一个进程,这违背了初衷。

我需要在本地下载 Chromium 吗?

不需要。安装 puppeteer-core,它提供客户端库而不捆绑浏览器二进制文件。AdsCrawl 运行 Chromium 实例。

browser.disconnect() 会结束远程会话吗?

不会。断开连接只会分离你的 Puppeteer 客户端。会话会继续运行,直到你调用 DELETE /cdp/sessions/:sessionId。始终将该调用放在 finally 块中。

删除会话后我可以重用它吗?

不可以。已删除的会话就消失了。为每个任务创建一个新的临时会话。如果你需要设置和 Cookie 在任务之间持久保存,请改用 Cloud Browser API。

我可以将 Puppeteer 流量路由到特定国家吗?

可以。AdsCrawl 支持住宅代理路由,拥有 100 多个轮换代理,你可以在支持的端点上提供自己的代理配置。将代理凭据放在指定字段中,而不是放在代理服务器 URL 内。

这适用于 Firefox 或 WebDriver BiDi 吗?

不适用。该集成针对基于 CDP 的 Chromium。通过此路径不支持 Firefox 和 WebDriver BiDi 会话。

对于 HTTP 429 错误我该怎么办?

降低并发数并使用指数退避重试。429 意味着你超出了计划的浏览器并发限制,该限制因层级而异。

我可以使用 Cookie 访问已登录页面吗?

支持的任务和 Cloud Browser 工作流程接受 Cookie。仅对你被授权访问的账户使用它们,并验证域、路径和过期时间。对于多步登录流程,CDP 会话是更好的选择。

结论

将 Puppeteer 连接到 AdsCrawl 归结为五个动作:创建 CDP 会话,通过 /json/version 发现 WebSocket 端点,使用 puppeteer.connect 连接,运行页面逻辑,然后断开连接并删除会话。回报是你不再维护浏览器二进制文件和代理池,而是开始将浏览器自动化视为你调用的基础设施,而不是你托管的软件。

最常见的错误是生命周期错误,而不是代码错误——忘记删除会话、泄露带令牌的 URL,或者在本可以通过单个 POST /html 调用完成工作时却使用 Puppeteer。把这些做对,集成确实维护成本很低。从免费层的 500 个一次性积分开始,在一个真实目标页面上验证工作流程,只有在清理逻辑无懈可击后才扩展并发。