🍺

Cloudflare Browser Renderingに入門してみる

に公開

1. Cloudflare Browser Renderingとは

Cloudflare Browser Rendering (CBR)を使うと、Cloudflareのエッジネットワーク上でいわゆるヘッダレスブラウザやクローラ的なものが簡単に作れそうなので、入門してみました。

https://developers.cloudflare.com/browser-rendering/

公式サイトのユースケースを意訳してまとめると

  • Webサイトのスクリーンショットを撮る
  • HTMLの描画結果をPDFに変換
  • 放り込んだWebサイトのHTMLの要素をスクレイピング
  • 構造化データ(JSONなど)のスクレイピング
  • WebサイトからMarkdownコンテンツをスクレイピング

などをAPI Callだけで実現できるようです。自前で頑張ってPuppeteerなどの環境を用意しなくても良いのは便利ですね。

価格帯系

https://developers.cloudflare.com/browser-rendering/platform/pricing/

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の描画結果のスクリーンショットを撮ってみる

参考ページ

まずは/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として描画する

参考ページ

こちらは先ほどの /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
}

@cloudflare/puppeteer reference

Discussion