ホスト型ブラウザ
API を 1 回呼び出すだけで当社のサーバー上に Clearcote ブラウザを起動し、Playwright や Puppeteer、その他任意の CDP クライアントから Chrome DevTools Protocol 経由で操作できます。自分で何かをインストールしたり運用したりする必要はありません。トラフィックはデフォルトで住宅用 IP から出ていき、料金はプリペイド残高から GB 単位で支払います。
- データセンターではなく住宅用 IP。Web サイトから見えるのは、一般消費者向けプロバイダーの実在する家庭用インターネット回線であり、ホスティングやクラウドのアドレスではありません。
- VPS ではなく実機。ブラウザは共有のクラウド仮想マシンではなく、当社が所有する専用の物理サーバー上で動作します。
無料GitHub を連携すると €5 分の通信量が無料。カード登録は不要です。
1 回限り、GitHub アカウント 1 つにつき 1 回です。GitHub アカウントは作成から 30 日以上経っている必要があります。
料金
- 1 GB あたり €1.00、住宅用プロキシ込み。デフォルトでは、すべてのセッションが住宅用 IP からインターネットに出ます。これは一般消費者向けインターネットプロバイダーの実在する家庭用回線で、データセンターやホスティングのアドレスではありません。€1.00 で支払うのはこのトラフィックの料金であり、プロキシ代が別途請求されることはありません。
- トラフィックはブラウザとインターネットの間で計測し、アップロードとダウンロードを合算します(1 GB = 109 バイト)。詳しくは「トラフィックに含まれるもの」を参照してください。
- 利用時間、セッション数、CDP メッセージには課金されません。
- プリペイド制です。残高はダッシュボードでチャージします。ブラウザの起動には最低 €0.50 の残高が必要です。各セッションの上限は起動時点の残高でまかなえる額で、実行中のブラウザ全体で共有されます(自分で指定した
maxGbのほうが低ければ、そちらが適用されます)。残高がゼロになると、実行中のブラウザは停止されます(使用量の報告はおよそ 15 秒ごとのため、最後の報告で残高がわずかにマイナスになることがあります)。
トラフィックに含まれるもの
ブラウザが Web サイトとの間で送受信するすべてのバイトを、プロキシ事業者と同じく実際の通信経路上で計測します。一般的なページでいえば、内訳は次のとおりです。
- カウントされるもの:ページ本体と、そのページが読み込むすべてのもの(スクリプト、スタイルシート、画像、フォント、動画、API 呼び出し、広告やトラッカー、WebSocket)に加え、リクエストヘッダー、Cookie、各接続の暗号化(TLS)のオーバーヘッド。待っている間もページは読み込みを続けるため、バックグラウンドのポーリングやアナリティクスもカウントされます。
- カウントされないもの:コードとブラウザの間の CDP 接続(コマンド、結果、スクリーンショット、PDF、読み出したページ内容)、ダッシュボードのライブビュー、そしてブラウザが実際には取得しないもの(ブロックしたリクエスト、ブラウザ自身のキャッシュから返されるファイル)。
おおよその目安として、軽いテキストページは 1 MB を大きく下回り、一般的なニュースサイトや EC サイトのページは 2〜5 MB、重いシングルページアプリや動画を含むページは 10 MB 以上になります。1 GB あたり €1.00 の場合、3 MB のページ 1,000 件でおよそ 3 GB です。実際の数値は自分のセッションで確認できます。ダッシュボードには各セッションのトラフィックとバイト数上位 20 サイトが表示されます。また、maxGb でセッションに上限をかければ、暴走したページに残高を食いつぶされることはありません。
トラフィックを減らす
ページの容量の大半は、たいていスクリプトにとって不要なものです。削減効果の大きい順に挙げます。
- 画像・メディア・フォントをブロックする。これだけでページの半分以上を占めることもよくあります。ブラウザ内で URL パターンによってブロックすれば、リクエストはブラウザの外に出ないため課金されません。しかも、ブラウザのキャッシュはそのまま使えます。
// Playwright: block by URL pattern over CDP (keeps the browser cache on)
const cdp = await context.newCDPSession(page);
await cdp.send("Network.enable");
await cdp.send("Network.setBlockedURLs", {
urls: ["*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.svg", "*.woff", "*.woff2", "*.ttf", "*.mp4", "*.webm"],
});
// Puppeteer: the same, through its CDP session
const client = await page.createCDPSession();
await client.send("Network.enable");
await client.send("Network.setBlockedURLs", { urls: ["*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.woff2"] });リクエストを破棄するだけの目的で page.route() / context.route() や Puppeteer のリクエストインターセプトを使うのは避けてください。これらを使うとブラウザのキャッシュが無効になり、ページごとにスクリプトやスタイルを再ダウンロードするため、節約した画像の分よりも高くつきます。使うのは、リクエストを書き換える必要がある場合だけにしましょう。なお、adblock オプションを使えば、広告やトラッカーのブロックも任せられます。
- 広告・アナリティクス・トラッカーをブロックする。不要なサードパーティドメインへのリクエストは中止します。どのドメインに最もコストがかかっているかは、ダッシュボードにあるセッションの上位サイト一覧でわかります。
- 必要以上に待たない。
waitUntil: "networkidle"は、広告も含めてページが読み込むすべてを待ちます。"domcontentloaded"を使い、そのうえで本当に必要な要素 1 つだけを待つようにしましょう。 - 終わったらすぐにブラウザを閉じる。開いたままのページはバックグラウンドでポーリングを続けます。
idleTimeoutSecを短くしておけば、閉じ忘れたセッションも自動的に終了します。 - 1 つのブラウザで多くのページを回る。キャッシュが効くため、同じサイト内のページ間で共通するスクリプトやスタイルは、ページごとではなく 1 回だけダウンロードされます。URL ごとに新しいセッションを起動するのではなく、同じセッション内でページを移動しましょう。
- 可能ならサイトの API を直接呼ぶ。ブラウザがサイトとの有効なセッションを確立したら、必要な JSON をページ内から
fetch()で取得しましょう。ページを読み込み直すのに比べて、ごくわずかなデータ量で済みます。 - 上限を設ける。すべてのセッションに
maxGbを設定しておけば、予想外に重いページがあっても、残高を使い切る前に停止します。
ブロックはほとんどのサイトで問題なく機能しますが、画像やフォントが実際に読み込まれたかを確認するサイトも一部あります。ブロックを有効にするとサイトの挙動が変わる場合は、そのサイトに限って該当する種類を再び許可してください。
まず実際に動かしてみたい場合は、Playground を使ってください。ダッシュボードから直接クラウドブラウザでスクリプトを実行でき、ライブビュー、コンソール出力、スクリーンショットを並べて確認できます。
1. API キーを取得する
ダッシュボードの「API keys」ページで作成します。キーは cc_live_ で始まり、Bearer トークンとして送信します。他人に知られないようにしてください。キーを持っている人なら誰でも、あなたの残高を使えてしまいます。
2. ブラウザを起動して接続する
POST /api/v1/browsers は connectUrl を返します。これはそのブラウザ専用の、1 回限り有効な WebSocket URL です。2 分以内に接続してください。同じ URL は 2 回使えません。
// Node.js + Playwright
import { chromium } from "playwright";
const res = await fetch("https://www.clearcotelabs.com/api/v1/browsers", {
method: "POST",
headers: { authorization: "Bearer cc_live_...", "content-type": "application/json" },
body: JSON.stringify({ identity: "account-1", country: "us" }),
});
const { connectUrl, id, error } = await res.json();
if (error) throw new Error(error);
const browser = await chromium.connectOverCDP(connectUrl);
const context = browser.contexts()[0];
const page = context.pages()[0] ?? (await context.newPage());
await page.goto("https://example.com");
await browser.close(); // ends the session// Puppeteer: the same connectUrl
const browser = await puppeteer.connect({ browserWSEndpoint: connectUrl, defaultViewport: null });# Python + Playwright
import requests
from playwright.sync_api import sync_playwright
r = requests.post("https://www.clearcotelabs.com/api/v1/browsers",
headers={"authorization": "Bearer cc_live_..."},
json={"identity": "account-1", "country": "de"}).json()
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(r["connectUrl"])
page = browser.contexts[0].new_page()
page.goto("https://example.com")
browser.close()作成リクエストは 201 を返します。レスポンスには、接続と料金の把握に必要な情報がすべて含まれています。
{
"id": "bs_…", // use it with GET / DELETE /api/v1/browsers/<id>
"connectUrl": "wss://…/v1/connect/bs_…?token=…",
"expiresAt": "2026-09-24T10:02:00.000Z", // connect before this (two minutes)
"worker": "w_…",
"pricing": { "eurPerGb": 1, "eurPerHour": 0 },
"limits": { "maxSeconds": 14400, "idleSeconds": 300 } // plus maxBytes when capped
}オプション
すべて省略可能です。作成リクエストの JSON ボディとして送信します。
| フィールド | 型 | 説明 |
|---|---|---|
identity | string | One label per account you run: the same device fingerprint AND the same residential IP, for as long as that IP stays online. |
fingerprint | string | Device seed only (no IP pinning). The same seed gives the same device profile every time; with lightStealth on it picks from a small set of metadata profiles. |
lightStealth | boolean | Default true: varies only the metadata axes the host can back up. Set false to turn it off. |
platform | windows | macos | linux | android | Operating system the persona presents. |
brand | Chrome | Edge | Opera | Vivaldi | Browser brand the persona presents. |
timezone | IANA name | e.g. America/New_York. Use geoip instead to follow the exit IP. |
locale | string | Accept-Language, e.g. en-US,en. |
geoip | boolean | Timezone and language follow the exit IP. Default true unless you set timezone or locale yourself. |
proxy | "managed" | { server, username?, password? } | Omitted = managed residential pool. Or your own proxy: http://, socks5:// or socks5h:// (rules below). |
country | 2-letter code | Managed pool: exit country, e.g. us, de, gb. |
state | region code | Managed pool: exit state/region, e.g. ca, ny. Needs country. |
city | city name | Managed pool: exit city, e.g. "los angeles". Needs state. |
proxySession | string | Managed pool: sticky label. The same label returns the same exit IP later (kept for 24 hours). |
timeoutSec | number | Hard limit on the session length, in seconds (10 up to the account maximum below). |
idleTimeoutSec | number | End the session after this long without a CDP command (10–1800). |
maxGb | number | Stop the session after this much traffic (0.001–1000). |
headless | boolean | Default true. |
keepAlive | boolean | Default false. Keep the browser running when your client disconnects, until you end it (DELETE, or the CDP command Browser.close) or a limit does; reconnect with POST /api/v1/browsers/<id>/connect. |
version | string | Run a specific Clearcote release, e.g. "152.0.7977.82-r21" or "r21". Omitted = the current release. See "Pinning a release". |
profile | "name" | { name, persist? } | Load a saved profile (cookies + site storage). With persist: true, save it back when the session ends. See "Profiles". |
url | http(s) URL | Opened in the first tab before you connect: you find it already loading. |
adblock | boolean | Refuse known ad and tracker hosts before they load, so they are never billed. Default false. |
note | string | Your label for the session (at most 256 characters). Shown in the dashboard; filter by it in the list. |
worker | string | Place the session on the same server as an earlier one (its worker). 503 if that server is full. |
ステルスのデフォルト設定とアイデンティティ
すべてのセッションは「推奨設定」ページの設定から始まります。つまり lightStealth が有効で、シードが割り当てられ、タイムゾーンと言語は出口 IP に合わせられます。lightStealth(デフォルト)では、CPU コア数、メモリ、ピクセル比を変えた少数のデバイスプロファイルの中から、シードが 1 つを選びます。canvas、WebGL、オーディオは、セッションが動作しているマシンのものがそのまま使われます。シードごとに完全なペルソナを使うには lightStealth: false を設定します。この場合、canvas と WebGL の読み取り結果にはシードに基づくサイトごとのノイズが加わり、GPU 文字列、画面、オーディオ設定(サンプルレート、レイテンシ)はペルソナに従います。identity も fingerprint も指定しない場合、セッションごとにランダムなシードと新しい IP が割り当てられます。
identity: "account-42" を渡すと、以降のセッションでも同じデバイスプロファイルで、しかもその住宅用 IP がオンラインである限り同じ IP で戻ってこられます。ログイン済みのアカウントにとっては、これが自然な状態です。アイデンティティはアカウント単位で非公開です。別の顧客が同じラベルを使っても、その顧客には別のシードと IP が割り当てられます。アイデンティティだけでは Cookie は保持されません。Cookie を保持するにはプロファイルを使ってください。
プロファイル:ログインは一度だけ
プロファイルはセッションの Cookie、localStorage、IndexedDB を名前を付けて保存するため、同じ名前で次に起動したセッションはログイン済みの状態から始まります。profile: { name: "shop-account", persist: true } を渡すとプロファイルを読み込み、セッション終了時に書き戻します。profile: "shop-account" だけを渡すと、読み取り専用で読み込みます。新しい名前での最初のセッションは空の状態で始まり、そのプロファイルを作成します。
// Every run: the same body. The first one starts signed out; sign in, then close the browser
// and the session saves the cookies and site storage. Every later run starts signed in.
const res = await fetch("https://www.clearcotelabs.com/api/v1/browsers", {
method: "POST",
headers: { authorization: "Bearer cc_live_...", "content-type": "application/json" },
body: JSON.stringify({ profile: { name: "shop-account", persist: true }, country: "de" }),
});- 同じデバイス、同じ IP。プロファイルは専用のアイデンティティ(
profile:<name>)を持つため、サイトからは同じフィンガープリントに見え、その IP がオンラインである限り同じ住宅用 IP からのアクセスになります。再訪した顧客と同じ見え方です。別の設定にしたい場合はidentityまたはfingerprintを自分で渡してください。また、実行ごとに国を変えないようにしてください。 - 書き込めるのは同時に 1 セッションだけ。プロファイルに保存できる実行中のセッションは 1 つだけで、
persist: trueを指定した 2 つ目のセッションには409 PROFILE_IN_USEが返ります。読み取り専用のセッションは並行して実行でき、最後に保存された状態を参照できます。 - ブラウザを閉じた場合、切断した場合、停止した場合、制限によって終了した場合のいずれでも、セッション終了時に保存されます。ブラウザがクラッシュした場合は、不完全な状態で上書きせず、前回保存された状態を保持します。セッション Cookie(有効期限のないもの)は、実際のブラウザが再起動時に破棄するのと同じく破棄されます。
- 圧縮後で最大約 3.5 MB です。サイトの IndexedDB のせいでこれを超える場合は IndexedDB を除外しますが、Cookie と localStorage は引き続き保存されます。
- 非公開。プロファイルはあなた専用で(別の顧客の「shop-account」は別のプロファイルです)、暗号化して保存され、あなたのセッションを実行するサーバーにだけ渡されます。一覧の取得は
GET /api/v1/browsers/profiles、削除はDELETE /api/v1/browsers/profiles/<name>で行えるほか、ダッシュボードからも操作できます。
出口 IP:ローテーション、スティッキー、地域指定
- デフォルト:ブラウザセッションごとに専用の住宅用出口 IP が割り当てられ、セッション中はずっとその IP を使います。
- スティッキー:同じ
proxySessionラベル(たとえば管理するアカウントごとに 1 つ)を渡すと、以降のセッションでも同じ出口 IP を再び使えます。ラベルはあなたのアカウント内でのみ有効です。住宅用 IP はそのピアがオンラインである間(通常は数時間)利用でき、オフラインになると同じネットワークの別の IP が割り当てられます。 - ロケーション:
countryを指定し、必要に応じてstateとcityも指定します。対象を絞り込むほど、IP プールは小さくなります。 - 独自のプロキシ:
proxy: { server: "http://host:port", username, password }、またはsocks5://host:port(名前解決は当社側)やsocks5h://host:port(名前解決はお使いのプロキシ側)を指定します。リクエストは厳密にチェックされます。ポートは明示する必要があり、認証情報はusername/passwordに入れます(user:pass@host形式の URL は 400 になります。長さはそれぞれ最大 255 バイトです)。また、country、state、city、proxySessionはマネージドプールを指定するためのものなので、無視されるのではなく拒否されます。プロキシ自体のアドレスはパブリックなものでなければなりません。トラフィックの課金方法は同じです。
タイムゾーンと言語は、デフォルトで出口 IP に合わせられます(geoip)。自分で指定するには timezone / locale を渡し、無効にするには geoip: false を渡します。
リリースを固定する
セッションでは現行の Clearcote リリースが動作します。古いリリースを使うには version を渡します。指定できるのは、完全なリリース名("152.0.7977.82-r21")、リビルド番号のみ("r21")、Chromium のバージョンまたはメジャーバージョン("152" ならその最新ビルドが選ばれます)、または "latest" です。これらは SDK の version オプションがダウンロードするリリースと同じなので、同じバージョンに固定すれば、ホスト型セッションとローカル実行で同じビルドが使われます。
const res = await fetch("https://www.clearcotelabs.com/api/v1/browsers", {
method: "POST",
headers: { authorization: "Bearer cc_live_...", "content-type": "application/json" },
body: JSON.stringify({ identity: "account-1", version: "152.0.7977.82-r21" }),
});
const { connectUrl, engine, warnings } = await res.json();
// engine -> { version: "152.0.7977.82", revision: "r21", pinned: true }
// warnings -> [ "This session runs 152.0.7977.82-r21, older than the current ... " ]warningsを確認する。古いリリースには、それ以降のリリースで追加されたものが含まれていません。後から追加されたオプションや修正は存在しないか、拒否されずに黙って無視されることがあります。そのため、現行リリースで機能する設定が、固定したリリースでは何の効果もないまま終わる可能性があります。- 固定しているかどうかにかかわらず、レスポンスの
engineには、そのセッションが実行しているリリースが常に示されます。 - 存在しないリリースを指定すると、コード
UNKNOWN_VERSION付きの400が返り、メッセージには選択可能なリリースが列挙されます。 - 当社のサーバーでまだ使われたことのないリリースで最初のセッションを起動すると、ビルドを取得するため、起動に最大 1 分ほど余計にかかることがあります。それ以降のセッションは、ほかと同じ速さで起動します。
開始ページと広告ブロック
urlを指定すると、接続前に最初のタブでページが開かれます。そのため、スクリプトが接続した時点ですでに読み込みが始まっています。adblock: trueは、よく知られた広告、広告検証、アナリティクスのホストへのリクエストを送信前に拒否するため、それらが課金されることはありません。リストは意図的に控えめにしてあります(タグマネージャー、同意管理ツール、ログイン SDK、CAPTCHA には手を加えません)が、広告が表示されないことに気づくサイトも一部あります。それが問題になるサイトでは無効のままにしてください。
ライブビュー:視聴、操作、共有
ダッシュボードでセッションをクリックすると、そのトラフィックの送信先サイトを確認したり、ライブで視聴したりできます。「Take control」を押すと、自分でクリック、入力、スクロール、貼り付け、ページ移動ができます。たとえば、ログインしたり、スクリプトでは通過できないチェックを通過したりするときに使います。その間もスクリプトは接続されたままなので、操作中はスクリプトを一時停止してください。人による入力もアクティビティとみなされるため、操作中のセッションがアイドル状態として閉じられることはありません。「Share」を使うと、アカウントがなくても誰でも開けるリンクを作成できます。視聴のみか操作付きかを選べ、有効期間は 15 分から 4 時間で、セッションの終了後まで有効になることはありません。
API からは次のように行います。
# a live-view WebSocket for a running session (open it within 60 s)
# binary messages are JPEG frames, text messages are {"url","title","tabs"}
curl -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>/live
# with control: the answer says "interactive": true when it was granted
curl -H "authorization: Bearer cc_live_..." "https://www.clearcotelabs.com/api/v1/browsers/<id>/live?control=1"
# a share link: control optional, 1 to 240 minutes (default 30)
curl -X POST -H "authorization: Bearer cc_live_..." -H "content-type: application/json" -d '{"control": false, "minutes": 60}' https://www.clearcotelabs.com/api/v1/browsers/<id>/share操作権限がある場合は、同じ WebSocket で JSON のテキストメッセージを送信します。座標は、表示しているフレームに対する割合(0〜1)で指定します。それ以外は無視されます。
| メッセージ | 動作 |
|---|---|
{"t":"mouse", | 押下(down)、解放(up)、または move。n はクリック回数、m は修飾キー(Alt 1、Ctrl 2、Meta 4、Shift 8)です。 |
{"t":"wheel", | 指定した位置で、ピクセル単位でスクロールします。 |
{"t":"key", | キーボードが送るのと同じ形式の、キーの押下または解放です。 |
{"t":"text", | 入力したかのようにテキストを挿入します(最大 5000 文字)。 |
{"t":"nav",、forward、reload、または {"t":"nav", | 履歴の移動、再読み込み、または http(s) アドレスを開きます。 |
GET /api/v1/browsers/<id> のレスポンスには traffic が含まれます。これはそのセッションのバイト数上位 20 サイトです。
セッションの管理
# one session: status, traffic, seconds, cost so far
curl -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>
# stop it (a running browser closes within about 15 seconds)
curl -X DELETE -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>
# balance + your 20 most recent sessions
curl -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers
# filtered by status and note text, up to 100; page back with before=<a createdAt you got>
curl -H "authorization: Bearer cc_live_..." "https://www.clearcotelabs.com/api/v1/browsers?status=active,ended¬e=shop-de&limit=50"
# label a session (null clears it)
curl -X PATCH -H "authorization: Bearer cc_live_..." -H "content-type: application/json" -d '{"note": "shop-de nightly"}' https://www.clearcotelabs.com/api/v1/browsers/<id>作成時にセッションへ note を付けておくと、一覧やダッシュボードで見つけやすくなります。以前のセッションと同じサーバーで新しいセッションを起動するには(キャッシュが温まった状態の同じマシン)、そのセッションの worker を渡します。そのサーバーが満杯の場合は、別のサーバーが割り当てられるのではなく 503 が返ります。
セッションは、ブラウザを閉じたときや切断したとき、また後述の制限に達したときにも終了します。停止リクエストを送ると、誰も接続していないセッションは即座に終了します。実行中のブラウザは、1 回の報告間隔(約 15 秒)以内にサーバーによって閉じられます。GET のレスポンスは次のとおりです。
{
"id": "bs_…",
"status": "active", // see the table below
"proxy": "managed", // or "custom"
"createdAt": "…", "startedAt": "…", "endedAt": null,
"endReason": null, // set once ended, e.g. "user", "balance", "launch_failed"
"stopRequested": false,
"usage": { "bytesUp": 120334, "bytesDown": 4812009, "gb": 0.0049, "seconds": 41 },
"traffic": [ { "site": "example.com", "bytesUp": 20400, "bytesDown": 3100000 }, … ], // top 20 sites
"costEur": 0.0050,
"pricing": { "eurPerGb": 1, "eurPerHour": 0 }
}| status | 説明 |
|---|---|
pending | Created; nobody has connected yet. Counts towards the concurrency limit until it starts or expires. |
active | A browser is running and reporting usage. |
lost | No usage report for 5 minutes. Billed up to the last report; a late report puts it back to active. |
ended | Closed: you disconnected, stopped it, or a limit or the balance ended it. endReason says which. |
expired | Nobody connected within two minutes of creating it. Never billed. |
一覧取得の GET /api/v1/browsers は、同じ形式のセッションオブジェクトを新しい順に並べた { balanceEur, sessions: [...] } を返します。
ブラウザを開いたままにして再接続する
デフォルトでは、クライアントが切断するとセッションは終了します。keepAlive: true を指定して起動すると、ブラウザはタブ、Cookie、出口 IP を保ったまま動作し続けます。そのため、後から実行するスクリプト(あるいはクラッシュ後やノート PC を閉じた後の同じスクリプト)で、前回の続きから作業を再開できます。
# a new single-use connect URL for a running keepAlive session (connect within two minutes)
curl -X POST -H "authorization: Bearer cc_live_..." https://www.clearcotelabs.com/api/v1/browsers/<id>/connect- 切断すれば実行したままにできます。Playwright の
browser.close()(connectOverCDP経由では切断するだけです)、Puppeteer のbrowser.disconnect()、あるいは単にプロセスを終了するだけでもかまいません。 DELETE /api/v1/browsers/<id>を送るか、CDP コマンドBrowser.closeを送ると終了します。Puppeteer のbrowser.close()はこのコマンドを送ります。Playwright ではawait (await browser.newBrowserCDPSession()).send("Browser.close")を使います。終了するまでは、同時実行数の枠を 1 つ占有し続けます。- 接続できるクライアントは同時に 1 つだけです。別のクライアントが接続している間の再接続は
409で拒否されます。keepAliveなしで起動したセッションへの再接続も同様です。 - 誰も接続していない間も、制限は適用されます。対象は
idleTimeoutSec(後で戻ってくるつもりのブラウザでは、最大 1800 まで引き上げてください)、timeoutSec、maxGb、そして残高です。開いたままのページはバックグラウンドのトラフィックを読み込み続け、その分もほかと同様に課金されます。
制限
- 1 アカウントあたり、同時に実行中または起動中にできるブラウザは 24 個までです。
- セッションの最長時間は 4 時間です。
- CDP コマンドが 5 分間送られないセッションは閉じられます(
idleTimeoutSecで変更できます)。 - 名前付きのプロファイル(セッション間で Cookie やサイトストレージを保持します)を使わない限り、各セッションは新しいブラウザプロファイルで始まり、そのプロファイルはセッション終了時に削除されます。
- 安全のため、ブラウザはローカルファイル(
file://)を開くこと、サーバー上のファイルをアップロードすること、プライベートネットワークや内部ネットワークにアクセスすること、ポート 25 でメールを送信することができません。 - ファイルのアップロードは Playwright から行えます。
setInputFiles()は手元のマシンからファイルを送信します(最大 50 MB)。Puppeteer のuploadFile()は拒否されます。ダウンロードしたファイルは当社のサーバーに残り、セッションとともに削除されます。ファイルを残したい場合は、ページ内から取得してその内容を返してください。 - ホスト型ブラウザには Chrome 拡張機能を読み込めません。
- ブラウザはコンソールメッセージやページエラーを転送しないため、
page.on("console")には何も届きません。必要な情報はページ内で収集し、evaluateで読み出してください。
エラー
| ステータス | code | 説明 |
|---|---|---|
| 400 | — | The body is not JSON, or an option is invalid; the message says which. |
| 400 | UNKNOWN_VERSION | No release matches version; the message lists the ones you can pick. |
| 401 | — | Missing, malformed or revoked API key. |
| 402 | INSUFFICIENT_BALANCE | Balance below the minimum. Top up in the dashboard. |
| 404 | NOT_FOUND | No session with that id on your account. |
| 409 | NOT_RUNNING | Live view or a reconnect asked for before the browser started or after it ended. |
| 409 | NOT_KEEPALIVE | This session cannot be reconnected. Start it with keepAlive: true. |
| 409 | PROFILE_IN_USE | Another session is already saving to that profile. Stop it, or open the profile with persist: false. |
| 429 | CONCURRENCY_LIMIT | Too many browsers running or starting at once. Close one first. |
| 429 | — | More than 60 create calls in a minute from one address. Slow down. |
| 503 | NO_CAPACITY | No free browser slot right now. Retry after a few seconds. |
| 503 | NO_WORKER | The server running that session is not reachable at the moment. |
| 503 | NOT_CONFIGURED | Hosted browsers are not configured on this server. |
| 503 | NOT_AVAILABLE | Notes or profiles are not enabled on this server yet. |
エラーは JSON 形式で返ります({ "error": "...", "code": "..." })。WebSocket 接続そのものが拒否された場合は、新しいセッションを作成してください。接続 URL は 1 回限りで、2 分後に失効します。WebSocket のアップグレードが拒否された場合は、HTTP ステータスと JSON の error が返ります。URL が使用済みの場合、セッションがキャンセルされた場合、keepAlive なしで起動された場合、すでに接続済みの場合は 409、URL の有効期限が切れている場合は 401 です。
リトライすべきもの
- バックオフ付きでリトライする:
503 NO_CAPACITYと503 NO_WORKER(多少のジッターを加えながら 1、2、4… 秒と待ち、数回で諦めます)、およびコードのない429(アドレス単位のレート制限)。 - バックオフ付きで数回だけリトライする:その他の
5xxレスポンスと、スクリプトの開始前に拒否された接続(新しいセッションで行います。古い接続 URL は使用済みです)。 - ループでリトライしない:
400(リクエストを修正します)、401、402(クレジットを追加します)、429 CONCURRENCY_LIMIT(先にブラウザを閉じます)、409 PROFILE_IN_USE。人が対処する必要があり、リトライしてもリクエストを無駄にするだけです。
ベストプラクティス
- 起動するのではなく接続する。
connectUrlを指定してconnectOverCDPまたはpuppeteer.connectを使ってください。chromium.launch()では、手元のマシンでブラウザが起動してしまいます。 - 既存のものを使う。新しいコンテキストを作らずに、
browser.contexts()[0]とその最初のページを使ってください。新しいコンテキストはプロファイルの Cookie やストレージなしで始まり、しかも Playwright はブラウザウィンドウと一致しない 1280×720 のエミュレートされたビューポートを割り当てます。 - ペルソナはスクリプトからではなく作成時に設定する。国、タイムゾーン、言語は作成リクエストで指定します。スクリプトからユーザーエージェント、ビューポート、navigator のプロパティを上書きすると、検知が探しているまさにその不整合を生んでしまいます。
- CDP フックは最小限にする。広範なリスナー、全リクエストのインターセプト、初期化スクリプトは、それ自体が自動化のフィンガープリントになります。標準の Playwright と Puppeteer はそのままで動作します。エンジンが
Runtime.enableの副作用をページ自体から切り離しているため、Patchright のようなパッチ適用済みドライバーは必須ではなく任意です。 - 1 つのセッションで多くのページを。時間がかかるのはブラウザの起動なので、起動したブラウザの中でページを移動してください。ログインは実行のたびに行うのではなく、プロファイルを使って一度だけ行います。
- 必ず停止する。
finallyでブラウザを閉じ、ジョブに合わせてmaxGbとidleTimeoutSecを設定してください。そうすれば、バグがあってもブラウザが残高を消費しながら動き続けることはありません。 - コードを足す前に様子を見る。サイトの挙動がおかしいときは、待機処理やワークアラウンドを追加する前に、ライブビューで様子を見てください(または操作を引き継いでください)。
フレームワーク
CDP 経由で Chrome に接続するものであれば、何でも connectUrl で動作します。URL は 1 回限りなので、自動で再接続するフレームワークでは接続ごとに新しいセッションが必要です。ブラウザは接続時に起動し、通常は数秒以内に立ち上がります。事前にステータスをポーリングする必要はありません。
// Patchright (optional; a Playwright fork): npm i patchright
import { chromium } from "patchright";
const browser = await chromium.connectOverCDP(connectUrl);
const page = browser.contexts()[0].pages()[0];# Browser Use
from browser_use import Agent, Browser
agent = Agent(task="Find the cheapest flight to Lisbon next Friday", llm=llm, browser=Browser(cdp_url=connect_url))
await agent.run()
# Crawl4AI
from crawl4ai import AsyncWebCrawler, BrowserConfig
config = BrowserConfig(browser_mode="custom", cdp_url=connect_url, use_managed_browser=True)
async with AsyncWebCrawler(config=config) as crawler:
result = await crawler.arun("https://example.com")// Stagehand v4
import { localBrowser, Stagehand } from "@browserbasehq/stagehand";
const stagehand = await Stagehand.create({ browser: await localBrowser.connect({ cdpUrl: connectUrl }) });たたき台にできるヘルパー
上記のリトライ付きの作成、接続、確実な停止を 1 つの関数にまとめたものです。
// clearcote-hosted.ts
import { chromium, type Browser } from "playwright"; // or "patchright"
const API = "https://www.clearcotelabs.com/api/v1/browsers";
const AUTH = { authorization: "Bearer " + process.env.CLEARCOTE_API_KEY };
const RETRY_CODES = new Set(["NO_CAPACITY", "NO_WORKER"]);
export async function createSession(options: Record<string, unknown> = {}, attempts = 5) {
for (let i = 0; ; i++) {
const res = await fetch(API, {
method: "POST",
headers: { ...AUTH, "content-type": "application/json" },
body: JSON.stringify(options),
});
const body = await res.json().catch(() => ({}));
if (res.ok) return body as { id: string; connectUrl: string; worker: string };
const retry = RETRY_CODES.has(body.code) || (res.status === 429 && !body.code) || [500, 502, 504].includes(res.status);
if (!retry || i + 1 >= attempts) throw new Error([res.status, body.code, body.error].filter(Boolean).join(" "));
await new Promise((r) => setTimeout(r, Math.min(15_000, 1000 * 2 ** i) * (0.5 + Math.random())));
}
}
export async function withBrowser<T>(options: Record<string, unknown>, work: (browser: Browser) => Promise<T>) {
const session = await createSession(options);
try {
const browser = await chromium.connectOverCDP(session.connectUrl);
try {
return await work(browser);
} finally {
await browser.close().catch(() => {});
}
} finally {
// Ends the session if closing the browser did not (a no-op otherwise).
await fetch(API + "/" + session.id, { method: "DELETE", headers: AUTH }).catch(() => {});
}
}
// await withBrowser({ profile: { name: "shop", persist: true }, country: "de" }, async (browser) => {
// const page = browser.contexts()[0].pages()[0];
// await page.goto("https://example.com");
// });Playground
ダッシュボードの Playground では、これらのブラウザの 1 つでスクリプトを実行し、その横にライブビュー、コンソール、スクリーンショットを表示できます。その下の「Use in your code」パネルには、上記のコードと同じセッションオプションが表示されます。Playground の page は Playwright ではなく、CDP を直接扱う小さなヘルパーです。そのため、Playground のスクリプトはそのまま貼り付けて使うファイルではなく、書き換えて使うためのたたき台です。
| ヘルパー | 動作 |
|---|---|
page.goto(url, { timeout? }) | ページに移動し、load イベントを待ちます。 |
page.click(sel) · page.type(sel, text) · page.press(key) | 要素を表示領域までスクロールしてから、実際のマウスとキーボードで入力します。 |
page.evaluate(fn, ...args) | ページ内で関数を実行し、その結果を JSON で受け取ります。 |
page.waitForSelector(sel, { timeout? }) · page.waitForNavigation() | 要素の出現、または次のページ読み込みを待ちます。 |
page.scroll(px) · page.screenshot({ fullPage? }) | マウスホイールでのスクロール。スクリーンショットは JPEG で「Screenshots」タブに表示されます。 |
page.title() · page.url() · page.content() | ドキュメントのタイトル、アドレス、HTML。 |
log(...values) · sleep(ms) | コンソールへの出力(オブジェクトは整形して表示)と一時停止。 |
cdp(method, params) | ブラウザへの生の CDP コマンド(Target.*、Browser.*、Storage.*)。 |
page.cdp(method, params) | ページへの生の CDP コマンド(Page.*、Runtime.*、DOM.*、Network.*)。 |
エラーには、発生元のスクリプトの行番号が示されます。「Share」は、スクリプトとそのセッションオプションを URL に含めたリンクをコピーするため、当社側には何も保存されません。リンクを開いた人は、自分の残高でそのスクリプトを実行します。