跳到正文

推荐设置——哪些该开启,哪些别去动

在我们追查到配置原因的拦截中,大多数是因为伪装得更多,而不是更少。本页是我们实测结果的精简版:值得使用的设置、得不偿失的设置,以及任何设置都解决不了的少数问题。

从这里开始

如果这一页你别的都不看,就从这份配置开始。它只改变那些可以安全改变的维度,渲染、TLS 和浏览器版本都保持原样。

javascript
import { launch } from "clearcote";

const browser = await launch({
  lightStealth: true,        // metadata identity only — rendering, TLS and version stay real
  fingerprint: "account-42", // picks this identity's metadata bundle; keep it stable per identity
  proxy: { server: "http://gateway:8080", username: "u", password: "p" },
  geoip: true,               // timezone + languages + the address WebRTC reports follow the proxy exit
  humanize: true,            // trusted native input; navigator.webdriver stays false
});

Python 的接口完全相同,只是采用 snake_case:light_stealth、fingerprint、geoip、humanize。添加维度要有明确的理由,一次只加一个,并且只有当你能说清主机上有什么能佐证它时才添加。下文有几行——细粒度的 r12 控制项、使用录制动作轨迹的 humanize、socks5Udp、第三方 cookie 和代理相关的开关——适用于授权版构建(GitHub 免费版或 Pro)。开源版构建会忽略它们:humanize 会退回到生成的轨迹,而带密码的 SOCKS5 代理会拒绝连接。

原则:伪装就是一项需要主机来佐证的声明

检测并不是在寻找不寻常的值,而是在寻找矛盾——对同一台机器来说不可能同时成立的两个信号。一个没人伪装过的普通值,不会与任何东西矛盾。一个背后没有任何支撑的伪装值,则是一个阳性信号,而且这个信号比它所掩盖的那个值还要强。

所以,在启用任何选项之前,真正有用的问题不是“这个值合理吗?”,而是“这台机器上有什么能被拿来佐证它,得到的答案又是否一致?”屏幕尺寸会与渲染表面相互比对。GPU 字符串会与该 GPU 实际生成的像素相互比对。声称的操作系统会与该平台解析出的字体相互比对。如果主机无法为这项声明提供支撑,诚实的值反而更安全。具体机制参见检测的工作原理。

推荐这样做

设置原因
lightStealth: true通过原生的单值开关,只应用一组由种子派生、彼此一致的元数据维度——hardwareConcurrency、deviceMemory、colorDepth、devicePixelRatio、maxTouchPoints。Canvas、WebGL 和音频输出、TLS 以及真实的浏览器版本都保持原样。种子只决定一个身份分到一小组配置包中的哪一个,而且没有渲染噪声,所以同一台机器上的各个账号共享同一个 canvas 哈希。在缩放过或不常见的显示器上,请在 audit 页面上核对配置包选出的像素比与你的屏幕是否相符。
每个身份使用一个固定的 fingerprint 种子相同种子 ⇒ 相同身份。在账号的整个生命周期内一直使用它,并且在显卡厂商相同的机器上使用(参见不同机器上的同一个种子)。每次请求都轮换种子,会让同一个已登录账号每次都像是从一台新机器上访问,这个信号远比任何被轮换的值都要明显。如果同一台机器上有多个账号,且它们不能共享同一个 canvas,请使用带种子的身份画像(去掉 lightStealth),让每个账号都有自己的渲染噪声。
每次启动都设置 geoip: true时区、语言以及 WebRTC 报告的地址都跟随出口 IP——无论是否使用代理(也可以自行设置 timezone 和 acceptLanguage)。两者都没有时,SDK 会默认使用纽约和 en-US。美国住宅出口却报告 Europe/Amsterdam,只需一行代码就能查出来,而这种不匹配又很容易无意中造成。如果无法解析出地区,启动会以 GeoipError 中止,而不会使用本机的时钟启动——请修复代理,或调高 CLEARCOTE_GEOIP_TIMEOUT_SECONDS。
humanize: true输入会作为受信任的原生事件生成,沿着类人的光标轨迹移动(在 Pro 上,坐标点击使用录制的真人动作,其他情况使用生成的轨迹)。不要在上面再叠加你自己的逐步缓动——设置好途经点,让 humanize 去插值;手写的缓动会产生原生输入从来不会产生的运动统计特征。
在 Chrome 品牌下设置 widevine: true默认身份画像以 Google Chrome 的身份呈现,而 Google Chrome 会响应 Widevine DRM 查询。没有 CDM 时,该查询会被拒绝,任何页面都能读出这一点。参见 Widevine & DRM。
保持 allowThirdPartyCookies 的默认值第三方 cookie 默认允许,与原版 Chrome 一致(SDK 0.45.0+),嵌入式登录、支付和验证 iframe 依赖它们。只有对不应看到它们的网站才设为 false(授权版构建,152 r22+;更早的构建始终阻止它们)。
每个身份一个持久化 contextCookie、站点存储和缓存都是身份的一部分。一台每次访问都像从未见过该站点的机器,本身就是一种特征模式。
在容器中借助 Xvfb 以有头模式运行有头模式的 Chrome 直接避开了无头模式的各种破绽。官方镜像默认就是这样运行的。
在你打算声称的操作系统上运行CSS2 系统字体关键字由浏览器之下的平台来解析,而不是由页面或 user agent 所能控制的任何东西决定,因此即使更换 user agent,它们也不会变。如果做不到,请保持 Linux 字体包完整——见下文。
tlsProfile 保持为 match-personaClientHello 会跟随身份画像声称的 Chrome 版本,因此网络层与 UA 保持一致。默认值本来就是正确的;之所以要知道有这个选项,是为了避免把它固定成与你的品牌版本不一致的值。

