Cloudflare Browser Rendering 技能参考:在 Cloudflare 全球网络驱动无头 Chrome 完成截图、PDF 与自动化抓取
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
Cloudflare Browser Rendering 是 Cloudflare 平台提供的浏览器自动化服务,允许你在其全球边缘网络上控制无头 Chrome,用于网页截图、PDF 生成、网页抓取、Web 应用测试与内容生成。本文以本仓库cloudflare-deploy技能中browser-rendering参考集为核心,完整覆盖技术选型决策树、配额限制、部署配置、REST API 与 Workers Bindings 双入口、常见实战模式与避坑要点,读完即可在 Workers 中落地一套可运行的浏览器自动化方案。
一、这是什么:技能在仓库中的定位
在cloudflare-deploy技能的总入口 SKILL.md 中,"我需要媒体/内容"决策树将Browser automation/screenshots → browser-rendering/列为唯一答案,与图片优化(images/)、视频流(stream/)并列;而在 bindings 参考 中,Browser Rendering 被定义为以env.BROWSER.fetch(url)形式暴露的Headless Chrome绑定。也就是说,它是面向"需要在边缘执行真实浏览器行为"这一类任务的权威参考。
该技能参考的适用场景非常明确:任何涉及 Cloudflare Browser Rendering 的任务,包括截图、PDF 生成、网页抓取、浏览器自动化、Web 应用测试、结构化数据提取、页面指标采集,以及自动化的浏览器交互。无论你是第一次接触该服务,还是已有 Workers 经验,本文都以原文档为骨架、以同级四篇深度文档为血肉,给出可复制的完整方案。
二、技术选型决策树:两条主干路线
原文档给出的第一个关键决策,是如何接入 Browser Rendering:走REST API还是Workers Bindings;以及用什么库驱动:Puppeteer还是Playwright。
REST API 与 Workers Bindings 怎么选
| 场景 | 推荐路线 | 理由 |
|---|---|---|
| 一次性、无状态任务(截图、PDF、内容抓取) | REST API | 无需部署 Workers,一次调用即得结果 |
| 尚无 Workers 基础设施 | REST API | 从外部服务即可简单集成 |
| 需要快速原型验证、不想经历部署流程 | REST API | curl 即可完成验证 |
| 复杂的浏览器自动化工作流 | Workers Bindings | 可编排多步骤交互 |
| 需要会话复用以获得性能 | Workers Bindings | 复用 keep-alive 会话,避免冷启动 |
| 单次请求内多个页面交互 | Workers Bindings | 同一 Browser 实例内操作多个 Page |
| 需要自定义脚本与业务逻辑 | Workers Bindings | 完整 TypeScript 编程环境 |
| 构建生产级应用 | Workers Bindings | 可结合 KV、队列等平台能力 |
简单记忆:REST 适合"测一下、用一次",Workers 适合"做产品、持续跑"。
Puppeteer 与 Playwright 怎么选
| 特性 | Puppeteer | Playwright |
|---|---|---|
| API 风格 | Chrome DevTools Protocol(CDP) | 高层抽象 |
| 选择器 | CSS、XPath | CSS、text、role、test-id |
| 最适合 | 高级控制、直接 CDP 访问 | 快速自动化、测试 |
| 学习曲线 | 较陡 | 较平缓 |
- 选Puppeteer:需要 CDP 协议级访问、使用 Chrome 特有功能、或从既有 Puppeteer 代码迁移。
- 选Playwright:需要现代选择器 API(
getByRole/getByText/getByTestId)、跨浏览器风格的写法、追求更快的开发速度。
三、配额限制总览(成文前必读)
原文档在 Tier Limits Summary 中给出了免费版与付费版的核心配额,这是所有方案设计的硬约束:
| 限制项 | 免费版 | 付费版 |
|---|---|---|
| 每日浏览器时长 | 10 分钟 | 无限* |
| 并发会话数 | 3 | 30 |
| 每分钟请求数 | 6 | 180 |
* 受公平使用政策(fair-use policy)约束。此外,gotchas.md 补充了一项对所有层级一致的约束:会话 keep-alive 上限均为 10 分钟。这意味着免费版每天累计只有 10 分钟的浏览器执行时间,并发会话最多 3 个——架构上必须围绕"单会话多页面"而非"多会话"来设计(详见第六节的并发优化)。
四、配置与部署:从安装到第一个截图
本节完整继承 configuration.md 的配置内容,并补充参数注释。
4.1 安装:必须使用 Cloudflare 专用包
npm install @cloudflare/puppeteer # 或 @cloudflare/playwright关键前提:标准puppeteer/playwright包在 Workers 中无法工作,必须使用 Cloudflare 维护的专用发行版,二者 API 与官方版本保持一致但针对 Workers 运行时做了适配。
4.2 wrangler.json:两个必需项
{ "name": "browser-worker", "main": "src/index.ts", "compatibility_date": "2025-01-01", "compatibility_flags": ["nodejs_compat"], "browser": { "binding": "MYBROWSER" } }两个必需配置项:
compatibility_flags: ["nodejs_compat"]:启用 Node.js 兼容层,Puppeteer/Playwright 依赖它运行;browser.binding: "MYBROWSER":声明 Browser Rendering 绑定,绑定名可自定义(如BROWSER),后续代码通过env.MYBROWSER访问。
在 pulumi 参考 中还可以看到以 IaC 方式声明同一绑定的等价写法:browserBinding: {name: "BROWSER"}。
4.3 TypeScript 类型声明
interface Env { MYBROWSER: Fetcher; } export default { async fetch(request: Request, env: Env): Promise<Response> { // 使用 env.MYBROWSER 启动浏览器 } } satisfies ExportedHandler<Env>;4.4 本地开发:必须--remote
wrangler dev --remote # --remote 是使用 browser binding 的硬性要求本地模式(local mode)不支持 Browser Rendering——绑定只能在远程(边缘)环境真实生效,这是调试时最常见的坑之一,务必牢记。
4.5 不走 Workers:纯 REST 方式
如果选择 REST API 路线,则无需任何 wrangler 配置,只需在 Cloudflare 控制台创建一个带"Browser Rendering - Edit"权限的 API Token:
curl -X POST \ 'https://api.cloudflare.com/client/v4/accounts/{accountId}/browser-rendering/screenshot' \ -H 'Authorization: Bearer TOKEN' \ -d '{"url": "https://example.com"}' --output screenshot.png4.6 环境要求清单
| 要求 | 取值 |
|---|---|
| Node.js 兼容性 | nodejs_compat兼容标志 |
| Compatibility date | 2023-03-01 及以上 |
| 模块格式 | 仅 ES modules |
| 浏览器 | Chromium 119+(不支持 Firefox/Safari) |
明确不支持:WebGL、WebRTC、浏览器扩展、file://协议、Service Worker 语法。
4.7 常见故障排查表
| 错误 | 解决方案 |
|---|---|
MYBROWSER is undefined | 使用wrangler dev --remote运行 |
nodejs_compat not enabled | 在compatibility_flags中添加该标志 |
Module not found | 执行npm install @cloudflare/puppeteer |
Browser Rendering not available | 在 Cloudflare 控制台启用该服务 |
五、API 参考:REST 端点与 Workers Bindings
本节完整继承 api.md 的内容。
5.1 REST API 概览
- Base:
https://api.cloudflare.com/client/v4/accounts/{accountId}/browser-rendering - 认证:
Authorization: Bearer <token>(需 Browser Rendering - Edit 权限)
端点清单:
| 端点 | 说明 | 关键选项 |
|---|---|---|
/content | 获取渲染后的 HTML | url、waitUntil |
/screenshot | 捕获图片 | screenshotOptions: {type, fullPage, clip} |
/pdf | 生成 PDF | pdfOptions: {format, landscape, margin} |
/snapshot | HTML + 内联资源 | url |
/scrape | 按选择器提取 | selectors: ["h1", ".price"] |
/json | AI 结构化提取 | schema: {name: "string", price: "number"} |
/links | 获取所有链接 | url |
/markdown | 转换为 Markdown | url |
完整截图调用示例:
curl -X POST '.../browser-rendering/screenshot' \ -H "Authorization: Bearer $TOKEN" \ -d '{"url":"https://example.com","screenshotOptions":{"fullPage":true}}'5.2 Workers Binding 声明
// wrangler.jsonc { "browser": { "binding": "MYBROWSER" } }5.3 Puppeteer 完整用法
import puppeteer from "@cloudflare/puppeteer"; const browser = await puppeteer.launch(env.MYBROWSER, { keep_alive: 600000 }); const page = await browser.newPage(); await page.goto('https://example.com', { waitUntil: 'networkidle0' }); // 内容 const html = await page.content(); const title = await page.title(); // 截图/PDF await page.screenshot({ fullPage: true, type: 'png' }); await page.pdf({ format: 'A4', printBackground: true }); // 交互 await page.click('#button'); await page.type('#input', 'text'); await page.evaluate(() => document.querySelector('h1')?.textContent); // 会话管理 const sessions = await puppeteer.sessions(env.MYBROWSER); const limits = await puppeteer.limits(env.MYBROWSER); await browser.close();5.4 Playwright 完整用法
import { launch, connect } from "@cloudflare/playwright"; const browser = await launch(env.MYBROWSER, { keep_alive: 600000 }); const page = await browser.newPage(); await page.goto('https://example.com', { waitUntil: 'networkidle' }); // 现代选择器 await page.locator('.button').click(); await page.getByText('Submit').click(); await page.getByTestId('search').fill('query'); // 上下文隔离 const context = await browser.newContext({ viewport: { width: 1920, height: 1080 }, userAgent: 'custom' }); await browser.close();5.5 会话管理三件套
// 列出当前会话 await puppeteer.sessions(env.MYBROWSER); // 连接既有会话(复用) await puppeteer.connect(env.MYBROWSER, sessionId); // 检查配额 await puppeteer.limits(env.MYBROWSER); // 返回形如 { remaining: ms, total: ms, concurrent: n }5.6 关键选项速查
| 选项 | 可选值 |
|---|---|
waitUntil | load、domcontentloaded、networkidle0、networkidle2 |
keep_alive | 最大 600000ms(10 分钟) |
screenshot.type | png、jpeg |
pdf.format | A4、Letter、Legal |
六、实战模式:可直接复制的代码骨架
本节完整继承 patterns.md 的全部模式。
6.1 最简 Worker:抓取页面 HTML
import puppeteer from "@cloudflare/puppeteer"; export default { async fetch(request, env) { const browser = await puppeteer.launch(env.MYBROWSER); try { const page = await browser.newPage(); await page.goto("https://example.com"); return new Response(await page.content()); } finally { await browser.close(); // 必须放在 finally 中 } } };6.2 会话复用:用 KV 存 sessionId
会话复用的核心价值在于性能:冷启动约 1~2 秒,而热连接仅约 100~200 毫秒。将 sessionId 存入 KV 即可实现跨请求复用:
let sessionId = await env.SESSION_KV.get("browser-session"); if (sessionId) { browser = await puppeteer.connect(env.MYBROWSER, sessionId); } else { browser = await puppeteer.launch(env.MYBROWSER, { keep_alive: 600000 }); await env.SESSION_KV.put("browser-session", browser.sessionId(), { expirationTtl: 600 }); } // 注意:此处不要关闭浏览器,以保持会话存活6.3 常用操作速查表
| 任务 | 代码 |
|---|---|
| 截图 | await page.screenshot({ type: "png", fullPage: true }) |
await page.pdf({ format: "A4", printBackground: true }) | |
| 提取数据 | await page.evaluate(() => document.querySelector('h1').textContent) |
| 填写表单 | await page.type('#input', 'value'); await page.click('button') |
| 等待导航 | await Promise.all([page.waitForNavigation(), page.click('a')]) |
6.4 并行抓取:单浏览器多页面
const pages = await Promise.all(urls.map(() => browser.newPage())); await Promise.all(pages.map((p, i) => p.goto(urls[i]))); const titles = await Promise.all(pages.map(p => p.title()));6.5 Playwright 现代选择器
import { launch } from "@cloudflare/playwright"; const browser = await launch(env.MYBROWSER); await page.getByRole("button", { name: "Sign in" }).click(); await page.getByLabel("Email").fill("user@example.com"); await page.getByTestId("submit-button").click();6.6 隐身上下文:无需多个浏览器即可隔离会话
const ctx1 = await browser.createIncognitoBrowserContext(); const ctx2 = await browser.createIncognitoBrowserContext(); // 每个上下文拥有独立的 cookies 与存储6.7 配额检查:提前熔断
const limits = await puppeteer.limits(env.MYBROWSER); if (limits.remaining < 60000) return new Response("Quota low", { status: 429 });6.8 错误处理模板
try { await page.goto(url, { timeout: 30000, waitUntil: "networkidle0" }); } catch (e) { if (e.message.includes("timeout")) return new Response("Timeout", { status: 504 }); if (e.message.includes("Session limit")) return new Response("Too many sessions", { status: 429 }); } finally { if (browser) await browser.close(); }七、避坑指南:配额、生命周期与性能
本节完整继承 gotchas.md 的全部要点。
7.1 完整配额表与配额自检
| 限制项 | 免费版 | 付费版 |
|---|---|---|
| 每日浏览器时长 | 10 分钟 | 无限* |
| 并发会话 | 3 | 30 |
| 每分钟请求 | 6 | 180 |
| 会话 keep-alive | 上限 10 分钟 | 上限 10 分钟 |
* 受公平使用政策约束。
代码自检配额:
const limits = await puppeteer.limits(env.MYBROWSER); // 返回形如 { remaining: 540000, total: 600000, concurrent: 2 }7.2 务必关闭浏览器(生命周期差异是最大的坑)
const browser = await puppeteer.launch(env.MYBROWSER); try { const page = await browser.newPage(); await page.goto("https://example.com"); return new Response(await page.content()); } finally { await browser.close(); // 始终放在 finally }Workers 与 REST 的生命周期差异:REST API 会在超时后自动关闭会话;而 Workers 必须显式调用close(),否则会话会一直存活到keep_alive过期——既消耗配额,又占用并发会话额度。
7.3 并发优化:用页面代替会话
免费版只有 3 个并发会话,因此:
// ❌ 3 个会话(直接触顶免费版上限) const browser1 = await puppeteer.launch(env.MYBROWSER); const browser2 = await puppeteer.launch(env.MYBROWSER); // ✅ 1 个会话、多个页面 const browser = await puppeteer.launch(env.MYBROWSER); const page1 = await browser.newPage(); const page2 = await browser.newPage();7.4 常见错误速查
| 错误 | 原因 | 修复 |
|---|---|---|
| Session limit exceeded | 并发会话过多 | 关闭未使用的浏览器,多用页面少用浏览器 |
| Page navigation timeout | 页面加载慢或忙碌页面上等待networkidle | 增大 timeout,改用waitUntil: "load" |
| Session not found | 会话已过期 | 捕获异常后启动新会话 |
| Evaluation failed | DOM 元素缺失 | 使用?.可选链 |
| Protocol error: Target closed | 关闭前仍在执行操作 | 关闭前先 await 全部操作 |
7.5page.evaluate()作用域陷阱
// ❌ 外部作用域不可用——selector 在 Worker 作用域,回调在浏览器作用域 const selector = "h1"; await page.evaluate(() => document.querySelector(selector)); // ✅ 必须作为参数传入 await page.evaluate((sel) => document.querySelector(sel)?.textContent, selector);7.6 性能优化三板斧
1. 按需选择waitUntil(从快到慢):
domcontentloaded- DOM 就绪即可load- load 事件(默认)networkidle0- 网络空闲 500ms
2. 拦截并阻断非必要资源:
await page.setRequestInterception(true); page.on("request", (req) => { if (["image", "stylesheet", "font"].includes(req.resourceType())) { req.abort(); } else { req.continue(); } });3. 会话复用:冷启动约 1~2 秒,热连接约 100~200 毫秒,用 KV 持久化 sessionId(见 6.2 节)可显著降低延迟。
八、按角色推荐的阅读路径
原文档给出了清晰的学习路径,转换为仓库内全局相对路径后如下:
第一次接触 Browser Rendering:
- configuration.md - 安装与部署
- patterns.md - 常见用例与示例代码
- api.md - API 参考
- gotchas.md - 常见坑与规避
按任务直达:
- 安装/部署 → configuration.md
- API 参考/端点 → api.md
- 示例代码/模式 → patterns.md
- 调试/排障 → gotchas.md
REST API 用户:从 api.md 的 REST API 章节开始,再查看 gotchas.md 了解速率限制。
Workers 用户:先读 configuration.md,再对照 patterns.md 掌握会话管理,最后以 api.md 的 Workers Bindings 部分为准。
九、小结
围绕 Cloudflare Browser Rendering,决策的关键可以浓缩为三点:按任务性质选入口(REST 一次即用,Workers 长期编排)、按自动化需求选库(Puppeteer 走 CDP 深度控制,Playwright 走现代选择器快速开发)、按配额上限设计架构(免费版 10 分钟/天、3 并发会话、6 请求/分钟,务必单会话多页面 + 会话复用)。在此基础上,把finally { browser.close() }、keep_alive上限 10 分钟、page.evaluate参数传值这三条铁律内化为肌肉记忆,即可在 Cloudflare 边缘稳定跑通截图、PDF、抓取与自动化测试。继续深入可阅读本技能目录下的 api.md 与 gotchas.md,或在cloudflare-deploy技能总入口 SKILL.md 中探索 KV、D1、Queues 等周边服务,组合出完整的边缘应用。
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考