做爬虫和自动化的同学,大概率都用过 Python 的 DrissionPage:语法顺手、上手快。但当业务往「高并发、低内存、还要过反爬」的方向走时,Python 的 GIL、运行时开销、以及「验证码得调第三方打码平台」这几件事,就开始变得别扭。
于是我用 Rust 把这套体验重写了一遍,做成了一个库:drission。一句话概括它:
Rust 编写的高性能浏览器自动化库 ,默认驱动 Camoufox 反检测浏览器,内置字符验证码 OCR 与图片滑块缺口距离识别,语法对齐 DrissionPage,面向高并发爬虫与自动化。
本文会从「为什么这么设计」讲到「怎么用」,并把验证码 OCR、滑块、过盾、高并发池这几个硬核能力的原理拆开讲清楚。代码都对应仓库里真实可跑的示例。
它解决了什么问题
先把痛点摆出来,再看 drission 怎么逐个回应:
| 传统做法的痛点 | drission 的回应 |
|---|---|
| 验证码要花钱调打码平台,还得联网 | 内置离线 OCR,纯 Rust 推理,首次自动下模型 |
| 滑块缺口距离算不准、过不了 | 整块形状/颜色对齐算法,极验/顶象预设 |
| 反检测要自己拼一堆 stealth 补丁 | 默认 Camoufox 内核,webdriver=false + 指纹定制开箱即用 |
| Cloudflare 盾拦在门口 | pass_cloudflare() 自动过盾 |
| Python 高并发吃内存、受 GIL 限制 | tokio 异步 + 浏览器池,Driver/Session 双模省内存 |
| 换语言就得重学一套 API | 语法对齐 DrissionPage:tab.get / tab.ele / ele.click |
下面逐项展开。
技术选型:为什么是 Rust + Camoufox + Juggler
三个关键决策,先讲清楚,后面的能力都建立在它们之上:
- 语言选 Rust :异步并发用
tokio,多标签可真正并行操作、各标签独立会话与 cookie;没有 GIL,内存可控,适合长跑的规模化采集。 - 内核选 Camoufox :它是 Firefox 的反检测分支,把
navigator.webdriver、canvas/webgl/audio 指纹、WebRTC 泄漏这些「自动化特征」从底层做了处理,比在 Chromium 上层打 stealth 补丁更干净。首次运行会自动下载分发 对应平台的浏览器到~/.cache/camoufox,无需手动装。 - 协议用 Juggler(而非 CDP) :Camoufox 只支持 Firefox 的 Juggler 协议,所以本库自研了一套
tokio异步 Juggler 客户端。这也意味着它不是又一个 CDP 套壳。(同时提供可选的cdpfeature 驱动 Chromium 系,见后文。)
整体分层大致如此:
text
你的业务代码 (DrissionPage 风格 API)
│
Browser / Tab / Element ── 元素定位、交互、监听、截图
│
能力层:OCR · Slider · Cloudflare · BrowserPool · WebPage(双模)
│
Juggler 异步客户端 (codec: null 分隔 JSON 编解码 + protocol 消息)
│
transport:Unix(socket/pipe) · Windows(命名管道)
│
Camoufox / Firefox 进程 (默认自动下载分发)
快速开始
加依赖。核心默认精简,验证码能力按需开 feature:
toml
[dependencies]
drission = "0.1"
# 需要验证码识别时再开(避免给不用的人引入重依赖):
# drission = { version = "0.1", features = ["ocr", "slider"] }
一个最小闭环:启动浏览器 → 开标签 → 访问 → 查元素读文本 → 退出。binary_path 留空即首次自动下载 Camoufox。
rust
use drission::prelude::*;
#[tokio::main]
async fn main() -> drission::Result<()> {
// 反检测内核 + 本地化指纹一起设好
let opts = BrowserOptions::new()
.headless(true)
.locale("zh-CN")
.timezone("Asia/Shanghai");
let browser = Browser::launch(opts).await?;
let tab = browser.latest_tab().await?;
tab.get("https://example.com").await?;
println!("标题 = {:?}", tab.title().await?);
println!("h1 = {:?}", tab.ele("tag:h1").await?.text().await?);
browser.quit().await?;
Ok(())
}
元素定位沿用 DrissionPage 的前缀语法,迁移几乎零成本:
| 写法 | 含义 |
|---|---|
tab.ele("@id:kw") |
按 id 定位 |
tab.ele("#submit") / tab.ele(".btn") |
CSS id / class 简写 |
tab.ele("css:form input") |
CSS 选择器 |
tab.ele("xpath://form//button") |
XPath |
tab.ele("tag:h1") |
标签名 |
tab.ele("text:登录") |
按文本 |
亮点一:内置验证码 OCR(离线、纯 Rust 推理)
这是我最想做的能力。字母/数字、扭曲粘连的字符验证码,离线识别------不调第三方打码平台、不联网。

