# rpage 🦀🌐
> Rust 版 DrissionPage — 浏览器自动化 + HTTP 会话 + Cookie 互通,三合一。**580+ 公开方法,12,000+ 行 Rust**。内置 AI Agent 诊断接口(`page_snapshot` / `smart_click` / `eval_agent` / `page_diagnostics`)。
`rpage` 是一个受 [DrissionPage](https://github.com/g1879/DrissionPage) 启发的 Rust 浏览器自动化库。
## ✨ 核心特性
- **一个函数启动** — `WebPage::new()` 自动检测 Chrome → 启动子进程 → CDP 连接
- **零自动化标记** — 不传 `--enable-automation`,永不触发验证码
- **智能标签接管** — `connect()` 自动选择当前激活标签页,不会误开新标签
- **自动反检测** — stealth 脚本自动注入
- **Cookie 互通** — 浏览器 ↔ HTTP 会话共享 Cookie,支持导入/导出文件
- **智能等待** — `get()` 等加载,`ele()`/`eles()` 自动重试,超时可配置
- **中文完美** — `fill()` 用 JS `nativeInputValueSetter`
- **鲁棒交互** — `click()` 自动 fallback CDP→JS,拖拽到元素或坐标
- **链式调用** — `page.goto(url).await?.type_text("#kw", "rust").await?` 一行完成导航+输入
- **一步到位 API** — `click_ele()`/`get_text()`/`get_attr()` 无需先 `ele()` 再操作
- **AI Agent 接口** — `page_snapshot()` 给 LLM 结构化页面快照;`smart_click()`/`smart_fill()` 自然语言定位;`eval_agent()` 执行 JS 并捕获新增异常;`page_diagnostics()` 一次性聚合 JS 异常 + 错误日志 + 失败请求 + 加载时序(专为 LLM 调试前端设计)
- **网络包实时监听** — `on_request()`/`on_response()` 回调式捕获每个请求和响应
- **网络监听模式** — listen_start/stop + continue_request/fail_request 请求级控制
- **设备模拟** — 设备像素比/触摸模式/地理位置+刷新
- **资源分析** — links/images/ele_count 一键提取页面资源
- **网络控制** — URL 拦截/离线模式/清除缓存/禁用图片
- **标签页增强** — tab_to_front/get_tab_by_title/get_tab_by_url/wait_new_tab
- **Tab 管理** — 创建/切换/关闭/列表
- **条件等待** — 等元素出现/消失/删除,等标题/URL 变化,等 JS 表达式
- **Element 等待** — wait_for_visible/hidden/stale/clickable/enabled/text/attribute
- **运行时修改** — headers/user_agent/viewport
- **窗口管理** — maximize/minimize/fullscreen/restore/set_size
- **加载策略** — normal/eager/none 三种页面加载模式
- **事件回调** — on_dialog/on_load/on_close 事件监听
- **批量操作** — `eles().texts()` 一行获取所有文本
- **文件上传** — 浏览器 + Session multipart 双模式
- **PDF/截图** — 页面级和元素级
- **下载管理** — CDP 下载事件监听 + 等待下载完成
- **网络拦截** — Fetch.enable 拦截/修改/阻止请求
- **ActionChain** — 复杂鼠标键盘序列
- **iframe 上下文** — 进入 iframe 执行操作
- **网络监控** — 记录所有请求/响应/失败
- **Console 捕获** — 拦截 console.log/warn/error
- **WebSocket 监听** — 监听 WebSocket 帧
- **性能指标** — 页面计时 + JS Heap 等运行时指标
- **Init Script** — 页面加载前注入 JS
- **CSS 注入** — 动态注入/移除 CSS 样式
- **灵活配置** — 环境变量 `RPAGE_CHROME_PATH`,PATH 搜索,标准路径
- **同步 API** — `SyncPage` 零 await 封装,`SyncPage::connect()` 一步到位,自动接管当前激活标签页
- **Agent 智能接口** — `interactive_elements()`/`page_snapshot()`/`smart_click()`/`smart_fill()` 直接给 AI Agent 用
- **JS XPath 回退** — 非 CSS 定位器(`text:`/`tag:`)自动回退 XPath
## 🚀 快速开始
```rust
use rpage::prelude::*;
#[tokio::main]
async fn main() -> rpage::Result<()> {
let page = WebPage::new().await?; // 一行启动 Chrome
page.get("https://www.baidu.com").await?; // 自动等待加载
page.ele("#kw").await?.fill("rust教程").await?; // 中文输入
page.ele("#su").await?.click().await?; // 搜索
let results = page.eles("h3").await?; // 自动重试等待
for (i, text) in results.texts().iter().enumerate() {
println!("{}. {}", i + 1, text);
}
Ok(())
}
```
### 链式调用 — 一行完成复杂操作
```rust
// 链式:导航 → 输入 → 点击,一步到位
page.goto("https://www.baidu.com").await?
.type_text("#kw", "rust教程").await?
.click_ele("#su").await?;
```
### 同步 API — 零 await,零 tokio
`SyncPage` 将全部 async API 封装为同步调用,无需 `#[tokio::main]`:
```rust
use rpage::sync::SyncPage;
fn main() -> rpage::Result<()> {
let p = SyncPage::connect("http://127.0.0.1:9222")?; // 自动接管当前激活标签页
p.get("https://httpbin.org/forms/post")?;
p.type_text("input[name='custname']", "张三")?;
p.click_ele("input[value='medium']")?;
p.screenshot("form.png")?;
let title = p.title()?;
let cookies = p.cookies()?;
let links = p.links()?;
println!("title={}, {} cookies, {} links", title, cookies.len(), links.len());
Ok(())
}
```
### Agent 智能接口 — 为 AI Agent 设计
```rust
// 1. 分析页面交互元素
let elements = p.interactive_elements()?;
for el in &elements {
println!("{:?} {} type={:?}", el.tag, el.text, el.input_type);
}
// 2. 页面快照(给 LLM 上下文)
let snap = p.page_snapshot()?;
// snap.url, snap.title, snap.viewport_size, snap.interactive_elements, snap.visible_text
// 3. 智能操作 — 用自然语言描述即可
let result = p.smart_click("提交");
let result = p.smart_fill("用户名", "agent_bot");
// 4. DOM 快照(结构化 JSON)
let dom = p.dom_snapshot()?;
// 5. 性能指标
let metrics = p.performance_metrics()?;
```
### 能力展示
运行 `cargo run --example showcase` 查看 **10 大场景 50+ 项操作** 的完整演示:
```
1. 页面导航 & DOM 操作 — 导航、表单填写、复选框/单选框、截图
2. JavaScript 执行 — execute、run_async_js、run_js_with_args、全局注入
3. 元素高级操作 — DOM 树遍历、属性/样式操作、拖拽
4. 多标签页管理 — new_tab、switch_to_tab、跨标签导航
5. Cookie 管理 — 读取、设置、删除、跨请求验证
6. 网络控制 — UA伪装、自定义Headers、URL屏蔽
7. 截图 & PDF — 全页截图、元素截图、PDF生成
8. 智能等待 — wait_ele、wait_js、wait_url_contains
9. 设备模拟 & 视口 — viewport、device_scale、touch、geolocation、timezone
10. Agent 智能接口 — interactive_elements、page_snapshot、smart_click/fill
```
## 📦 安装
```toml
[dependencies]
rpage = "0.1"
```
## 📖 API 参考 (568 方法)
### 链式便捷 API (8)
无需先 `ele()` 再操作,一步到位。所有方法返回 `&Self`,支持链式调用。
```rust
page.goto(url).await?; // 导航(返回 &Self)
page.type_text("#kw", "rust教程").await?; // 定位+清空+输入
page.click_ele("#btn").await?; // 定位+点击
page.get_text("h1").await?; // 定位+获取文本
page.get_attr("#link", "href").await?; // 定位+获取属性
page.input_text("#input", "追加文本").await?; // 定位+追加输入
page.hover_ele("#menu").await?; // 定位+悬停
page.scroll_to_ele("#section").await?; // 定位+滚动到元素
// 链式组合示例
page.goto("https://example.com").await?
.type_text("#search", "query").await?
.click_ele("#submit").await?;
```
### 快速查询 (3)
```rust
let exists = page.exists("#result").await?; // bool — 元素是否存在
let n = page.count("li.item").await?; // usize — 匹配数量
let el = page.ele_or_none("#opt").await?; // Option<Element> — 无则 None
```
### DrissionPage 别名 (3)
兼容 DrissionPage 命名习惯:
```rust
let url = page.current_url().await?; // 等同 page.url()
let title = page.current_title().await?; // 等同 page.title()
let src = page.page_source().await?; // 等同 page.html()
```
### 页面导航 + 信息 (9)
```rust
page.get(url).await?; // 自动等 DOMContentLoaded
page.refresh().await?; // 智能等待
page.back().await?;
page.forward().await?;
let title = page.title().await?;
let url = page.url().await?;
let html = page.html().await?;
let val = page.execute("1+1").await?;
page.evaluate_on_new_document("...").await?;
```
### 元素定位 — 自动重试 (2)
```rust
let el = page.ele("#id").await?;
let els = page.eles("h3").await?;
```
| `#id`, `.class` | CSS |
| `@class=btn` | 属性精确 |
| `@class*=btn` | 属性包含 |
| `@class^=btn` | 属性前缀 |
| `@class$=btn` | 属性后缀 |
| `text:登录` | 文本精确 |
| `text*:登录` | 文本包含 |
| `tag:form@@text:登录` | 链式定位 |
### 条件等待 (10)
```rust
let el = page.wait_ele("#result", 10).await?;
page.wait_ele_hidden("#loading", 5).await?;
page.wait_ele_deleted(".modal", 5).await?;
page.wait_title_contains("搜索结果", 5).await?;
page.wait_url_contains("search", 5).await?;
page.wait_js("document.querySelectorAll('.item').length > 5", 10).await?;
let dl = page.wait_download(30).await?;
// ── 精确匹配等待 ──
page.wait_url_is("https://example.com/done", 10).await?;
page.wait_title_is("完成", 10).await?;
page.wait_for_navigation(10).await?;
```
### 标签页 (10)
```rust
page.new_tab().await?;
let titles = page.tab_titles().await?;
let urls = page.tab_urls().await?;
page.switch_to_tab(1).await?;
page.close_tab(0).await?;
let tabs = page.tabs().await?;
// ── iter10 增强 ──
page.tab_to_front(0).await?; // 将标签页置顶
let idx = page.get_tab_by_title("百度").await?; // 按标题查找
let idx = page.get_tab_by_url("example").await?; // 按 URL 查找
page.wait_new_tab(5).await?; // 等待新标签页
page.set_download_file_name("report.pdf").await?; // 设置下载文件名
```
### 元素操作 (55+ 方法)
```rust
// ── 基础交互 ──
el.click().await?; // CDP → JS 自动 fallback
el.fill("rust教程").await?; // 清空+输入
el.input("追加").await?;
el.clear().await?;
el.hover().await?;
el.submit().await?;
el.right_click().await?;
el.double_click().await?;
el.press_key("Enter").await?;
// ── 拖拽 ──
el.drag_to(&target).await?; // 拖到另一个元素
el.drag_to_offset(100.0, 50.0).await?; // 拖到相对坐标
// ── 下拉框 / 文件 ──
el.select("选项").await?;
el.select_by_value("val").await?;
el.upload_file("/path/to/file").await?;
el.upload_files(&["/a", "/b"]).await?;
// ── 复选框 ──
el.check().await?;
el.uncheck().await?;
let checked = el.checked();
// ── 截图 ──
el.screenshot("el.png").await?;
let bytes = el.screenshot_bytes().await?;
// ── 属性 / 状态 ──
let v = el.attr("href"); // Option<&str>
let v = el.value().await?;
let (x,y,w,h) = el.rect().await?;
let bbox = el.bounding_box().await?; // CDP + JS fallback
let s = el.style("color").await?;
el.set_attr("class", "active").await?;
el.is_displayed(); // 同步
el.is_visible().await?; // 异步,CSS+几何双重检测
el.is_enabled();
el.is_selected().await?;
// ── 相对定位 ──
let p = el.parent().await?;
let c = el.first_child().await?;
let n = el.next().await?;
let pv = el.prev().await?;
let child = el.ele("a")?;
let children = el.eles("li")?;
let children = el.children()?;
let sib = el.sibling()?;
let inner = el.inner_html()?;
let outer = el.outer_html()?;
// ── 焦点 / 选择 ──
el.focus().await?;
el.blur().await?;
el.select_text().await?;
el.scroll_into_view().await?;
el.scroll_to_top().await?;
// ── Shadow DOM ──
let shadow = el.shadow_ele("div").await?;
let shadows = el.shadow_eles("li").await?;
// ── JS ──
el.js("this.style.color='red'").await?;
```
### Element 等待方法 (40+)
```rust
// ── 可见性 ──
el.wait_for_visible().await?;
el.wait_for_visible_with_timeout(Duration::from_secs(10)).await?;
el.wait_for_hidden().await?;
el.wait_for_hidden_with_timeout(Duration::from_secs(5)).await?;
// ── 状态 ──
el.wait_for_enabled().await?;
el.wait_for_clickable().await?;
el.wait_for_stale().await?;
// ── 文本 ──
el.wait_for_text("加载完成").await?;
el.wait_for_text_eq("精确匹配").await?;
el.wait_for_text_contains("部分").await?;
// ── 属性 ──
el.wait_for_attribute("class", "active").await?;
el.wait_for_attribute_contains("class", "act").await?;
// ── 完全自定义 ──
let opts = WaitOptions::new(Duration::from_secs(30), Duration::from_millis(200));
el.wait_for_visible_with_options(opts).await?;
```
### 批量操作 — ElementBatch
```rust
let els = page.eles("h3").await?;
let texts = els.texts(); // Vec<&str>
let hrefs = els.attr_values("href"); // Vec<Option<&str>>
let visible = els.displayed(); // Vec<&Element>
```
### 页面操作 (20+)
```rust
page.scroll_to(0, 500).await?;
page.scroll_by(0, 300).await?; // 相对滚动
page.scroll_to_top().await?;
page.scroll_to_bottom().await?;
page.screenshot("shot.png").await?;
page.pdf("page.pdf").await?;
page.press("Enter").await?;
page.set_viewport(1920, 1080).await?;
page.handle_alert(true, None).await?;
page.frame_html("iframe").await?;
page.frame_execute("iframe", "document.title").await?;
page.scroll_to_element(&el).await?;
page.run_async_js("await fetch('/api')").await?;
page.keys("hello world").await?; // 逐字符输入(支持中文)
page.refresh_ele(&el).await?; // 重新定位元素
```
### 窗口管理 (7)
```rust
page.maximize().await?;
page.minimize().await?;
page.fullscreen().await?;
page.restore().await?;
page.set_window_size(1280, 800).await?;
let (l, t, w, h) = page.get_window_bounds().await?;
```
### 加载策略
```rust
page.set_load_strategy("eager").await?; // normal / eager / none
let strategy = page.load_strategy(); // &str
```
### 事件回调 (3)
```rust
page.on_dialog(|msg, r#type| {
println!("Dialog: {} ({})", msg, r#type);
true // accept
}).await?;
page.on_load(|url| {
println!("Page loaded: {}", url);
}).await?;
page.on_close(|| {
println!("Page closed");
}).await?;
```
### Cookie (10)
```rust
let cookies = page.cookies().await?;
page.set_cookie(cookie).await?;
page.delete_cookie("name").await?;
page.clear_cookies().await?;
page.sync_cookies().await?;
page.save_cookies_to_file("cookies.json").await?;
page.load_cookies_from_file("cookies.json").await?;
```
### 运行时修改 (3)
```rust
page.set_extra_headers(headers).await?;
page.set_user_agent("Mozilla/5.0 ...").await?;
page.set_viewport(1920, 1080).await?;
```
### Session HTTP (7)
```rust
let resp = page.session_get(url).await?;
let resp = page.session_post(url, body).await?;
let resp = page.session_put(url, body).await?;
let resp = page.session_delete(url).await?;
let resp = page.session_head(url).await?;
let resp = page.session_patch(url, body).await?;
let resp = page.session_post_json(url, &data).await?; // JSON body
```
此外还有:
```rust
page.post(url, body).await?;
page.post_multipart(url, fields, "file", "/path").await?;
page.post_json(url, data).await?;
page.session_download(url, "file.zip").await?;
let status = page.session_status(url).await?;
```
### 网络包实时监听 (3)
回调式捕获浏览器每个请求和响应:
```rust
// 监听请求
page.on_request(|req| {
println!("→ {} {}", req.method, req.url);
}).await?;
// 监听响应
page.on_response(|resp| {
println!("← {} {}", resp.status, resp.url);
}).await?;
// 清除所有监听器
page.clear_listeners().await?;
```
### Console 捕获 (3)
```rust
page.execute("console.log('hello')").await?;
let logs = page.console_log(); // Vec<ConsoleMessage>
page.clear_console();
```
### Init Script + CSS 注入 (4)
```rust
let id = page.add_init_script("my", "window.test = 42").await?;
page.remove_init_script(&id).await?;
let css_id = page.inject_css("body { background: red }").await?;
page.remove_css(&css_id).await?;
```
### 性能指标 (3)
```rust
let timing = page.page_timing().await?; // HashMap<String, f64>
let metrics = page.performance_metrics().await?; // Vec<(String, f64)>
let snapshot = page.dom_snapshot().await?; // serde_json::Value
```
### 下载管理 (4)
```rust
let downloads = page.downloads(); // 获取所有下载
let dl = page.wait_download(30).await?; // 等待最近下载完成
println!("{:?}", dl.save_path); // 下载路径
page.download_manager().completed(); // 已完成的下载
```
### 网络拦截 (3)
```rust
let guard = page.enable_intercept("*/api/*").await?;
tokio::time::sleep(Duration::from_secs(5)).await;
for req in guard.paused_requests() {
guard.continue_request(&req.request_id, None).await?;
}
guard.disable().await?; // 或 drop(guard) 自动关闭
```
### ActionChain — 复杂操作序列 (8)
```rust
page.actions()
.move_to(100.0, 200.0)
.click_at(100.0, 200.0)
.double_click_at(150.0, 200.0)
.right_click_at(200.0, 200.0)
.key_down("Control")
.press("a")
.key_up("Control")
.pause(Duration::from_millis(500))
.perform()
.await?;
```
### iframe 上下文 (4)
```rust
let ctx = page.enter_frame("iframe").await?;
ctx.execute("document.title").await?;
let el = ctx.ele("#btn").await?;
let html = ctx.html().await?;
```
### 网络监控 (5)
```rust
page.network_monitor().requests(); // 所有请求
page.network_monitor().responses(); // 所有响应
page.network_monitor().failures(); // 失败请求
page.network_monitor().find_requests_by_url("api");
page.network_monitor().clear();
```
### WebSocket 监听 (2)
```rust
page.listen_websocket().await?;
let frames = page.websocket_frames();
```
### 网络监听模式 (4) — iter10
```rust
page.listen_start().await?; // 开启网络监听
// ... 用户操作 ...
let reqs = page.network_monitor().requests(); // 获取捕获的请求
page.listen_stop().await?; // 停止监听
page.continue_request("req_id", None).await?; // 继续请求
page.continue_request("req_id", Some("https://new.url")).await?; // 重定向
page.fail_request("req_id").await?; // 拒绝请求
```
### 设备模拟 + 网络控制 (9) — iter10
```rust
page.set_device_scale(2.0).await?; // 设置设备像素比
page.set_touch(true).await?; // 开启触摸模式
page.set_location_and_reload(39.9, 116.4).await?; // 地理位置+刷新
page.smooth_scroll(0, 1500, 500).await?; // 平滑滚动(x, y, ms)
page.set_blocked_urls(&["*://*/*/*.css"]).await?; // URL 拦截
page.set_offline(true).await?; // 离线模式
page.clear_cache().await?; // 清除缓存
page.disable_images().await?; // 禁用所有图片
page.get_and_wait(url, 10).await?; // 导航+等待标题
```
### 资源分析 (3) — iter10
```rust
let links = page.links().await?; // Vec<String> 所有链接
let imgs = page.images().await?; // Vec<String> 所有图片
let n = page.ele_count("a").await?; // 匹配元素数量
```
### 模式切换 + 生命周期
```rust
page.to_session().await?; // 浏览器 → HTTP
page.to_chromium().await?; // HTTP → 浏览器
page.sleep(Duration::from_secs(1)).await;
page.close().await?;
page.quit().await?;
```
### 自定义配置
```rust
let opts = WebPageOptions {
chromium: ChromiumOptions {
headless: true,
timeout: Duration::from_secs(10), // 自定义超时
proxy: Some("http://proxy:8080".into()),
user_agent: "Mozilla/5.0 ...".into(),
browser_path: Some("/usr/bin/chromium".into()),
disable_gpu: true,
no_sandbox: true,
extension_dirs: vec!["/path/to/ext".into()],
..Default::default()
},
..Default::default()
};
let page = WebPage::with_options(opts).await?;
```
环境变量:`RPAGE_CHROME_PATH` 指定 Chrome 路径
### 低级访问
```rust
page.is_chromium(); // bool
page.is_session(); // bool
page.inner_page(); // Option<&Page>
page.options(); // Option<&ChromiumOptions>
```
## 项目结构
```
rpage/ (12,000+ 行 Rust, 580+ 方法)
├── src/
│ ├── chromium_page.rs # CDP 浏览器控制 (190+ 方法) + Agent 接口
│ ├── element.rs # 元素操作 (100+ 方法 + ElementBatch + Wait)
│ ├── sync.rs # SyncPage 同步封装 (100+ 方法)
│ ├── agent.rs # Agent 数据结构 (InteractiveElement/PageSnapshot/ActionAttempt)
│ ├── js_helpers.rs # JS 代码片段 (XPath 回退/DOM 遍历等)
│ ├── web_page.rs # 统一双模式页 (120+ 方法)
│ ├── session_page.rs # HTTP 会话 (25+ 方法)
│ ├── download.rs # 下载管理 (13 方法)
│ ├── network.rs # 网络监控 + 实时监听 + 监听模式 (20 方法)
│ ├── locator.rs # 定位器解析 + XPath 回退 (6 方法)
│ ├── config.rs # 配置 (24 方法)
│ ├── stealth.rs # 反检测 (5 方法)
│ ├── cookie-hub.rs # Cookie 同步 + 文件 (10 方法)
│ ├── wait.rs # 等待策略 (6 方法)
│ ├── console.rs # Console 捕获 (5 方法)
│ ├── websocket.rs # WebSocket 监听 (5 方法)
│ ├── error.rs # 错误类型
│ ├── prelude.rs # 预导入
│ └── lib.rs # 入口
├── examples/
│ └── showcase.rs # 能力展示:10 大场景 50+ 项操作
└── tests/ # 145 个测试
```
## 更新日志
### 1.0.3
- **文档更新**:README 补充 AI Agent 诊断接口的特性说明与 1.0.1/1.0.2 更新日志;Cargo.toml description 更新以反映 agent 能力。
### 1.0.2
- **类型序列化补全**:`CookieInfo`、`DownloadInfo`、`DownloadStatus` 补 `Serialize`/`Deserialize`,使诊断/下载数据可直接序列化给 LLM。
### 1.0.1
- **新增 AI Agent 诊断接口**(`agent.rs` + `chromium_page.rs`):
- `PageDiagnostics` — 页面诊断快照,一次性聚合 JS 异常(含文件名/行号/调用栈)、Error/Warn 日志、失败网络请求、加载时序。`has_errors` 字段快速判断页面健康。
- `EvalResult` — JS 执行结果 + 执行期间新增的异常,构成"注入→诊断"闭环。
- **新增方法**(`ChromiumPage` / `WebPage` / `SyncPage` 三处统一):
- `eval_agent(js)` — 执行 JS,只回报本次执行**新增**的异常(不混入历史噪音)。
- `page_diagnostics()` — 页面健康报告(过滤掉 log/info/debug,只保留 Error/Warn 省 token)。
- `clear_diagnostics()` — 清空控制台 + 网络记录,操作前清零确保后续诊断干净。
- **底层类型补序列化**:`ConsoleEntry`、`ConsoleLevel`、`JsException`、`RequestRecord`、`ResponseRecord`、`FailedRequest`、`RequestInfo`、`ResponseInfo`、`WsFrame`、`WsEvent` 全部补 `Serialize`/`Deserialize`。
- **根级 re-export**:`ActionAttempt`、`EvalResult`、`PageDiagnostics`、`PageSnapshot`、`PageSummary` 可直接 `rpage::X` 访问。
## License
MIT