Cloudflare Browser Renderingに入門してみる
1. Cloudflare Browser Renderingとは
Cloudflare Browser Rendering (CBR)を使うと、Cloudflareのエッジネットワーク上でいわゆるヘッダレスブラウザやクローラ的なものが簡単に作れそうなので、入門してみました。
公式サイトのユースケースを意訳してまとめると
- Webサイトのスクリーンショットを撮る
- HTMLの描画結果をPDFに変換
- 放り込んだWebサイトのHTMLの要素をスクレイピング
- 構造化データ(JSONなど)のスクレイピング
- WebサイトからMarkdownコンテンツをスクレイピング
などをAPI Callだけで実現できるようです。自前で頑張ってPuppeteerなどの環境を用意しなくても良いのは便利ですね。
価格帯系
2025年9月12日現在は、Cloudflare Workersのバインドとして利用するパターンと、REST APIとして利用するパターンがあります。
課金体系
- REST APIの場合 - 実行時間のみでの課金 ($/ブラウザ/時間)
- Workers Bindingsの場合: 実行時間と同時実行ブラウザ数での課金 ($/ブラウザ/時間 & 同時実行ブラウ数)
料金表
※画像参照 https://developers.cloudflare.com/browser-rendering/platform/pricing/
- 両方、10分/dayの稼働が無料
- REST API版: 無料枠超過分は$0.09/時間
- Workers Bindings版: 無料枠超過分は$0.09/時間 & 同時に処理するブラウザ * $2
- 無料版だと最大同時に3つ、有料版でも最大10までしか起動しておけない(結構ネック)
API Callがタイムアウトで失敗した場合はブラウザセッションとしてはカウントされないみたいです。
2. REST API版を使ってみる
API Tokenを作成
API Token作成画面で、Edit Cloudflare Workersの「Use template」を選択
次に、Permission一覧画面でBrowser RenderingリソースのEdit権限を付与します。
そしてsubmitし、API Tokenを作成します(今回はREST API版を使うので過剰に権限が含まれていますが、Worker Bindingsとして使いたい方のために、そちらの場合でも使えるような設定にしてみました)。
/screen - HTMLの描画結果のスクリーンショットを撮ってみる
参考ページ
- 公式docs https://developers.cloudflare.com/browser-rendering/rest-api/screenshot-endpoint/
- 公式リファレンス https://developers.cloudflare.com/api/resources/browser_rendering/subresources/screenshot/methods/create/
まずは/screen
APIを使い、指定のURLまたは任意のHTMLを動的描画した結果を画像として返す機能を動かします。TypeScript SDKがあるようですが、今回はREST APIを使って公式サンプルをそのまま実行してみます。
$ curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/screenshot' \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"html": "Hello World!",
"screenshotOptions": {
"omitBackground": true
}
}' \
--output "screenshot.png"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 9258 100 9163 100 95 2250 23 0:00:04 0:00:04 --:--:-- 2274
4秒程度で下記の画像が.pngで返ってきました。めっちゃ簡単ですね。
サイズは1920x1080で、データサイズは9KBでした。公式ドキュメントにも書いてありますが、デフォルトのブラウザviewportは1920x1080で、自由に変更できる模様です。
リファレンス によると、cookieも指定できるので、cookieで認証しているタイプのURLであれば下記のようにパラメータ指定すると認証突破した上でスクリーンショットが撮れそうです。
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/screenshot' \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/protected-page",
"cookies": [
{
"name": "session_id",
"value": "your-session-cookie-value",
"domain": "example.com",
"path": "/"
}
]
}' \
--output "authenticated-screenshot.png"
また詳細は公式ページ記載の通りなので割愛しますが、下記のようなユースケースにも対応しています。
- スクロールオプションを有効にした上でのフルページスクリーンショット(screenshotOptions.fullPageオプション)
- CSSとJavaScriptのオーバーライド(addScriptTag & addStyleTagオプション)
- 特定要素のみ(selectorオプション)のスクリーンショット
/pdf - 指定のURLのサイトをPDFとして描画する
参考ページ
- 公式docs https://developers.cloudflare.com/browser-rendering/rest-api/pdf-endpoint/
- 公式リファレンス https://developers.cloudflare.com/api/resources/browser_rendering/subresources/pdf/
こちらは先ほどの /screen APIのoutputが PDF になったものです。まずは公式サンプルをそのまま実行してみます。https://example.com にアクセスし、描画結果をPDFで取得します。
$ curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-rendering/pdf' \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/",
"addStyleTag": [
{ "content": "body { font-family: Arial; }" }
]
}' \
--output "output.pdf"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 39072 100 38953 100 119 8662 26 0:00:04 0:00:04 --:--:-- 9116
自分の環境だと4秒程度で、下記のような見た目のPDFが返ってきました(PDFを貼れないのでスクリーンショットで)。
また、下記のようなpayloadでカスタムHTMLをPDFすることも可能です。
$ curl -X POST https://api.cloudflare.com/client/v4/accounts/<acccountID>/browser-rendering/pdf \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"html": "<html><body>Advanced Snapshot</body></html>",
"addStyleTag": [
{ "content": "body { font-family: Arial; }" },
{ "url": "https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" }
]
}' \
--output "invoice.pdf"
これを実行すると下記のようなPDFが返ってきました。PDFエクスポート機能を作るときなどに活用できそうですね。ちなみに/pdf APIも 先の /screen APIと同様にviewportの指定やcookiesヘッダの指定ができます。詳しくは リファレンス を参照してください。
/json - AIにスクレイピングさせる
参考ページ
こちらはプロンプトを投げつけると、指定のJSONスキーマでアウトプットをまとめて返してくれるというAPIです。なんかこういうものを見ると、本格的にAI時代になってきたなという気持ちになりますね。さてこのAPIは裏でWorkers AIが動くらしく、custom_aiパラメータで任意のAIモデルを指定できる模様です。
個人的にはOpenAI Function Callingのヘッドレスブラウザ特化版という解釈をしています。
まずは威力を体感ください。
curl --request POST 'https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/browser-rendering/json' \
--header 'authorization: Bearer CF_API_TOKEN' \
--header 'content-type: application/json' \
--data '{
"url": "https://developers.cloudflare.com/",
"prompt": "Get me the list of AI products",
"response_format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"products": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"link": {
"type": "string"
}
},
"required": [
"name"
]
}
}
}
}
}
}'
すると、レスポンスでプロンプトの結果が構造化データで返ってきています↓
{
"success": true,
"result": {
"products": [
{
"name": "Build a RAG app",
"link": "https://developers.cloudflare.com/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/"
},
{
"name": "Workers AI",
"link": "https://developers.cloudflare.com/workers-ai/"
},
{
"name": "Vectorize",
"link": "https://developers.cloudflare.com/vectorize/"
},
{
"name": "AI Gateway",
"link": "https://developers.cloudflare.com/ai-gateway/"
},
{
"name": "AI Playground",
"link": "https://playground.ai.cloudflare.com/"
}
]
}
}
また公式サンプルにもあるとおり、このpayloadでカスタムモデルを適用することもできました(手元で動いた)。
curl --request POST \
--url https://api.cloudflare.com/client/v4/accounts/CF_ACCOUNT_ID/browser-rendering/json \
--header 'authorization: Bearer CF_API_TOKEN' \
--header 'content-type: application/json' \
--data '{
"url": "http://demoto.xyz/headings",
"prompt": "Get the heading from the page in the form of an object like h1, h2. If there are many headings of the same kind then grab the first one.",
"response_format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"h1": {
"type": "string"
},
"h2": {
"type": "string"
}
},
"required": [
"h1"
]
}
},
"custom_ai": [
{
"model": "anthropic/claude-sonnet-4-20250514",
"authorization": "Bearer <ANTHROPIC_API_KEY>"
}
]
}
3. Workers Bindingsを利用してみる
利用手順
Cloudflare公式が、PuppeteerとPlaywrightの独自フォークライブラリを公開していました。今回はPuppeteer版の手順を要所のみ まとめました。
1. wrangler.jsoncにbrowser設定を追加
参考. https://developers.cloudflare.com/browser-rendering/workers-bindings/screenshots/
{
...
"browser": {
"binding": "MYBROWSER"
}
...
}
2. ライブラリのインストール
pnpm add -D @cloudflare/puppeteer
3.実装(サンプルコード)
参照. https://developers.cloudflare.com/browser-rendering/platform/puppeteer/#use-puppeteer-in-a-worker
import puppeteer from "@cloudflare/puppeteer";
interface Env {
MYBROWSER: Fetcher;
}
export default {
async fetch(request, env): Promise<Response> {
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
await page.goto("https://example.com");
const metrics = await page.metrics();
await browser.close();
return Response.json(metrics);
},
} satisfies ExportedHandler<Env>;
実装としてはこんな感じです。
Workers Bindings利用の上でキモになりそうな部分
1. KeepAlive
参照. https://developers.cloudflare.com/browser-rendering/platform/puppeteer/#keep-alive
browser.closeを明示的に呼ばない限り、ブラウザセッションが開き続ける。デフォルトだと1分開き続けた後に自動でクローズされる模様。keep_aliveオプションで10分まで引き延ばせる。制約上同時オープンできるブラウザセッションが有料版でも10しかないので、open/close数を極力減らし少ないセッション数でパフォーマンスを上げるためのチューニングパラメータとして活用できそう。
const browser = await puppeteer.launch(env.MYBROWSER, { keep_alive: 600000 });
2. SessionManagement
参照. https://developers.cloudflare.com/browser-rendering/platform/puppeteer/#session-management
@cloudflare/puppeteer
では元のpuppeteerの拡張メソッド機能として、セッション管理系のpuppeteer.sessions() / puppeteer.history() / puppeteer.limits() メソッドが提供されている。
たとえば puppeteer.limits()
ではオープン可能セッション数などがリアタイで取得できるため、これを駆使して、session制限中は即時処理を終了するような処理を組んだり、 timeUntilNextAllowedBrowserAcquisition
経過するまで待ってからsessionを確保しに行くような処理を組むことができそう。
{
"activeSessions": [
{ "id": "478f4d7d-e943-40f6-a414-837d3736a1dc" },
{ "id": "565e05fb-4d2a-402b-869b-5b65b1381db7" }
],
"allowedBrowserAcquisitions": 1,
"maxConcurrentSessions": 2,
"timeUntilNextAllowedBrowserAcquisition": 0
}
Discussion