Obscura Rust 库集成——Browser/Page/Element 完整 API
将 Obscura 作为 Rust 库嵌入自有项目。Browser/Page/Element/CookieStore API 详解,请求拦截、事件回调与编译注意。
亿牛云技术团队2026年7月8日3 分钟阅读
为什么需要 Rust 库
CDP 模式走 WebSocket 通信,适合多语言客户端(Node.js、Python 等)。但对于 Rust 项目,进程内直接调用浏览器引擎比走 WebSocket 快得多:
- 零序列化开销:DOM 操作不需要 JSON 序列化
- 零网络延迟:不经过 TCP/WebSocket
- 直接内存访问:V8 Isolate 在同一个进程中
Obscura 的 obscura crate 提供了完整的嵌入式 API。
添加依赖
Obscura 编译 V8 需要从源码构建,因此无法发布到 crates.io。必须通过 Git 依赖引入:
[dependencies]
obscura = { git = "https://example.16yun.cn/obscura" }
tokio = { version = "1", features = ["rt", "macros"] }
anyhow = "1"(上面的 git 地址为示例,实际替换为项目仓库地址。)
建议锁定版本标签确保可重现构建:
obscura = { git = "https://example.16yun.cn/obscura", tag = "v0.1.7" }首次编译需要下载 V8 源码并编译,约 5 分钟。后续增量编译只需几秒。
Browser
Browser 是顶层句柄,负责创建页面和管理配置。
use obscura::Browser;
// 默认配置
let browser = Browser::new()?;
// 自定义配置
let browser = Browser::builder()
.stealth(true)
.proxy("http://user:pass@proxy.16yun.cn:8888")
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...")
.storage_dir("/data/obscura")
.build()?;Browser::builder() 支持的配置:
| 方法 | 作用 |
|---|---|
.stealth(bool) | 启用反检测 |
.proxy(url) | 设置 HTTP/SOCKS5 代理 |
.user_agent(ua) | 自定义 User-Agent |
.storage_dir(dir) | Cookie/localStorage 持久化目录 |
Page
Page 代表一个浏览器页面,可以导航、执行 JS、操作 DOM。
use std::time::Duration;
// 创建新页面
let mut page = browser.new_page().await?;
// 导航并等待加载
page.goto("https://example.com").await?;
// 获取渲染后 URL
println!("URL: {}", page.url());
// 获取渲染后 HTML
println!("HTML bytes: {}", page.content().len());
// 执行 JavaScript
let title = page.evaluate("document.title");
println!("Title: {}", title);
// 等待选择器出现
let el = page.wait_for_selector("h1", Duration::from_secs(5)).await?;
if let Some(element) = el {
println!("Heading: {}", element.text());
}Page API 总览
| 方法 | 作用 |
|---|---|
goto(url) | 导航并等待 load 事件 |
content() | 返回渲染后 HTML |
url() | 返回当前 URL |
evaluate(js) | 执行 JS,返回 serde_json::Value |
query_selector(css) | 返回第一个匹配的 Element |
wait_for_selector(css, Duration) | 轮询直到元素出现 |
settle(max_ms) | 驱动事件循环让异步任务完成 |
on_request(cb) | 请求回调(被动) |
on_response(cb) | 响应回调(被动) |
enable_interception() | 启用请求拦截通道 |
add_preload_script(js) | 导航前注入脚本 |
Element
Element 代表一个 DOM 元素。
let el = page.query_selector("a.link").await?;
if let Some(element) = el {
// 读取文本
let text = element.text();
println!("Text: {}", text);
// 读取属性
let href = element.attribute("href");
println!("Href: {:?}", href);
// 点击
element.click();
}CookieStore
CookieStore 提供了完整的 Cookie 管理能力。
// 获取所有 Cookie
let cookies = page.context.cookie_jar.get_all_cookies();
// 为特定 URL 获取 Cookie
let url = url::Url::parse("https://example.com")?;
let cookies = page.context.cookie_jar.get_for_url(&url);
// 设置 Cookie
page.context.cookie_jar.set(Cookie::new("session", "abc123"));
// 持久化
page.context.cookie_jar.save_to_file("/data/cookies.json")?;
page.context.cookie_jar.load_from_file("/data/cookies.json")?;请求拦截
主动拦截
enable_interception() 返回一个通道,收到每个 JS fetch()/XHR 请求:
let mut rx = page.enable_interception();
tokio::spawn(async move {
while let Some(req) = rx.recv().await {
let action = if req.url.contains("/track") {
InterceptResolution::Fail { reason: "blocked".into() }
} else if req.url.ends_with("/api/data") {
InterceptResolution::Fulfill {
status: 200,
headers: Default::default(),
body: r#"{"data": "mocked"}"#.into(),
}
} else {
InterceptResolution::Continue {
url: None, method: None, headers: None, body: None,
}
};
let _ = req.resolver.send(action);
}
});被动回调
用于观察和日志:
page.on_request(Arc::new(|info| {
tracing::info!("→ {} {}", info.method, info.url);
}));
page.on_response(Arc::new(|info, resp| {
if info.resource_type == ResourceType::Fetch {
tracing::info!("← API: {} ({} bytes)", info.url, resp.body.len());
}
}));Preload Script
page.add_preload_script("window.__obscura = true;");
page.goto("https://example.com").await?;完整示例:爬虫循环
use obscura::{Browser, InterceptResolution, ResourceType};
use std::sync::Arc;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let browser = Browser::builder()
.stealth(true)
.proxy("http://user:pass@proxy.16yun.cn:8888")
.build()?;
let mut page = browser.new_page().await?;
// 拦截 API 响应
let intercepted = Arc::new(tokio::sync::Mutex::new(Vec::new()));
let intercepted_clone = intercepted.clone();
page.on_response(Arc::new(move |info, resp| {
if info.resource_type == ResourceType::Fetch {
let mut data = intercepted_clone.blocking_lock();
data.push((info.url.clone(), resp.body.clone()));
}
}));
// 导航
page.goto("https://example.com").await?;
page.settle(2000).await;
// 提取标题
if let Some(h1) = page.wait_for_selector("h1", Duration::from_secs(5)).await? {
println!("Title: {}", h1.text());
}
// 打印拦截到的 API 响应
let data = intercepted.lock().await;
for (url, body) in data.iter() {
println!("API: {} ({} bytes)", url, body.len());
}
Ok(())
}编译注意事项
- 首次编译慢:V8 从源码编译,约 5 分钟。后续增量秒级
- cmake 依赖:Stealth 功能需要 cmake 编译 BoringSSL
- Git 标签锁定:建议固定
tag = "v0.1.x"确保可重现 - 不是 crates.io 包:必须使用 Git 依赖
- release profile:
panic = "unwind"必须保留(已在 workspace 配置中固定)
CDP vs 嵌入式的选择
| 对比项 | CDP 模式 | 嵌入式 |
|---|---|---|
| 语言 | 不限(HTTP/WebSocket) | 仅 Rust |
| 隔离 | 进程隔离 | 同一进程 |
| 性能 | 有序列化+网络开销 | 零开销 |
| 部署 | 独立进程 | 嵌入现有进程 |
| 调试 | 可独立连接 DevTools | 需进程内日志 |
总结
obscura crate 提供了完整的 Rust 嵌入式浏览器 API。对于已经在使用 Rust 的项目,嵌入 Obscura 避免了 CDP 的序列化和网络开销,同时保留了完整的浏览器能力——页面渲染、JS 执行、请求拦截、Cookie 管理、Stealth 反检测。
需要企业代理方案?
我们可根据目标站点、并发规模与稳定性目标提供定制方案。