Rust 浏览器自动化:用 chaser-oxide 绕过反爬检测

上个月做自动化测试,写了个登录脚本,结果每次跑到提交按钮就弹验证码。代码没问题,手动操作就能过,但一用 Puppeteer 就被识别。

后来发现问题出在 CDP(Chrome DevTools Protocol)上。现代反爬系统会检测浏览器是否处于调试模式,而 Puppeteer、Playwright 这些工具都依赖 CDP 控制浏览器,天然就带着"我是机器人"的标签。

最近发现一个 Rust 项目 chaser-oxide,它的思路不一样:不是在页面加载后注入 JS 伪装,而是从协议层就开始隐身。跑下来效果确实好,而且内存占用只有 50MB,比 Node.js 方案省了 80%。

反爬系统到底在检测什么

先说说为什么自动化脚本容易被识别。

CDP 通信痕迹

大多数浏览器自动化工具要执行 JS,必须先发送 Runtime.enable 命令。这个命令会让 Chromium 启用调试上下文,暴露一些内部对象。

我之前用 Puppeteer 时,在控制台打印 window 对象,能看到一些调试相关的属性。有些反爬脚本就专门检测这些属性是否存在。

指纹不一致

浏览器指纹包括很多维度:User-Agent、硬件信息、WebGL 参数、Canvas 渲染结果等。

问题在于,这些信息必须逻辑自洽。比如你的 UA 显示是 Windows + Chrome 130,但 navigator.hardwareConcurrency 返回 16 核,deviceMemory 却只有 2GB,这在真实用户中几乎不可能。

我见过最离谱的案例:有人用 Linux 服务器跑 Chrome,WebGL 却返回 NVIDIA RTX 4090 的信息。这种矛盾一眼就能被识别。

行为模式异常

真人操作鼠标不会走直线,打字会有错别字,点击速度也不恒定。

但自动化脚本的行为太"完美"了:鼠标瞬间从 (0,0) 移动到 (500,300),点击间隔恒定 100ms,打字无错别字。这比直接写"我是机器人"还明显。

chaser-oxide 的协议层隐身方案

chaser-oxide 是基于 chromiumoxide(Rust 版 CDP 客户端)的 fork,但它不是简单封装,而是直接修改 CDP 通信协议。

绕过 Runtime.enable 检测

标准流程中,执行 JS 需要先启用 Runtime 调试域。chaser-oxide 的做法是:不用 Runtime.evaluate,改用 Page.createIsolatedWorld

// chaser-oxide 内部实现
let world_id = page.create_isolated_world("neutral_world").await?;
page.evaluate_in_world(world_id, script).await?;

IsolatedWorld 是 Chromium 提供的隔离执行环境,常用于 Chrome 扩展的内容脚本。关键在于:

  • 不需要启用 Runtime 调试域
  • 执行上下文与主页面隔离,不会污染全局对象
  • 反爬脚本通常不会监控这种"扩展式"执行环境

而且 chaser-oxide 把默认的 world 名字(如 "puppeteer")改成了无意义字符串,彻底切断关联。

指纹一致性:从启动就"说谎"

很多工具在页面加载后再注入 JS 修改 navigator,但高级反爬会在 <head> 里就运行检测脚本。你还没来得及伪装,就已经暴露了。

chaser-oxide 通过 Page.addScriptToEvaluateOnNewDocument,在每个新文档加载前注入脚本:

// 注入的脚本示例
Object.defineProperty(navigator, 'hardwareConcurrency', {
  value: 16,
  writable: false,
  configurable: false
});

Object.defineProperty(navigator, 'deviceMemory', {
  get: () => 32,
  configurable: false
});

// 同步 WebGL 上下文参数
const originalGetContext = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(type, ...args) {
  const ctx = originalGetContext.call(this, type, ...args);
  if (type === 'webgl' || type === 'experimental-webgl') {
    // 重写 getParameter,返回预设的 GPU 信息
  }
  return ctx;
};

这段脚本在页面 JS 执行前就生效,确保所有属性从一开始就"正确"。

而且这些值不是随便填的,它提供了一套操作系统+硬件的预设组合:

Os::Windows     // Windows 10, RTX 3080, 1920x1080
Os::MacOSArm    // macOS M4 Max, 1728x1117, 2x DPR
Os::Linux       // Linux, GTX 1660, 1920x1080

Os::Windows,它会自动配置:

  • User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
  • navigator.platform: "Win32"
  • WebGL vendor: "NVIDIA Corporation"
  • renderer: "NVIDIA GeForce RTX 3080/PCIe/SSE2"

所有字段逻辑自洽,没有矛盾点。

人类行为模拟

就算指纹完美,如果行为像机器,照样会被抓。

chaser-oxide 内置了 Human Interaction Engine,专门模拟人类操作的"不完美"。

鼠标移动:贝塞尔曲线 + 加速度

真人移动鼠标不会走直线,会先加速,快到目标时减速,甚至轻微过冲再回调。

async fn move_mouse_human(&self, x: f64, y: f64) -> Result<()> {
    let start = self.get_mouse_position().await?;
    let path = generate_bezier_path(start, (x, y), num_points: 20);

    for point in path {
        self.move_mouse_to(point.x, point.y).await?;
        tokio::time::sleep(random_delay(20..100)).await;
    }
}

