Cloudflare 的 Browser Rendering 一直是开发者做网页抓取、自动化测试时的热门选择,但底层跑的是第三方托管方案,并发和延迟都有天花板。最近 Cloudflare 把整个 Browser Run 搬到了自家 Containers 平台上,并发能力翻了 4 倍、响应速度快了 50%。更重要的是,这一步把 Cloudflare 的 Agent 基础设施从五块补成了六块——一个从计算到交易的完整栈终于闭环了。
为什么必须自建容器
Browser Run 之前的架构依赖外部容器调度,问题很典型:
- 并发瓶颈——外部平台的容器实例有硬性上限,高峰期排队严重。
- 冷启动慢——每次启动浏览器实例都要走跨平台调度链路,延迟不可控。
- 成本不透明——第三方计费和 Cloudflare 自身 Workers 体系割裂,难以做统一用量管理。
搬到自建 Containers 之后,浏览器实例直接跑在 Cloudflare 边缘网络的容器里,调度链路缩短到内部系统,冷启动大幅压缩,实例池可以按边缘节点弹性扩缩。4 倍并发和 50% 响应提速,本质上是"把中间商去掉"后的自然结果。
六层 Agent 基础设施全貌
Browser Run 补位之后,Cloudflare 的 Agent 平台栈变成了六层,每一层对应 Agent 运行的一个关键能力:
| 层级 | 能力 | 产品 | 作用 |
|---|---|---|---|
| 1 | 计算 | Dynamic Workers | Agent 主逻辑运行,按请求动态调度 |
| 2 | 安全沙箱 | Containers / Sandboxes | 隔离执行不可信代码、跑浏览器实例 |
| 3 | 编排 | Dynamic Workflows | 多步骤 Agent 流程的 DAG 编排与重试 |
| 4 | 记忆 | Agent Memory | 跨会话的状态持久化与上下文召回 |
| 5 | 浏览 | Browser Run | 真实浏览器环境,用于网页交互与数据提取 |
| 6 | 商务 | Stripe Projects | Agent 产生的交易、支付与计费闭环 |
这六层不是简单堆叠,而是互相衔接。一个典型 Agent 的生命周期:Workers 接收请求 → Workflows 编排多步流程 → Memory 存取上下文 → Browser Run 执行网页操作 → Sandboxes 处理生成物 → Stripe Projects 完成付费。每一层都在 Cloudflare 边缘网络内闭环,不需要跨云。
Browser Run 的实际用法
下面用一个完整示例演示:在 Cloudflare Workers 中调用 Browser Rendering API,抓取页面标题和关键文本,然后把结果写入 Agent Memory。这是五层联动的最小可行路径。
1. 项目配置
# 创建 Workers 项目
mkdir agent-browser && cd agent-browser
npm init -y
npm install @cloudflare/workers-types
wrangler.toml 配置——绑定 Browser Rendering 和 KV(用作 Agent Memory):
name = "agent-browser"
main = "src/index.ts"
compatibility_date = "2024-09-23"
# Browser Rendering 绑定
[browser]
binding = "BROWSER"
# KV 绑定,用作 Agent Memory
[[kv_namespaces]]
binding = "AGENT_MEMORY"
id = "你的KV命名空间ID"
2. Worker 代码
// src/index.ts
interface Env {
BROWSER: Fetcher; // Browser Rendering 绑定
AGENT_MEMORY: KVNamespace; // Agent Memory 绑定
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const targetUrl = url.searchParams.get("url");
if (!targetUrl) {
return new Response("请提供 ?url= 参数", { status: 400 });
}
// 第一步:启动浏览器实例
const browserResp = await env.BROWSER.fetch(
"https://browser.cloudflare.com/new"
);
if (!browserResp.ok) {
return new Response("浏览器实例启动失败", { status: 500 });
}
const browserId = await browserResp.text();
try {
// 第二步:通过 WebSocket 连接浏览器,执行 CDP 命令
const wsUrl = `https://browser.cloudflare.com/${browserId}/ws`;
const wsResp = await env.BROWSER.fetch(wsUrl, {
headers: { Upgrade: "websocket" },
});
const ws = wsResp.webSocket!;
ws.accept();
// 导航到目标页面
ws.send(JSON.stringify({
id: 1,
method: "Page.navigate",
params: { url: targetUrl },
}));
// 等待页面加载
await new Promise((r) => setTimeout(r, 3000));
// 提取页面标题
ws.send(JSON.stringify({
id: 2,
method: "Runtime.evaluate",
params: { expression: "document.title" },
}));
// 提取页面主要文本内容
ws.send(JSON.stringify({
id: 3,
method: "Runtime.evaluate",
params: {
expression: `
Array.from(document.querySelectorAll('h1, h2, p'))
.map(el => el.innerText)
.filter(t => t.length > 20)
.slice(0, 5)
`,
returnByValue: true,
},
}));
// 收集结果
const results: Record<number, any> = {};
ws.addEventListener("message", (event: MessageEvent) => {
const data = JSON.parse(event.data as string);
if (data.id) results[data.id] = data.result?.value;
if (Object.keys(results).length >= 2) ws.close();
});
await new Promise((r) => setTimeout(r, 2000));
const title = results[2] || "未知标题";
const keyTexts = results[3] || [];
// 第三步:写入 Agent Memory(KV)
const memoryKey = `page:${targetUrl}:${Date.now()}`;
await env.AGENT_MEMORY.put(memoryKey, JSON.stringify({
url: targetUrl,
title,
keyTexts,
capturedAt: new Date().toISOString(),
}));
return Response.json({
url: targetUrl,
title,
keyTexts,
memoryKey,
});
} finally {
// 关闭浏览器实例,释放资源
await env.BROWSER.fetch(
`https://browser.cloudflare.com/${browserId}/close`,
{ method: "POST" }
);
}
},
};
3. 部署与测试
# 部署到 Cloudflare
npx wrangler deploy
# 测试调用
curl "https://agent-browser.你的子域.workers.dev/?url=https://example.com"
返回示例:
{
"url": "https://example.com",
"title": "Example Domain",
"keyTexts": [
"This domain is for use in illustrative examples..."
],
"memoryKey": "page:https://example.com:1718000000000"
}
这个示例串联了 Workers(计算)、Browser Run(浏览)、Agent Memory/KV(记忆)三层。加上 Workflows 做多步编排、Sandboxes 做结果后处理、Stripe Projects 做按次计费,就是完整的六层联动。
从五层到六层,意味着什么
Browser Run 补位之前,Cloudflare 的 Agent 栈缺了"真实浏览器交互"这一环。Agent 能算、能记、能编排、能交易,但到了需要和真实网页打交道的时候——登录、填表、滚动加载、处理 JS 渲染的内容——只能外接 Puppeteer 远程服务或自建浏览器集群,链路断裂。
现在闭环之后,几个变化值得关注:
- 延迟确定性——浏览器实例和 Worker 在同一边缘网络,不再受跨区域调度抖动影响。对需要多轮网页交互的 Agent(比如比价、监控、自动填报),延迟稳定性比峰值速度更重要。
- 并发弹性——4 倍并发意味着同一个 Agent 可以并行开更多浏览器 tab 做批量操作,而不用排队等实例释放。
- 计费统一——Browser Run 的用量现在纳入 Cloudflare 统一体系,和 Workers、KV 一样在 Dashboard 里看,不再有第三方账单黑洞。
落地前需要想清楚的几件事
- 浏览器实例生命周期管理——每次调用完必须关闭实例,否则容器资源会持续占用。上面的示例用了
finally块确保清理,生产环境建议加超时兜底。 - CDP 协议熟练度——Browser Run 的底层是 Chrome DevTools Protocol,不是 Puppeteer 那套高层 API。你需要自己拼 JSON 命令、管理
id映射、处理异步回调。团队里至少要有一个人熟悉 CDP。 - 冷启动仍有代价——虽然比之前快 50%,但浏览器实例启动依然在秒级。高频短任务(比如只取一个
<title>)考虑用 HTTP 抓取替代;只有需要 JS 渲染或交互操作时才值得开浏览器。 - Memory 选型——示例用 KV 做简单记忆,适合键值场景。如果 Agent 需要向量检索(比如"找上次类似任务的上下文"),KV 不够用,需要外接向量数据库或等 Cloudflare Vectorize 成熟后迁移。
- 成本模型——Browser Run 按浏览器实例运行时间计费,长时间挂机等待的 Agent(比如等页面异步加载)成本会快速累积。设计流程时尽量把等待逻辑拆成短轮询 + Workflows 重试,而不是一个浏览器实例傻等。
Cloudflare 用自建容器重写 Browser Run,表面上是性能优化,实质上是把 Agent 基础设施的最后缺口补上了。六层栈闭环之后,开发者可以在一个平台上从计算到交易完整跑通 Agent,不再需要拼凑外部服务。如果你已经在用 Cloudflare Workers 做 Agent,现在值得重新评估 Browser Run 的并发和延迟表现——和半年前已经不是同一个东西了。