# 浏览器自动化技术文档 > agent 框架的浏览器操作模块:会话管理、工具体系、内容提取、Cookie 管理。 --- ## 目录 1. [整体架构](#整体架构) 2. [浏览器会话管理](#浏览器会话管理) 3. [工具体系](#工具体系) 4. [结果转换机制](#结果转换机制) 5. [URL 清洗](#url-清洗) 6. [文件存储](#文件存储) 7. [Skill 集成](#skill-集成) --- ## 整体架构 浏览器操作的核心实现位于 `agent/tools/builtin/browser/`,采用适配器模式将第三方库 [browser-use](https://github.com/browser-use/browser-use) 的能力封装为 agent 框架的标准工具。 ``` agent/tools/builtin/browser/ ├── __init__.py # 统一导出所有浏览器工具 ├── baseClass.py # 核心实现(~2200行):会话管理 + 27个工具函数 └── sync_mysql_help.py # 同步 MySQL 辅助类(Cookie 查询) agent/skill/skills/ └── browser.md # 浏览器工具使用指南(Skill 注入到 LLM system prompt) ``` 关键依赖关系: ``` agent 框架 (@tool 装饰器, ToolResult) ↓ 适配 browser-use (BrowserSession, Tools, ActionResult) ↓ 底层 Chrome DevTools Protocol (CDP) ``` 不直接依赖 Playwright,完全基于 CDP 协议与浏览器通信。 --- ## 浏览器会话管理 ### 全局单例模式 使用模块级全局变量维护唯一的浏览器会话,避免重复创建/销毁浏览器实例: ```python # baseClass.py:98-101 _browser_session: Optional[BrowserSession] = None _browser_tools: Optional[Tools] = None _file_system: Optional[FileSystem] = None _last_browser_type: str = "local" _last_headless: bool = True _live_url: Optional[str] = None ``` ### 三种浏览器模式 通过 `init_browser_session(browser_type=...)` 初始化,支持三种运行模式: | 模式 | browser_type | 底层实现 | 适用场景 | |------|-------------|---------|---------| | 本地浏览器 | `"local"` | 启动本地 Chrome,通过 `user_data_dir` 持久化 profile | 开发调试,速度最快 | | 云浏览器 | `"cloud"` | 连接 browser-use 云服务,通过 `cdp_url` 远程控制 | 生产环境,不占本地资源 | | 容器浏览器 | `"container"` | 调用远程 API 创建 Docker 容器,内含 Chrome,通过 CDP 连接 | 隔离性好,支持预配置账户 | 初始化流程(`baseClass.py:230-322`): ``` init_browser_session() ├─ local: 检测 macOS Chrome 路径 → 创建 user_data_dir → BrowserSession(is_local=True) ├─ cloud: BrowserSession(use_cloud=True) → 解析 cdp_url 生成 live_url └─ container: create_container() API → 等待浏览器启动 → BrowserSession(cdp_url=...) ``` ### 容器创建流程 容器模式通过 HTTP API 与远程容器管理服务交互(`baseClass.py:105-228`): ``` 步骤 1.1: POST /api/v1/container/create → 返回 container_id, vnc, cdp 地址 → 等待 5 秒让容器内浏览器启动 步骤 1.2: POST /api/v1/browser/page/create → 传入 container_id, url, account_name → 返回 connection_id → 内置重试机制(最多 3 次) ``` ### 会话健康检查与自动恢复 `get_browser_session()`(`baseClass.py:330-372`)在每次工具调用前检查 CDP 连接是否存活: ```python # 通过 CDP 执行 Runtime.evaluate('1+1') 探测连接 cdp_session = await _browser_session.get_or_create_cdp_session() await asyncio.wait_for( cdp_session.cdp_client.send.Runtime.evaluate( params={'expression': '1+1'}, session_id=cdp_session.session_id ), timeout=3.0, ) ``` 如果连接断开(WebSocket 超时等),自动 cleanup 并重新初始化。 ### 会话生命周期 ``` init_browser_session() → 创建会话(幂等,已存在则直接返回) get_browser_session() → 获取会话(自动健康检查 + 重连) cleanup_browser_session() → 优雅停止(session.stop()) kill_browser_session() → 强制终止(session.kill()) ``` --- ## 工具体系 所有工具通过 `@tool()` 装饰器注册到 agent 框架的 `ToolRegistry`,LLM 可直接调用。共 27 个工具,分为 8 类。 ### 导航类(Navigation) | 工具 | 功能 | 底层调用 | |------|------|---------| | `browser_navigate_to_url(url, new_tab)` | 导航到 URL | `tools.navigate()` | | `browser_search_web(query, engine)` | 搜索引擎搜索(支持 bing/google/duckduckgo) | `tools.search()` | | `browser_go_back()` | 浏览器后退 | `tools.go_back()` | | `browser_wait(seconds)` | 等待指定秒数 | `tools.wait()` | | `browser_get_live_url()` | 获取云浏览器实时画面链接 | 读取全局 `_live_url` | ### 元素交互类(Interaction) | 工具 | 功能 | 特殊处理 | |------|------|---------| | `browser_click_element(index)` | 点击元素 | 挂载日志监听器自动捕获下载链接 | | `browser_input_text(index, text, clear)` | 输入文本 | 支持清除已有内容 | | `browser_send_keys(keys)` | 发送键盘按键/快捷键 | 支持组合键如 `Control+A` | | `browser_upload_file(index, path)` | 上传文件 | 需要绝对路径 | `browser_click_element` 的下载链接捕获机制(`baseClass.py:711-846`): ```python # 1. 挂载自定义 logging.Handler 到 browser_use 命名空间 capture_handler = DownloadLinkCaptureHandler() logger = logging.getLogger("browser_use") logger.addHandler(capture_handler) # 2. 执行点击 result = await tools.click(index=index, browser_session=browser) # 3. 检查是否捕获到下载链接(通过正则匹配日志中的 URL) if capture_handler.captured_url: # 将链接注入到 ToolResult.output,LLM 可以看到并决定下一步 ``` ### 滚动与视图类(Scroll & View) | 工具 | 功能 | 特殊处理 | |------|------|---------| | `browser_scroll_page(down, pages, index)` | 滚动页面 | 通过 CDP 检测 scrollY 变化,判断是否到达边界;限制单次最大 10 页 | | `browser_find_text(text)` | 查找文本并滚动到位 | `tools.find_text()` | | `browser_screenshot()` | 截图 | `tools.screenshot()` | | `browser_get_visual_selector_map()` | 获取带元素索引标注的截图 + 交互元素列表 | 使用 `create_highlighted_screenshot_async` 生成标注图 | | `browser_get_selector_map()` | 获取交互元素索引映射(纯文本) | 通过 `BrowserStateRequestEvent` 触发 DOM 更新 | `browser_get_visual_selector_map`(`baseClass.py:1066-1161`)是最核心的观察工具: ``` 1. 触发 BrowserStateRequestEvent(include_dom=True, include_screenshot=True) 2. 等待浏览器返回完整状态(DOM 树 + 截图) 3. 从 DOM 状态提取 selector_map(所有可交互元素的索引) 4. 调用 create_highlighted_screenshot_async 在截图上标注元素索引号 5. 构建元素列表(tag, aria-label, href, type, role 等属性) 6. 返回 ToolResult(images 字段含标注截图,output 含元素列表) ``` ### 标签页管理类(Tab) | 工具 | 功能 | |------|------| | `browser_switch_tab(tab_id)` | 切换标签页(4 字符 ID) | | `browser_close_tab(tab_id)` | 关闭标签页 | ### 下拉框类(Dropdown) | 工具 | 功能 | |------|------| | `browser_get_dropdown_options(index)` | 获取下拉框选项 | | `browser_select_dropdown_option(index, text)` | 选择下拉框选项 | ### 内容提取类(Content Extraction) | 工具 | 功能 | 底层 | |------|------|------| | `browser_extract_content(query, extract_links)` | LLM 驱动的结构化数据提取 | `tools.extract()` + Qwen LLM | | `browser_read_long_content(goal, source)` | 智能长内容读取(自动检测 PDF) | `tools.read_long_content()` | | `browser_get_page_html()` | 获取完整 HTML | CDP `Runtime.evaluate` | | `browser_download_direct_url(url, save_name)` | HTTP 直链下载 | `httpx.AsyncClient` 流式下载 | #### 内容提取的 LLM 适配 `extraction_adapter`(`baseClass.py:1387-1409`)将 browser-use 的 LangChain Runnable 接口适配为 Qwen LLM 调用: ```python async def extraction_adapter(input_data): response = await qwen_llm_call(messages=[{"role": "user", "content": prompt}]) content = response["content"] # 自动清洗搜索引擎重定向 URL(Bing Base64 解码、Google url 参数提取) urls = re.findall(r'https?://[^\s<>"\']+', content) for original_url in urls: clean_url = scrub_search_redirect_url(original_url) if clean_url != original_url: content = content.replace(original_url, clean_url) return Namespace(completion=content) ``` #### PDF 自动检测与下载 `_detect_and_download_pdf_via_cdp`(`baseClass.py:1458-1550`): ``` 1. 检查 URL 是否以 .pdf 结尾 2. 如果不明显,通过 CDP 检查 document.contentType 3. 确认是 PDF 后,通过浏览器内 fetch API 下载(自动携带 cookies/session) 4. 将 data URL 中的 base64 解码为 PDF 文件保存到本地 5. 将 source 参数改为本地文件路径,交给 pypdf 解析 ``` ### Cookie 管理类 | 工具 | 功能 | |------|------| | `browser_export_cookies(name, account)` | 导出当前域名 Cookie 到 `.cache/.cookies/` | | `browser_load_cookies(url, name)` | 自动匹配 Cookie 文件并注入浏览器 | | `browser_ensure_login_with_cookies(cookie_type, url)` | 检查登录状态,需要时从 MySQL 查询 Cookie 注入 | #### Cookie 加载匹配策略 `browser_load_cookies`(`baseClass.py:2046-2177`): ``` 1. 精确匹配:{domain}.json(如 xiaohongshu.com.json) 2. 前缀匹配:{domain}*.json 3. 模糊匹配:{主域名}*.json(如 xiaohongshu*.json) 4. 未找到时:根据 auto_navigate 参数决定是否直接导航到目标页面 ``` #### 从 MySQL 加载 Cookie `browser_ensure_login_with_cookies` 流程: ``` 1. 导航到目标 URL 2. 执行 JS 检测登录状态(查找登录按钮/用户头像) 3. 如果需要登录: a. 从 agent_channel_cookies 表查询 Cookie b. 解析 Cookie(支持 JSON 数组、JSON 对象、分号分隔字符串) c. 通过 CDP _cdp_set_cookies 注入 d. 刷新页面 ``` ### 控制流类 | 工具 | 功能 | |------|------| | `browser_evaluate(code)` | 在页面执行任意 JavaScript | | `browser_wait_for_user_action(message, timeout)` | 暂停等待用户手动操作(如验证码) | | `browser_done(text, success)` | 标记任务完成 | --- ## 结果转换机制 所有工具的返回值统一为 agent 框架的 `ToolResult`,通过 `action_result_to_tool_result()` 将 browser-use 的 `ActionResult` 转换: ```python # baseClass.py:407-431 def action_result_to_tool_result(result: ActionResult, title: str = None) -> ToolResult: if result.error: return ToolResult(title=..., output="", error=result.error, long_term_memory=result.long_term_memory or result.error) return ToolResult(title=..., output=result.extracted_content or "", long_term_memory=..., metadata=result.metadata or {}) ``` `ToolResult` 支持双层记忆管理(`agent/tools/models.py`): - `output`:完整内容,可配置 `include_output_only_once=True` 只给 LLM 看一次 - `long_term_memory`:简短摘要,永久保留在对话历史中 - `images`:截图等图片数据(base64) --- ## URL 清洗 `scrub_search_redirect_url()`(`baseClass.py:1347-1385`)自动解析搜索引擎重定向链接: | 引擎 | 处理方式 | |------|---------| | Bing | 提取 `u` 参数,去掉 `a1` 前缀,Base64 解码 | | Google | 提取 `url` 参数,URL 解码 | | 通用 | 检查 `target`/`dest`/`destination`/`link` 参数 | --- ## 文件存储 所有浏览器产生的文件统一存储在工作目录下: ``` .cache/ ├── .browser_use_files/ # 浏览器下载、截图、PDF 等临时文件 └── .cookies/ # Cookie 持久化文件({domain}_{account}.json) ``` --- ## Skill 集成 `agent/skill/skills/browser.md` 作为 Skill 注入到 LLM 的 system prompt,指导 LLM 正确使用浏览器工具。核心规则: 1. 操作前必须先通过 `browser_get_visual_selector_map` 获取元素索引 2. 任何触发页面变化的操作后都要 `browser_wait` 3. 登录优先用 `browser_load_cookies`,首次登录需请求人类协助 4. 优先使用高级提取工具(`extract_content`/`read_long_content`)而非手动解析 DOM