路径点之间还有随机延迟(20~100ms),模拟人类神经反应时间。

打字:带错别字和修正

真人打字会犯错。比如想打 "Search query",可能先敲成 "Seach quert",然后按退格删除,再补上。

// 正常打字(带随机延迟)
chaser.type_text("Search query").await?;

// 带错别字模式(约 5% 错字率,自动修正)
chaser.type_text_with_typos("Search query").await?;

内部实现会:

  • 随机选择几个字符替换成邻近键(QWERTY 键盘布局)
  • 模拟删除动作(Backspace)
  • 重新输入正确字符
  • 整体打字速度符合人类分布(平均 40 WPM,标准差 10)

实战:一行代码启动隐身模式

chaser-oxide 的 API 设计很简洁。一行代码搞定所有隐身配置:

use chaser_oxide::{ChaserPage, Os};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 启动一个 Windows 风格的隐身浏览器
    let (browser, chaser) = ChaserPage::launch(Os::Windows).await?;

    chaser.goto("https://example.com").await?;

    // 安全执行 JS(走 IsolatedWorld,无 Runtime 泄露)
    let title = chaser.evaluate("document.title").await?;
    println!("Title: {}", title.unwrap());

    // 人类式交互
    chaser.move_mouse_human(400.0, 300.0).await?;
    chaser.click_human(500.0, 400.0).await?;
    chaser.type_text("Hello, I'm not a bot!").await?;

    Ok(())
}

对比 Puppeteer 的等效代码:

const puppeteer = require('puppeteer-extra');
puppeteer.use(require('puppeteer-extra-plugin-stealth')());

const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://example.com');

// 但这里仍有 Runtime.enable 风险
const title = await page.evaluate(() => document.title);

关键区别:Puppeteer 的 evaluate 最终会调用 Runtime.evaluate,而 chaser-oxide 走的是另一条路。

自定义指纹

需要更精细控制?用 ChaserProfile

use chaser_oxide::{ChaserProfile, Gpu};

let profile = ChaserProfile::windows()
    .chrome_version(130)
    .gpu(Gpu::NvidiaRTX4080)
    .memory_gb(32)
    .cpu_cores(16)
    .locale("de-DE")
    .timezone("Europe/Berlin")
    .screen_size(2560, 1440)
    .build();

let (browser, chaser) = ChaserPage::launch_with_profile(profile).await?;

它甚至内置了 Apple M 系列芯片的选项:

Gpu::AppleM4Max // 对应 macOS M4 Max, 1728x1117, 2x DPR

这意味着,你可以模拟一台 MacBook Pro 用户访问网站,服务器完全无法区分。

性能优势:Rust 的内存魔法

除了隐身,chaser-oxide 还有一个杀手锏:极低的内存占用。

方案内存占用(单实例)
Puppeteer (Node.js)500MB+
Playwright (Node.js)400MB+
chaser-oxide (Rust)50~100MB

为什么差这么多?

  • Node.js 的 V8 引擎本身就要吃 100MB+
  • Puppeteer 启动时会加载大量 JS 模块
  • Rust 是编译型语言,无运行时开销
  • chaser-oxide 直接与 CDP 通信,没有中间层

对于需要并发运行上百个浏览器实例的场景(比如大规模数据采集),内存节省意味着成本直降 80%。

我之前在一台 16GB 内存的服务器上跑 Puppeteer,最多只能开 20 个实例。换成 chaser-oxide 后,能跑 100 个实例,而且 CPU 占用也更低。

适用场景

虽然 chaser-oxide 常被用于绕过反爬,但它的价值远不止于此:

自动化测试
在 CI/CD 中运行 E2E 测试,避免被测试网站误判为攻击。

数据采集
合法采集公开数据时,避免因自动化特征被封禁。

广告验证
广告平台常屏蔽自动化流量。用 chaser-oxide 可真实模拟用户点击。

学术研究
研究反爬机制时,需要可控的"隐身"环境。

注意事项

尽管 chaser-oxide 很强大,但也要清醒认识几点:

它不能 100% 绕过所有检测
如果网站用人脸识别、行为生物特征(如鼠标微震颤分析),仍可能失败。

更新需跟进 Chromium 版本
CDP 协议随 Chrome 更新而变。chaser-oxide 需定期同步上游 chromiumoxide。

法律与道德边界
绕过反爬可能违反网站服务条款。请确保用途合法,尊重 robots.txt。

我的建议是:只在合法场景下使用,比如自己的测试环境、公开数据采集、学术研究。如果是商业用途,最好咨询法务。

快速上手

安装 Rust(1.75+)后,在 Cargo.toml 添加:

[dependencies]
chaser-oxide = { git = "https://github.com/ccheshirecat/chaser-oxide" }
tokio = { version = "1", features = ["full"] }

运行前确保系统已安装 Chrome/Chromium。

项目地址:https://github.com/ccheshirecat/chaser-oxide


如果你厌倦了被验证码折磨,如果你想用更低的成本完成自动化任务,不妨试试这个 Rust 项目。在浏览器的世界里,最快的脚本,往往是那个从未被发现的。