用法只有一行。ocr_image 会自动「定位元素 → 取图(<img> 的 data: URL 直接解码,否则元素截图)→ 识别」:
rust
use drission::prelude::*;
#[tokio::main]
async fn main() -> drission::Result<()> {
let browser = Browser::launch(BrowserOptions::new().headless(true)).await?;
let tab = browser.latest_tab().await?;
tab.get("https://apizero.cn/login").await?;
// 一步:定位验证码 <img> → 取图 → 识别(首次自动下载模型到缓存)
let code = tab.ocr_image("xpath://form//button/img").await?;
println!("验证码 = {code}"); // 例:"P38W"
Ok(())
}
原理:ddddocr 模型 + tract 推理
它没有重造轮子,而是站在巨人肩上做了「纯 Rust 化」:
- 模型 :复用 ddddocr 的预训练
common.onnx(~54MB,对常见 4~6 位扭曲验证码开箱即用),首次使用自动下载 到缓存目录;字符集(8210 字)内置在库里。可用DRISSION_OCR_MODEL指定本地模型、DRISSION_OCR_MODEL_URL换源。 - 推理引擎 :用纯 Rust 的 tract,不依赖原生 onnxruntime,跨平台交叉编译干净,没有 C++ 动态库的烦恼。
- 流水线 (对齐 ddddocr):解码图 → 灰度 → 等比缩放到高 64 → 归一化
(p/255-0.5)/0.5→ CNN-LSTM 推理 → CTC 贪心解码。
CTC 解码是关键一步:逐时间步取 argmax,再折叠连续相同字符、去掉 blank 。核心逻辑简化如下(库内置了对 [T,1,C]/[1,T,C] 等不同轴序的自适应):
rust
// 每个时间步取概率最大的字符;与上一步相同则折叠,blank(索引 0)跳过
let mut out = String::new();
let mut prev = usize::MAX;
for t in 0..time_steps {
let best = argmax_over_charset(t); // 该步最可能的字符索引
if best != 0 && best != prev { // 非 blank 且非重复
out.push_str(&charset[best]);
}
prev = best;
}
端到端实测
仓库里有个 apizero_login 示例:用本库打开 apizero.cn 登录页 → React 兼容方式填账号密码 → OCR 识别验证码并填入 → 点登录。判定标准很实在:用假账号,只要站点回的是「账号或密码错误」而不是「验证码错误」,就说明验证码识别对了。
bash
HL=0 N=5 cargo run --example apizero_login --features ocr
实测有头/无头各 5/5、4/4 通过。注意一个细节:多数字母在验证码里大小写形同(S/s、W/w...),模型可能输出小写,而验证码登录通常大小写不敏感 ,按需 .to_uppercase() 即可。
亮点二:图片滑块缺口距离识别
滑块验证码的共性:一张带缺口的底图 + 一块拼图,把拼图水平拖到缺口即过。难点是两件事:① 算准「要移动多远」;② 拟人地把它拖到位 。drission 把这两件事做成了与厂商无关的通用能力,并内置极验/顶象预设。
rust
use drission::prelude::*;
// 假设已拿到 tab: &Tab
// 极验 v4:canvas 三图模板匹配,缺口距离 + 闭环纠偏拖动,一把梭
let r = tab.solve_geetest_slide().await?;
// 顶象:拼图跨域 taint 读不到像素 → 截图 + 内容 NCC 匹配,只算缺口距离
let gap = tab.dingxiang_slide_gap(4).await?;
println!("需移动 {:.0}px,置信 {:.2}", gap.displace, gap.confidence);
缺口算法:不靠单侧边缘,靠整块对齐
这里有个踩过的坑值得说:早期版本用「缺口边缘 − 拼图边缘」做差,结果系统性偏移 ------因为缺口的低对比沿会被漏检、拼图的辉光又会外扩。后来改成整块形状/颜色对齐,误差互相抵消,才真正稳定过码。库会按手头素材自动选法:
| 方法 | 适用素材 | 思路 |
|---|---|---|
TwoImage 双图法 |
bg + full_bg + piece(极验) |
拼图真实颜色对完整底图找最小色差,叠加形状重叠互校,最准 |
PieceTemplate 模板法 |
只有 bg + piece |
拼图轮廓在底图边缘幅度图上滑动,最大化重叠 |
Notch 缺口探测 |
只有 bg |
取纵向边缘最强的列当缺口(兜底) |
ContentNcc 内容相关 |
bg + 截图拼图(顶象) |
绿环掩膜 + 彩色内容 NCC + 暗度门控 + 描边对齐 |
拖动用 minimum-jerk 拟人轨迹 ;给了拼图素材就开闭环纠偏 (标定把手位移比 + 读真实位置校正),否则按比例开环。换厂商基本只换 SliderConfig 配置:
bash
cargo run --example geetest_slide --features slider # 极验
cargo run --example dx_slide --features slider # 顶象(HL=0 看界面)
小贴士:滑块/拖拽前建议导航前 调用
tab.apply_pointer_stealth().await?,让指针行为更像真人。
亮点三:反检测与自动过盾

