Obscura Rust Library — Browser/Page/Element Complete API

Embed Obscura as a Rust library in your project. Browser/Page/Element/CookieStore API, request interception, event callbacks, and build notes.

16Yun Engineering TeamJul 8, 20262 min read

Why the Rust Library

CDP mode communicates over WebSocket and works with any language. For Rust projects, calling the engine in-process is significantly faster:

  • Zero serialization overhead
  • Zero network latency
  • Direct memory access to the V8 Isolate

The obscura crate provides the complete embedded API.

Add Dependency

Obscura builds V8 from source, so it is not on crates.io. Use a Git dependency:

[dependencies]
obscura = { git = "https://example.com/obscura" }
tokio = { version = "1", features = ["rt", "macros"] }
anyhow = "1"
```bash
 
Pin a tag for reproducible builds:
 
```toml
obscura = { git = "https://example.com/obscura", tag = "v0.1.7" }
```bash
 
## Browser
 
```rust
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()?;
```bash
 
## Page
 
```rust
use std::time::Duration;
 
let mut page = browser.new_page().await?;
page.goto("https://example.com").await?;
 
println!("URL: {}", page.url());
println!("HTML bytes: {}", page.content().len());
 
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());
}
```bash
 
### Page API
 
| Method | Description |
|--------|-------------|
| `goto(url)` | Navigate and wait for load |
| `content()` | Rendered HTML |
| `url()` | Current URL |
| `evaluate(js)` | Run JS, returns `serde_json::Value` |
| `query_selector(css)` | First matching Element |
| `wait_for_selector(css, Duration)` | Poll until element appears |
| `settle(max_ms)` | Drive event loop |
| `on_request(cb)` | Request callback |
| `on_response(cb)` | Response callback |
| `enable_interception()` | Interception channel |
| `add_preload_script(js)` | Pre-navigation script |
 
## Element
 
```rust
if let Some(el) = page.query_selector("a.link").await? {
    println!("Text: {}", el.text());
    println!("Href: {:?}", el.attribute("href"));
    el.click();
}
```bash
 
## CookieStore
 
```rust
let all = page.context.cookie_jar.get_all_cookies();
 
let url = url::Url::parse("https://example.com")?;
let cookies = page.context.cookie_jar.get_for_url(&url);
 
page.context.cookie_jar.set(Cookie::new("session", "abc123"));
 
page.context.cookie_jar.save_to_file("/data/cookies.json")?;
```bash
 
## Request Interception
 
```rust
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.goto("https://example.com").await?;
page.settle(2000).await;
```bash
 
### Preload Script
 
```rust
page.add_preload_script("window.__obscura = true;");
page.goto("https://example.com").await?;
```bash
 
### Passive Callbacks
 
```rust
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());
    }
}));
```bash
 
## Complete Example: Scraping Loop
 
```rust
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?;
 
    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());
    }
 
    let data = intercepted.lock().await;
    for (url, body) in data.iter() {
        println!("API: {} ({} bytes)", url, body.len());
    }
 
    Ok(())
}
```bash
 
## Build Notes
 
1. **First build is slow**: V8 compiles from source (~5 min). Incremental builds are seconds.
2. **cmake required**: Stealth feature needs cmake for BoringSSL.
3. **Pin a Git tag**: For reproducible builds.
4. **Not on crates.io**: Must use Git dependency.
5. **`panic = "unwind"`**: Must stay in release profile (already set in workspace).
 
## CDP vs Embedded
 
| Aspect | CDP | Embedded |
|--------|-----|----------|
| Language | Any (HTTP/WS) | Rust only |
| Isolation | Process-level | Same process |
| Performance | Serialization + network | Zero overhead |
| Deployment | Separate process | Embedded |
| Debugging | Remote DevTools | In-process logs |
 
## Summary
 
The `obscura` crate provides a complete Rust embedded browser API. For Rust projects, embedding Obscura avoids CDP serialization and network overhead while retaining full browser capabilities — page rendering, JS execution, request interception, cookie management, and Stealth anti-detection.

Need an enterprise proxy plan?

We can tailor architecture to your target domains, concurrency, and reliability goals.