Trình duyệt được lưu trữ sẵn (hosted)
Khởi động một trình duyệt Clearcote trên máy chủ của chúng tôi chỉ với một lệnh gọi API và điều khiển nó qua Chrome DevTools Protocol, từ Playwright, Puppeteer hoặc bất kỳ client CDP nào. Bạn không phải tự cài đặt hay vận hành gì cả. Mặc định, lưu lượng đi ra qua IP dân cư, và bạn trả tiền theo GB từ số dư trả trước.
- IP dân cư, không phải datacenter. Các trang web thấy một kết nối internet gia đình thật của một nhà mạng dân dụng, không phải địa chỉ của dịch vụ hosting hay cloud.
- Phần cứng thật, không phải VPS. Trình duyệt chạy trên các máy chủ vật lý chuyên dụng của chính chúng tôi, không phải trên máy ảo cloud dùng chung.
Miễn phíNhận €5 lưu lượng miễn phí khi kết nối GitHub. Không cần thẻ.
Áp dụng một lần, mỗi tài khoản GitHub chỉ nhận một lần. Tài khoản GitHub phải được tạo ít nhất 30 ngày.
Bảng giá
- €1.00 mỗi GB, đã bao gồm proxy dân cư. Mặc định, mọi phiên đều ra internet qua một IP dân cư: một kết nối gia đình thật của một nhà mạng dân dụng, không phải địa chỉ datacenter hay hosting. Khoản €1.00 chính là để trả cho lưu lượng đó; không có hóa đơn proxy riêng.
- Lưu lượng được đo giữa trình duyệt và internet, tính gộp cả upload lẫn download (1 GB = 109 byte). Xem những gì được tính là lưu lượng.
- Không tính phí theo thời gian, số phiên hay số message CDP.
- Trả trước: nạp tiền trong dashboard. Một trình duyệt cần số dư tối thiểu €0.50 để khởi động, và mỗi phiên bị giới hạn ở mức số dư có thể chi trả vào lúc phiên bắt đầu, dùng chung giữa các trình duyệt bạn đang chạy (nếu
maxGbcủa bạn thấp hơn thì áp dụng giá trị đó). Trình duyệt đang chạy sẽ bị dừng khi số dư về 0 (mức sử dụng được báo cáo khoảng mỗi 15 giây, nên lần báo cáo cuối có thể khiến số dư xuống dưới 0 một chút).
Những gì được tính là lưu lượng
Mọi byte mà trình duyệt gửi tới hoặc nhận về từ một trang web đều được đếm trên đường truyền, giống cách một nhà cung cấp proxy tính. Với một trang thông thường, điều đó có nghĩa là:
- Được tính: bản thân trang và mọi thứ nó tải: script, stylesheet, hình ảnh, font, video, lệnh gọi API, quảng cáo và tracker, WebSocket, cùng với request header, cookie và phần overhead mã hóa (TLS) của mỗi kết nối. Trang vẫn tiếp tục tải trong lúc bạn chờ, nên polling chạy nền và analytics cũng bị tính.
- Không tính: kết nối CDP giữa code của bạn và trình duyệt (lệnh, kết quả, ảnh chụp màn hình, PDF, nội dung trang bạn đọc ra), chế độ xem trực tiếp (live view) trong dashboard, và mọi thứ trình duyệt không hề tải về (request bạn chặn, file được lấy từ cache của chính trình duyệt).
Ước lượng sơ bộ: một trang văn bản nhẹ tốn ít hơn hẳn 1 MB, một trang tin tức hay trang bán hàng thông thường tốn 2 đến 5 MB, còn một single-page app nặng hoặc bất cứ thứ gì có video thì từ 10 MB trở lên. Với giá €1.00 mỗi GB, 1.000 trang, mỗi trang 3 MB, là khoảng 3 GB. Phiên của chính bạn sẽ cho thấy số liệu thực: dashboard liệt kê lưu lượng của từng phiên cùng 20 site tốn nhiều byte nhất, và maxGb đặt giới hạn cho một phiên để một trang mất kiểm soát không thể ngốn hết số dư của bạn.
Giảm lưu lượng
Phần lớn dung lượng của một trang thường là những thứ script không cần đến. Những cách tiết kiệm nhiều nhất, theo thứ tự:
- Chặn hình ảnh, media và font. Chúng thường chiếm từ một nửa trang trở lên. Hãy chặn chúng theo mẫu URL ngay trong trình duyệt, khi đó chúng không bao giờ rời khỏi trình duyệt nên cũng không bao giờ bị tính phí — và trình duyệt vẫn giữ được cache:
// 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"] });Đừng dùng page.route() / context.route() hay request interception của Puppeteer chỉ để bỏ request: chúng tắt cache của trình duyệt, nên trang nào cũng phải tải lại script và style, tốn nhiều hơn phần hình ảnh bạn tiết kiệm được. Chỉ dùng chúng khi bạn cần sửa request. Tùy chọn adblock cũng chặn sẵn quảng cáo và tracker cho bạn.
- Chặn quảng cáo, analytics và tracker. Hủy các request tới những domain bên thứ ba mà bạn không cần. Danh sách top site của phiên trong dashboard cho biết domain nào tốn của bạn nhiều nhất.
- Đừng chờ lâu hơn mức cần thiết.
waitUntil: "networkidle"chờ mọi thứ mà trang tải, kể cả quảng cáo. Nên dùng"domcontentloaded"rồi chờ đúng phần tử bạn thực sự cần. - Đóng trình duyệt ngay khi xong việc. Một trang còn mở sẽ tiếp tục polling ở chế độ nền. Hạ
idleTimeoutSecxuống để một phiên bị bỏ quên tự đóng. - Dùng lại một trình duyệt cho nhiều trang. Nhờ cache, script và style dùng chung giữa các trang trên cùng một site chỉ phải tải một lần, không phải mỗi trang một lần. Hãy điều hướng trong cùng một phiên thay vì mở phiên mới cho từng URL.
- Gọi API của site khi có thể. Khi trình duyệt đã có một phiên dùng được trên site, một lệnh
fetch()từ bên trong trang để lấy đúng phần JSON bạn cần chỉ nặng bằng một phần nhỏ so với tải lại cả trang. - Đặt giới hạn. Đặt
maxGbcho mọi phiên để một trang nặng bất ngờ sẽ dừng lại thay vì rút cạn số dư của bạn.
Chặn tài nguyên hoạt động tốt với hầu hết các site, nhưng một số ít site kiểm tra xem hình ảnh hoặc font có thực sự được tải hay không. Nếu một site hoạt động khác đi khi bật chặn, hãy cho phép lại loại tài nguyên đó cho site ấy.
Muốn xem nó chạy thử trước? Playground chạy một script trong trình duyệt cloud ngay từ dashboard của bạn, với live view, output console và ảnh chụp màn hình đặt cạnh nhau.
1. Lấy API key
Tạo một key trên trang “API keys”. Key bắt đầu bằng cc_live_ và được gửi dưới dạng bearer token. Hãy giữ bí mật: bất kỳ ai có key đều có thể tiêu số dư của bạn.
2. Khởi động trình duyệt và kết nối
POST /api/v1/browsers trả về một connectUrl: một URL WebSocket dùng một lần cho trình duyệt đó. Hãy kết nối trong vòng hai phút; URL này không dùng lại được.
// 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()Lệnh tạo trả về 201 kèm mọi thứ code của bạn cần để kết nối và để biết giá:
{
"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
}Tùy chọn
Tất cả đều không bắt buộc. Gửi chúng dưới dạng JSON body của lệnh tạo.
| Trường | Kiểu | Ý nghĩa |
|---|---|---|
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. |
Mặc định stealth và danh tính
Mỗi phiên bắt đầu từ các thiết lập trên trang thiết lập khuyến nghị: bật lightStealth, một seed, cùng múi giờ và ngôn ngữ đi theo IP đầu ra. Với lightStealth (mặc định), seed chọn device profile từ một tập nhỏ có thay đổi số nhân CPU, bộ nhớ và pixel ratio; canvas, WebGL và audio là của chính máy chạy phiên đó. Đặt lightStealth: false để có persona đầy đủ theo từng seed: kết quả readback của canvas và WebGL nhận noise theo từng site từ seed, còn chuỗi GPU, màn hình và thiết lập audio (sample rate, latency) đi theo persona. Nếu không có identity hoặc fingerprint, mỗi phiên nhận một seed ngẫu nhiên và một IP mới.
Truyền identity: "account-42" để các phiên sau quay lại với cùng device profile, và cùng IP chừng nào IP dân cư đó còn online; đó chính là điều một tài khoản đã đăng nhập mong đợi. Danh tính là riêng tư trong tài khoản của bạn: một khách hàng khác dùng cùng nhãn sẽ nhận seed và IP của riêng họ. Bản thân danh tính không giữ cookie: muốn vậy, hãy dùng profile.
Profile: đăng nhập một lần
Profile lưu cookie, localStorage và IndexedDB của một phiên dưới một cái tên, để phiên tiếp theo dùng tên đó bắt đầu ở trạng thái đã đăng nhập. Truyền profile: { name: "shop-account", persist: true } để nạp profile và lưu lại khi phiên kết thúc, hoặc chỉ profile: "shop-account" để nạp ở chế độ chỉ đọc. Phiên đầu tiên với một tên mới sẽ bắt đầu trống và tạo ra profile đó.
// 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" }),
});- Cùng thiết bị, cùng IP. Một profile mang theo danh tính riêng (
profile:<name>), nên site thấy cùng một fingerprint, và cùng IP dân cư chừng nào IP đó còn online, giống một khách hàng quay lại. Tự truyềnidentityhoặcfingerprintnếu bạn muốn chọn khác, và giữ nguyên quốc gia giữa các lần chạy. - Mỗi lúc chỉ một phiên được ghi. Chỉ một phiên đang chạy được lưu vào một profile; phiên thứ hai với
persist: truesẽ nhận409 PROFILE_IN_USE. Các phiên chỉ đọc có thể chạy song song và thấy trạng thái đã lưu gần nhất. - Được lưu khi phiên kết thúc, dù bạn đóng trình duyệt, ngắt kết nối, dừng phiên hay phiên kết thúc do chạm một giới hạn. Nếu trình duyệt bị crash, trạng thái đã lưu trước đó được giữ nguyên thay vì bị thay bằng một trạng thái không đầy đủ. Session cookie (cookie không có thời hạn) bị bỏ đi, giống như trình duyệt thật bỏ chúng khi khởi động lại.
- Tối đa khoảng 3,5 MB sau khi nén. Nếu IndexedDB của một site khiến profile lớn hơn mức đó, IndexedDB sẽ bị bỏ ra; cookie và localStorage vẫn được lưu.
- Riêng tư. Profile chỉ thuộc về bạn (“shop-account” của một khách hàng khác là một profile khác), được lưu dưới dạng mã hóa và chỉ được giao cho máy chủ đang chạy phiên của bạn. Liệt kê bằng
GET /api/v1/browsers/profiles, xóa một profile bằngDELETE /api/v1/browsers/profiles/<name>, hoặc dùng dashboard.
IP đầu ra: xoay vòng, cố định (sticky) và nhắm theo vị trí
- Mặc định: mỗi phiên trình duyệt nhận một IP dân cư đầu ra riêng và giữ nó trong suốt phiên.
- Sticky: truyền cùng một nhãn
proxySession(ví dụ mỗi tài khoản bạn quản lý một nhãn) để lấy lại cùng IP đầu ra ở một phiên sau. Nhãn là riêng tư trong tài khoản của bạn. Một IP dân cư còn dùng được chừng nào peer của nó còn online, thường là vài giờ; khi peer offline, bạn nhận một IP khác từ cùng mạng đó. - Vị trí:
country, sau đó có thể thêmstatevàcity. Nhắm càng hẹp, pool IP càng nhỏ. - Proxy của riêng bạn:
proxy: { server: "http://host:port", username, password }, hoặcsocks5://host:port(tên miền được phân giải ở phía chúng tôi) haysocks5h://host:port(tên miền do proxy của bạn phân giải). Request được kiểm tra nghiêm ngặt: phải có port tường minh, thông tin đăng nhập nằm trongusername/password(URL dạnguser:pass@hostsẽ bị trả về 400; mỗi trường tối đa 255 byte), còncountry,state,cityvàproxySessionbị từ chối chứ không bị lặng lẽ bỏ qua, vì chúng mô tả pool được quản lý. Địa chỉ của chính proxy phải là địa chỉ public. Lưu lượng được tính phí theo cùng cách.
Mặc định, múi giờ và ngôn ngữ đi theo IP đầu ra (geoip). Truyền timezone / locale để tự chọn, hoặc geoip: false để tắt tính năng này.
Ghim một bản phát hành
Các phiên chạy bản phát hành Clearcote hiện tại. Để chạy một bản cũ hơn, hãy truyền version: bản phát hành đầy đủ ("152.0.7977.82-r21"), chỉ phần rebuild ("r21"), một phiên bản Chromium hoặc major ("152" chọn bản build mới nhất của major đó), hoặc "latest". Đây cũng chính là các bản phát hành mà tùy chọn version của SDK tải về, nên một phiên hosted và một lần chạy local ghim cùng một giá trị sẽ dùng cùng một bản build.
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 ... " ]- Kiểm tra
warnings. Một bản phát hành cũ không có những gì các bản sau bổ sung. Các tùy chọn và bản sửa lỗi ra đời sau nó có thể bị thiếu, hoặc bị lặng lẽ bỏ qua thay vì bị từ chối, nên một thiết lập chạy được trên bản hiện tại có thể âm thầm không có tác dụng gì trên một bản đã ghim. - Trường
enginetrong response luôn cho biết phiên đang chạy bản phát hành nào, dù có ghim hay không. - Một bản phát hành không tồn tại sẽ trả về
400với mãUNKNOWN_VERSION, và message liệt kê các bản phát hành bạn có thể chọn. - Phiên đầu tiên trên một bản phát hành mà máy chủ của chúng tôi chưa từng dùng có thể mất thêm tối đa một phút để khởi động trong lúc bản build được tải về. Các phiên sau trên bản đó khởi động nhanh như mọi phiên khác.
Trang khởi đầu và chặn quảng cáo
urlmở một trang trong tab đầu tiên trước khi bạn kết nối, nên trang đã đang tải sẵn khi script của bạn gắn vào.adblock: truetừ chối các request tới những host quảng cáo, xác minh quảng cáo và analytics phổ biến trước khi chúng được gửi đi, nên chúng không bao giờ bị tính phí. Danh sách này được cố ý giữ ở mức thận trọng (tag manager, công cụ quản lý consent, SDK đăng nhập và CAPTCHA được để nguyên), nhưng một số ít site nhận ra việc thiếu quảng cáo; hãy tắt nó ở những nơi điều đó quan trọng.
Xem trực tiếp: theo dõi, tự điều khiển, chia sẻ
Trong dashboard, nhấp vào một phiên để xem lưu lượng của nó đã đi tới những site nào và để xem trực tiếp. Nhấn “Take control” để tự nhấp, gõ, cuộn, dán và điều hướng trong đó, ví dụ để đăng nhập hoặc hoàn tất một bước kiểm tra mà script của bạn không tự làm được. Script của bạn vẫn giữ kết nối suốt thời gian đó, nên hãy tạm dừng nó trong lúc bạn thao tác. Thao tác của con người được tính là hoạt động, nên một phiên bạn đang điều khiển sẽ không bị đóng vì idle. “Share” tạo một liên kết mà bất kỳ ai cũng mở được mà không cần tài khoản, chỉ để xem hoặc kèm quyền điều khiển, có hiệu lực từ 15 phút đến 4 giờ và không bao giờ kéo dài quá thời điểm phiên kết thúc.
Qua 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>/shareKhi có quyền điều khiển, hãy gửi các message văn bản dạng JSON trên cùng WebSocket đó. Tọa độ là phân số (0 đến 1) của khung hình bạn đang xem; mọi thứ khác đều bị bỏ qua.
| Message | Tác dụng |
|---|---|
{"t":"mouse", | Nhấn (down), nhả (up) hoặc di chuyển (move); n là số lần nhấp, m là phím bổ trợ (Alt 1, Ctrl 2, Meta 4, Shift 8). |
{"t":"wheel", | Cuộn theo số pixel tại một điểm. |
{"t":"key", | Một phím được nhấn xuống hoặc nhả ra, đúng như bàn phím gửi đi. |
{"t":"text", | Chèn văn bản như thể được gõ vào (tối đa 5000 ký tự). |
{"t":"nav",, forward, reload, hoặc {"t":"nav", | Lịch sử, tải lại, hoặc mở một địa chỉ http(s). |
GET /api/v1/browsers/<id> có kèm traffic: 20 site tốn nhiều byte nhất của phiên đó.
Quản lý phiên
# 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>Đặt note cho phiên khi tạo để tìm lại nó trong danh sách và trên dashboard. Để khởi động một phiên trên cùng máy chủ với một phiên trước đó (cache còn nóng, cùng một máy), hãy truyền worker của phiên đó; khi máy chủ đó đã đầy, bạn nhận 503 chứ không được chuyển sang máy chủ khác.
Một phiên cũng kết thúc khi bạn đóng trình duyệt hoặc ngắt kết nối, và khi chạm một trong các giới hạn bên dưới. Yêu cầu dừng sẽ kết thúc ngay một phiên chưa có ai kết nối; còn trình duyệt đang chạy sẽ được máy chủ của nó đóng trong vòng một chu kỳ báo cáo, khoảng 15 giây. GET trả về:
{
"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 | Ý nghĩa |
|---|---|
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. |
Lệnh liệt kê, GET /api/v1/browsers, trả về { balanceEur, sessions: [...] } với cùng các đối tượng phiên, mới nhất xếp trước.
Giữ trình duyệt mở và kết nối lại
Mặc định, phiên kết thúc khi client của bạn ngắt kết nối. Nếu khởi động với keepAlive: true, trình duyệt sẽ tiếp tục chạy, giữ nguyên các tab, cookie và IP đầu ra, để một script sau (hoặc chính script đó sau khi bị crash hay khi bạn gập laptop) có thể tiếp tục từ chỗ lần trước dừng lại:
# 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- Để trình duyệt tiếp tục chạy bằng cách ngắt kết nối:
browser.close()của Playwright (quaconnectOverCDPnó chỉ ngắt kết nối),browser.disconnect()của Puppeteer, hoặc đơn giản là kết thúc process của bạn. - Kết thúc phiên bằng
DELETE /api/v1/browsers/<id>, hoặc bằng cách gửi lệnh CDPBrowser.close:browser.close()của Puppeteer làm việc này, còn trong Playwright thì dùngawait (await browser.newBrowserCDPSession()).send("Browser.close"). Cho đến lúc đó, trình duyệt vẫn chiếm một slot trong giới hạn đồng thời của bạn. - Mỗi lúc chỉ một client: kết nối lại trong khi đang có client khác gắn vào sẽ bị từ chối với
409, và kết nối lại vào một phiên được khởi động không cókeepAlivecũng vậy. - Các giới hạn vẫn áp dụng khi không có ai kết nối:
idleTimeoutSec(hãy tăng lên, tối đa 1800, cho trình duyệt mà bạn định quay lại dùng),timeoutSec,maxGbvà số dư của bạn. Một trang để mở vẫn tiếp tục tải lưu lượng nền, và phần này bị tính phí như mọi lưu lượng khác.
Giới hạn
- 24 trình duyệt đang chạy hoặc đang khởi động cùng lúc trên mỗi tài khoản.
- Mỗi phiên kéo dài tối đa 4 giờ.
- Phiên không nhận lệnh CDP nào trong 5 phút sẽ bị đóng (thay đổi bằng
idleTimeoutSec). - Mỗi phiên bắt đầu với một profile trình duyệt mới, bị xóa khi phiên kết thúc, trừ khi bạn dùng một profile có tên, loại profile giữ cookie và dữ liệu lưu trữ của site giữa các phiên.
- Vì lý do an toàn, trình duyệt không thể mở file local (
file://), upload file từ máy chủ, truy cập mạng riêng hoặc mạng nội bộ, hay gửi email qua cổng 25. - Upload file hoạt động từ Playwright:
setInputFiles()gửi file từ máy của bạn (tối đa 50 MB).uploadFile()của Puppeteer bị từ chối. File tải xuống nằm lại trên máy chủ của chúng tôi và bị xóa cùng phiên; để giữ một file, hãy fetch nó từ bên trong trang và trả về nội dung của nó. - Không thể nạp Chrome extension vào trình duyệt hosted.
- Trình duyệt không chuyển tiếp console message hay lỗi trang, nên
page.on("console")sẽ im lặng. Hãy thu thập những gì bạn cần bên trong trang rồi đọc lại bằngevaluate.
Lỗi
| Trạng thái | code | Ý nghĩa |
|---|---|---|
| 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. |
Lỗi được trả về dạng JSON: { "error": "...", "code": "..." }. Nếu chính kết nối WebSocket bị từ chối, hãy tạo một phiên mới: connect URL chỉ dùng được một lần và hết hạn sau hai phút. Một WebSocket upgrade bị từ chối sẽ trả về một mã trạng thái HTTP kèm error dạng JSON: 409 khi URL đã được dùng, phiên đã bị hủy, không được khởi động với keepAlive, hoặc đang có kết nối rồi; 401 khi URL đã hết hạn.
Khi nào nên thử lại
- Thử lại với backoff:
503 NO_CAPACITYvà503 NO_WORKER(chờ 1, 2, 4… giây kèm một chút jitter, và dừng sau vài lần), cùng429không có code (rate limit theo địa chỉ). - Thử lại vài lần với backoff: các phản hồi
5xxkhác, và một lần kết nối bị từ chối trước khi script của bạn bắt đầu (với một phiên mới: connect URL cũ đã dùng rồi). - Không thử lại trong vòng lặp:
400(sửa request),401,402(nạp thêm tiền),429 CONCURRENCY_LIMIT(đóng bớt một trình duyệt trước) và409 PROFILE_IN_USE. Phải có người xử lý; thử lại chỉ tốn request vô ích.
Các thực hành tốt nhất
- Kết nối, đừng launch. Dùng
connectOverCDPhoặcpuppeteer.connectvớiconnectUrl;chromium.launch()thì lại khởi động một trình duyệt trên chính máy của bạn. - Dùng những gì đã có sẵn. Lấy
browser.contexts()[0]và trang đầu tiên của nó thay vì tạo context mới: context mới bắt đầu mà không có cookie và dữ liệu lưu trữ của profile, và Playwright gán cho nó một viewport giả lập 1280×720 không khớp với cửa sổ trình duyệt. - Đặt persona lúc tạo phiên, không phải từ script. Quốc gia, múi giờ và ngôn ngữ thuộc về lệnh tạo. Ghi đè user agent, viewport hay các thuộc tính navigator từ script sẽ tạo ra đúng những điểm bất nhất mà cơ chế phát hiện đang tìm.
- Giữ CDP hook ở phạm vi hẹp. Listener diện rộng, việc chặn (interception) mọi request và init script chính là fingerprint riêng của automation. Playwright và Puppeteer nguyên bản vẫn chạy bình thường: engine giữ các tác dụng phụ của
Runtime.enabletránh xa chính trang web, nên một driver đã được vá như Patchright chỉ là tùy chọn chứ không bắt buộc. - Một phiên, nhiều trang. Khởi động trình duyệt là phần chậm; hãy điều hướng bên trong nó. Đăng nhập một lần với một profile thay vì đăng nhập ở mỗi lần chạy.
- Luôn dừng phiên. Đóng trình duyệt trong khối
finally, và đặtmaxGbcùngidleTimeoutSecphù hợp với công việc, để một bug không thể để trình duyệt chạy mãi và tiêu số dư của bạn. - Quan sát trước khi thêm code. Khi một site hoạt động bất thường, hãy xem nó trong live view (hoặc giành quyền điều khiển) trước khi thêm các lệnh chờ và cách xử lý tạm thời.
Framework
Bất cứ thứ gì gắn vào Chrome qua CDP đều hoạt động với connectUrl. URL này chỉ dùng một lần, nên framework nào tự kết nối lại sẽ cần một phiên mới cho mỗi kết nối. Trình duyệt khởi động khi bạn kết nối, thường trong vòng vài giây; không có trạng thái nào cần poll trước.
// 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 }) });Một helper để bắt đầu
Tạo phiên với cơ chế thử lại ở trên, kết nối, và luôn dừng phiên, tất cả trong một hàm:
// 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 trong dashboard chạy một script trên một trong các trình duyệt này, với live view, console và ảnh chụp màn hình ngay bên cạnh, còn panel “Use in your code” bên dưới hiển thị cùng các tùy chọn phiên như code ở trên. page của nó là một helper nhỏ giao tiếp trực tiếp qua CDP, không phải Playwright, nên script Playground là bản phác thảo cần chuyển đổi, không phải file để dán nguyên vào:
| Helper | Chức năng |
|---|---|
page.goto(url, { timeout? }) | Điều hướng và chờ sự kiện load. |
page.click(sel) · page.type(sel, text) · page.press(key) | Thao tác chuột và bàn phím thật, cuộn phần tử vào tầm nhìn trước. |
page.evaluate(fn, ...args) | Chạy một hàm trong trang và nhận lại kết quả JSON. |
page.waitForSelector(sel, { timeout? }) · page.waitForNavigation() | Chờ một phần tử, hoặc chờ lần tải trang tiếp theo. |
page.scroll(px) · page.screenshot({ fullPage? }) | Cuộn bằng con lăn chuột; một ảnh JPEG xuất hiện trong tab “Screenshots”. |
page.title() · page.url() · page.content() | Tiêu đề, địa chỉ và HTML của tài liệu. |
log(...values) · sleep(ms) | In ra console (object được định dạng cho dễ đọc); tạm dừng. |
cdp(method, params) | Một lệnh CDP thô gửi tới trình duyệt (Target.*, Browser.*, Storage.*). |
page.cdp(method, params) | Một lệnh CDP thô gửi tới trang (Page.*, Runtime.*, DOM.*, Network.*). |
Lỗi cho biết dòng script gây ra nó. “Share” sao chép một liên kết chứa script và các tùy chọn phiên ngay trong URL, nên không có gì được lưu ở phía chúng tôi; ai mở liên kết sẽ chạy script bằng số dư của chính họ.