playwright 安装:
pip install playwright --break-system-packages
→ Successfully installed greenlet-3.3.2 playwright-1.58.0 pyee-13.0.1
Chromium 下载:
playwright install chromium
→ Chrome for Testing 145.0.7632.6 (playwright chromium v1208) downloaded to /root/.cache/ms-playwright/chromium-1208
→ Chrome Headless Shell 145.0.7632.6 (playwright chromium-headless-shell v1208) downloaded to /root/.cache/ms-playwright/chromium_headless_shell-1208
→ FFmpeg (playwright ffmpeg v1011) downloaded to /root/.cache/ms-playwright/ffmpeg-1011
修改文件:/root/.openclaw/workspace/scripts/web_fetch.py
新增内容:
1. --js flag(argparse)
2. fetch_with_playwright(url, proxy=None, timeout=30) 函数:
- launch(headless=True, args=["--no-sandbox", "--disable-setuid-sandbox"]) — root 必须
- browser.new_context(proxy={"server": proxy}) — Context 级别代理
- page.goto(url, wait_until="networkidle") — 等待 JS 渲染完成
- with sync_playwright() 确保资源按需启动/关闭
3. main() 中按 --js 分支:有 --proxy 直接轮换,无 --proxy 先直连再 fallback 代理
4. 无 --js 时:现有 requests 逻辑完全不变
测试1:静态页面(无 --js),验证现有功能不受影响
python3 web_fetch.py https://example.com --mode text --max-chars 500
结果:
# Example Domain
Example Domain This domain is for use in documentation examples without needing permission. Avoid use in operations. Learn more
EXIT:0
✅ 通过
测试2:--js 直连渲染
python3 web_fetch.py https://example.com --js --mode text --max-chars 500
结果:
[js+direct]
# Example Domain
Example Domain This domain is for use in documentation examples without needing permission. Avoid use in operations. Learn more
EXIT:0
✅ 通过
测试3:--js + --proxy 组合
python3 web_fetch.py https://example.com --js --proxy --mode text --max-chars 500
结果:
[js+proxy via socks5://127.0.0.1:50003]
# Example Domain
Example Domain This domain is for use in documentation examples without needing permission. Avoid use in operations. Learn more
EXIT:0
✅ 通过
| 组合 | 行为 | 状态 |
|---|---|---|
| 无 --js,无 --proxy | requests 直连 → fallback 代理 | ✅ 不变 |
| 无 --js,有 --proxy | requests 代理轮换 | ✅ 不变 |
| 有 --js,无 --proxy | Playwright 直连 → fallback 代理 | ✅ 新增 |
| 有 --js,有 --proxy | Playwright 代理轮换 | ✅ 新增 |
审计时间: 2026-02-23
审计人: Ai.Rev
web_fetch.py 源码要点1:4种组合(--js × --proxy)是否都正确处理
| 组合 | 代码路径 | 实测结果 |
|---|---|---|
| 无js 无proxy | 直连→fallback代理(requests) | ✅ 通过 |
| 无js 有proxy | 代理轮换(requests) | ✅ 通过(代码逻辑正确) |
| 有js 无proxy | 直连→fallback代理(Playwright) | ✅ 实测通过 |
| 有js 有proxy | 代理轮换(Playwright) | ✅ 实测通过 |
要点2:--no-sandbox 是否传给 Playwright
✅ 代码第55-56行:launch_args = ["--no-sandbox", "--disable-setuid-sandbox"],且 p.chromium.launch(headless=True, args=launch_args)。额外加了 --disable-setuid-sandbox,对 root 运行更稳健。
要点3:代理是否用 Context 级别传入
✅ 代码第58-61行:context_options["proxy"] = {"server": proxy},通过 browser.new_context(**context_options) 传入,不是 Browser 级别。完全符合要求。
要点4:现有 requests 逻辑是否完好
✅ --js 分支完全独立,非 --js 路径代码未变动。实测静态页面通过。
要点5:异常处理是否合理
✅ 实测错误场景(无效域名):
- RC=1,STDOUT 为空,错误信息写入 STDERR
- 错误信息包含具体的失败原因(每个代理各自的异常)
- 用户可通过 RC 和 STDERR 判断失败原因
fetch_with_playwright 内部使用 try/finally 确保 browser.close() 必定执行;外层 with sync_playwright() 确保 Playwright 进程清理。资源管理正确。
要点6:依赖安装方式
✅ pip install playwright --break-system-packages 与现有脚本环境一致(其他依赖如 readability 也用此方式)。Chromium 安装到 ~/.cache/ms-playwright/,不污染系统包。
[低] [js+direct] / [js+proxy via ...] 状态信息写入 STDERR,与原有 requests 的 [via proxy] 风格一致,无问题。但 session.md 测试输出中这些信息显示在内容前面,实际上它们是 STDERR——这只是 coder 合并 stderr+stdout 展示的记录方式,代码本身无误。
[低] --proxy 模式下(有--js),若所有代理均失败,错误信息写到了 sys.stderr,但代码写的是:
print(f"ERROR: Playwright 所有方式均失败\n" + "\n".join(errors), file=sys.stderr)
实测确认 STDOUT 为空,STDERR 有错误信息,RC=1。✅ 正确。
通过,无需修改。
代码实现质量良好:
- 4种组合全部正确处理
- 关键参数(--no-sandbox、Context级别代理)均正确
- 资源管理(browser/playwright 生命周期)正确
- 错误处理规范(RC=1,STDERR输出,STDOUT为空)
- 现有功能完全不受影响