避免这些做法

以下每一条都是我们实测中出过问题的情况,而不是泛泛的提醒。

避免会出什么问题
在显示器对不上的主机上伪装 screen / avail*伪造的屏幕尺寸如果无法与真实窗口和渲染表面对上,在最严格的反机器人系统上就是一个稳定的拦截触发条件。这正是屏幕尺寸不属于 lightStealth、需要主动开启的原因。如果你确实要设置,就设置完整且一致的一整组——screenWidth、screenHeight、availWidth、availHeight——并留出一个合理的任务栏间隙,而不是让 avail 等于 screen。在无头模式下,SDK 已经会替你选择一个合理的屏幕。
在软件渲染的主机上声称拥有 GPU在容器中,或在通过 WARP、SwiftShader 或 llvmpipe 进行光栅化的云虚拟机上,声称拥有桌面 GPU 的身份画像描述的是一块芯片,而这块芯片生成不了页面能读回的那些像素。从授权版构建 150 r12 起(开源版构建会忽略它),gpuStringSpoof: false 会报告主机真实的 WebGL vendor 和 renderer,其他一切保持不变——在这类硬件上,这往往是更好的取舍。需要注意的是:WebGPU 仍然跟随范围更广的 disableGpuFingerprint,因此只用这个细粒度开关时,navigator.gpu 仍在描述身份画像,而 WebGL 报告的却是真实设备。
为了改一项设置而动用 disableGpuFingerprint 或 fingerprintNoise: false两者都是打包在一起的开关。disableGpuFingerprint 改变的是字符串,加上参数表,再加上身份画像字段屏蔽(在开源版构建和 154 r34 之前的授权版构建上,还包括 readPixels 扰动);fingerprintNoise: false 会把 canvas 2D 和 WebGL 噪声一起关掉。自授权版构建 150 r12 起有了细粒度控制:gpuStringSpoof: false 只针对字符串,canvasNoise: false 只针对 canvas 2D。这两个细粒度控制都有值得了解的代价:gpuStringSpoof: false 会改变 WebGL 字符串,但不会改变 navigator.gpu,于是同一页面上 WebGL 和 WebGPU 报出的厂商各不相同——分数好看,细看却经不起推敲。而 canvasNoise: false 与种子一起使用时,会彻底去掉每个账号各自的 canvas。请优先使用范围更广的 disableGpuFingerprint,它会让 WebGL 和 WebGPU 一起改变。
在会话中途轮换身份在会话进行中更换种子、代理出口或时区,会与该会话此前的状态相矛盾。要在身份之间轮换,切勿在同一个身份内部轮换。
在 Linux 上使用 Windows 身份画像,却去掉了字体包Linux 版本自带度量兼容的替代字体(Segoe UI→Selawik、Arial→Arimo、Times New Roman→Tinos 等),SDK 会在启动时通过 FONTCONFIG_FILE 把它们接入。如果把它们去掉,或者构建一个丢弃了 <binDir>/fonts/ 的自定义镜像,身份画像声称的那些 Windows 字体族就会全部坍缩成同一个默认字体。这一点不需要任何参考数据就能看出来:Windows 自带的两种不同字体,测出来的尺寸却完全相同。
在 Windows 电脑上使用 Linux 身份画像Windows 通过 Direct3D 驱动 GPU,这会压低 WebGL 和 WebGPU 的限制值(顶点 uniform 向量数、最大纹理尺寸……)。身份画像可以调低某个限制值,但无法把它调到超过驱动实际执行的值,因此 Windows 上的 Linux 身份画像报告的限制值是任何真实 Linux 机器都不会有的。从 SDK 0.41.0 起,在 Windows 主机上声明 Linux 的本地启动会在每个进程中警告一次。请在 Windows 上声明 Windows,或者在 Linux 上或 Docker 中运行 Linux 身份画像。
在上面再叠加一个 JS stealth 插件Clearcote 的各项控制是编译进引擎的。在其上再加一层脚本级的垫片(shim),恰恰重新引入了引擎方案本来要消除的那种自我暴露的层——被覆盖的 getter 字符串化后会显示它自己的源码,一次 toString() 就能读出来。
不加限定地在桌面引擎上使用移动端身份画像platform: "android" 会改变 UA、触控、指针类型、屏幕和视口,但像素仍然来自桌面 GPU(只是挂着移动 GPU 的名称),精细的页面几何特征也仍是桌面的。它只是一个尽力而为的身份画像;请把渲染表面当作你仍需自行解决的部分。

