“我不相信,热光学迷彩!”
这是一个 HTTP 代理,旨在绕过使用 JA4+ 等指纹识别技术来阻止某些 HTTP 客户端的服务。使用此代理,你可以继续使用你偏好的 HTTP 客户端(如 curl),同时拥有与真实(Chrome/Chromium)网页浏览器神奇地无法区分的指纹。thermoptic 还附带了一些有趣的功能,用于缓解基于 JavaScript 的指纹识别。它还使得同时使用网页浏览器和底层 HTTP 客户端进行混合抓取变得容易。
即使你不熟悉 JA4+ 指纹识别,如果你做过任何抓取工作,你很可能之前就被它阻止过。像 Cloudflare 这样的流行服务使用此类技术(以及其他技巧)来检测“非人类”HTTP 客户端的使用,从而阻止请求。这些服务还可以使用这种指纹识别来检测你是否用真实浏览器开始会话,然后稍后切换到像 curl 这样的底层客户端。thermoptic 通过为所有抓取请求呈现统一的“真实”浏览器指纹,解决了所有这些问题。
以下是不使用代理时 curl 的 JA4H(HTTP)指纹示例:
$ curl https://ja4db.com/id/ja4h/
ge11nn090000_b6a016211e8a_000000000000_e3b0c44298fc
这与直接访问该 URL 时 Chrome 生成的指纹截然不同:
ge11cn19enus_f2808f0d04cf_9a10d4221160_7068f58def6e
然而,当我们使用代理发起请求时,我们的 JA4H 指纹神奇地变得完全一致:
$ curl --proxy http://thermoptic:1234 https://ja4db.com/id/ja4h/
ge11cn19enus_f2808f0d04cf_9a10d4221160_7068f58def6e
(我们的 JA4 TLS 指纹同样如此,等等)。
要在 Ubuntu 22.04 上启动一个通过容器化 Chrome 实例伪装流量的 thermoptic 代理:
常规 Docker 设置(适用于没有 GPU 运行时的主机):
docker compose up --build
就是这样,现在你可以通过它代理流量了:
curl --proxy http://127.0.0.1:1234 --insecure https://ja4db.com/id/ja4h/
重要说明:
PROXY_USERNAME 和 PROXY_PASSWORD 设置身份验证。---insecure,则需要使用位于 ./ssl/rootCA.crt 的生成的 CA 文件。该文件在您首次运行 thermoptic 时生成。thermoptic 连接到任何使用 --remote-debugging-port 标志启动的 Chrome/Chromium 实例。这一点至关重要,因为您会希望在更常用的环境中设置和代理,以尽可能保持您的指纹低调(例如 Windows 上的 Chrome)。/dev/dri,并让捆绑的 Chrome 容器切换到 NVIDIA/Vulkan 渲染路径。要使用此功能,请运行 docker compose -f docker-compose.yml -f docker-compose.gpu.yml up --build。curl、requests 等)与 thermoptic 集成,只需设置代理,您的指纹就会被处理好。./hooks/onstart.js 中查看。http://127.0.0.1:14111),用于控制 Docker 化的 Chrome 浏览器窗口。这对于手动登录网站,然后无缝地使用代理以您已登录的会话发出请求(以及用于调试)非常有用。docker-compose.yml 中的 UPSTREAM_PROXY 环境变量设置上游 HTTP 或 SOCKS 代理 URI。
curl)发出 HTTP 请求,并将 thermoptic 设置为代理。thermoptic 分析请求,以最佳方式确定它应该是什么类型的浏览器请求(例如手动访问 URL?表单提交?fetch() 请求?)。thermoptic 使用 Chrome 调试协议 (CDP) 来操控浏览器,并设置一个页面,以完全模拟该请求在真实 Web 浏览器中通常发生的方式。thermoptic 通过模拟的上下文触发请求并捕获 HTTP 响应。thermoptic 将 HTTP 响应发送回客户端。由于浏览器实际上是使用其完整技术栈发出请求的,因此生成的 JA4 指纹是完全相同的。
注意:由于许多 WAF 采用针对 Web 浏览器的 JavaScript 级别指纹识别,thermoptic 还暴露了钩子,以便在抓取过程的关键步骤中利用浏览器。有关更多信息,请参阅本节。
直截了当地说:其他方法存在根本性缺陷,使其无法成为浏览器指纹识别问题的实用长期解决方案。
许多其他试图“击败”浏览器 JA4+ 指纹识别的尝试,都是通过重新实现浏览器技术栈的各个层来实现的。这种方法有许多严重缺点,例如:
相比之下,由于 thermoptic 使用浏览器本身来执行 HTTP 请求:
thermoptic 控制的浏览器即可匹配最新的指纹集。当然,没有解决方案是没有缺点的。请参阅 DOWNSIDES.md 文档,了解 thermoptic 方法缺点的完整列表。
thermoptic?“Thermoptic”(“thermoptic camouflage”的缩写)是对《攻壳机动队》(1995 年动画)中少佐使用的虚构伪装的引用。在电影中,这种伪装能够跨越多种探测频谱隐藏穿戴者,包括可见光和热辐射。类似地,此工具试图在多个通道(HTTP、TLS 等)中隐藏用户,使其免于被指纹识别。
此工具会将以下 JA4 指纹伪装得与您连接的 Chrome/Chromium 浏览器完全一致:
thermoptic 现在通过内部 proxyrouter 服务路由受控的 Chrome 实例,因此您可以将 Chrome 指向上游 HTTP 或 SOCKS 代理(包括需要凭据的代理)。通过编辑 docker-compose.yml 中 proxyrouter 服务下的 UPSTREAM_PROXY 值来设置上游代理 URI。当您将其留空时,Chrome 会通过集群内未经身份验证的代理直接与互联网通信。
设置上游 SOCKS 代理的示例:
proxyrouter:
environment:
UPSTREAM_PROXY: "socks5://username:[email protected]:1080"
请注意,某些上游代理可能会改变底层指纹(例如 TCP 元数据),这可能会降低与住宅浏览器的相似度。
thermoptic 会使用你的客户端在 Cookie 头中指定的 cookies 来加载浏览器。请求在浏览器上下文中执行后,就会包含这些 cookies。这样做是为了确保服务器无法对 cookies 的顺序或任何其他类似的小伎俩进行指纹识别。
注意:这些 cookies 在请求之后也会保留。如果你想为 cookies 实现清理逻辑,请编写一个 thermoptic 钩子。
是的,thermoptic 支持这种混合用法,请参阅本节了解更多信息。
你需要确保正确设置了 X-Fetch-*、Origin 和 Referer 等头。如果你不把这些头告诉 thermoptic,它就无法以适当的隐蔽方式执行请求。
如果不设置上下文头,thermoptic 会设置默认值,这些值可能无法准确反映你的目标站点所期望的内容。例如,如果你不设置 Origin 头,它会将 Origin 设置为 null;如果你不设置 Referer 头,它根本不会发送 Referer。
包含这些上下文头对你最有利,这样你的请求才能尽可能隐蔽!thermoptic 无法读心,它只能读取你的请求 :)。
一般来说,这仅适用于 thermoptic 钩子临时利用完整网页浏览器来通过 JavaScript/浏览器级别检查的情况。在临时使用这些钩子和完整浏览器模式时,你必须注意不要被指纹识别为机器人(例如避免 Runtime.enable 之类的陷阱)。
这里的伦理考量和复杂的博弈论超出了 README 所能回答的范围。不过,当你通过电子邮件/Twitter/Github 对我开喷时,欢迎反驳以下任何一个过度简化的观点:
关于抓取军备竞赛的更多调侃,我要求你至少先请我喝杯啤酒。说实话,我讨厌在 README 里写这些无聊的伦理文章,所以你可以随意把我想象成一个想让你的生活更艰难的邪恶书呆子。
thermoptic 钩子处理浏览器 JavaScript 指纹识别thermoptic 允许你配置自定义脚本,以便在以下情况下执行浏览器操作:
ON_START_HOOK_FILE_PATH)BEFORE_REQUEST_HOOK_FILE_PATH)AFTER_REQUEST_HOOK_FILE_PATH)这允许你使用 Chrome 调试协议来点击操作,并为需要真实网页浏览器进行验证步骤的站点设置适当的 cookies。然后你可以使用 thermoptic 代理,通过同一个浏览器继续你的隐蔽会话。
为此,请使用你的自定义代码修改相应的钩子 JavaScript 文件,以便通过提供的 chrome-remote-interface 接口适当地编排浏览器:
// `cdp` is an instance of a connected browser, use it to run your browser actions
export async function hook(cdp) {
console.log(`[STATUS] Browser start hook called successfully!`);
}