反检测分两层:底层指纹 交给 Camoufox 内核,上层策略由库提供配置与「过盾」动作。
rust
use drission::prelude::*;
let opts = BrowserOptions::new()
.headless(true)
.locale("zh-CN")
.timezone("Asia/Shanghai")
.geolocation(31.23, 121.47) // 经纬度与时区/语言自洽
.block_webrtc(true) // 堵 WebRTC 真实 IP 泄漏
.humanize(true) // 拟人化行为
.proxy(Proxy::new("http://user:pass@host:port"));
let browser = Browser::launch(opts).await?;
要点:navigator.webdriver=false、canvas/webgl/audio 指纹定制、block_webrtc 防 IP 泄漏,这些都在内核层生效,不是 JS 注入能轻易识破的那种。
遇到 Cloudflare 盾,一行过:
rust
use std::time::Duration;
// 交互式 Turnstile 复选框「可信点击」+ 非交互式自动放行
let ok = tab.pass_cloudflare(Duration::from_secs(20)).await?;
println!("过盾 = {ok}");
亮点四:网络监听与请求拦截
爬虫最常要的是「直接拿接口响应体」,而不是去解析渲染后的 DOM。drission 支持 XHR/Fetch 监听抓响应体:
rust
use drission::prelude::*;
// 假设已拿到 tab: &Tab
tab.listen_start(&["api/data"]).await?; // 先开监听(关键词匹配 URL)
tab.get("https://example.com").await?; // 再触发请求
tab.ele("@id:kw").await?.input("drission").await?;
tab.ele("#submit").await?.click().await?;
let packet = tab.listen_wait().await?; // 抓到目标 XHR(含响应体)
println!("{}", packet.response.body);
需要改写/伪造/拦截请求时,用拦截 API。每个被拦请求必须恰好决策一次 (resume / resume_with / fulfill / abort),类型系统在编译期就替你保证了这一点(决策方法消费 self):
rust
use drission::prelude::*;
// 假设已拿到 tab: &Tab
tab.intercept_xhr(&["/track", "/ads"]).await?;
let req = tab.intercept_next().await?;
if req.url.contains("/ads") {
req.abort("blockedbyclient").await?; // 拦掉广告/埋点
} else {
// 也可 req.fulfill(200, headers, body) 直接伪造响应
req.resume().await?; // 原样放行
}
此外还支持 WebSocket 帧监听、控制台监听,以及「吐环境(补环境)」:采集 canvas/webgl/audio 真实指纹 + 定位签名 sink,一键导出可 node 运行的补环境工程。
亮点五:高并发浏览器池
规模化采集靠 BrowserPool:它管理一组浏览器 worker,提供并发限流、每任务轮换代理/指纹、失败重试、健康自愈、断点续抓 。总并发槽 = size × tabs_per_worker。
rust
use drission::prelude::*;
#[tokio::main]
async fn main() -> drission::Result<()> {
let pool = BrowserPool::launch(
PoolOptions::new()
.size(4) // 4 个浏览器进程
.tabs_per_worker(3) // 每进程 3 标签 → 并发 12
.base_options(BrowserOptions::new().headless(true))
.fingerprints(FingerprintPool::presets()) // 每 context 轮换指纹
.retry(RetryPolicy::new(2)), // 失败重试 2 次
)
.await?;
let urls: Vec<u32> = (0..100).collect();
let results = pool
.map(urls, |i, tab| async move {
tab.get(&format!("https://example.com/p/{i}")).await?;
let title = tab.title().await?;
Ok::<String, drission::Error>(title)
})
.await;
println!("完成 {} 个任务", results.len());
pool.shutdown().await?;
Ok(())
}
断点续抓
长跑任务最怕中途挂掉重来。map_resumable 配合 Checkpoint 落盘:首跑完成的 key 记账,再跑只补未完成项。
rust
// 假设已有 pool: &BrowserPool
let ckpt = Checkpoint::load("checkpoint.jsonl").await?;
let results = pool
.map_resumable(
(0..1000).collect::<Vec<_>>(),
|i| format!("page-{i}"), // 每个任务的去重 key
&ckpt,
|i, tab| async move {
tab.get(&format!("https://example.com/p/{i}")).await?;
Ok::<u32, drission::Error>(i)
},
)
.await;
println!("本轮新增完成 {} 项,累计 {}", results.len(), ckpt.done_count().await);
代理池还能做出口 IP 地理 ↔ 指纹自洽 (ProxyGeo / ProxyHealth):住宅代理落在哪个国家,语言/时区就跟着对齐,降低被风控的概率。
亮点六:Driver + Session 双模
不是所有请求都值得开一个浏览器。drission 借鉴 DrissionPage 的 WebPage,提供 Driver(浏览器)/ Session(纯 HTTP)双模 ,且共享 cookie :重活(登录、过盾、JS 渲染)走 Driver,海量轻请求走 Session,省内存、对旧机器友好。
rust
use drission::prelude::*;
#[tokio::main]
async fn main() -> drission::Result<()> {
// 先用浏览器把登录/过盾这种重活干了
let mut page = WebPage::new_driver(BrowserOptions::new().headless(true)).await?;
page.get("https://site/login").await?;
// ... 这里做登录交互 ...
// 切到会话模式高速抓列表:自动把浏览器 cookie 同步过去,登录态无缝衔接
page.change_mode(PageMode::Session).await?;
page.get("https://site/api/list?page=1").await?;
let rows = page.s_eles("css:.item").await?;
println!("抓到 {} 行", rows.len());
page.quit().await?;
Ok(())
}
change_mode 切换时会自动双向同步 cookie,你不用手动搬运登录态。
feature 设计:核心精简,按需加料
库的依赖做了分层,默认只编核心,重依赖按需开:
| feature | 能力 | 依赖 | 默认 |
|---|---|---|---|
camoufox |
Camoufox/Juggler 后端(核心) | 始终编译 | ✅ 开 |
ocr |
字符验证码识别 | image + tract-onnx |
关 |
slider |
滑块缺口距离识别 | 纯 JS + std,零额外依赖 | 关 |
cdp |
Chromium(CDP)后端(实验) | 无额外重依赖 | 关 |
不需要 OCR 的项目不会被 tract-onnx(几十 MB 级)拖累编译时间,这点对 CI 很友好。
适用与不适用
为了不让你踩坑,把边界说清楚:
- 适合:Rust 技术栈的爬虫/自动化、需要反检测过盾、需要离线识别字符验证码或滑块、要长跑高并发规模化采集。
- 暂不适合:点选/文字点选类验证码(在路线图上)、强 MMTLS/证书 pinning 的私有协议、把过盾当「万能钥匙」的预期(反爬是持续对抗,没有银弹)。
- 平台:macOS(arm64,主力)、Linux、Windows(命名管道传输已打通);Rust ≥ 1.85(edition 2024)。
路线图
接下来重点在:点选/文字点选与计算题验证码、滑块行为轨迹模型化、OCR 自训模型接入(dddd_trainer)、更多深指纹注入与「吐环境」补全、WS 接管多路复用与 wss:// TLS、更多厂商滑块/盾预设。
写在最后
drission 想做的,是把「反检测浏览器 + 验证码识别 + 高并发采集 」这三件常常要东拼西凑的事,收进一个 Rust 库里,同时保留 DrissionPage 那种「打开就会用」的手感。
如果你正好在找 Rust 的验证码识别 / 滑块缺口距离 / 反检测浏览器 / 高并发爬虫方案,欢迎试试:
- crates.io:
cargo add drission(或features = ["ocr", "slider"]) - 文档:docs.rs/drission
- 仓库:github.com/MageGojo/drission-rs
⚠️ 免责声明 :本项目仅供学习与合法、非盈利用途。使用者须遵守目标站点的
robots协议与当地法律法规,禁止用于任何违法、侵害他人利益或采集受保护数据的行为。使用本库产生的一切后果由使用者自行承担。
如果觉得有用,点个 star、评论区聊聊你的使用场景,我会持续更新。