Camoufox 反检测浏览器实战:内核级指纹欺骗与 AI Agent 浏览器完整指南
Camoufox 反检测浏览器实战:内核级指纹欺骗与 AI Agent 浏览器完整指南
【免费下载链接】camoufox🦊 Anti-detect browser项目地址: https://gitcode.com/gh_mirrors/ca/camoufox
Camoufox 是一个基于 Firefox 深度改造的开源反检测浏览器,面向网页抓取与 AI Agent 场景。它的做法不是在页面里塞 JavaScript 去改值,而是把"这台机器长什么样"整套身份在 C++ 层面报给页面,再配一条页面看不见的自动化通道。适合已经用过 Playwright 或 Selenium、却被navigator.webdriver、指纹交叉校验反复拦下的开发者。
一、先讲痛点:为什么你的自动化脚本"一眼假"
如果你用 Playwright 驱动一个现成浏览器去抓数据,大概率已经踩过这几类坑:
- 网站一查
navigator.webdriver就返回true,直接判定你是机器人; - 你用
page.addInitScript或Object.defineProperty覆写了navigator.platform,但对方用Object.getOwnPropertyDescriptor一检查,发现这个 getter 根本不像原生函数; - 你改了主世界,却漏了
iframe和Web Worker,里面的navigator还是原始值,前后对不上; - 你在 HTTP 头里写了一套
User-Agent,页面脚本里navigator.userAgent又是另一套,服务器一对就穿帮。
这类问题的根子在于:大多数方案是"在页面上层做文章"。值是被脚本临时改掉的,而现代反爬系统恰恰会去校验"这个值到底是不是浏览器原生报出来的"。覆写痕迹、上下文不一致、头与脚本不一致,任何一条都足以暴露。
Camoufox 的思路反过来:不改页面,改浏览器本身报值的源头。这样页面调navigator.platform拿到的就是一个"原生返回值",而不是"被 JS 拦截改过的返回值",两者在检测层面是完全不同的。
二、它是怎么做到的:拦截在 C++,隔离在协议层
Camoufox 能"骗得干净",靠的是两件互补的事:把伪造值放到浏览器最底层去返回,再把自动化代码藏到页面看不见的地方。
1. 内核级拦截:页面读到的是"原生值"
每次启动,启动器(Python 或 TypeScript 包)会先生成一套完整的设备身份,再把它打包成一个 JSON,塞进浏览器的CAMOU_CONFIG环境变量。浏览器内核里的 C++ 代码负责读取并返回这些值。
核心就一个读取器 browser/additions/camoucfg/MaskConfig.hpp。它在进程启动时解析一次 JSON 并缓存,之后所有被改造过的 Web API(navigator、screen、WebGL、字体、音频、WebRTC)都直接从这里取值。节选如下:
// browser/additions/camoucfg/MaskConfig.hpp(节选) inline const nlohmann::json& GetJson() { static const nlohmann::json jsonConfig = LoadJson(); // 从 CAMOU_CONFIG 解析一次并缓存 return jsonConfig; } inline std::optional<std::string> GetString(std::string_view key) { const auto* value = Find(key); if (!value) return std::nullopt; return value->get<std::string>(); }打个比方:普通方案是站在门口对来访者(网页)喊"你当我是 Windows",而 Camoufox 是把访客登记系统本身换了——页面每问一次"你是什么系统",登记的 C++ 逻辑就如实回一句,和真机行为没有任何差别。可配置的键(navigator.*、screen.*、headers.User-Agent、webrtc:ipv4等)在 browser/settings/properties.json 里集中声明,你可以据此核对到底哪些值会被改。
这里有个容易被忽略的工程细节:因为页面可以对 getter 计时,MaskConfig特意把每次查找压到和原生 getter 几乎相同的开销(一个函数内静态量 + 轻量哈希索引),避免"读取这个值明显比标准浏览器慢"本身成为指纹。
2. 协议隔离:页面根本看不到自动化代码
Playwright 驱动 Chromium 走的是 CDP(Chrome DevTools Protocol),驱动 Firefox 走的则是 Camoufox 里的 Juggler 模块,代码在 browser/additions/juggler/。Juggler 的 page agent 跑在一个"隔离执行世界"(isolated world)里:你的evaluate、选择器、init script 都在这个隔离世界里执行,真实页面里既不会出现多余的 getter,也不会冒出监听器。
同时,输入事件走的是 Firefox 自己的输入处理路径,所以点击、填表对页面而言和真人操作是同一种事件类型,而不是"脚本伪造的事件"。再叠加navigator.webdriver返回false、headless 报告成桌面细指针等补丁,自动化侧的痕迹基本被抹平。一句话概括:页面负责"被欺骗",自动化负责"隐形",两条线互不串味。
三、快速上手:安装与第一个实例
安装分两步,先装 Python 包,再把配套浏览器构建拉下来(camoufox fetch会装好这个版本配套测试过的浏览器构建,并固定 fpgen 指纹模型):
pip install -U "camoufox[geoip]" camoufox fetch[geoip]这个额外依赖是geoip=True用的,需要下载 GeoIP 数据库,所以一开始就带上。装完后,启动一个实例和普通 Playwright 几乎一样,返回的就是标准 Playwright 对象,你已有的脚本基本只改启动部分即可:
from camoufox.sync_api import Camoufox # proxy 走代理,geoip=True 让时区/语言/地理/WebRTC 都对齐到出口 IP with Camoufox( headless=True, proxy={"server": "http://proxy.example.com:8080", "username": "u", "password": "p"}, geoip=True, ) as browser: page = browser.new_page() page.goto("https://example.com")几个最常用的启动参数,建议先记住这几个,其余的在 python/README.md 里有完整列表:
| 参数 | 作用 | 典型取值 |
|---|---|---|
headless | 无头 / 虚拟显示 / 虚拟 GPU | True、"virtual"、"virtual-gpu" |
proxy | Playwright 代理格式 | {"server", "username", "password"} |
geoip | 按出口 IP 对齐时区/语言/地理/WebRTC | True |
os | 限定抽取身份的目标系统 | "windows"、"macos"、"linux" |
humanize | 人类化鼠标轨迹,数值则限制单次时长 | True |
fingerprint_preset | 用录制的真机指纹预设替代随机抽取 | True |
Node.js 侧是同一套接口的移植:npm install camoufox playwright-core后npx camoufox fetch,用法与 Python 一致,详见 typescript/README.md。
四、核心能力精讲:真正值钱的是"一致"
Camoufox 能用的点很多(字体、WebGL、音频、媒体设备、语音等一整套都被同源生成),但决定成败的不是"能改多少字段",而是"这些字段之间能不能互相圆上"。下面挑两个最有含金量的机制展开。
1. 指纹一致性:为什么每个字段都要彼此对得上
反爬系统会交叉校验:User-Agent 说你是 64 位 Linux,那navigator.platform和navigator.oscpu就不能说 ARM;WebGL 报的是 Apple M 芯片,navigator.hardwareConcurrency就不能是 2,因为 Apple Silicon 没有 2 核的型号。任何一处对不上,整台"机器"就不存在。
Camoufox 的身份不是各字段独立随机,而是先从一个模拟真实流量分布的模型(fpgen)抽一台"真机",再过一遍 python/src/camoufox/coherence.py 里的整套规则。这套规则每条都对应一条真实机器上恒成立的不变量,能修复的就修,修不了的(比如把 Windows 的 GPU 硬塞进 macOS 身份)就上报给调用方去重抽。举几个它会拦的例子:
- Apple Silicon 的核数必须是真实出货过的档位(M 系列起步就是 8 核);
- Intel Mac 的核数不能配一块带刘海屏的面板(那种面板只有 Apple Silicon 有);
devicePixelRatio只能落在该 OS 真实提供的缩放档位上,1.818这种值会被就近修到合法值;screen.availWidth不允许大于screen.width,窗口outerHeight减去innerHeight必须够得着真实窗口框架的高度。
理解这一点很关键:单个字段"看起来合理"没用,整台机器的字段必须构成一台真实存在的机器。这也是 Camoufox 相比"自己拍脑袋拼指纹"最大的差别。
2. 代理地理一致性:让 IP、时区、语言、WebRTC 讲同一个故事
你用了代理,但网站一比对就发现:出口 IP 在美国,Intl却说你是东八区,WebRTC 还漏出国内真实 IP——这类"身份与 IP 地区打架"是代理场景的高频暴露点。
Camoufox 的geoip=True会穿透代理查一次出口 IP,然后据此同时设定:时区、语言(locale按该地区通行语言挑)、地理位置(并授权geolocation),以及把 WebRTC 的候选 IP 改写成出口 IP。也就是说,"你是谁"和"你从哪来"被强行拉到同一地区,服务器做交叉校验时拿到的是一组自洽的证据。
3. 顺带一提:GPU 与字体的"同源"
webgl_config指定的 GPU 必须是 fpgen 针对该 OS 真实录制过的那个,随便填一对别的 GPU 会直接抛错;字体列表也不是随机拼的,而是"该 OS 某个版本的基础字体集 + 按真实安装概率抽取的附加字体"(Office 约在 60% 的 Windows 机器上,而某些欧洲补充字包只有约 2.8%)。字体这块的细节在 docs/FONTS.md,媒体设备在 docs/MEDIA-DEVICES.md。思路都一样:不是造一个值,而是还原一台真机的完整画像。
五、场景实战:用多身份跑一个 AI Agent
AI Agent 场景的核心诉求是:长期挂着多个会话、每个会话都要"像真人"且"互不串号"。Camoufox 允许一个浏览器里开多个上下文,每个上下文各自独立一套身份(navigator、screen、WebGL、字体、语音、时区、WebRTC IP),通过 docs/per-context-patches.md 描述的 per-context 机制在页面脚本运行前注入。
下面是一个贴近真实形态的异步示例:一个浏览器、多个 Agent 身份、各自绑不同代理,并开启人类化输入:
import asyncio from camoufox.async_api import AsyncCamoufox, AsyncNewContext async def main(): async with AsyncCamoufox(headless=True, canvas_noise=True) as browser: # 每个 Agent 一套独立身份 + 独立代理,humanize 让鼠标像人 alice = await AsyncNewContext(browser, os="windows", proxy={"server": "http://p1.example.com:8080", "username": "u", "password": "p"}) bob = await AsyncNewContext(browser, os="macos", proxy={"server": "http://p2.example.com:8080", "username": "u", "password": "p"}) for ctx in (alice, bob): page = await ctx.new_page() await page.goto("https://chat.example.com") await page.locator("textarea").first.type("你好,帮我总结一下这篇文章", delay=60) asyncio.run(main())几点说明,都是实战里容易踩的地方:
- 跨 OS 身份(在 Linux 宿主上跑
os="windows"的上下文)需要canvas_noise=True,否则 Canvas/WebGL 回读的是宿主真实像素,会暴露宿主系统。 humanize打开后,鼠标轨迹不是平滑贝塞尔曲线,而是从 2356 段真人录制里挑一段变形后按原节奏回放,连停顿和过冲都保留(实现在 browser/additions/juggler/input/cursory/),这比"匀速移动"更接近真人。- 每个身份绑各自的代理与
geoip,保证"Alice 在美国、Bob 在德国"这种多地区并行不会互相污染。
六、避坑指南:四个高频问题
1. 现象:身份报的是 macOS,但 Canvas 回读像 Linux。原因:像素来自真实 OS、驱动与 GPU,与身份声明无关。当身份声称的不是宿主 OS 时,真实像素就会露馅。 解决:要么让身份就用宿主 OS(并让身份声称宿主的 GPU,像素自然吻合),要么对另一 OS 身份开canvas_noise,让回读变成随机数据。取舍逻辑见 python/README.md 的 "The canvas on another OS" 一节。
2. 现象:navigator.hardwareConcurrency报 16,但页面用并行 worker 一测,可用核数对不上。原因:页面可以对"N 个并行任务"计时来反推真实可用核数,报得比实测多、或比实测少都会被抓。 解决:默认情况下启动器已把报告值对齐到宿主真实核数;若你想保留抽到的更多核,配合pin_cpu_cores=True把浏览器实际钉到那么多核,让"报的"和"测的"由构造上就一致。
3. 现象:明明用了代理,还是被判定为代理 / 身份与 IP 地区不符。原因:WebRTC 漏出真实 IP,或时区、语言、出口 IP 三者地区不一致;另外没走代理时,机器的 IPv6 地址可能把真实位置暴露出来。 解决:proxy=...加geoip=True把 WebRTC、时区、语言、地理位置一起对齐;关注启动时打印的LeakWarning,它会说明是因为 IPv6 地区不一致而保持开启还是已按 IPv4-only 处理。
4. 现象:改了ff_version或手填了某个身份字段,反而更容易被识别。原因:声称的 Firefox 版本与真实引擎行为对不上是可检测的;手填一个"真实机器不会这样组合"的值,本身就是异常。 解决:版本默认跟随浏览器自身;用config=手动覆盖时,凡是与真实身份冲突的值都会触发带字段名的LeakWarning(i_know_what_im_doing也压不掉这一类),看到就该改回。
七、总结与展望
把整条链路串起来看,Camoufox 的"骗得干净"来自两点叠加:一是把伪造值放到浏览器最底层去返回,页面读到的是原生值而非 JS 覆写;二是用 Juggler 的隔离执行世界,让自动化代码在页面里彻底隐身。而让这套机制真正可靠的,是身份的一致性——从 fpgen 抽一台真机,再用coherence把"一台真机该有的样子"逐字段校准。
几点务实的判断:
- 它本质是 Firefox 构建,只呈现 Firefox 系身份;一些 WAF 会去试探 SpiderMonkey 引擎行为,这部分不是靠欺骗能变成 Chromium 的。
- 作者自己也强调它仍可能被检测——成百上千个值必须两两吻合,反爬厂商专找那一个不吻合的。所以它不是"一次配置永不再被拦",而是"把被拦的概率压得很低、且每次都有据可查"。
- 工程上它跟 Firefox 上游走(补丁升级流程见 docs/patch-upgrading-guide.md),Python / TypeScript 双接口,还能用
launch_server()起一个远程 Playwright 服务,让其它语言也能连进来。
对大多数从"脚本总被拦"升级到"要长期稳定地跑抓取或 Agent"的团队,Camoufox 的价值不在于它藏得最深,而在于它把"一致"这件事做成了默认行为,而不是让你去逐字段手调。把它当成一个"自带身份一致性引擎的浏览器"来用,往往比逐条打补丁更省心。
【免费下载链接】camoufox🦊 Anti-detect browser项目地址: https://gitcode.com/gh_mirrors/ca/camoufox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考