rootbrowse 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rootbrowse-0.2.0/.gitignore +18 -0
- rootbrowse-0.2.0/CHANGELOG.md +83 -0
- rootbrowse-0.2.0/PKG-INFO +203 -0
- rootbrowse-0.2.0/PROJECT.md +313 -0
- rootbrowse-0.2.0/README.md +179 -0
- rootbrowse-0.2.0/example.py +23 -0
- rootbrowse-0.2.0/project_tree.json +160 -0
- rootbrowse-0.2.0/pyproject.toml +43 -0
- rootbrowse-0.2.0/src/rootbrowse/__init__.py +39 -0
- rootbrowse-0.2.0/src/rootbrowse/_version.py +3 -0
- rootbrowse-0.2.0/src/rootbrowse/browser.py +181 -0
- rootbrowse-0.2.0/src/rootbrowse/constants.py +62 -0
- rootbrowse-0.2.0/src/rootbrowse/element_operator.py +219 -0
- rootbrowse-0.2.0/src/rootbrowse/exceptions.py +41 -0
- rootbrowse-0.2.0/src/rootbrowse/page_scanner.py +467 -0
- rootbrowse-0.2.0/src/rootbrowse/tab_manager.py +97 -0
- rootbrowse-0.2.0/src/rootbrowse/types.py +66 -0
- rootbrowse-0.2.0/src/rootbrowse/utils/__init__.py +0 -0
- rootbrowse-0.2.0/test/__init__.py +1 -0
- rootbrowse-0.2.0/test/conftest.py +8 -0
- rootbrowse-0.2.0/test/test_browser.py +149 -0
- rootbrowse-0.2.0/test/test_constants.py +150 -0
- rootbrowse-0.2.0/test/test_element_operator.py +248 -0
- rootbrowse-0.2.0/test/test_exceptions.py +162 -0
- rootbrowse-0.2.0/test/test_page_scanner.py +320 -0
- rootbrowse-0.2.0/test/test_tab_manager.py +150 -0
- rootbrowse-0.2.0/test/test_types.py +98 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-05-16
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **区域识别** — 实现真正的语义区域检测
|
|
13
|
+
- `browser.page` → `browser.view` 属性重命名
|
|
14
|
+
- `_detect_regions()`: body 直接子元素动态检测,排除噪音标签
|
|
15
|
+
- `_guess_region_label()`: 根据 id/class 关键词猜测区域名称
|
|
16
|
+
- `_element_to_region`: 记录元素与区域的归属关系
|
|
17
|
+
- `_get_refs_in_region()`: 按 region_id 过滤元素
|
|
18
|
+
- `save_state(path)`: cookies 序列化到 JSON 文件
|
|
19
|
+
- `load_state(path)`: 从 JSON 文件恢复 cookies
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- `Browser.page` 属性重命名为 `Browser.view`
|
|
24
|
+
- Region id 格式从 `"main"` 改为 `"region_N"`
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-05-10
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **types.py** — 数据类定义
|
|
31
|
+
- `Region`: 页面语义区块
|
|
32
|
+
- `Element`: 可交互元素
|
|
33
|
+
- `RegionSummary`: 区域摘要
|
|
34
|
+
- `ElementPreview`: 元素摘要
|
|
35
|
+
- `OperationResult`: 操作结果
|
|
36
|
+
|
|
37
|
+
- **constants.py** — 常量配置
|
|
38
|
+
- `INTERACTIVE_TAGS`: 可交互 HTML 标签列表
|
|
39
|
+
- `INPUT_TAGS`: 可输入标签列表
|
|
40
|
+
- `CLICKABLE_TAGS`: 可点击标签列表
|
|
41
|
+
- `ROLE_TAG_MAP`: ARIA role 映射
|
|
42
|
+
- `DEFAULT_LIMIT`, `DEFAULT_OFFSET`: 分页配置
|
|
43
|
+
- `REF_PREFIX`: ref 前缀 ('r')
|
|
44
|
+
|
|
45
|
+
- **exceptions.py** — 自定义异常
|
|
46
|
+
- `RootBrowseError` (基异常)
|
|
47
|
+
- `BrowserError`, `ElementNotFoundError`, `RegionNotFoundError`
|
|
48
|
+
- `TabNotFoundError`, `OperationError`, `StateFileError`, `PageLoadError`
|
|
49
|
+
|
|
50
|
+
- **tab_manager.py** — 标签页管理器
|
|
51
|
+
- `new_tab(url)`: 打开新标签页
|
|
52
|
+
- `close_tab(index)`: 关闭标签页
|
|
53
|
+
- `switch_to_tab(index)`: 切换标签页
|
|
54
|
+
- `tabs_count()`: 获取标签页数量
|
|
55
|
+
- `current_index()`: 获取当前索引
|
|
56
|
+
|
|
57
|
+
- **element_operator.py** — 元素操作器
|
|
58
|
+
- `click(locator)`: 点击元素
|
|
59
|
+
- `input_by_ref(locator, text, clear)`: 向输入框写入文字
|
|
60
|
+
- `hover(locator)`: 悬停
|
|
61
|
+
- `double_click(locator)`: 双击
|
|
62
|
+
- `right_click(locator)`: 右键
|
|
63
|
+
- `submit(locator)`: 提交表单
|
|
64
|
+
- `clear(locator)`: 清空输入框
|
|
65
|
+
- `send_enter()`: 发送回车键
|
|
66
|
+
- `ref_to_xpath(ref)`: 通过 ref 获取 xpath
|
|
67
|
+
|
|
68
|
+
- **page_scanner.py** — 页面扫描器
|
|
69
|
+
- `get_regions()`: 获取页面语义区块列表
|
|
70
|
+
- `get_region_summary(region_id)`: 获取区域统计摘要
|
|
71
|
+
- `match_element(**filters)`: 按条件筛选元素摘要列表
|
|
72
|
+
- `get_element(ref)`: 获取完整元素信息
|
|
73
|
+
- `find_element(by, value, filter)`: 多维度精确定位元素
|
|
74
|
+
|
|
75
|
+
- **browser.py** — 浏览器主入口
|
|
76
|
+
- `get(url, timeout)`: 打开 URL
|
|
77
|
+
- `screenshot(path, annotate)`: 页面截图
|
|
78
|
+
- `save_state(path)`: 保存浏览器状态
|
|
79
|
+
- `load_state(path)`: 恢复浏览器状态
|
|
80
|
+
- `close()`: 关闭浏览器
|
|
81
|
+
|
|
82
|
+
- **test/** — 完整测试套件
|
|
83
|
+
- 124 tests covering all modules
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rootbrowse
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python browser automation MCP framework for AI Agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/zimvir/RootBrowse
|
|
6
|
+
Project-URL: Repository, https://github.com/zimvir/RootBrowse
|
|
7
|
+
Author: zimvir
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent,ai,automation,browser,mcp
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# RootBrowse
|
|
26
|
+
|
|
27
|
+
Python 浏览器自动化 MCP 框架,为 AI Agent 提供结构化的浏览器交互能力。基于 DrissionPage 构建。
|
|
28
|
+
|
|
29
|
+
## 核心特性
|
|
30
|
+
|
|
31
|
+
- **区域化拆分** — 将页面 DOM 按语义区块分组,解决复杂页面的信息过载问题
|
|
32
|
+
- **ref 精确引用** — 为每个可交互元素生成内部 ID (r1, r2, r3...),避免文字匹配的不确定性
|
|
33
|
+
- **渐进式获取** — get_regions → get_region_summary → match_element → get_element,按需逐步深入
|
|
34
|
+
- **TabManager 状态自管理** — 内部维护标签页状态,不依赖 DrissionPage API 查询
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install rootbrowse
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from DrissionPage import ChromiumPage
|
|
46
|
+
from rootbrowse import Browser
|
|
47
|
+
|
|
48
|
+
# 创建浏览器实例
|
|
49
|
+
page = ChromiumPage()
|
|
50
|
+
browser = Browser(page)
|
|
51
|
+
|
|
52
|
+
# 打开网页
|
|
53
|
+
browser.get('https://example.com')
|
|
54
|
+
|
|
55
|
+
# 获取页面区块
|
|
56
|
+
regions = browser.view.get_regions()
|
|
57
|
+
print(regions) # [Region(id='region_1', label='容器', node_count=87), ...]
|
|
58
|
+
|
|
59
|
+
# 获取区块统计摘要
|
|
60
|
+
summary = browser.view.get_region_summary('region_1')
|
|
61
|
+
print(f"元素数量: {summary.count}")
|
|
62
|
+
|
|
63
|
+
# 按条件筛选元素
|
|
64
|
+
elements = browser.view.match_element(tag='a', text_contains='Python', limit=20)
|
|
65
|
+
print(elements) # [ElementPreview(ref='r5', text='Python 入门', ...), ...]
|
|
66
|
+
|
|
67
|
+
# 获取完整元素信息
|
|
68
|
+
ele = browser.view.get_element('r5')
|
|
69
|
+
print(f"点击: {ele.text} -> {ele.attrs['href']}")
|
|
70
|
+
|
|
71
|
+
# 执行操作
|
|
72
|
+
result = browser.act.click('r5')
|
|
73
|
+
print(f"点击结果: {result.success}")
|
|
74
|
+
|
|
75
|
+
# 关闭浏览器
|
|
76
|
+
browser.close()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## AI 工作流
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
用户指令
|
|
83
|
+
↓
|
|
84
|
+
get_regions() → 查看页面有哪些区块
|
|
85
|
+
get_region_summary(id) → 查看区块统计(标签分布、role 分布)
|
|
86
|
+
match_element(id, ...) → 按条件筛选元素(摘要列表)
|
|
87
|
+
get_element(ref) → 获取元素完整信息
|
|
88
|
+
ElementOperator 操作 → 点击、输入、悬停等
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 核心概念
|
|
92
|
+
|
|
93
|
+
### ref(内部引用 ID)
|
|
94
|
+
|
|
95
|
+
HTML 元素大多数没有 `id` 属性。RootBrowse 为每个可交互元素生成 `ref`(r1, r2, r3...),作为内部引用。
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
# 原始 HTML: <a href="/python">Python 入门</a>
|
|
99
|
+
# 生成 ref 后
|
|
100
|
+
Element(ref="r1", tag="a", text="Python 入门", xpath="...", attrs={...})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
AI 用 `ref` 操作元素,不依赖原始 HTML 的 id 或文字匹配。
|
|
104
|
+
|
|
105
|
+
### 区域化拆分
|
|
106
|
+
|
|
107
|
+
页面 DOM 有几千个节点,直接给 AI 无法处理。RootBrowse 按语义区块拆分:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
页面 DOM
|
|
111
|
+
↓ 按语义区域分组(header, main, sidebar 等)
|
|
112
|
+
↓ 过滤噪音(script, style, meta)
|
|
113
|
+
↓ 统计每个区域的节点数
|
|
114
|
+
返回:Region 列表,AI 选择进入哪个区域
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 渐进式获取
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
get_regions() → 看有哪些区块
|
|
121
|
+
get_region_summary() → 看区块统计(tag 分布)
|
|
122
|
+
match_element() → 按条件筛选元素(摘要)
|
|
123
|
+
get_element() → 最后才看完整信息
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
每一步都返回精简数据,AI 按需逐步深入。
|
|
127
|
+
|
|
128
|
+
## API 概览
|
|
129
|
+
|
|
130
|
+
### Browse — 主入口
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
browser.get(url) # 打开 URL
|
|
134
|
+
browser.screenshot(path) # 截图
|
|
135
|
+
browser.save_state(path) # 保存会话状态
|
|
136
|
+
browser.load_state(path) # 恢复会话状态
|
|
137
|
+
browser.close() # 关闭浏览器
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### TabManager — 标签页管理
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
browser.tabs.new_tab(url) # 新建标签页
|
|
144
|
+
browser.tabs.close_tab(index) # 关闭标签页
|
|
145
|
+
browser.tabs.switch_to_tab(index) # 切换标签页
|
|
146
|
+
browser.tabs.tabs_count() # 获取标签页数量
|
|
147
|
+
browser.tabs.current_index() # 获取当前索引
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### PageScanner — 页面扫描
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
browser.view.get_regions() # 获取区块列表
|
|
154
|
+
browser.view.get_region_summary(region_id) # 获取区块统计
|
|
155
|
+
browser.view.match_element(**filters) # 筛选元素
|
|
156
|
+
browser.view.get_element(ref) # 获取完整元素
|
|
157
|
+
browser.view.find_element(by, value) # 精确定位
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### ElementOperator — 元素操作
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
browser.act.click(ref) # 点击
|
|
164
|
+
browser.act.input_by_ref(ref, text) # 输入
|
|
165
|
+
browser.act.hover(ref) # 悬停
|
|
166
|
+
browser.act.double_click(ref) # 双击
|
|
167
|
+
browser.act.right_click(ref) # 右键
|
|
168
|
+
browser.act.submit(ref) # 提交表单
|
|
169
|
+
browser.act.clear(ref) # 清空输入框
|
|
170
|
+
browser.act.send_enter() # 发送回车
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 数据类型
|
|
174
|
+
|
|
175
|
+
| 类型 | 说明 |
|
|
176
|
+
|------|------|
|
|
177
|
+
| `Region` | 页面语义区块 `{id, label, node_count}` |
|
|
178
|
+
| `Element` | 可交互元素 `{ref, tag, role, text, xpath, attrs}` |
|
|
179
|
+
| `RegionSummary` | 区域统计 `{count, tag_counts, role_counts, text_preview}` |
|
|
180
|
+
| `ElementPreview` | 元素摘要 `{ref, text, attrs_preview}` |
|
|
181
|
+
| `OperationResult` | 操作结果 `{success, error?, new_url?}` |
|
|
182
|
+
|
|
183
|
+
## 异常
|
|
184
|
+
|
|
185
|
+
| 异常 | 说明 |
|
|
186
|
+
|------|------|
|
|
187
|
+
| `BrowserError` | 浏览器相关错误 |
|
|
188
|
+
| `ElementNotFoundError` | 元素未找到 |
|
|
189
|
+
| `RegionNotFoundError` | 区域未找到 |
|
|
190
|
+
| `TabNotFoundError` | 标签页未找到 |
|
|
191
|
+
| `OperationError` | 操作失败 |
|
|
192
|
+
| `StateFileError` | 状态文件错误 |
|
|
193
|
+
| `PageLoadError` | 页面加载失败 |
|
|
194
|
+
|
|
195
|
+
## 技术栈
|
|
196
|
+
|
|
197
|
+
- **底层引擎**: [DrissionPage](https://github.com/g18792951860/DrissionPage) — Python CDP 连接,无需驱动
|
|
198
|
+
- **MCP 框架**: FastMCP(官方 Python MCP SDK)
|
|
199
|
+
- **Python 版本**: >= 3.10
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# RootBrowse
|
|
2
|
+
|
|
3
|
+
## 项目定位
|
|
4
|
+
|
|
5
|
+
Python 浏览器自动化 MCP 框架,为 AI Agent 提供结构化的浏览器交互能力。基于 DrissionPage 构建。
|
|
6
|
+
|
|
7
|
+
## 解决什么问题
|
|
8
|
+
|
|
9
|
+
| 问题 | 现状 | RootBrowse |
|
|
10
|
+
|------|------|------------|
|
|
11
|
+
| DOM 超限 | 复杂页面直接报错 | 区域化拆分 + 截图兜底 |
|
|
12
|
+
| 点击不精确 | 文字匹配模糊,点击错位置 | ref 引用精确点击 |
|
|
13
|
+
| 信息噪音大 | 原始 DOM 转储,AI 无法处理 | 可交互元素树(无噪音) |
|
|
14
|
+
| 搜索场景弱 | 返回单节点 | 结构化搜索结果列表 |
|
|
15
|
+
| 状态管理 | 每次重新登录 | save/load state |
|
|
16
|
+
|
|
17
|
+
**本质:让 AI Agent 能稳定、准确地操作浏览器。**
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 核心概念
|
|
22
|
+
|
|
23
|
+
### ref(内部引用 ID)
|
|
24
|
+
|
|
25
|
+
HTML 元素大多数没有 `id` 属性。RootBrowse 为每个可交互元素生成 `ref`(r1, r2, r3...),作为内部引用。
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
# 原始 HTML: <a href="/python">Python 入门</a>
|
|
29
|
+
# 生成 ref 后
|
|
30
|
+
Element(ref="r1", tag="a", text="Python 入门", xpath="...", attrs={...})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
AI 用 `ref` 操作元素,不依赖原始 HTML 的 id 或文字匹配。
|
|
34
|
+
|
|
35
|
+
### 区域化拆分
|
|
36
|
+
|
|
37
|
+
页面 DOM 有几千个节点,直接给 AI 无法处理。RootBrowse 按语义区块拆分:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
页面 DOM
|
|
41
|
+
↓ 按语义区域分组(header, main, sidebar 等)
|
|
42
|
+
↓ 过滤噪音(script, style, meta)
|
|
43
|
+
↓ 统计每个区域的节点数
|
|
44
|
+
返回:Region 列表,AI 选择进入哪个区域
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 渐进式获取
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
get_regions() → 看有哪些区块
|
|
51
|
+
get_region_summary() → 看区块统计(tag 分布)
|
|
52
|
+
match_element() → 按条件筛选元素(摘要)
|
|
53
|
+
get_element() → 最后才看完整信息
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
每一步都返回精简数据,AI 按需逐步深入。
|
|
57
|
+
|
|
58
|
+
### TabManager 状态自管理
|
|
59
|
+
|
|
60
|
+
TabManager **不依赖 DrissionPage API 查询标签页列表**,自身维护状态:
|
|
61
|
+
|
|
62
|
+
- `_tabs` — 所有标签页信息 `[{url, title}, ...]`
|
|
63
|
+
- `_current_index` — 当前标签页索引
|
|
64
|
+
|
|
65
|
+
每次调用 DrissionPage 方法后,同步更新内部状态。
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## AI 工作流
|
|
70
|
+
|
|
71
|
+
### 核心流程
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
用户指令 → get_regions() → get_region_summary() → match_element() → get_element() → ElementOperator 操作
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 具体例子
|
|
78
|
+
|
|
79
|
+
**场景:在新闻网站搜索"Python"文章**
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
1. get_regions()
|
|
83
|
+
→ [Region(id="header", node_count=15), Region(id="main", node_count=347), ...]
|
|
84
|
+
|
|
85
|
+
2. get_region_summary("main")
|
|
86
|
+
→ {count: 347, tag_counts: {a: 280, button: 23, input: 12}, role_counts: {link: 280, button: 23}}
|
|
87
|
+
|
|
88
|
+
3. match_element("main", tag="a", text_contains="Python", limit=20)
|
|
89
|
+
→ [{ref: "r5", text: "Python 入门", attrs_preview: {href: "/python"}}, ...]
|
|
90
|
+
|
|
91
|
+
4. get_element("r5")
|
|
92
|
+
→ Element(ref="r5", tag="a", role="link", text="Python 入门", xpath="...", attrs={...})
|
|
93
|
+
|
|
94
|
+
5. ElementOperator.click("r5")
|
|
95
|
+
→ {success: true, new_url: "...", error: null}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**场景:截图兜底**
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
操作后页面崩溃 / DOM 超限
|
|
102
|
+
↓
|
|
103
|
+
screenshot(annotate=[[x1,y1,x2,y2]])
|
|
104
|
+
↓
|
|
105
|
+
返回 base64 图片,高亮标注指定区域
|
|
106
|
+
AI 从截图看页面状态,不依赖 DOM
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 类结构
|
|
112
|
+
|
|
113
|
+
### TabManager — 标签页管理器
|
|
114
|
+
|
|
115
|
+
**职责**:管理浏览器标签页(新增、切换、关闭)
|
|
116
|
+
|
|
117
|
+
**内部状态**:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
_page # DrissionPage 底层实例
|
|
121
|
+
_tabs # [{url, title}, ...] 所有标签页信息
|
|
122
|
+
_current_index # 当前标签页索引
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**方法**:
|
|
126
|
+
|
|
127
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
128
|
+
|------|------|------|------|
|
|
129
|
+
| `new_tab` | `url: str` | `int` | 打开新标签页,返回索引 |
|
|
130
|
+
| `close_tab` | — | `None` | 关闭当前标签页 |
|
|
131
|
+
| `switch_to_tab` | `index: int` | `None` | 切换到指定标签页 |
|
|
132
|
+
| `tabs_count` | — | `int` | 返回标签页数量 |
|
|
133
|
+
| `current_index` | — | `int` | 返回当前标签页索引 |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### ElementOperator — 元素操作器
|
|
138
|
+
|
|
139
|
+
**职责**:对元素执行点击、输入、悬停等操作
|
|
140
|
+
|
|
141
|
+
**内部状态**:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
_element_map # ref -> Element 映射表
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**方法**:
|
|
148
|
+
|
|
149
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
150
|
+
|------|------|------|------|
|
|
151
|
+
| `click` | `ref: str` | `dict` | 通过 ref 精确点击 |
|
|
152
|
+
| `input_by_ref` | `ref: str, text: str, clear: bool = False` | `dict` | 向输入框写入文字 |
|
|
153
|
+
| `hover` | `ref: str` | `dict` | 悬停 |
|
|
154
|
+
| `double_click` | `ref: str` | `dict` | 双击 |
|
|
155
|
+
| `right_click` | `ref: str` | `dict` | 右键 |
|
|
156
|
+
| `submit` | `ref: str` | `dict` | 提交表单 |
|
|
157
|
+
| `clear` | `ref: str` | `dict` | 清空输入框 |
|
|
158
|
+
| `send_enter` | — | `None` | 发送回车键 |
|
|
159
|
+
|
|
160
|
+
**返回值格式**:`{success, new_url?, error?}`
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### PageScanner — 页面扫描器
|
|
165
|
+
|
|
166
|
+
**职责**:获取页面结构化信息(区块、元素摘要、完整元素)
|
|
167
|
+
|
|
168
|
+
**内部状态**:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
_page # DrissionPage 底层实例
|
|
172
|
+
_element_map # ref -> Element 映射表
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**方法**:
|
|
176
|
+
|
|
177
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
178
|
+
|------|------|------|------|
|
|
179
|
+
| `get_regions` | — | `list[Region]` | 返回页面语义区块列表 |
|
|
180
|
+
| `get_region_summary` | `region_id: str` | `dict` | 返回区域统计摘要 |
|
|
181
|
+
| `match_element` | 见下表 | `list[dict]` | 按条件筛选元素摘要 |
|
|
182
|
+
| `get_element` | `ref: str` | `Element` | 返回完整元素信息 |
|
|
183
|
+
| `find_element` | `by, value, filter` | `Element \| None` | 多维度精确定位 |
|
|
184
|
+
|
|
185
|
+
**match_element 参数**:
|
|
186
|
+
|
|
187
|
+
| 参数 | 类型 | 说明 |
|
|
188
|
+
|------|------|------|
|
|
189
|
+
| `region_id` | `str \| list[str] \| None` | 搜索区域,可单选/多选/全选 |
|
|
190
|
+
| `query` | `str \| None` | 关键词搜索 |
|
|
191
|
+
| `tag` | `str \| None` | HTML 标签筛选 |
|
|
192
|
+
| `role` | `str \| None` | ARIA role 筛选 |
|
|
193
|
+
| `text_contains` | `str \| None` | 文字包含筛选 |
|
|
194
|
+
| `limit` | `int = 20` | 最多返回数量 |
|
|
195
|
+
| `offset` | `int = 0` | 分页偏移 |
|
|
196
|
+
|
|
197
|
+
**返回值"元素摘要"结构**:
|
|
198
|
+
```python
|
|
199
|
+
{"ref": "r1", "text": "文章标题", "attrs_preview": {"href": "/article/1"}}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**get_region_summary 返回值结构**:
|
|
203
|
+
```python
|
|
204
|
+
{
|
|
205
|
+
"count": 347,
|
|
206
|
+
"tag_counts": {"a": 280, "button": 23, "input": 12},
|
|
207
|
+
"role_counts": {"link": 280, "button": 23},
|
|
208
|
+
"text_preview": [{"tag": "a", "text": "标题1", "ref": "r1"}, ...]
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### Browse — 主入口
|
|
215
|
+
|
|
216
|
+
**职责**:组合 TabManager、ElementOperator、PageScanner,提供统一入口
|
|
217
|
+
|
|
218
|
+
**方法**:
|
|
219
|
+
|
|
220
|
+
| 方法 | 参数 | 返回 | 说明 |
|
|
221
|
+
|------|------|------|------|
|
|
222
|
+
| `get` | `url: str` | `dict` | 打开 URL,返回 {url, title} |
|
|
223
|
+
| `screenshot` | `path, annotate` | `str` | 截图,可高亮标注区域 |
|
|
224
|
+
| `save_state` | `path: str` | `None` | 保存 cookies + localStorage |
|
|
225
|
+
| `load_state` | `path: str` | `None` | 恢复会话状态 |
|
|
226
|
+
|
|
227
|
+
**AI 调用方式**:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
browser.tabs.new_tab('https://example.com') # TabManager
|
|
231
|
+
browser.page.get_regions() # PageScanner
|
|
232
|
+
browser.act.click('r1') # ElementOperator
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 数据类型
|
|
238
|
+
|
|
239
|
+
### Region(页面区块)
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
Region(
|
|
243
|
+
id="main", # 区块 ID
|
|
244
|
+
label="主内容", # 人类可读名称
|
|
245
|
+
node_count=347 # 可交互元素数量
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Element(可交互元素)
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
Element(
|
|
253
|
+
ref="r1", # 内部引用 ID
|
|
254
|
+
tag="a", # HTML 标签
|
|
255
|
+
role="link", # ARIA role
|
|
256
|
+
text="文章标题", # 显示文字
|
|
257
|
+
xpath="/html/body/div[2]/a", # 元素路径
|
|
258
|
+
attrs={"href": "/article/1", "target": "_blank"}
|
|
259
|
+
)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 返回值 dict
|
|
263
|
+
|
|
264
|
+
| 方法 | 返回结构 |
|
|
265
|
+
|------|---------|
|
|
266
|
+
| `get` | `{url, title}` |
|
|
267
|
+
| `screenshot` | 文件路径或 base64 字符串 |
|
|
268
|
+
| `click` | `{success, new_url, error}` |
|
|
269
|
+
| `input_by_ref` | `{success, error}` |
|
|
270
|
+
| `get_region_summary` | `{count, tag_counts, role_counts, text_preview}` |
|
|
271
|
+
| `match_element` | `[{ref, text, attrs_preview}, ...]` |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 目录结构
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
src/RootBrowse/src/rootbrowse/
|
|
279
|
+
├── __init__.py # 包导出
|
|
280
|
+
├── _version.py # 版本信息
|
|
281
|
+
├── browser.py # Browse 主类
|
|
282
|
+
├── tab_manager.py # TabManager 类
|
|
283
|
+
├── element_operator.py # ElementOperator 类
|
|
284
|
+
├── page_scanner.py # PageScanner 类
|
|
285
|
+
├── types.py # Region, Element 数据类
|
|
286
|
+
├── constants.py # 可交互标签列表等常量
|
|
287
|
+
└── exceptions.py # 自定义异常
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 技术选型
|
|
293
|
+
|
|
294
|
+
- **底层引擎**: DrissionPage(Python CDP 连接,无需驱动)
|
|
295
|
+
- **MCP 框架**: FastMCP(官方 Python MCP SDK)
|
|
296
|
+
- **Python 版本**: >= 3.10
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## 开发状态
|
|
301
|
+
|
|
302
|
+
**当前阶段:实现完成**
|
|
303
|
+
|
|
304
|
+
- [x] project_tree.json — 类结构定义(JSON Schema)
|
|
305
|
+
- [x] types.py — Region, Element 数据类
|
|
306
|
+
- [x] constants.py — 可交互标签列表
|
|
307
|
+
- [x] exceptions.py — 自定义异常
|
|
308
|
+
- [x] tab_manager.py — TabManager 实现
|
|
309
|
+
- [x] element_operator.py — ElementOperator 实现
|
|
310
|
+
- [x] page_scanner.py — PageScanner 实现
|
|
311
|
+
- [x] browser.py — Browse 主类实现
|
|
312
|
+
- [x] _version.py — 版本信息
|
|
313
|
+
- [x] __init__.py — 包导出
|