无 GUI Linux 服务器浏览器能力解锁 SOP(Xvfb + Web VNC + Chrome + 手动安装 CDP Bridge)

下载 .md

无 GUI Linux 服务器浏览器能力解锁 SOP(Xvfb + Web VNC + Chrome + 手动安装 CDP Bridge)

适用场景:云服务器、容器、SSH 远程主机等不带桌面 GUI 的 Linux。目标是在服务器上提供一个可通过浏览器访问的虚拟桌面,在里面运行有界面的 Chrome,手动安装 tmwd_cdp_bridge 扩展,再通过 Chrome DevTools Protocol(CDP)/ TMWebDriver 解锁完整浏览器自动化能力。

核心理念:无 GUI Linux → Xvfb 虚拟显示器 → 轻量窗口管理器 → noVNC/Websockify 提供 Web 访问 → GUI Chrome → 手动安装 CDP Bridge 扩展 → CDP/TMWebDriver 自动化。

重点:这里不是 Chrome headless 模式。因为 CDP Bridge 扩展需要在 GUI Chrome 的扩展页中手动安装/确认,所以必须先把“看得见、点得到”的 Chrome GUI 暴露到 Web。


0. 架构与已验证结论

架构

Xvfb虚拟屏幕 (:99)
  ↓
Chrome (--remote-debugging-port=9222, --load-extension=tmwd_cdp_bridge)
  ↓
