Playwright & Puppeteer
Clearcote 本质上就是 Chromium,因此可以直接替换你现有自动化工具所用的浏览器。
使用 SDK(推荐)
clearcote 包(npm、PyPI 和 NuGet)把身份选项变成具名参数,返回的是普通的 Playwright 对象。它会在首次使用时下载浏览器并做 SHA-256 校验——没有许可证密钥时是开源版构建,有密钥时是最新的授权版构建(参见安装)——并应用下文介绍的默认设置。
# pip install clearcote
from clearcote import launch
browser = launch(fingerprint="seed-123", platform="windows", brand="Chrome")
page = browser.new_page()
page.goto("https://example.com")
browser.close()当前 SDK 版本:0.31.1。从 0.23 起,Python 和 Node 中的 launch() 不再使用无痕模式,而是运行在一个真实的临时 profile 目录上(关闭时删除),这样与 profile 相关的可检测面就和真实 Chrome 一致,widevine: true 也能加载 DRM 模块。它返回一个类似 browser 的句柄:newPage() 照常可用,但 newContext() 返回的是同一个 profile 上下文,而不是一个隔离的新上下文。需要相互独立的 Cookie 存储时,请分别启动多个浏览器;传入 ephemeralProfile: false / ephemeral_profile=False 可以拿回旧的无痕 Browser,传入 userDataDir / user_data_dir 则可以保留 profile。
异步 API(clearcote.async_api)在 asyncio 循环中接受同样的身份画像(persona)和代理选项,返回 Playwright 异步对象;它的 launch() 使用无痕模式,所以需要 profile 时(以及使用 widevine=True 时)请用 launch_persistent_context()。.NET SDK 支持 LaunchEphemeralProfileAsync(推荐——.NET 的 LaunchAsync 使用无痕模式,且无法在无头模式下让窗口适配屏幕)、LaunchPersistentContextAsync、ServeAsync、经过校验的下载、授权、Geoip 以及拟人化输入(通过显式调用 HumanClickAsync / HumanTypeAsync / HumanSelectOptionAsync 实现,而不是启动参数)。已保存的 profile、profile: "auto"、渲染一致性检查、Widevine 和智能体辅助功能目前仅支持 Python & Node。完整的可复制工作流请参见示例。
Puppeteer 及其他 CDP 客户端(serve)
SDK 没有提供 Puppeteer 启动器,而是提供了 serve():它用 SDK 的启动设置(身份画像、代理、默认值)启动 Clearcote,并在回环地址上开放一个 CDP 端点,任何 CDP 客户端都可以接入——Puppeteer、Playwright 的 connectOverCDP、browser-use、Crawl4AI、Stagehand。它支持授权版构建,而且整个过程中没有任何环节会添加 --enable-automation。humanize 是在 Playwright 侧实现的,因此对以这种方式接入的客户端不起作用。
import { serve } from "clearcote";
import puppeteer from "puppeteer-core";
const srv = await serve({ fingerprint: "seed-123", platform: "windows" });
const browser = await puppeteer.connect({ browserURL: srv.cdpUrl, defaultViewport: null });
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.disconnect();
await srv.close();在无头模式下,serve() 会为浏览器提供一个真实尺寸的显示器,并在任何客户端接入之前把窗口适配到工作区(0.31+);如需更小的窗口,请传入 windowSize / window_size。在命令行中,clearcote serve 以常驻服务的形式实现同样的功能——还可以为每个连接分配独立的浏览器,其身份、代理、时区和语言都取自连接 URL。参见部署。
直接驱动二进制文件(开源版构建)
你也可以用自己的启动器驱动开源版构建:通过 executablePath(Node)或 executable_path(Python)指定路径,并以 args 传入身份选项。授权版构建无法以这种方式启动——它需要 SDK 申领的许可证令牌,所以请使用上文的 launch() 或 serve()。像 SDK 那样去掉 --enable-automation(Puppeteer 和较旧版本的 Playwright 会添加它;它会让 Chromium 切换到自动化模式)。走这条路时,SDK 的默认设置(语言、WebRTC 策略、窗口几何尺寸)一概不生效。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
executable_path=r"C:\clearcote\chrome.exe",
headless=False,
ignore_default_args=["--enable-automation"],
args=[
"--fingerprint=seed-123",
"--fingerprint-platform=windows",
"--timezone=America/New_York",
],
)
page = browser.new_page()
page.goto("https://abrahamjuliot.github.io/creepjs/")
browser.close()解析或预取经过校验的二进制文件
SDK 按以下顺序确定使用哪个浏览器:显式指定的 executablePath / executable_path,然后是 CLEARCOTE_BINARY,然后是你指定的 version(或 CLEARCOTE_BROWSER_VERSION),然后是授权版构建(前提是找到了许可证密钥:licenseKey 选项、CLEARCOTE_LICENSE_KEY 或 ~/.clearcote/license.key),最后是当前 SDK 版本锁定的开源版构建。你提供的路径会在启动前检查是否有缺失或被截断的文件。如果你想在不启动浏览器的情况下预热缓存、使用自定义缓存目录,或在运行时主动选用 GitHub 上最新的开源版构建,请调用 download / executable_path。
from clearcote import download, launch
chrome = download(cache_dir=r"C:\clearcote-cache", auto_update=True)
browser = launch(executable_path=chrome, fingerprint="seed-123")锁定模式会校验 SDK 内置的 SHA-256 值。autoUpdate / auto_update 需要手动开启,它会校验发行版的校验和清单;如果系统中有 gpg,还会用内置锁定的 Clearcote 签名密钥指纹核验已签名的清单。它只适用于开源版构建:设置了许可证密钥时,download() 会改为获取当前的授权版构建(可用 version 或 releaseChannel 选择——参见选择构建)。在 .NET 中,DownloadAsync 只获取开源版构建;要预取授权版构建,请使用 ExecutablePathAsync(new LaunchOptions { LicenseKey = … })。
导入真实 Chrome 的指纹 profile
除了由种子(seed)派生的合成身份画像,你还可以让 Clearcote 上报从真实机器采集的值。可以用 tools/fingerprint-collect 中的采集器从一台作为样本来源的 Chrome 上采集(打开 collect.html,点击 Capture,它会下载一个 JSON profile),也可以用自带的 convert_dataset.py 转换器,基于开源的 chrome-fingerprints 数据集(10k 条记录)快速起步。采集内容包括 navigator、屏幕几何参数、WebGL vendor/renderer + getParameter 限制值、Web Audio、语音合成音色、字体、编解码器,以及 CSS @media 特性。
把 profile 以文件路径、对象或 JSON 字符串的形式传给 SDK 即可——gzip + base64 打包由它代劳。profile 中存在的字段会覆盖浏览器原本上报的值;缺失的字段则回退到浏览器的默认值。如果你没有显式设置 Accept-Language,SDK 还会根据 profile 的 navigator.languages 推导出它。两种构建都能读取导入的 profile;在授权版构建上,从 151 r19 起完全生效。
不要在使用 profile 的同时传入 fingerprint 种子。种子会启用 farbling 层,严格的评分会把它判读为 canvas 篡改,而且它提供的东西 profile 本来就已经提供了。二者选其一。profile 只替换上报的值——它不会改变实际负责渲染像素的部分,所以同一台机器上的两个账号即使用了两个不同的 profile,产生的 canvas 仍然完全相同。如果需要按账号区分渲染结果,请为每个账号使用单独的种子,并且不使用 profile。
from clearcote import launch
browser = launch(fingerprint_profile="profile.json", disable_gpu_fingerprint=True, fingerprint_noise=False)
page = browser.new_page()
page.goto("https://example.com")
browser.close()默认保持一致(无需额外参数)
无论你用什么参数启动,引擎都会让次要可检测面与你选择的身份画像保持一致——对于 Windows 身份画像,就是与真实的 Windows 桌面版 Chrome 上报的内容一致。身份画像的平台默认跟随宿主操作系统,所以在 Linux 主机上,除非传入 platform,这些值会遵循 Linux 身份画像。WebGL getParameter 限制值(WebGL1 + WebGL2)上报的是身份画像的数值,但不会超过本机 GPU 实际能提供的上限;UNMASKED_RENDERER / UNMASKED_VENDOR 在整个会话内保持不变(每个站点看到的都是同一块 GPU,并与身份画像对应)。对于 Windows 身份画像,navigator.getBattery() 上报的是接通电源的台式机,navigator.connection 上报住宅网络连接,AudioContext 上报与之匹配的 Windows WASAPI 采样率和延迟。getScreenDetails() 上报单个显示器,@media (pointer: fine) / (hover: hover) 与使用鼠标的台式机相符。
已在 153 r26 上验证,Windows 有头模式(2026 年 9 月):BrowserScan 的结果为未发现机器人,CreepJS 显示 0% stealth。使用代理时,请加上 geoip,让时区和 WebRTC 与出口保持一致。
控制台与页面错误事件
引擎不会把控制台事件或页面错误事件转发给自动化客户端,因此 page.on("console") 和 page.on("pageerror") 收不到任何内容。这是有意为之——检测自动化是否存在的探针,测的恰恰就是这种转发。页面内的 window.onerror 和 unhandledrejection 处理函数会正常触发,所以要捕获控制台输出,请在页面内收集,再用 page.evaluate() 读回来。Python 和 Node SDK 会在启动时就此打印一次提示。
自动匹配代理所在地区(geoip)
在传入代理的同时传入 geoip,SDK 就会解析代理的出口 IP——在离线的 geoip-all-in-one 数据库中查询——并为该地区设置一致的时区 + navigator 首选语言 + Accept-Language + WebRTC IP。再也不用为每个代理手动匹配时区:
from clearcote import launch
browser = launch(
fingerprint="user-7423",
proxy={"server": "http://host:8080", "username": "u", "password": "p"},
geoip=True, # timezone + language auto-matched to the proxy's region
)它还会为该地区设置地理位置和完整的 navigator.languages 列表,支持 HTTP 和 SOCKS5 代理(包括带凭据的代理),三个 SDK 都支持(.NET 中为 Geoip = true)。首次运行会下载该数据库(约 50 MB)。如果无法解析出地区,启动会以 GeoipError 中止,而不是悄悄用本机的时钟和语言启动;如果仍要启动,请同时设置 timezone 和 acceptLanguage。查询的时限为 20 秒(CLEARCOTE_GEOIP_TIMEOUT_SECONDS);在 .NET 中,该错误为 GeoipException。如果既没有 geoip 也没有显式指定 timezone,时区会跟随语言——en-US 就意味着纽约时区,哪怕用的是德国代理。
想自己设置?使用 acceptLanguage(Node)/ accept_language(Python),例如 "en-US,en"——它会设置 Accept-Language 请求头、完整的 navigator.languages 数组以及 navigator.language——Intl / toLocaleString 也会随之变化。
拟人化输入(humanize & showCursor)
传入 humanize 后,所有输入——移动、点击、拖拽、滚动和打字——都遵循同一套拟人化标准,既覆盖页面级操作(page.click / hover / type / fill / mouse.* / keyboard.type),也覆盖 locator 级操作(locator.click / type / fill / pressSequentially / dragTo / …)。移动轨迹是一条从上一次光标位置出发、略带弧度的三次贝塞尔曲线,并按最小加加速度(min-jerk)子运动叠加的方式走完(一次弹道式主运动 + 一次修正运动——也就是真实伸手动作那种多峰的速度曲线,而不是单个对称的钟形曲线),所有操作都以真实、可信的事件派发(isTrusted === true,且 navigator.webdriver 保持为 false)。在 Pro 上,坐标点击(mouse.click(x, y))沿用从真人身上录制的运动轨迹;其他所有操作,以及开源版构建和 GitHub 免费版上的每一次点击,都使用生成的路径。加上 showCursor 会绘制一个跟随运动的圆点,方便你观察。
由于移动使用的是原生输入,用 mouse.down() 按下的鼠标按键会在整个移动过程中保持按住——因此 down → move → up 是一次真正按住按键的拖拽(滑块之类的拖动定位控件收到的是真实按下状态的拖拽),locator.dragTo 同样是拟人化的。打字逐键进行,按键间隔随机化,在词语边界处会停顿,偶尔还会打错再改正;滚动采用 ease-out 惯性,偶尔会有阅读停顿。fill 会先聚焦输入框再逐字输入(超过约 200 个字符的值仍一次性填入,免得批量填充慢如蜗牛)。
from clearcote import launch
browser = launch(fingerprint="seed-123", humanize=True, show_cursor=True)
page = browser.new_page()
page.goto("https://example.com")
page.click("text=Sign in") # eased curve, then a trusted click
page.fill("#email", "you@example.com") # focus + key-by-key human typing
page.locator("#password").type("s3cr3t") # locators are humanized too
# held-button drag (e.g. a slider): the press stays held across the move
x0, y0, x1 = 100, 300, 400 # the handle's start, and where to release it
page.mouse.move(x0, y0); page.mouse.down()
page.mouse.move(x1, y0); page.mouse.up()
browser.close()渲染后端一致性检查(checkRenderCoherence)
身份画像可以声称自己用的是某块 GPU,但如果页面实际上是由软件光栅化器绘制的(SwiftShader / llvmpipe——在没有 GPU 的无头环境中很常见),严格的检测器就能分辨出来。对一个已打开的页面执行探测:它会读取页面实际看到的(未屏蔽的)WebGL vendor/renderer,标记出软件光栅化器回退(这是致命的无头破绽——请启用 canvas bridge,或在真实 GPU 上以有头模式运行)或不一致的 vendor/renderer 组合,并返回结构化的判定结果。传入声称的 GPU,还可以同时断言实际渲染的 GPU 系列。同步、异步和 Node 版本均可用。
from clearcote import launch, check_render_coherence
browser = launch(fingerprint="seed-123")
page = browser.new_page(); page.goto("about:blank")
verdict = check_render_coherence(page) # {'renderer', 'software_suspected', 'coherent', 'warnings'}
if not verdict["coherent"]:
print(verdict["warnings"]) # e.g. software rasterizer / incoherent GPU family
browser.close()Profile & 持久化
重复使用同一个 fingerprint 种子,就能在多次运行之间保持稳定的身份;再配合 user-data 目录,即可持久化 Cookie 和存储:
from clearcote import launch_persistent_context
ctx = launch_persistent_context(r"C:\clearcote\profiles\acme", fingerprint="acme-tenant-7", headless=False)
page = ctx.pages[0] if ctx.pages else ctx.new_page()profile 目录中的 Cookie 使用与创建该目录的机器绑定的密钥加密。如需把 profile 连同 Cookie 一起复制到另一台机器,请传入 portableProfile: true / portable_profile=True(密钥随 profile 一起迁移)或 encryptionKey / encryption_key(密钥由你提供的 secret 派生,不会有任何敏感信息写入磁盘)。仅限授权版构建,支持 Python & Node。
SDK 还支持保存身份画像:Profile 会把指纹选项、代理设置、canvas bridge 设置及其他启动选项以 JSON 形式存放在 ~/.clearcote/profiles 下(可用 CLEARCOTE_PROFILE_DIR 覆盖)。
from clearcote import Profile, launch, launch_persistent_context
Profile("acct-1", {
"fingerprint": "acct-1",
"gpu_vendor": "Google Inc. (Intel)",
"gpu_renderer": "ANGLE (Intel, Intel(R) UHD Graphics ... D3D11)",
"canvas_bridge": {"url": "ws://127.0.0.1:8443", "auth": "user:secret"},
}).save()
ctx = launch_persistent_context(r"C:\clearcote\profiles\acct-1", profile="acct-1")
browser = launch(profile="acct-1", headless=False)已保存的 profile 是明文存储的,可能包含 canvasBridge.auth 之类的凭据。请把 profile 文件当作受信任的输入对待,不要提交到代码仓库,也不要分享给他人。更多启动选项
extensions——未打包扩展的目录路径列表(会生成--load-extension+--disable-extensions-except)。disablePrivacySandbox/disable_privacy_sandbox——设为true可关闭 Privacy Sandbox API(Topics、FLEDGE / Protected Audience、Shared Storage、Private Aggregation、Fenced Frames)。自 0.23 起此选项默认关闭:默认身份画像呈现为 Google Chrome,而 Google Chrome 提供了上述所有 API。只有当身份画像是去 Google 化的 Chromium 时才开启它。WebUSB 不受影响。agentTyping/agent_typing——智能体的击键节奏(默认human/fast/instant)。参见智能体。tlsProfile——让 TLS ClientHello 与身份画像声称的 Chrome 版本保持一致,使网络层跟随 UA(而不是构建自身原生的 TLS)。默认值"match-persona"跟随brandVersion;"native"保持原样不做改动;"chrome-<major>"固定为某个主版本。参见指纹参数。platform: "android"——尽力而为的移动端身份画像(触控、粗略指针、移动端屏幕/DPR、Mali/Adreno WebGL、手机视口)。在桌面引擎上,GPU 渲染仍然是桌面端的——请搭配 canvas bridge 实现渲染一致性。storageQuota、fingerprintProfile、canvasBridge、webrtcIp、acceptLanguage、disableGpuFingerprint、fingerprintNoise——参见指纹参数。
较新的选项(授权版构建)
以下选项需要授权版构建(GitHub 免费版或 Pro)。方括号中是各选项所需的引擎修订版本;在较旧的引擎上,SDK 会跳过标注 152 r22 的选项并给出警告,其余选项则会被引擎忽略。
allowThirdPartyCookies/allow_third_party_cookies——像原版 Chrome 一样允许第三方 Cookie。去 Google 化的底座默认会阻止第三方 Cookie,导致依赖它们的嵌入式登录、支付和验证框架无法正常工作。[152 r22]transparentProxy/transparent_proxy——在请求头和连接时序中隐藏代理(纯 HTTP 请求不携带代理请求头;经代理的连接上报的时序与复用连接相同)。需要配合代理使用。[152 r22]fingerprintVoices: false/fingerprint_voices=False——保留本机自己的语音,而不使用身份画像的语音列表。[152 r22]fingerprint: "off"——完全不使用身份画像启动,用于排查问题。[152 r22]socks5Udp/socks5_udp——让 WebRTC 的 UDP 流量经由socks5://代理传输,这样语音、视频和点对点连接都能正常工作,而且仍然从代理的地址发出。代理必须允许这样做;很多住宅代理池并不允许。[151 r17]portableProfile/encryptionKey——可在机器之间复制的 profile(见上文)。[151 r14;Python & Node]personaSchema: 2/persona_schema=2——一种可选的身份模型,其中屏幕和显卡芯片与身份画像声称的处理器和内存相匹配。默认关闭,因此每个现有种子都保持原来的身份。只在配有真实显卡的机器上添加realGpuHost/real_gpu_host。[151 r19;Python & Node]shaderDialect: "hlsl"——参见着色器方言。[151 r15]profile: "auto"——启动一个为本机挑选的真实采集指纹,而不是使用种子;可以用profileSelect/profile_select调整。支持 Python & Node。在 Node 中,从 SDK 0.31.1 起它可用于默认的launch()、launchPersistentContext()和serve();0.31.0 及更早版本需要ephemeralProfile: false,且配合serve()使用时会失败。
与引擎构建无关的选项:
version/releaseChannel——选择构建;参见选择构建。licenseKey/license_key/LicenseKey——许可证密钥(如果它不在CLEARCOTE_LICENSE_KEY或~/.clearcote/license.key中)。licenseThroughProxy/license_through_proxy(或CLEARCOTE_LICENSE_THROUGH_PROXY=1)——通过本次启动所用的代理发送许可证请求,而不是从本机直接发出。launch()上的ephemeralProfile/userDataDir——参见上文使用 SDK 一节中的说明。widevine: true——DRM 播放(两种构建均支持);参见 Widevine & DRM。quiet——关闭 SDK 的启动警告和进度输出。
一致的默认设置(可覆盖)
SDK 会应用几项符合隐蔽性要求的默认设置,避免露出明显的破绽:
- 有头模式启动默认不模拟视口(
viewport: null/no_viewport=True),这样window.innerWidth会跟随真实的操作系统窗口——在真实窗口上套一个模拟的 1280×720 视口,就是一个“不可能存在的窗口”破绽。传入显式的viewport可覆盖此行为。 - WebRTC 默认使用
disable_non_proxied_udp,因此不会有 UDP 流量不经代理直接发出,本机的真实地址也不会外泄。只有你自己在args中传入的--webrtc-ip-handling-policy能覆盖它——此外,在开源版构建上,webrtcIp/geoip也会让 WebRTC UDP 重新从你自己的网络连接发出(授权版构建无论如何都会阻止 WebRTC UDP)。在该策略下且没有设置webrtcIp时,页面完全拿不到任何 ICE candidate——使用代理时,请传入geoip(或webrtcIp),让 WebRTC 上报代理的地址,或者用socks5Udp通过 SOCKS5 代理传输真实的 UDP。 - 使用代理时,QUIC / HTTP-3 处于关闭状态,与真实 Chrome 使用代理时一样——因此不会尝试在代理之外发送任何 UDP。
- 身份画像的平台默认跟随宿主操作系统,品牌默认为 Google Chrome。如果既没有
timezone也没有geoip,时区会跟随语言(en-US→ 纽约)。 - 在引擎支持的情况下,代理凭据交给浏览器处理,而不是交给 Playwright:SOCKS5 始终如此(Playwright 根本无法对 SOCKS5 进行认证),HTTP(S) 则从 151 r19+ 起如此,这样页面缓存可以保持开启(Python & Node)。见下文。
humanize会在每次可信点击之前执行可操作性预检(可见 / 已启用 / 稳定 + 基于elementFromPoint的遮挡检查),不满足条件时回退到原生点击,因此可信点击不会在元素被遮挡或动画进行中时触发。
带凭据的 SOCKS5
原版 Chromium 根本无法对 SOCKS5 代理进行认证——它没有实现用户名/密码子协商——所以常见的变通做法是使用一个保存凭据的本地中继。授权版构建在引擎中实现了这一功能(RFC 1929),因此无需中继。把用户名和密码作为单独的字段传入,或写在地址里(socks5://user:pass@host:port);无论哪种方式,SDK 都会把它们交给引擎。写在地址里的凭据需要 SDK 0.31.1 或更新版本:旧版 SDK 会让 Playwright 把凭据丢掉,代理收不到任何登录信息。
from clearcote import launch_persistent_context
ctx = launch_persistent_context(
"./profile",
proxy={"server": "socks5://proxy.example.net:1080", "username": "user", "password": "pass"},
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://api.ipify.org?format=json") # confirm the exit IP is the proxy's需要授权版构建(GitHub 免费版或 Pro,引擎 151 r14 或更新);开源版构建无法对 SOCKS5 代理进行认证,因此请配合本地中继或 HTTP 代理使用。
在信任一个会话之前,务必先核实出口地址。代理如果静默失效并直接放行,流量就会从你自己的 IP 发出,其他所有防范措施都将失去意义。请在启动时用一个回显地址的服务检查一次,而不是想当然。
提示:用你自己的账号/租户 ID 派生种子,让每个身份都可以复现——同样的种子,每次都得到同样的浏览器指纹。完整的开关列表见指纹参数。