默认情况下,每个浏览器会话请求都会启动新的浏览器实例。复用会话通过重新连接到现有浏览器而非启动新浏览器来消除冷启动时间并提升性能。
此功能适用于浏览器会话(Puppeteer、Playwright 和 CDP)。Quick Actions 自动处理会话生命周期。
复用会话有两种方式:
- 断开并重新连接(本页介绍):使用
browser.disconnect()而非browser.close()保持浏览器存活,然后在下次请求时重新连接。最适合无状态工作负载,任何可用的浏览器会话都可以。 - Durable Objects:在 Durable Object 中持久化长时间运行的浏览器以进行有状态会话管理。最适合需要在请求之间保持状态或将特定用户路由到特定浏览器实例的场景。
Cloudflare Workers 提供无服务器执行环境,可创建新应用或扩展现有应用,无需配置或维护基础设施。Worker 应用是与无头浏览器交互的容器,可执行截图等操作。
运行以下命令创建名为 browser-worker 的新 Worker 项目:
npm create cloudflare@latest -- browser-workeryarn create cloudflare browser-workerpnpm create cloudflare@latest browser-worker进行设置时,请选择以下选项:
- 对于 What would you like to start with?,选择
Hello World example。 - 对于 Which template would you like to use?,选择
Worker only。 - 对于 Which language do you want to use?,选择
TypeScript。 - 对于 Do you want to use git for version control?,选择
Yes。 - 对于 Do you want to deploy your application?,选择
No(部署前我们还会做一些修改)。
在 browser-worker 目录中,安装 Cloudflare 的 Puppeteer fork:
npm i -D @cloudflare/puppeteeryarn add -D @cloudflare/puppeteerpnpm add -D @cloudflare/puppeteerbun add -d @cloudflare/puppeteer3. 配置 Wrangler 配置文件
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "browser-worker",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-08-17",
"compatibility_flags": ["nodejs_compat"],
"browser": {
"binding": "MYBROWSER",
},
}"$schema" = "./node_modules/wrangler/config-schema.json"
name = "browser-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"
compatibility_flags = [ "nodejs_compat" ]
[browser]
binding = "MYBROWSER"以下脚本首先获取当前运行的会话。如果有尚未建立 worker 连接的会话,它会随机选择一个会话 ID 并尝试连接(puppeteer.connect(..))。如果失败或一开始没有运行的会话,则启动新的浏览器会话(puppeteer.launch(..))。然后访问网站并获取 DOM。完成后断开连接(browser.disconnect()),使连接可供其他 worker 使用。
请注意,如果浏览器空闲(即未收到任何命令)超过当前限制,它将自动关闭,因此你必须有足够的每分钟请求来保持其存活。
import puppeteer from "@cloudflare/puppeteer";
export default {
async fetch(request, env) {
const url = new URL(request.url);
let reqUrl = url.searchParams.get("url") || "https://example.com";
reqUrl = new URL(reqUrl).toString(); // normalize
// Pick random session from open sessions
let sessionId = await this.getRandomSession(env.MYBROWSER);
let browser, launched;
if (sessionId) {
try {
browser = await puppeteer.connect(env.MYBROWSER, sessionId);
} catch (e) {
// another worker may have connected first
console.log(`Failed to connect to ${sessionId}. Error ${e}`);
}
}
if (!browser) {
// No open sessions, launch new session
browser = await puppeteer.launch(env.MYBROWSER);
launched = true;
}
sessionId = browser.sessionId(); // get current session id
// Do your work here
const page = await browser.newPage();
const response = await page.goto(reqUrl);
const html = await response.text();
// All work done, so free connection (IMPORTANT!)
browser.disconnect();
return new Response(
`${launched ? "Launched" : "Connected to"} ${sessionId} \n-----\n` + html,
{
headers: {
"content-type": "text/plain",
},
},
);
},
// Pick random free session
// Other custom logic could be used instead
async getRandomSession(endpoint) {
const sessions = await puppeteer.sessions(endpoint);
console.log(`Sessions: ${JSON.stringify(sessions)}`);
const sessionsIds = sessions
.filter((v) => {
return !v.connectionId; // remove sessions with workers connected to them
})
.map((v) => {
return v.sessionId;
});
if (sessionsIds.length === 0) {
return;
}
const sessionId =
sessionsIds[Math.floor(Math.random() * sessionsIds.length)];
return sessionId;
},
};import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
let reqUrl = url.searchParams.get("url") || "https://example.com";
reqUrl = new URL(reqUrl).toString(); // normalize
// Pick random session from open sessions
let sessionId = await this.getRandomSession(env.MYBROWSER);
let browser, launched;
if (sessionId) {
try {
browser = await puppeteer.connect(env.MYBROWSER, sessionId);
} catch (e) {
// another worker may have connected first
console.log(`Failed to connect to ${sessionId}. Error ${e}`);
}
}
if (!browser) {
// No open sessions, launch new session
browser = await puppeteer.launch(env.MYBROWSER);
launched = true;
}
sessionId = browser.sessionId(); // get current session id
// Do your work here
const page = await browser.newPage();
const response = await page.goto(reqUrl);
const html = await response!.text();
// All work done, so free connection (IMPORTANT!)
browser.disconnect();
return new Response(
`${launched ? "Launched" : "Connected to"} ${sessionId} \n-----\n` + html,
{
headers: {
"content-type": "text/plain",
},
},
);
},
// Pick random free session
// Other custom logic could be used instead
async getRandomSession(endpoint: puppeteer.BrowserWorker): Promise<string> {
const sessions: puppeteer.ActiveSession[] =
await puppeteer.sessions(endpoint);
console.log(`Sessions: ${JSON.stringify(sessions)}`);
const sessionsIds = sessions
.filter((v) => {
return !v.connectionId; // remove sessions with workers connected to them
})
.map((v) => {
return v.sessionId;
});
if (sessionsIds.length === 0) {
return;
}
const sessionId =
sessionsIds[Math.floor(Math.random() * sessionsIds.length)];
return sessionId!;
},
};除 puppeteer.sessions() 外,我们还添加了其他方法以方便会话管理。
运行 npx wrangler dev 在本地测试 Worker。
要测试,请访问以下 URL:
<LOCAL_HOST_URL>/?url=https://example.com运行 npx wrangler deploy 将 Worker 部署到 Cloudflare 全球网络,然后访问以下 URL:
<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/?url=https://example.com