CDP HTTP接口 (127.0.0.1:9222/json/*)
  ↓
tmwd_cdp_bridge扩展 (WS监听 127.0.0.1:18765)
  ↓
TMWebDriver Python类 (会话管理 + JS注入)

已验证坑点

  • TMWebDriver.get_all_sessions() 可能返回空,但 Chrome 与扩展仍可能已通过 WS 连上;不能只凭会话表为空判失败
  • Chrome DevTools /json/new?... 创建新标签页时,当前环境已验证 GET 会返回 405,应使用 PUT
  • chrome://extensions/?errors=... 里的 ERR_CONNECTION_REFUSED 可能是历史残留;需与端口监听、连接状态、service worker 状态交叉确认。

1. 安装系统组件

Ubuntu/Debian 示例:

sudo apt-get update
sudo apt-get install -y \
  xvfb x11vnc openbox websockify novnc \
  dbus-x11 x11-utils curl wget unzip ca-certificates

Chrome 二选一:

# 优先:Google Chrome
wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt-get install -y ./google-chrome-stable_current_amd64.deb

# 或:Chromium
sudo apt-get install -y chromium-browser || sudo apt-get install -y chromium

检查:

which google-chrome || which chromium || which chromium-browser
which Xvfb
which websockify

2. 启动虚拟桌面 DISPLAY=:99

export DISPLAY=:99

# 若 :99 已占用,改成 :100 并同步修改后续 DISPLAY
Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp &

# 启动窗口管理器,否则 Chrome 窗口可能无法正常移动/聚焦
openbox >/tmp/openbox.log 2>&1 &

# 可选:设置背景,帮助确认桌面已启动
xsetroot -solid '#202020' || true

验证:

echo $DISPLAY                  # :99
xdpyinfo -display :99 | grep dimensions

3. 提供 Web 访问虚拟桌面(noVNC)

3.1 启动 VNC 到 Xvfb

export DISPLAY=:99
x11vnc -display :99 -forever -shared -nopw -rfbport 5900 >/tmp/x11vnc.log 2>&1 &

安全提醒:-nopw 只适合本机隧道或临时内网环境。公网服务器请加 -localhost,再用 SSH 隧道访问,或为 noVNC 加反向代理鉴权。

3.2 启动 noVNC/Websockify

不同发行版 noVNC 路径不同,常见路径:

# Debian/Ubuntu 常见
websockify --web=/usr/share/novnc 6080 localhost:5900 >/tmp/novnc.log 2>&1 &

# 若路径不存在,查找:
dpkg -L novnc | grep 'vnc.html$'

访问:

http://<server-ip>:6080/vnc.html

更安全的 SSH 隧道方式:

ssh -L 6080:127.0.0.1:6080 user@server
# 本地浏览器打开 http://127.0.0.1:6080/vnc.html

验收:浏览器里能看到一个空桌面,后续启动 Chrome 后能看到 Chrome 窗口,并可鼠标键盘操作。


4. 启动 GUI Chrome(不是 headless)

准备独立用户目录,便于保存扩展和登录态:

mkdir -p /tmp/chrome_profile
export DISPLAY=:99

启动:

google-chrome \
  --remote-debugging-address=127.0.0.1 \
  --remote-debugging-port=9222 \
  --user-data-dir=/tmp/chrome_profile \
  --no-first-run \
  --no-default-browser-check \
  --disable-dev-shm-usage \
  --disable-gpu \
  --no-sandbox \
  about:blank >/tmp/chrome.log 2>&1 &

如果命令是 Chromium,请替换成实际二进制:chromiumchromium-browser

验证:

curl -s http://127.0.0.1:9222/json/version | python3 -m json.tool
curl -s http://127.0.0.1:9222/json/list | python3 -m json.tool

在 noVNC 页面中应能看到 Chrome GUI。


5. 准备 CDP Bridge 扩展目录

扩展必须是 Chrome 可加载的解压目录,目录内应有 manifest.json

# 示例:把扩展放到 /opt/tmwd_cdp_bridge
sudo mkdir -p /opt/tmwd_cdp_bridge
sudo chown -R $USER:$USER /opt/tmwd_cdp_bridge

# 如果已有 zip 包:
unzip tmwd_cdp_bridge.zip -d /opt/tmwd_cdp_bridge

# 验证:
test -f /opt/tmwd_cdp_bridge/manifest.json && echo OK

如果解压后多了一层目录,例如 /opt/tmwd_cdp_bridge/tmwd_cdp_bridge/manifest.json,加载时要选择包含 manifest.json 的那一层。


6. 在 Web GUI Chrome 中手动安装 CDP Bridge

这是本 SOP 的关键步骤:通过 noVNC 打开的 Chrome GUI 手动安装扩展

  1. 在 noVNC 页面里操作 Chrome。
  2. 地址栏打开:
    chrome://extensions/
    
  3. 右上角打开 Developer mode / 开发者模式
  4. 点击 Load unpacked / 加载已解压的扩展程序
  5. 在文件选择器中选择 CDP Bridge 扩展目录,例如:
    /opt/tmwd_cdp_bridge
    
    必须选择含 manifest.json 的目录。
  6. 扩展出现后,确认没有红色错误。必要时点 Details / 详细信息,允许所需权限。
  7. 如扩展需要保持 service worker 活跃,至少保留一个普通页面标签页,例如 about:blank 或目标网页。

安装后验证:

# CDP 列表里应能看到扩展 service_worker/background
curl -s http://127.0.0.1:9222/json/list | grep -E 'chrome-extension://|service_worker|background' || true

# CDP Bridge 常见监听端口
ss -ltnp | grep 18765 || true
ss -tnp  | grep 18765 || true

判定:

  • /json/listtype: service_worker 且 URL 为 chrome-extension://.../background...:扩展已被 Chrome 加载。
  • 127.0.0.1:18765 有监听:bridge 后端/扩展通信端口就绪。
  • ESTAB 连接:Chrome 与 bridge 通信通常已通。

7. 可选:用命令行预加载扩展,但仍以手动确认为准

有时可在启动 Chrome 时加:

--load-extension=/opt/tmwd_cdp_bridge

完整示例:

google-chrome \
  --remote-debugging-address=127.0.0.1 \
  --remote-debugging-port=9222 \
  --load-extension=/opt/tmwd_cdp_bridge \
  --user-data-dir=/tmp/chrome_profile \
  --no-first-run --no-default-browser-check \
  --disable-dev-shm-usage --disable-gpu --no-sandbox \
  about:blank &

但不要只依赖它。实际验收仍应通过 Web GUI 打开 chrome://extensions/,确认扩展已加载、无错误、权限已允许。


8. CDP/TMWebDriver 能力验证

curl -s http://127.0.0.1:9222/json/list | python3 -m json.tool

Python 验证:

from TMWebDriver import TMWebDriver
wd = TMWebDriver()
sessions = wd.get_all_sessions()
print("Sessions:", sessions)

# 注意:sessions 为空不代表桥断开;继续用 CDP /json/list、service_worker、18765 端口交叉确认

9. 浏览器能力解锁

A. 搜索任务(优先 Google)

import urllib.request, urllib.parse, json, time

# 方案1:TMWebDriver 会话可用时
from TMWebDriver import TMWebDriver
wd = TMWebDriver()
sessions = wd.get_all_sessions()
if sessions:
    wd.jump("https://www.google.com/search?q=" + urllib.parse.quote("广州天气"))
else:
    # 方案2:CDP 兜底(会话为空时)
    target = "https://www.google.com/search?q=" + urllib.parse.quote("广州天气")
    req = urllib.request.Request(
        "http://127.0.0.1:9222/json/new?" + urllib.parse.quote(target, safe=":/?=&"),
        method="PUT",  # 必须用 PUT,GET 会 405
    )
    result = urllib.request.urlopen(req, timeout=10).read().decode()
    print("Created tab:", result)
    
    # 验收:检查标签页是否已打开
    time.sleep(2)
    tabs = json.loads(urllib.request.urlopen("http://127.0.0.1:9222/json/list", timeout=10).read())
    for t in tabs:
        if t.get("type") == "page" and "广州天气" in t.get("title", ""):
            print("✓ 搜索页已打开:", t.get("title"))

B. 文件上传(CDP batch)

# 通过 web_execute_js 调用 tmwd_cdp_bridge 扩展
script = {
    "cmd": "batch",
    "commands": [
        {"cmd": "cdp", "method": "DOM.getDocument", "params": {"depth": 1}},
        {"cmd": "cdp", "method": "DOM.querySelector", "params": {
            "nodeId": "$0.root.nodeId",
            "selector": "input[type=file]"
        }},
        {"cmd": "cdp", "method": "DOM.setFileInputFiles", "params": {
            "nodeId": "$1.nodeId",
            "files": ["/absolute/path/to/file.pdf"]
        }}
    ]
}

# 使用 web_execute_js 工具执行
# web_execute_js(script=json.dumps(script))

C. 验证码识别(CDP 截图)

script = {
    "cmd": "cdp",
    "method": "Page.captureScreenshot",
    "params": {"format": "png"}
}

# 返回 base64 编码的 PNG 图片
# result = web_execute_js(script=json.dumps(script))
# image_data = result['data']  # base64 字符串

D. 跨域 iframe 操作

script = {
    "cmd": "batch",
    "commands": [
        {"cmd": "cdp", "method": "Page.getFrameTree"},
        {"cmd": "cdp", "method": "Page.createIsolatedWorld", "params": {
            "frameId": "$0.frameTree.childFrames[0].frame.id"
        }},
        {"cmd": "cdp", "method": "Runtime.evaluate", "params": {
            "expression": "document.querySelector('button').click()",
            "contextId": "$1.executionContextId"
        }}
    ]
}

10. 扩展报错排查顺序

当遇到 ERR_CONNECTION_REFUSED 或其他错误时:

  1. 查监听ss -ltnp | grep 18765
  2. 查连接ss -tnp | grep 18765
  3. 查 Chrome 调试页curl http://127.0.0.1:9222/json/list
  4. 查 service worker:列表中应有 chrome-extension://.../background.js
  5. 只有上述均异常,才考虑重启扩展或 Chrome

11. 禁忌

  • ❌ 禁止把 /json/new 的 405 当作网络错误;先换 PUT
  • ❌ 禁止只凭 get_all_sessions()==[] 判定桥断开
  • ❌ 禁止只凭 chrome://extensions/?errors=... 的旧错误页判定当前失败
  • ❌ 禁止无新信息重复重启;每次重启前至少确认端口/连接/worker 三者之一异常
  • ❌ 禁止在 Windows 环境使用 Xvfb(Windows 用 --headless=new 或物理显示器)

12. 验收标准

  • ✅ Xvfb 进程存在,$DISPLAY 正确
  • ✅ Chrome 进程存在,--remote-debugging-port=9222 参数在
  • /json/list 能看到 page 和 service_worker
  • 127.0.0.1:18765 有监听和 ESTAB 连接
  • ✅ 搜索任务:标题形如 关键词 - Google 搜索
  • ✅ 文件上传:input 元素 files 属性已填充
  • ✅ 验证码识别:返回有效的 base64 图片数据

13. 常见问题

Q1: Chrome 启动后立即退出

A: 检查 --user-data-dir 是否有写权限,或改用 /tmp/chrome_profile_$(date +%s)

Q2: 扩展未加载

A: 确认 --load-extension 路径是绝对路径,且目录下有 manifest.json

Q3: CDP 连不上

A: 检查防火墙是否拦截 9222 端口,或 Chrome 是否用了 --remote-debugging-pipe(与 --remote-debugging-port 冲突)

Q4: TMWebDriver 会话一直为空

A: 正常现象,改用 CDP /json/new + PUT 兜底即可

Q5: Xvfb 显示 "Fatal server error"

A: 检查 :99 显示号是否被占用,改用 :100Xvfb :99 -nolisten tcp

评论(0)

登录 后可发表评论。

暂无评论。