代理

代理对结果的影响通常比任何浏览器参数都大,却也是最常被随手留作默认值的那一项。

  • 固定出口。每个身份一个出口 IP,并在整个会话中保持不变。一个在流程中途变更 IP 的会话会自相矛盾,无论指纹做得多好。
  • 让地理位置与身份画像相匹配,然后让 geoip: true 推导出时区和语言,而不是手动设置它们。
  • 带凭据的代理交给引擎处理。HTTP(S) 代理的密码由浏览器自身来应答(授权版构建 151 r19+,Python & Node SDK),因此页面缓存保持开启,请求也不会因拦截而被打上标记。在授权版构建上,带密码的 SOCKS5 可以直接使用——把用户名和密码作为单独的字段传入即可。transparentProxy: true(152 r22+)会在请求头和连接时序中隐藏代理的存在。
  • 理解 WebRTC 的取舍。Chromium 从不通过 HTTP 或 SOCKS 代理发送媒体流,因此对等连接的流量原本会走你的真实网络连接,中继服务器就能从中读到你的真实地址。所以 SDK 会阻止不经代理直接发出的 WebRTC UDP 流量。在开源版构建上,geoip / webrtcIp 会解除这一阻止;授权版构建则会保留它。请搭配 geoip: true(或 webrtcIp)使用,这样页面仍能看到真实 Chrome 会产生的候选地址,其中报告的是代理的地址;否则 WebRTC 什么也收集不到,看起来就像被屏蔽了。如需通过 SOCKS5 代理建立可用的对等连接,请设置 socks5Udp: true(授权版构建;你的代理服务商必须允许 UDP 中继),或者使用不设浏览器代理的全隧道(WireGuard/OpenVPN),并在 args 中传入你自己的 --webrtc-ip-handling-policy。其他情况下,请保留默认的阻止策略。

设置解决不了的部分

有些最常被读取的指纹面,取决于你租用的机器,而不是你在上面运行的浏览器。

指纹面由主机决定的内容
渲染没有 GPU 的主机只能用软件进行光栅化。这一类产品都只能二选一:要么声称拥有无法渲染的硬件,要么承认自己用的是软件光栅化器。如果渲染出的像素对你的目标站点很重要,解决办法是换一台有 GPU 的主机——或者使用 canvas bridge,它会把渲染工作转发给一台有 GPU 的机器。
字体字体列表受限于主机实际能绘制的字体——从授权版构建 150 r12 起,标准字体列表来自三千台真实机器,并且仍会与主机的字体取交集,因此页面不会被告知任何无法渲染的字体。在 Linux 上,SDK 使用随浏览器附带的字体包,并忽略主机上安装的字体,因此在你的镜像里安装软件包不会改变页面看到的内容,除非你自己列出某个目录(SDK 0.41.0+,参见在 Linux 上使用你自己的字体)。字体包无法绘制的字符(开源版构建中的 CJK;r32 之前的授权版构建中的几种印度系文字、高棉文和埃塞俄比亚文)会显示为空白方框,除非列出了相应的字体;自 sdk-0.41.0 起的 Docker 镜像自带这些字体。在 Windows 上使用的是主机自身的字体。字体包里的 Windows 替代字体能让宽度看起来合理,但页面也可以用 local() 按名称加载字体,而替代字体并不带有真实的字体名:在 Linux 上用 Windows 身份画像实测时,Segoe UI 和 Georgia 被测为已安装,却无法加载。在 r32 起的授权版构建上,用 fontDirs 列出的真正 Windows 字体会以其自己的名称渲染。
字体缩放器每个字形的尺寸都由操作系统的字体缩放器决定:Windows 上是 DirectWrite,Linux 上是 FreeType。任何设置都触及不到它。把字号按 0.01 px 逐步调整,在 Windows 上有 99% 的步进会改变文本宽度,在 Linux 上则是 66%;而一台以 Windows 身份呈现的 Linux 主机,测出来的仍然是 66%。如果页面会仔细读取你的文本,请在你所声称的操作系统上运行。参见 user agent 之下的字体栈。

去验证,而不是想当然

本页的每一条结论都可以在浏览器内部测量出来。把你配置好的会话指向 audit 页面,查看它标出不一致的那些行——这正是我们用来发现上述问题的同一套工具,它会告诉你当前的配置违背了本页的哪些建议。