跳转到内容
搜索文档

为爬虫预渲染页面

最后更新 查看 MarkdownAgent 设置

预渲染在将页面返回给客户端之前生成页面的最终 HTML。对于 JavaScript 密集型应用,这意味着在浏览器中加载页面,等待客户端 JavaScript 运行,然后返回渲染后的 HTML,而非初始应用 shell。

当搜索爬虫、社交预览 bot、AI 索引任务或合作伙伴集成需要你的应用通常在浏览器中创建的 HTML 内容时,预渲染很有用。通过 Cloudflare Browser RunCloudflare Workers,你可以在托管的无头 Chrome 中渲染公共 URL 并返回渲染后的 HTML。

在本教程中,你将:

  • 向 Worker 添加 Browser Run 绑定
  • 创建最小预渲染端点
  • 限制 Worker 可以渲染的主机名
  • 使用 remote 模式在本地测试端点

前提条件

要跟随本教程,你需要:

  • Cloudflare 账户
  • 使用 TypeScript 的 Worker 项目
  • 要预渲染的公共 URL

你预渲染的页面可以在任何地方运行。本教程中的 Worker 仅作为调用 Browser Run 的预渲染服务。

1. 配置 Browser Run

向 Wrangler 配置添加 Browser Run 绑定:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "my-prerender-worker",
  "main": "src/index.ts",
  // Set this to today's date
  "compatibility_date": "2026-08-17",
  "browser": {
    "binding": "BROWSER"
  }
}
name = "my-prerender-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"

[browser]
binding = "BROWSER"

2. 添加预渲染 Worker

src/index.ts 的内容替换为以下 Worker。更新 ALLOWED_HOSTNAMES 以包含 Worker 可以预渲染的主机名。

// Only render pages you control. This prevents the Worker from becoming
// an open browser-rendering proxy for arbitrary websites.
const ALLOWED_HOSTNAMES = new Set(["example.com", "www.example.com"]);

const getTargetUrl = (request) => {
	const requestUrl = new URL(request.url);
	const target = requestUrl.searchParams.get("url");

	if (!target) {
		throw new Error("Missing url query parameter");
	}

	const targetUrl = new URL(target);

	// Only render HTTP(S) pages. Other protocols are not valid web pages.
	if (!["http:", "https:"].includes(targetUrl.protocol)) {
		throw new Error("Only HTTP and HTTPS URLs are allowed");
	}

	if (!ALLOWED_HOSTNAMES.has(targetUrl.hostname)) {
		throw new Error("This hostname is not allowed");
	}

	return targetUrl;
};

const renderHtml = async (env, targetUrl) => {
	// The /content Quick Actions endpoint loads the page in Browser Run and returns
	// a JSON envelope containing the rendered HTML in the result field.
	const response = await env.BROWSER.quickAction("content", {
		url: targetUrl.toString(),
		gotoOptions: {
			waitUntil: "networkidle2",
			timeout: 30000,
		},
		// If your page has a specific readiness signal, use waitForSelector
		// instead of relying only on network activity.
		// waitForSelector: { selector: "[data-prerender-ready='true']", timeout: 30000 },
	});

	if (!response.ok) {
		const detail = (await response.text()).slice(0, 500);
		throw new Error(`Browser Run failed with ${response.status}: ${detail}`);
	}

	const data = await response.json();

	if (!data.success || typeof data.result !== "string") {
		throw new Error("Browser Run returned an unsuccessful response");
	}

	return data.result;
};

export default {
	async fetch(request, env) {
		try {
			// Read and validate the URL before sending it to Browser Run.
			const targetUrl = getTargetUrl(request);
			const html = await renderHtml(env, targetUrl);

			// Return the rendered HTML to the crawler or integration.
			return new Response(html, {
				headers: {
					"content-type": "text/html; charset=utf-8",
				},
			});
		} catch (error) {
			return Response.json(
				{ error: error instanceof Error ? error.message : "Unknown error" },
				{ status: 400 },
			);
		}
	},
};
interface Env {
	BROWSER: BrowserRun;
}

