9.1 KiB
9.1 KiB
| name | description | tags | version | created | ||||
|---|---|---|---|---|---|---|---|---|
| browser-automation | 补充浏览器自动化缺失能力 — 等待元素、文件上传、多标签页、下载拦截 |
|
1 | 2026-06-03 |
Browser Automation Extensions
补充内置浏览器工具缺失的高级能力。
1. 等待元素 (wait_for_element)
内置工具无显式等待,用轮询实现:
from hermes_tools import terminal
import time
def wait_for_element(selector, timeout=10, poll_interval=0.5):
"""等待元素出现在DOM中"""
start = time.time()
while time.time() - start < timeout:
result = terminal(f"""
node -e "
const el = document.querySelector('{selector}');
console.log(el ? 'found' : 'not_found');
"
""")
if 'found' in result.get('output', ''):
return True
time.sleep(poll_interval)
return False
# 使用示例
if wait_for_element('.loading-spinner', timeout=15):
# 元素出现后继续操作
pass
适用场景: SPA动态加载、异步渲染完成后操作
2. 文件上传 (upload_file)
通过CDP的DOM.setFileInputFiles实现:
from hermes_tools import terminal
def upload_file(selector, file_path):
"""上传文件到input[type=file]"""
# 1. 获取元素的backendNodeId
node_info = terminal(f"""
node -e "
const el = document.querySelector('{selector}');
if (!el) {{ console.log('ERROR: element not found'); process.exit(1); }}
// CDP会注入__cdpBinding
if (typeof __cdpBinding !== 'undefined') {{
__cdpBinding.getNodeId(el).then(id => console.log(id));
}} else {{
console.log('CDP_NOT_AVAILABLE');
}}
"
""")
# 2. 通过browser_cdp直接调用
# 需要先获取target_id,然后调用DOM.setFileInputFiles
terminal(f"""
hermes browser cdp DOM.setFileInputFiles \\
--params '{{"files": ["{file_path}"], "backendNodeId": NODE_ID}}'
""")
简化版 (直接用browser_cdp工具):
# 假设已通过browser_snapshot获取input元素的ref
# 用browser_console获取backendNodeId
result = browser_console(expression="""
(function() {
const el = document.querySelector('input[type=file]');
return el ? el.getAttribute('data-node-id') || 'need_cdp' : null;
})()
""")
# 然后调用browser_cdp
browser_cdp(
method='DOM.setFileInputFiles',
params={'files': ['/path/to/file.pdf'], 'backendNodeId': 12345}
)
注意: 需要先从DOM获取backendNodeId,可通过DOM.getDocument + DOM.querySelector链式调用
3. 多标签页管理 (manage_tabs)
通过CDP的Target API:
from hermes_tools import terminal
def list_tabs():
"""列出所有标签页"""
result = terminal("hermes browser cdp Target.getTargets --params '{}'")
# 解析JSON获取target列表
return result
def switch_tab(target_id):
"""切换到指定标签页"""
terminal(f"hermes browser cdp Target.attachToTarget --params '{{\"targetId\": \"{target_id}\"}}'")
def create_tab(url):
"""创建新标签页"""
result = terminal(f"hermes browser cdp Target.createTarget --params '{{\"url\": \"{url}\"}}'")
# 返回targetId
return result
# 使用示例
tabs = list_tabs()
# 解析tabs获取target_id
switch_tab('TARGET_ID_HERE')
直接用browser_cdp:
# 列出所有标签
targets = browser_cdp(method='Target.getTargets', params={})
# 创建新标签
new_tab = browser_cdp(method='Target.createTarget', params={'url': 'https://example.com'})
target_id = new_tab['targetId']
# 在指定标签执行JS
browser_cdp(
method='Runtime.evaluate',
params={'expression': 'document.title', 'returnByValue': True},
target_id=target_id
)
4. 下载拦截 (download_config)
通过CDP配置下载行为:
def config_download(download_path, allow=True):
"""配置下载目录和行为"""
browser_cdp(
method='Page.setDownloadBehavior',
params={
'behavior': 'allow' if allow else 'deny',
'downloadPath': download_path
}
)
# 使用示例
config_download('/tmp/downloads', allow=True)
# 然后点击下载链接,文件会保存到指定目录
# 可通过terminal检查文件是否存在
等待下载完成:
import os
import time
def wait_for_download(filename, timeout=30):
"""等待下载完成"""
download_path = '/tmp/downloads'
filepath = os.path.join(download_path, filename)
start = time.time()
while time.time() - start < timeout:
if os.path.exists(filepath) and not os.path.exists(filepath + '.crdownload'):
return filepath
time.sleep(0.5)
return None
5. 简单验证码识别 (captcha_ocr)
用vision_analyze识别简单验证码图片:
def solve_simple_captcha(image_selector):
"""识别简单验证码(数字/字母)"""
# 1. 获取图片URL或base64
img_info = browser_console(expression=f"""
(function() {{
const img = document.querySelector('{image_selector}');
return img ? img.src : null;
}})()
""")
# 2. 用vision分析
result = vision_analyze(
image_url=img_info,
question="识别这个验证码图片中的字符,只返回字符本身,不要其他文字"
)
# 3. 提取识别结果(需要从result解析)
return result # 如 "a3Bx"
# 使用示例
captcha_text = solve_simple_captcha('img.captcha')
browser_type(ref='@e15', text=captcha_text)
局限: 复杂验证码(滑块、点选、reCAPTCHA)无法可靠解决
6. 网络请求拦截 (network_intercept)
通过CDP拦截和修改网络请求:
def enable_network_intercept():
"""启用网络拦截"""
browser_cdp(method='Network.enable', params={})
browser_cdp(
method='Network.setRequestInterception',
params={'patterns': [{'urlPattern': '*'}]}
)
def mock_api_response(url_pattern, response_data):
"""Mock API响应"""
# 需要监听Network.requestIntercepted事件
# 然后用Network.continueInterceptedRequest返回mock数据
pass # 复杂场景,通常需要配合脚本
# 简化版:直接修改页面fetch/XHR
browser_console(expression="""
const originalFetch = window.fetch;
window.fetch = function(url, opts) {
if (url.includes('/api/user')) {
return Promise.resolve({
json: () => Promise.resolve({name: 'Mock User', id: 123})
});
}
return originalFetch(url, opts);
};
""")
使用模式
场景1: SPA表单提交后等待结果
browser_click(ref='@e10') # 点击提交
wait_for_element('.success-message', timeout=10)
browser_snapshot() # 获取结果
场景2: 多步骤流程跨标签页
# 主标签操作
browser_navigate('https://app.com/dashboard')
browser_click(ref='@e5') # 打开新标签的链接
# 获取新标签
targets = browser_cdp(method='Target.getTargets', params={})
new_tab_id = [t['targetId'] for t in targets['targetInfos'] if t['url'].endswith('/details')][0]
# 在新标签操作
browser_cdp(
method='Runtime.evaluate',
params={'expression': 'document.querySelector(".detail").innerText', 'returnByValue': True},
target_id=new_tab_id
)
场景3: 文件上传+下载
config_download('/tmp/exports')
upload_file('input[type=file]', '/path/to/data.csv')
browser_click(ref='@e20') # 点击处理按钮
wait_for_download('result.xlsx', timeout=60)
7. Bricks 框架兼容性
bricks widget 的 click 事件兼容 browser_click。 已验证通过(2026-08-07,pipeline.opencomputing.cn)。
bricks 通过 widget.bind('click', handler) → dom_element.addEventListener('click', handler) 绑定事件,browser_click 通过 CDP Input.dispatchMouseEvent 触发浏览器原生 click 事件合成,addEventListener 回调正常触发。
| 操作 | 结果 |
|---|---|
| browser_click Menu 项 | ✅ 触发 regen_menuitem_event() |
| browser_click toggle 按钮 | ✅ 触发 idset= sidebar_menu |
| browser_console 查日志 | ✅ 完整捕获 |
注意事项:
- 页面 JS 完全加载后才可点击(约 2-3s),否则点击无响应
- snapshot 中 bricks widget 的文本标签可能显示为 "generic",需
browser_console查 DOM 确认
注意事项
- CDP需要活跃连接 - 确保browser已连接(/browser connect或配置browser.cdp_url)
- backendNodeId获取 - 文件上传需要先通过DOM.getDocument获取节点ID
- 多标签页状态隔离 - 每个标签页有独立的JS上下文,cookie/session共享但DOM隔离
- 下载路径权限 - 确保download_path目录存在且有写权限
- 验证码识别准确率 - 简单验证码~80-90%,复杂验证码<50%
调试技巧
# 查看当前CDP连接状态
browser_cdp(method='Browser.getVersion', params={})
# 列出所有可用CDP方法
# 参考: https://chromedevtools.github.io/devtools-protocol/
# 检查网络请求
browser_cdp(method='Network.enable', params={})
# 后续请求会触发事件,可通过browser_console监听