关于 cloudflare-browser-rendering
面向开发者的 Cloudflare Browser Rendering 参考技能,介绍如何通过 Workers Bindings 使用 Puppeteer 或 Playwright 自动化浏览器,涵盖页面捕获、PDF、抓取和测试,并说明会话管理。
主要功能
- 01Puppeteer 与 Playwright API 指南
- 02浏览器会话复用与监控
- 03页面截图与 PDF 生成
- 04动态页面导航与内容提取
- 05Workers Binding 配置指南
适用场景
- 01捕获网页截图或 PDF
- 02抓取动态网站内容
- 03自动化前端测试
- 04在多个 Worker 间复用浏览器会话
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill cloudflare-browser-rendering安装后在 Claude Code 中输入/cloudflare-browser-rendering确认已加载。
附带文件README.mdSKILL.json
详细介绍
Cloudflare Browser Rendering - 完整参考
用于构建 Cloudflare Browser Rendering 浏览器自动化工作流的生产就绪知识域。
状态:生产就绪 ✅ 上次更新:2025-10-22 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:@cloudflare/[email protected]、@cloudflare/[email protected]、[email protected]
目录
快速入门(5 分钟)
1. 添加 Browser 绑定
wrangler.jsonc:
{
"name": "browser-worker",
"main": "src/index.ts",
"compatibility_date": "2023-03-14",
"compatibility_flags": ["nodejs_compat"],
"browser": {
"binding": "MYBROWSER"
}
}
为什么需要 nodejs_compat? Browser Rendering 需要 Node.js API 和 polyfill。
2. 安装 Puppeteer
npm install @cloudflare/puppeteer
3. 截取第一张屏幕截图
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { searchParams } = new URL(request.url);
const url = searchParams.get("url") || "https://example.com";
// Launch browser
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
// Navigate and capture
await page.goto(url);
const screenshot = await page.screenshot();
// Clean up
await browser.close();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
}
};
4. 部署
npx wrangler deploy
在以下地址测试:https://your-worker.workers.dev/?url=https://example.com
重要:
- 始终将
env.MYBROWSER传给puppeteer.launch()(不能是 undefined) - 完成后始终调用
browser.close()(或使用browser.disconnect()以便重用会话) - 使用
nodejs_compat兼容性标志
Browser Rendering 概述
什么是 Browser Rendering?
Cloudflare Browser Rendering 提供运行在 Cloudflare 全球网络上的无头 Chromium 浏览器。使用 Puppeteer 和 Playwright 等熟悉的工具自动执行浏览器任务:
- 屏幕截图 - 捕获网页的可视化快照
- PDF 生成 - 将 HTML/URL 转换为 PDF
- 网页抓取 - 从动态网站提取内容
- 测试 - 自动执行前端测试
- 爬取 - 浏览多页工作流
两种集成方式
| 方式 | 适用场景 | 复杂度 |
|---|---|---|
| Workers 绑定 | 复杂自动化、自定义工作流、会话管理 | 高级 |
| REST API | 简单的屏幕截图/PDF 任务 | 简单 |
本技能介绍 Workers 绑定(提供完整 Puppeteer/Playwright API 的高级方式)。
Puppeteer 与 Playwright
| 功能 | Puppeteer | Playwright |
|---|---|---|
| API 熟悉度 | 最受欢迎 | 使用人数不断增长 |
| 包 | @cloudflare/[email protected] | @cloudflare/[email protected] |
| 会话管理 | ✅ 高级 API | ⚠️ 基础 |
| 浏览器支持 | 仅 Chromium | 仅 Chromium(暂不支持 Firefox/Safari) |
| 适用场景 | 屏幕截图、PDF、抓取 | 测试、前端自动化 |
建议:大多数场景使用 Puppeteer。如果你已将 Playwright 用于测试,则它是理想选择。
Puppeteer API 参考
puppeteer.launch()
启动新的浏览器实例。
签名:
await puppeteer.launch(binding: Fetcher, options?: LaunchOptions): Promise<Browser>
参数:
binding(必需)- 来自env.MYBROWSER的浏览器绑定options(可选):keep_alive(数字)- 让浏览器保持运行 N 毫秒(最大值:600000 = 10 分钟)
返回值: Promise<Browser> - 浏览器实例
示例:
const browser = await puppeteer.launch(env.MYBROWSER, {
keep_alive: 60000 // Keep alive for 60 seconds
});
重要: 必须传入 env.MYBROWSER 绑定。出现错误“Cannot read properties of undefined (reading 'fetch')”表示未传入该绑定。
puppeteer.connect()
连接到现有浏览器会话。
签名:
await puppeteer.connect(binding: Fetcher, sessionId: string): Promise<Browser>
使用场景:
- 重用现有浏览器会话以提升性能
- 在多个 Worker 之间共享浏览器实例
- 缩短启动时间
示例:
const sessionId = "478f4d7d-e943-40f6-a414-837d3736a1dc";
const browser = await puppeteer.connect(env.MYBROWSER, sessionId);
puppeteer.sessions()
列出当前运行的浏览器会话。
签名:
await puppeteer.sessions(binding: Fetcher): Promise<SessionInfo[]>
返回值:
interface SessionInfo {
sessionId: string;
startTime: number;
connectionId?: string; // Present if worker is connected
connectionStartTime?: number;
}
示例:
const sessions = await puppeteer.sessions(env.MYBROWSER);
// Find sessions without active connections
const freeSessions = sessions.filter(s => !s.connectionId);
puppeteer.history()
列出最近的会话(包括打开和已关闭的会话)。
签名:
await puppeteer.history(binding: Fetcher): Promise<HistoryEntry[]>
返回值:
interface HistoryEntry {
sessionId: string;
startTime: number;
endTime?: number;
closeReason?: number;
closeReasonText?: string; // "NormalClosure", "BrowserIdle", etc.
}
使用场景: 监控使用模式并调试会话问题。
puppeteer.limits()
检查当前账户限额和可用会话。
签名:
await puppeteer.limits(binding: Fetcher): Promise<LimitsInfo>
返回:
interface LimitsInfo {
activeSessions: Array<{ id: string }>;
maxConcurrentSessions: number;
allowedBrowserAcquisitions: number;
timeUntilNextAllowedBrowserAcquisition: number; // milliseconds
}
示例:
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
return new Response("Rate limit reached", { status: 429 });
}
Browser API
launch() 或 connect() 返回的 Browser 对象上可用的方法。
browser.newPage()
在浏览器中创建一个新页面(标签页)。
签名:
await browser.newPage(): Promise<Page>
示例:
const page = await browser.newPage();
await page.goto("https://example.com");
性能提示: 复用浏览器实例并打开多个标签页,而不是启动新浏览器。
browser.sessionId()
获取当前浏览器会话 ID。
返回: string - 会话 ID
示例:
const sessionId = browser.sessionId();
console.log("Current session:", sessionId);
browser.close()
关闭浏览器并终止会话。
签名:
await browser.close(): Promise<void>
适用场景: 完全结束浏览器使用并希望释放资源时。
browser.disconnect()
断开与浏览器的连接,但不关闭浏览器。
签名:
await browser.disconnect(): Promise<void>
适用场景: 复用会话——允许其他 Worker 稍后连接到同一会话。
示例:
// Keep session alive for reuse
const sessionId = browser.sessionId();
await browser.disconnect(); // Don't close, just disconnect
// Later: puppeteer.connect(env.MYBROWSER, sessionId)
browser.createBrowserContext()
创建一个隔离的无痕浏览器上下文。
签名:
await browser.createBrowserContext(): Promise<BrowserContext>
适用场景:
- 在不同操作之间隔离 Cookie 和缓存
- 测试多用户场景
- 复用浏览器时保持会话隔离
示例:
const context1 = await browser.createBrowserContext();
const context2 = await browser.createBrowserContext();
const page1 = await context1.newPage();
const page2 = await context2.newPage();
// page1 and page2 have separate cookies/cache
Page API
browser.newPage() 返回的 Page 对象上可用的方法。
page.goto()
导航到一个 URL。
签名:
await page.goto(url: string, options?: NavigationOptions): Promise<Response>
选项:
waitUntil- 判定导航完成的时机:"load"- 等待 load 事件(默认)"domcontentloaded"- 等待 DOMContentLoaded"networkidle0"- 等待 500 毫秒,直到没有网络连接"networkidle2"- 等待 500 毫秒,直到网络连接数不超过 2
timeout- 最大导航时间,单位为毫秒(默认:30000)
示例:
await page.goto("https://example.com", {
waitUntil: "networkidle0",
timeout: 60000
});
最佳实践: 动态内容使用 "networkidle0",静态页面使用 "load"。
page.screenshot()
截取页面截图。
签名:
await page.screenshot(options?: ScreenshotOptions): Promise<Buffer>
选项:
fullPage(boolean) - 截取整个可滚动页面(默认:false)type(string) -"png"或"jpeg"(默认:"png")quality(number) - JPEG 质量,范围为 0-100(仅适用于 jpeg)clip(object) - 截取指定区域:{ x, y, width, height }
示例:
// Full page screenshot
const screenshot = await page.screenshot({ fullPage: true });
// JPEG with compression
const screenshot = await page.screenshot({
type: "jpeg",
quality: 80
});
// Specific region
const screenshot = await page.screenshot({
clip: { x: 0, y: 0, width: 800, height: 600 }
});
page.pdf()
生成页面的 PDF。
签名:
await page.pdf(options?: PDFOptions): Promise<Buffer>
选项:
format(string) - 页面格式:"Letter"、"A4"等printBackground(boolean) - 包含背景图形(默认:false)margin(object) -{ top, right, bottom, left }(例如:"1cm")landscape(boolean) - 横向(默认:false)scale(number) - 缩放比例,范围为 0.1-2(默认:1)
示例:
const pdf = await page.pdf({
format: "A4",
printBackground: true,
margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" }
});
return new Response(pdf, {
headers: { "content-type": "application/pdf" }
});
page.content()
获取页面的完整 HTML 内容。
签名:
await page.content(): Promise<string>
示例:
const html = await page.content();
console.log(html); // Full HTML source
page.setContent()
设置自定义 HTML 内容。
签名:
await page.setContent(html: string, options?: NavigationOptions): Promise<void>
适用场景: 根据自定义 HTML 生成 PDF。
示例:
await page.setContent(`
<!DOCTYPE html>
<html>
<head><style>body { font-family: Arial; }</style></head>
<body><h1>Hello World</h1></body>
</html>
`);
const pdf = await page.pdf({ format: "A4" });
page.evaluate()
在浏览器上下文中执行 JavaScript。
签名:
await page.evaluate<T>(fn: () => T): Promise<T>
适用场景:
- 从 DOM 提取数据
- 操作页面内容
- 作为 XPath 的替代方案(不直接支持 XPath)
示例:
// Extract text content
const title = await page.evaluate(() => document.title);
// Extract structured data
const data = await page.evaluate(() => ({
title: document.title,
url: window.location.href,
headings: Array.from(document.querySelectorAll("h1, h2")).map(el => el.textContent),
links: Array.from(document.querySelectorAll("a")).map(el => el.href)
}));
// XPath workaround (XPath selectors not directly supported)
const innerHtml = await page.evaluate(() => {
return new XPathEvaluator()
.createExpression("/html/body/div/h1")
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE)
.singleNodeValue.innerHTML;
});
page.waitForSelector()
等待元素出现在 DOM 中。
签名:
await page.waitForSelector(selector: string, options?: WaitForOptions): Promise<ElementHandle>
选项:
timeout(number) - 最大等待时间,单位为毫秒visible(boolean) - 等待元素可见
示例:
await page.goto("https://example.com");
await page.waitForSelector("#content", { visible: true });
const screenshot = await page.screenshot();
page.type()
在输入框中输入文本。
签名:
await page.type(selector: string, text: string): Promise<void>
示例:
await page.type('input[name="email"]', '[email protected]');
page.click()
点击元素。
签名:
await page.click(selector: string): Promise<void>
示例:
await page.click('button[type="submit"]');
await page.waitForNavigation();
Playwright API 参考
Playwright 提供与 Puppeteer 类似的 API,但略有差异。
安装
npm install @cloudflare/playwright
基本示例
import { env } from "cloudflare:test";
import { chromium } from "@cloudflare/playwright";
interface Env {
BROWSER: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const browser = await chromium.launch(env.BROWSER);
const page = await browser.newPage();
await page.goto("https://example.com");
const screenshot = await page.screenshot();
await browser.close();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
}
};
与 Puppeteer 的主要区别
| 功能 | Puppeteer | Playwright |
|---|---|---|
| 导入 | import puppeteer from "@cloudflare/puppeteer" | import { chromium } from "@cloudflare/playwright" |
| 启动 | puppeteer.launch(env.MYBROWSER) | chromium.launch(env.BROWSER) |
| 会话 API | ✅ 高级(会话、历史记录、限制) | ⚠️ 基础 |
| 自动等待 | 手动调用 waitForSelector() | 内置自动等待 |
| 选择器 | 仅 CSS | CSS、文本、XPath(通过 evaluate 变通实现) |
建议:除非你已有需要迁移的 Playwright 测试,否则请继续使用 Puppeteer。
会话管理
会话管理为何重要
问题:启动新浏览器速度慢,还会占用并发额度。
解决方案:在多个请求间复用浏览器会话。
优点:
- ⚡ 速度更快(无需冷启动)
- 💰 占用更少并发额度
- 📊 更好地利用资源
会话复用模式
import puppeteer from "@cloudflare/puppeteer";
async function getBrowser(env: Env): Promise<{ browser: Browser; launched: boolean }> {
// Check for available sessions
const sessions = await puppeteer.sessions(env.MYBROWSER);
const freeSessions = sessions.filter(s => !s.connectionId);
if (freeSessions.length > 0) {
// Reuse existing session
try {
const browser = await puppeteer.connect(env.MYBROWSER, freeSessions[0].sessionId);
return { browser, launched: false };
} catch (e) {
console.log("Failed to connect, launching new browser");
}
}
// Launch new session
const browser = await puppeteer.launch(env.MYBROWSER);
return { browser, launched: true };
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { browser, launched } = await getBrowser(env);
try {
const page = await browser.newPage();
await page.goto("https://example.com");
const screenshot = await page.screenshot();
// Disconnect (don't close) to allow reuse
await browser.disconnect();
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
} catch (error) {
await browser.close(); // Close on error
throw error;
}
}
};
重要:
- 使用
browser.disconnect()保持会话运行 - 出错时使用
browser.close() - 始终处理连接失败
无痕浏览器上下文
使用浏览器上下文,在共享浏览器实例的同时隔离 Cookie 和缓存。
优点:
- 共享浏览器(减少并发占用)
- 隔离会话(分别使用 Cookie 和缓存)
- 测试多用户场景
示例:
const browser = await puppeteer.launch(env.MYBROWSER);
// Create isolated contexts
const context1 = await browser.createBrowserContext();
const context2 = await browser.createBrowserContext();
// Each context has its own cookies/cache
const page1 = await context1.newPage();
const page2 = await context2.newPage();
await page1.goto("https://app.example.com"); // User 1
await page2.goto("https://app.example.com"); // User 2
await context1.close();
await context2.close();
await browser.close();
多个标签页与多个浏览器
场景:抓取 10 个 URL
❌ 不佳(10 个浏览器):
for (const url of urls) {
const browser = await puppeteer.launch(env.MYBROWSER); // 10 launches!
// ... scrape ...
await browser.close();
}
✅ 更佳(1 个浏览器,10 个标签页):
const browser = await puppeteer.launch(env.MYBROWSER);
const results = await Promise.all(
urls.map(async (url) => {
const page = await browser.newPage();
await page.goto(url);
const data = await page.evaluate(() => ({
title: document.title,
text: document.body.innerText
}));
await page.close();
return { url, data };
})
);
await browser.close();
优点:只占用 1 个并发浏览器,而不是 10 个。
常见模式
模式 1:使用 KV 缓存截图
缓存截图,以减少浏览器使用量并提升性能。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
CACHE: KVNamespace;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { searchParams } = new URL(request.url);
const url = searchParams.get("url");
if (!url) {
return new Response("Missing ?url parameter", { status: 400 });
}
const normalizedUrl = new URL(url).toString();
// Check cache
let screenshot = await env.CACHE.get(normalizedUrl, { type: "arrayBuffer" });
if (!screenshot) {
// Generate screenshot
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(normalizedUrl);
screenshot = await page.screenshot();
await browser.close();
// Cache for 24 hours
await env.CACHE.put(normalizedUrl, screenshot, {
expirationTtl: 60 * 60 * 24
});
}
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
}
};
模式 2:从 HTML 生成 PDF
将自定义 HTML 转换为 PDF。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const { html } = await request.json<{ html: string }>();
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
// Set custom HTML
await page.setContent(html, { waitUntil: "networkidle0" });
// Generate PDF
const pdf = await page.pdf({
format: "A4",
printBackground: true,
margin: {
top: "1cm",
right: "1cm",
bottom: "1cm",
left: "1cm"
}
});
await browser.close();
return new Response(pdf, {
headers: {
"content-type": "application/pdf",
"content-disposition": "attachment; filename=document.pdf"
}
});
}
};
模式 3:使用结构化数据进行网页抓取
从网页中提取结构化数据。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
interface ProductData {
title: string;
price: string;
description: string;
image: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { searchParams } = new URL(request.url);
const url = searchParams.get("url");
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(url!, { waitUntil: "networkidle0" });
// Extract structured data
const data = await page.evaluate<ProductData>(() => {
return {
title: document.querySelector("h1")?.textContent || "",
price: document.querySelector(".price")?.textContent || "",
description: document.querySelector(".description")?.textContent || "",
image: document.querySelector("img")?.src || ""
};
});
await browser.close();
return Response.json({ url, data });
}
};
模式 4:批量抓取多个 URL
使用标签页高效抓取多个 URL。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
async function scrapeUrl(browser: Browser, url: string): Promise<any> {
const page = await browser.newPage();
try {
await page.goto(url, { waitUntil: "networkidle0", timeout: 30000 });
const data = await page.evaluate(() => ({
title: document.title,
url: window.location.href,
text: document.body.innerText.slice(0, 500) // First 500 chars
}));
await page.close();
return { success: true, url, data };
} catch (error) {
await page.close();
return {
success: false,
url,
error: error instanceof Error ? error.message : "Unknown error"
};
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { urls } = await request.json<{ urls: string[] }>();
if (!urls || urls.length === 0) {
return new Response("Missing urls array", { status: 400 });
}
const browser = await puppeteer.launch(env.MYBROWSER);
// Scrape all URLs in parallel (each in its own tab)
const results = await Promise.all(
urls.map(url => scrapeUrl(browser, url))
);
await browser.close();
return Response.json({ results });
}
};
模式 5:AI 增强型抓取
将 Browser Rendering 与 Workers AI 结合,以提取结构化数据。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
AI: Ai;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { searchParams } = new URL(request.url);
const url = searchParams.get("url");
// Scrape page content
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto(url!, { waitUntil: "networkidle0" });
const bodyContent = await page.$eval("body", el => el.innerHTML);
await browser.close();
// Extract structured data with AI
const response = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", {
messages: [
{
role: "user",
content: `Extract product information as JSON from this HTML. Include: name, price, description, availability.\n\nHTML:\n${bodyContent.slice(0, 4000)}`
}
]
});
// Parse AI response
let productData;
try {
productData = JSON.parse(response.response);
} catch {
productData = { raw: response.response };
}
return Response.json({ url, product: productData });
}
};
模式 6:表单填写与自动化
自动提交表单并执行多步骤工作流。
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { email, password } = await request.json<{
email: string;
password: string;
}>();
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
// Navigate to login page
await page.goto("https://example.com/login");
// Fill form
await page.type('input[name="email"]', email);
await page.type('input[name="password"]', password);
// Submit and wait for navigation
await page.click('button[type="submit"]');
await page.waitForNavigation();
// Extract result
const result = await page.evaluate(() => ({
url: window.location.href,
title: document.title,
loggedIn: document.querySelector(".user-profile") !== null
}));
await browser.close();
return Response.json(result);
}
};
定价与限制
免费套餐(Workers Free)
| 功能 | 限制 |
|---|---|
| 浏览器使用时长 | 每天 10 分钟 |
| 并发浏览器数 | 每个账户 3 个 |
| 每分钟新建浏览器数 | 每分钟 3 个 |
| REST API 请求数 | 每分钟 6 次 |
| 浏览器超时 | 60 秒(空闲时) |
付费套餐(Workers Paid)
| 功能 | 套餐内额度 | 超出套餐额度 |
|---|---|---|
| 浏览器使用时长 | 每月 10 小时 | 每增加 1 个浏览器小时收费 $0.09 |
| 并发浏览器数 | 10 个(月平均值) | 每增加 1 个并发浏览器收费 $2.00 |
| 每分钟新建浏览器数 | 每分钟 30 个 | 可申请提高限额 |
| REST API 请求数 | 每分钟 180 次 | 可申请提高限额 |
| 浏览器超时 | 60 秒(可通过 keep_alive 延长至 10 分钟) | - |
| 最大并发浏览器数 | 每个账户 30 个 | 可申请提高限额 |
定价计算
时长费用:
- 按浏览器小时收费
- 计费周期结束时四舍五入到最近的整小时
- 失败的请求(超时)不收费
并发费用:
- 按每日峰值用量的月平均值计算
- 示例:15 天使用 10 个浏览器,15 天使用 20 个浏览器 = (10×15 + 20×15) / 30 = 平均 15 个
- 平均 15 个 - 套餐内额度 10 个 = 5 × $2.00 = $10.00
月度账单示例:
- 使用 50 个浏览器小时:(50 - 10) × $0.09 = $3.60
- 平均并发 15 个浏览器:(15 - 10) × $2.00 = $10.00
- 总计:$13.60
速率限制
每秒速率: 速率限制按秒执行。例如:
- 每分钟 180 个请求 = 每秒 3 个请求
- 不能一次性发送全部 180 个请求;必须均匀分散发送
处理速率限制:
async function launchWithRetry(env: Env, maxRetries = 3): Promise<Browser> {
for (let i = 0; i < maxRetries; i++) {
try {
return await puppeteer.launch(env.MYBROWSER);
} catch (error) {
if (i === maxRetries - 1) throw error;
// Check if rate limited
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
// Wait before retry
const delay = limits.timeUntilNextAllowedBrowserAcquisition || 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error("Failed to launch browser");
}
已知问题预防
此技能可预防6 个已记录的问题:
问题 #1:不支持 XPath 选择器
错误:“不支持 XPath 选择器”或选择器失败
来源: https://developers.cloudflare.com/browser-rendering/faq/#why-cant-i-use-an-xpath-selector-when-using-browser-rendering-with-puppeteer
原因: XPath 会对 Workers 构成安全风险
**预防方法:**使用 CSS 选择器,或通过 XPathEvaluator 调用 page.evaluate()
解决方案:
// ❌ Don't use XPath directly (not supported)
// await page.$x('/html/body/div/h1')
// ✅ Use CSS selector
const heading = await page.$("div > h1");
// ✅ Or use XPath in page.evaluate()
const innerHtml = await page.evaluate(() => {
return new XPathEvaluator()
.createExpression("/html/body/div/h1")
.evaluate(document, XPathResult.FIRST_ORDERED_NODE_TYPE)
.singleNodeValue.innerHTML;
});
问题 #2:未传递浏览器绑定
错误:“无法读取 undefined 的属性(读取 'fetch')”
来源: https://developers.cloudflare.com/browser-rendering/faq/#cannot-read-properties-of-undefined-reading-fetch
**原因:**调用 puppeteer.launch() 时未传入浏览器绑定
**预防方法:**始终将 env.MYBROWSER 传递给 launch
解决方案:
// ❌ Missing browser binding
const browser = await puppeteer.launch(); // Error!
// ✅ Pass binding
const browser = await puppeteer.launch(env.MYBROWSER);
问题 #3:浏览器超时(60 秒)
**错误:**浏览器在 60 秒后意外关闭
来源: https://developers.cloudflare.com/browser-rendering/platform/limits/#note-on-browser-timeout
**原因:**默认超时时间为不活动 60 秒
**预防方法:**使用 keep_alive 选项将时间延长至最多 10 分钟
解决方案:
// Extend timeout to 5 minutes for long-running tasks
const browser = await puppeteer.launch(env.MYBROWSER, {
keep_alive: 300000 // 5 minutes = 300,000 ms
});
**注意:**如果在指定时长内没有 devtools 命令,浏览器就会关闭。
问题 #4:达到并发限制
错误:“超出速率限制”或无法启动新浏览器
来源: https://developers.cloudflare.com/browser-rendering/platform/limits/
**原因:**超出并发浏览器数量限制(免费版为 3 个,付费版为 10–30 个)
**预防方法:**复用会话、使用标签页而非多个浏览器,并在启动前检查限制
解决方案:
// 1. Check limits before launching
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
return new Response("Concurrency limit reached", { status: 429 });
}
// 2. Reuse sessions
const sessions = await puppeteer.sessions(env.MYBROWSER);
const freeSessions = sessions.filter(s => !s.connectionId);
if (freeSessions.length > 0) {
const browser = await puppeteer.connect(env.MYBROWSER, freeSessions[0].sessionId);
}
// 3. Use tabs instead of multiple browsers
const browser = await puppeteer.launch(env.MYBROWSER);
const page1 = await browser.newPage();
const page2 = await browser.newPage(); // Same browser, different tabs
问题 #5:本地开发请求大小限制
**错误:**在 wrangler dev 中,超过 1MB 的请求会失败
来源: https://developers.cloudflare.com/browser-rendering/faq/#does-local-development-support-all-browser-rendering-features
**原因:**本地开发限制
**预防方法:**在浏览器绑定中使用 remote: true 进行本地开发
解决方案:
// wrangler.jsonc for local development
{
"browser": {
"binding": "MYBROWSER",
"remote": true // Use real headless browser during dev
}
}
问题 #6:始终触发机器人防护
**错误:**网站将请求拦截为机器人流量
来源: https://developers.cloudflare.com/browser-rendering/faq/#will-browser-rendering-bypass-cloudflares-bot-protection
**原因:**Browser Rendering 请求始终会被识别为机器人
**预防方法:**无法绕过;如果要抓取自己的区域,请创建 WAF 跳过规则
解决方案:
// ❌ Cannot bypass bot protection
// Requests will always be identified as bots
// ✅ If scraping your own Cloudflare zone:
// 1. Go to Security > WAF > Custom rules
// 2. Create skip rule with custom header:
// Header: X-Custom-Auth
// Value: your-secret-token
// 3. Pass header in your scraping requests
// Note: Automatic headers are included:
// - cf-biso-request-id
// - cf-biso-devtools
生产环境检查清单
将 Browser Rendering Workers 部署到生产环境之前:
配置
- 已在 wrangler.jsonc 中配置浏览器绑定
- 已启用 nodejs_compat 标志(Browser Rendering 必需)
- 如果任务耗时超过 60 秒,已设置 Keep-alive 超时时间
- 如有需要,已为本地开发启用远程绑定
错误处理
- 已为速率限制实现重试逻辑
- 已为 page.goto()处理超时
- 已在 try-finally 块中清理浏览器
- 已在启动浏览器前检查并发限制
- 浏览器不可用时可优雅降级
性能
- 已为高流量路由实现会话复用
- 使用多个标签页而非多个浏览器
- 使用无痕上下文隔离会话
- 为重复的屏幕截图/PDF 使用 KV 缓存
- 批量处理操作以最大限度地利用浏览器
监控
- 记录浏览器会话 ID 以便调试
- 跟踪浏览器运行时长以估算费用
- 使用 puppeteer.limits()监控并发使用情况
- 对速率限制错误发出警报
- 在 https://dash.cloudflare.com/?to=/:account/workers/browser-rendering 通过仪表板监控
安全
- 对 URL 进行输入验证(防止 SSRF)
- 设置超时限制以防止滥用
- 对公开端点实施速率限制
- 对敏感的抓取端点进行身份验证
- 如果抓取自己的区域,配置 WAF 规则
测试
- 使用不同页面尺寸测试截图捕获
- 使用自定义 HTML 测试 PDF 生成
- 使用动态内容(networkidle0)测试抓取
- 测试错误场景(无效 URL、超时)
- 进行负载测试,检查并发限制
错误处理模板
适用于生产环境的完整错误处理:
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
async function withBrowser<T>(
env: Env,
fn: (browser: Browser) => Promise<T>
): Promise<T> {
let browser: Browser | null = null;
try {
// Check limits
const limits = await puppeteer.limits(env.MYBROWSER);
if (limits.allowedBrowserAcquisitions === 0) {
throw new Error("Rate limit reached. Retry after: " + limits.timeUntilNextAllowedBrowserAcquisition + "ms");
}
// Launch or connect
const sessions = await puppeteer.sessions(env.MYBROWSER);
const freeSessions = sessions.filter(s => !s.connectionId);
if (freeSessions.length > 0) {
try {
browser = await puppeteer.connect(env.MYBROWSER, freeSessions[0].sessionId);
} catch {
browser = await puppeteer.launch(env.MYBROWSER);
}
} else {
browser = await puppeteer.launch(env.MYBROWSER);
}
// Execute user function
const result = await fn(browser);
// Disconnect (keep session alive)
await browser.disconnect();
return result;
} catch (error) {
// Close on error
if (browser) {
await browser.close();
}
throw error;
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
const screenshot = await withBrowser(env, async (browser) => {
const page = await browser.newPage();
await page.goto("https://example.com", {
waitUntil: "networkidle0",
timeout: 30000
});
return await page.screenshot();
});
return new Response(screenshot, {
headers: { "content-type": "image/png" }
});
} catch (error) {
console.error("Browser error:", error);
return new Response(
JSON.stringify({
error: error instanceof Error ? error.message : "Unknown error"
}),
{ status: 500, headers: { "content-type": "application/json" } }
);
}
}
};
使用捆绑资源
模板(templates/)
常见模式的即用型代码模板:
basic-screenshot.ts- 最简截图示例screenshot-with-kv-cache.ts- 使用 KV 缓存的截图pdf-generation.ts- 从 HTML 或 URL 生成 PDFweb-scraper-basic.ts- 基本网页抓取模式web-scraper-batch.ts- 批量抓取多个 URLsession-reuse.ts- 通过复用会话提升性能ai-enhanced-scraper.ts- 使用 Workers AI 进行抓取playwright-example.ts- Playwright 替代方案示例wrangler-browser-config.jsonc- 浏览器绑定配置
用法:
# Copy template to your project
cp ~/.claude/skills/cloudflare-browser-rendering/templates/basic-screenshot.ts src/index.ts
参考资料(references/)
深入文档:
session-management.md- 完整的会话复用指南pricing-and-limits.md- 详细的定价说明common-errors.md- 所有已知问题及解决方案puppeteer-vs-playwright.md- 功能对比和迁移
加载时机: 实现高级模式或调试特定问题时查阅。
依赖项
必需:
@cloudflare/[email protected]- 适用于 Workers 的 Puppeteer[email protected]+- Cloudflare 命令行工具
可选:
@cloudflare/[email protected]- 适用于 Workers 的 Playwright(替代方案)@cloudflare/[email protected]+- TypeScript 类型
相关技能:
cloudflare-worker-base- 使用 Hono 配置 Workercloudflare-kv- 用于截图的 KV 缓存cloudflare-r2- 用于存储生成文件的 R2cloudflare-workers-ai- AI 增强型抓取
官方文档
- Browser Rendering 文档:https://developers.cloudflare.com/browser-rendering/
- Puppeteer API:https://pptr.dev/api/
- Playwright API:https://playwright.dev/docs/api/class-playwright
- Cloudflare Puppeteer 分支:https://github.com/cloudflare/puppeteer
- Cloudflare Playwright 分支:https://github.com/cloudflare/playwright
- 定价:https://developers.cloudflare.com/browser-rendering/platform/pricing/
- 限制:https://developers.cloudflare.com/browser-rendering/platform/limits/
软件包版本(已于 2025-10-22 验证)
{
"dependencies": {
"@cloudflare/puppeteer": "^1.0.4"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20251014.0",
"wrangler": "^4.43.0"
}
}
替代方案(Playwright):
{
"dependencies": {
"@cloudflare/playwright": "^1.0.0"
}
}
故障排除
问题:“Cannot read properties of undefined (reading 'fetch')”
解决方案: 将浏览器绑定传递给 puppeteer.launch():
const browser = await puppeteer.launch(env.MYBROWSER); // Not just puppeteer.launch()
问题:XPath 选择器不起作用
解决方案: 使用 CSS 选择器,或通过 XPathEvaluator 调用 page.evaluate()(参见 Issue #1)
问题:浏览器在 60 秒后关闭
解决方案: 使用 keep_alive 延长超时时间:
const browser = await puppeteer.launch(env.MYBROWSER, { keep_alive: 300000 });
问题:达到速率限制
解决方案: 复用会话、使用标签页,并在启动前检查限制(参见 Issue #4)
问题:本地开发请求超过 1 MB 时失败
解决方案: 在 wrangler.jsonc 中启用远程绑定:
{ "browser": { "binding": "MYBROWSER", "remote": true } }
问题:网站将请求拦截为机器人流量
解决方案: 无法绕过。如果是自己的区域,请创建 WAF 跳过规则(参见 Issue #6)
有问题?遇到故障?
- 查看
references/common-errors.md,了解详细解决方案 - 查看
references/session-management.md,了解性能优化方法 - 确认已在 wrangler.jsonc 中配置浏览器绑定
- 查看官方文档:https://developers.cloudflare.com/browser-rendering/
- 确保已启用
nodejs_compat兼容性标志