01概览
chromebot 为 AI agent 提供可自动启停、抗风控的浏览器实例。每个实例 = 一个固定身份 + 独立指纹 + 持久 profile 卷 + 自带代理,身份跨重启稳定。
两种用法:
- 控制台 UI —— GitHub 登录后在
/app点选创建 / 启动,实时查看并操作(浏览器内置 DevTools),可上传自定义插件、开启存活检测。 - 编程 API —— 用
Authorization: Bearer <token>调 REST,拿到 CDP 端点后用 patchright 经wss://驱动真实 Chrome。
基址
| 用途 | 地址 |
|---|---|
| REST API | https://at.ci/v1/... |
| CDP 网关 | wss://at.ci/cdp/{id}?token=... |
| 控制台 | https://at.ci/app |
约定:id为 32 位十六进制;token为cb_前缀 opaque 串,明文仅签发时返回一次,服务端只存 SHA-256。
02快速开始
1 · 登录拿 token
浏览器打开 https://at.ci 点「用 GitHub 登录」,成功后跳 /app 并种 httponly cookie。右上「API token → 重新生成」复制明文 cb_...(只显示一次,旧 API token 立即失效,不影响登录会话)。
2 · 创建实例
curl -s https://at.ci/v1/instances \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"my-account-1","locale":"en-US","tz":"America/New_York"}'
3 · 启动,拿 CDP 端点
curl -s -X POST https://at.ci/v1/instances/$ID/start \
-H "Authorization: Bearer $TOKEN"
# → {"status":"running","cdp_endpoint":"wss://at.ci/cdp/...?token=...","cdp_token":"cb_..."}
cdp_endpoint 已含 token,只返回一次;stop 后失效,running 时再调 start 会轮换。
4 · 驱动
from patchright.async_api import async_playwright
async with async_playwright() as pw:
b = await pw.chromium.connect_over_cdp(CDP_ENDPOINT)
pg = b.contexts[0].pages[0]
await pg.goto("https://abrahamjuliot.github.io/creepjs/")
await b.close() # 只断开 CDP,实例继续运行
5 · 停止 / 删除
POST /stop 保留身份与 profile;DELETE 永久销毁卷,不可恢复。
03鉴权
两条等价路径落到同一用户:
- 登录会话 —— httponly cookie
cb_token,GitHub 登录自动种。无状态签名(不落库),30 天有效且每次访问滑动续期,同账号多设备并存互不挤掉。 - Bearer token —— 编程 API,放
Authorization: Bearer cb_...。
解析顺序:先 Authorization 头,无则回落 cookie。都没有或无效 → 401 invalid or missing token。
登录会话与 API token 相互独立:POST /v1/token/regenerate 只轮换 API token(旧 API token 立即失效),不影响登录会话(不会把你登出);重新登录也不动 API token。每用户同时只有一个有效 API token,服务端只存 sha256(token),丢了再 regenerate;登录会话则可多设备同时有效。
| 凭据 | 前缀 / 形态 | 用途 | 有效期 |
|---|---|---|---|
| 登录会话 cookie | 签名串 | 浏览器 UI 调 /v1/* | 30 天,滑动续期;多设备并存 |
| 账户 API token | cb_ | 编程调所有 /v1/* | 长期,至下次 regenerate |
| 实例 CDP token | cb_ | 仅连 /cdp/{id} | start 返回一次;stop 即失效;再 start 轮换 |
04API · 账户
/v1/me当前用户与配额 → 200 {login, email, instances_used, instances_max}(email 可能为 null,instances_max 默认 10)。
/v1/token/regenerate无请求体。签发新明文 token,旧的立即失效 → 200 {"token":"cb_..."}(明文仅一次)。未鉴权 → 401。
05API · 实例
两种投影:Summary(id, name, status, platform, locale, tz, proxy_set, pending_restart, health_check, extensions_count, updated_at)、Detail(Summary + container_name, cdp_endpoint, fingerprint_committed, extensions[], mem_rss_bytes, cpu_pct, last_started_at, last_error)。
status ∈ created · starting · running · stopping · stopped · error · deleting。
/v1/instances创建实例(不启动)→ 201 Summary。
| 字段 | 类型 | 说明 |
|---|---|---|
name | string 必填 | 1–128 字符 |
platform | string | 默认 windows(MVP 仅此) |
locale | string? | 如 en-US,留空用 profile 默认 |
tz | string? | IANA,如 America/New_York |
proxy | string? | scheme 须 socks5/socks5h/http/https |
health_check | bool | 浏览器存活检测,默认 true,见 存活检测 |
错误:429 配额满、422 校验失败。插件在创建后经插件 API 上传。
/v1/instances列出本人实例 → 200 Summary[]。
/v1/instances/{id}详情 → 200 Detail(running 时附 cdp_endpoint(不含 token)、mem_rss_bytes、cpu_pct)。404 = 不存在或越权(不暴露他人实例)。
/v1/instances/{id}停 + 删容器 + 销毁 profile 卷 + 删记录,不可恢复 → 200 {"status":"deleted"}。
06API · 生命周期 & 代理
/v1/instances/{id}/start从 created/stopped/error 起容器、首启固化身份、签发新 cdp_token 转 running;对已 running 再调幂等,不重启容器、只轮换 cdp_token → 200 {id, status:running, cdp_endpoint, cdp_token}(明文仅一次)。错误:409 cannot start from status=...、500 start failed(转 error)。
/v1/instances/{id}/stop停容器转 stopped、清除 cdp_token(已建 CDP 连接失效),身份 / profile 保留可再 start。对 stopped/created 幂等 → 200 {status:stopped}。
/v1/instances/{id}/proxy请求体 {"proxy":"socks5://user:pass@host:1080"} 或 {"proxy":null} 清除。代理进程级:running 改不热生效,需下次 start(此时 pending_restart=true)。scheme 非法 → 422 → 200 {proxy_set, pending_restart}。
07API · 插件
为实例挂自定义浏览器插件:上传 .zip(仅 Manifest V3),解压后挂进该实例的 Chromium。每实例独立、随 profile 卷持久,改动下次 start 生效(running 时置 pending_restart,同代理语义)。可在创建弹窗直接选文件,或事后调本 API。
/v1/instances/{id}/extensionsmultipart/form-data,字段名 files,可一次多个 .zip。逐个安全解压校验落卷 → 200 {installed:[{slug,name,version}], errors:[{filename,error}], extensions:[…], pending_restart}。全部失败 → 400。
curl -s -X POST https://at.ci/v1/instances/$ID/extensions \
-H "Authorization: Bearer $TOKEN" \
-F 'files=@adblock.zip' -F 'files=@my-ext.zip'
/v1/instances/{id}/extensions列出已装插件 → 200 [{slug,name,version}]。
/v1/instances/{id}/extensions/{slug}删除一个插件 → 200 {slug, removed:true, pending_restart}。404 = 无此插件。
约束
- 仅 Manifest V3(容器 Chromium 已废弃 MV2);非 MV3 拒。
- 每实例最多 5 个;单包压缩 ≤ 20MB、解压 ≤ 120MB、文件数 ≤ 2000(防 zip 炸弹)。
- 上传 zip 经净化:拒 zip-slip 路径穿越、跳符号链接;
manifest.json可在根或单层子目录。
抗检测提示:装插件会增加指纹暴露面(web_accessible_resources 可被探测),按需使用。
08存活检测
创建时开关 health_check(默认 开),让实例自动发现浏览器崩溃 / 卡死。两层协作:
- 容器内探活(开时):每 10s 直探浏览器 DevTools,连续 3 次失败 → 判定浏览器已死,令容器退出。
- 控制面对账(始终生效):读实例(
GET列表 / 详情)时若「DB 标 running 但容器实际已没」→ 自动置status=error并写last_error(如browser dead (health check)/container OOM-killed/container exited),清除 CDP token。
所以:开检测能捕获浏览器级死亡(崩溃/卡死);关检测仍能捕获容器级死亡(OOM/进程退出)。检测到后实例转 error,身份 / profile 不受影响,重新 POST /start 即原样恢复(检测不自动重启)。
health_check为创建时设定;控制台卡片上以💓 存活检测标签标示,异常时红色卡片显示last_error。
09实时查看 (DevTools)
控制台卡片「查看 / 操作」用浏览器自带的 Chrome DevTools 前端(经控制面网关本地反代,不依赖外网),打开即看到实例真实画面并直接操作,Elements / Console / Network 全功能。
/v1/instances/{id}/devtools为 running 实例返回一次性可打开的 DevTools URL → 200 {url, target_id, title}。会轮换 CDP token 并内嵌进回连 ws(与 start 幂等轮换一致)。非 running → 409。
前端与控制面同源同 scheme,无混合内容;版本随容器 Chromium 必然匹配;token 不外泄第三方。编程驱动仍走 CDP 网关。
10CDP 驱动 (patchright)
CDP 网关是实例唯一对外暴露点。用 start 返回的 cdp_endpoint(已含 ?token=)直喂 connect_over_cdp,网关校验 token + 确认 running + 双向转发到容器内真实 Chrome。
import asyncio
from patchright.async_api import async_playwright
CDP = "wss://at.ci/cdp/<id>?token=<cdp_token>"
async def main():
async with async_playwright() as pw:
browser = await pw.chromium.connect_over_cdp(CDP)
ctx = browser.contexts[0]
page = ctx.pages[0] if ctx.pages else await ctx.new_page()
await page.goto("https://example.com")
print(await page.title())
await browser.close() # 仅断开 CDP,不停实例
asyncio.run(main())
注意
- 用
connect_over_cdp,不是launch。 - 端点是 browser 级 flat CDP;
browser.close()只断 CDP 不停实例,释放资源调/stop。 - token 失效场景:stop 后 / running 再 start 轮换后,旧端点立即不可连。
WS 关闭码
| 码 | 含义 |
|---|---|
4401 | token 无效 / 越权 |
4409 | 实例非 running |
4404 | 容器无 IP |
4502 | 上游连接失败 |
11代理设置
每实例绑一个独立出口代理,凭据 Fernet 密文落库,API 永不回显明文,日志按 scheme://***:***@host:port 脱敏。
支持:socks5://、socks5h://(远端解析 DNS,推荐)、http://、https://(均可带或不带 user:pass),其余 → 422。
两种时机:创建时带 proxy,或事后 PUT /proxy。生效为进程级,running 改返回 pending_restart:true,stop→start 后生效。
geo 对齐:建议把 locale/tz pin 到代理出口的真实地理(德国代理配de-DE/Europe/Berlin),避免「IP 在德国但浏览器声称美东」的矛盾。
12配额 & 生命周期
默认每用户 10 个实例(instances_max),超额 429 instance quota reached (max 10)。按实例数计,不分启停;腾配额需 DELETE。
created ──start──▶ starting ──▶ running ──stop──▶ stopped
│ │ 失败 │
│ └──▶ error └──start──▶ running
└──delete──▶ deleting ──▶ (销毁) stopped ──delete──▶ (销毁)
- 持久化:每实例一个 profile 卷(磁盘持久),固化身份存卷里。stop 保留、DELETE 销毁。
- 同实例反复 start/stop 身份稳定不变;DELETE 后无法复现。
- running 期间浏览器 / 容器死亡(见 存活检测)→ 自动转
error并记last_error,重新 start 恢复。 - 资源上限:每容器有内存 / CPU / PID 上限,
mem_rss_bytes/cpu_pct可观测。
13抗检测说明
- 固化身份:首启一次性固化指纹写入持久 profile(
fingerprint_committed:true),跨重启 navigator / 屏幕 / 字体 / 硬件一致,删实例才丢。 - 真实分辨率:按 profile 屏幕(如 1920×1080)开窗,window/screen 自洽,不再是 headless 默认的过小视口。
- locale/tz pin:与代理 geo 一致,消除地理矛盾。
- per-profile canvas/audio 噪声:实例间互不相同(不可聚类关联),同实例每次一致(不暴露被改)。
- 驱动层 patchright:规避
navigator.webdriver与 CDP 痕迹。
自测:连上后访问 https://abrahamjuliot.github.io/creepjs/,关注多次重启指纹是否稳定、IP geo 与 tz/locale 是否对齐、有无 automation 红旗。
14错误码速查
REST 错误体 {"detail":"..."}:
| 码 | 含义 | 处理 |
|---|---|---|
400 | 插件上传全部失败(非 zip / 非 MV3 / 超限 / 路径非法) | 看返回 errors[],修正后重传 |
401 | 缺 / 无效 token 或会话 | 重新登录 / regenerate API token |
404 | 不存在或越权(实例 / 插件) | 核对 id / slug / 归属 |
409 | 状态冲突(对 starting/stopping 调 start;对非 running 取 devtools) | 轮询 GET /v1/instances/{id} 至稳定再操作 |
422 | 校验失败(name 空/超长、platform、proxy scheme) | 修正请求体 |
429 | 配额满 | DELETE 不用的实例 |
500 | 容器启动失败转 error | 看 last_error,重试 start |
503 | GitHub OAuth 未配置(仅登录接口) | — |
CDP WS 关闭码 4401/4409/4404/4502 见 CDP 驱动。遇 4401/4409 重新 POST /start 拿最新带 token 端点再连。