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(())
}

编译注意事项

  1. 首次编译慢:V8 从源码编译,约 5 分钟。后续增量秒级
  2. cmake 依赖:Stealth 功能需要 cmake 编译 BoringSSL
  3. Git 标签锁定:建议固定 tag = "v0.1.x" 确保可重现
  4. 不是 crates.io 包:必须使用 Git 依赖
  5. release profilepanic = "unwind" 必须保留(已在 workspace 配置中固定)

CDP vs 嵌入式的选择

对比项CDP 模式嵌入式
语言不限(HTTP/WebSocket)仅 Rust
隔离进程隔离同一进程
性能有序列化+网络开销零开销
部署独立进程嵌入现有进程
调试可独立连接 DevTools需进程内日志

总结

obscura crate 提供了完整的 Rust 嵌入式浏览器 API。对于已经在使用 Rust 的项目,嵌入 Obscura 避免了 CDP 的序列化和网络开销,同时保留了完整的浏览器能力——页面渲染、JS 执行、请求拦截、Cookie 管理、Stealth 反检测。

需要企业代理方案?

我们可根据目标站点、并发规模与稳定性目标提供定制方案。