// Only render pages you control. This prevents the Worker from becoming
// an open browser-rendering proxy for arbitrary websites.
const ALLOWED_HOSTNAMES = new Set(["example.com", "www.example.com"]);

const getTargetUrl = (request: Request) => {
	const requestUrl = new URL(request.url);
	const target = requestUrl.searchParams.get("url");

	if (!target) {
		throw new Error("Missing url query parameter");
	}

	const targetUrl = new URL(target);

	// Only render HTTP(S) pages. Other protocols are not valid web pages.
	if (!["http:", "https:"].includes(targetUrl.protocol)) {
		throw new Error("Only HTTP and HTTPS URLs are allowed");
	}

	if (!ALLOWED_HOSTNAMES.has(targetUrl.hostname)) {
		throw new Error("This hostname is not allowed");
	}

	return targetUrl;
};

const renderHtml = async (env: Env, targetUrl: URL) => {
	// The /content Quick Actions endpoint loads the page in Browser Run and returns
	// a JSON envelope containing the rendered HTML in the result field.
	const response = await env.BROWSER.quickAction("content", {
		url: targetUrl.toString(),
		gotoOptions: {
			waitUntil: "networkidle2",
			timeout: 30000,
		},
		// If your page has a specific readiness signal, use waitForSelector
		// instead of relying only on network activity.
		// waitForSelector: { selector: "[data-prerender-ready='true']", timeout: 30000 },
	});

	if (!response.ok) {
		const detail = (await response.text()).slice(0, 500);
		throw new Error(`Browser Run failed with ${response.status}: ${detail}`);
	}

	const data = (await response.json()) as {
		success: boolean;
		result?: string;
	};

	if (!data.success || typeof data.result !== "string") {
		throw new Error("Browser Run returned an unsuccessful response");
	}

	return data.result;
};

export default {
	async fetch(request, env): Promise<Response> {
		try {
			// Read and validate the URL before sending it to Browser Run.
			const targetUrl = getTargetUrl(request);
			const html = await renderHtml(env, targetUrl);

			// Return the rendered HTML to the crawler or integration.
			return new Response(html, {
				headers: {
					"content-type": "text/html; charset=utf-8",
				},
			});
		} catch (error) {
			return Response.json(
				{ error: error instanceof Error ? error.message : "Unknown error" },
				{ status: 400 },
			);
		}
	},
} satisfies ExportedHandler<Env>;

Worker 接受 url 查询参数,验证主机名,请求 Browser Run 渲染该 URL,并返回渲染后的 HTML。

waitUntil: "networkidle2" 选项等待页面在至少 500 毫秒内不超过两个网络连接。这通常足以处理客户端渲染的页面。如果页面需要更具体的就绪信号,向同一 Quick Actions payload 传递 waitForSelector,等待仅在内容加载后才出现的元素。更多信息请参阅 Browser Run Quick Actions 超时

3. 测试预渲染

  1. 在 remote 模式下启动 Worker:

    npx wrangler dev --remote

    .quickAction() 方法尚不支持本地开发模式。在本地测试 Browser Run Quick Actions 时,请使用 wrangler dev --remote

  2. 在另一个终端中请求渲染后的页面:

    curl "http://localhost:8787/?url=https://example.com/"

    响应应包含目标页面的渲染 HTML。

4. 部署

  1. 本地验证后部署 Worker:

    npx wrangler deploy
  2. 部署后,从你的 Worker URL 请求渲染后的页面:

    curl "https://<YOUR_WORKER_HOSTNAME>/?url=https://example.com/"

生产注意事项

  • 仅渲染你控制的主机名
  • 需要边缘路由时,使用 Worker 作为第一接触点
  • 仅对爬虫或集成请求调用 Browser Run
  • 如果预期有重复的爬虫请求,缓存渲染后的 HTML
  • 源内容变更时重新验证缓存的 HTML

相关资源

这篇文档对您有帮助吗?