Edge 远程调试只认一个客户端:一次浏览器自动化的三个坑
Edge 远程调试只认一个客户端:一次浏览器自动化的三个坑
最近在把一批重复的网页操作交给脚本跑,踩了三个坑,都不是文档里写着的。记下来,省得下次再花两小时。
坑一:TCP 能连上,WebSocket 就是不应答
现象很反直觉。端口在监听,netstat 显示 127.0.0.1:9222 LISTENING,telnet 能连进去。但走 WebSocket 连 /devtools/browser/ 时,服务端一个字节都不回,客户端就一直挂着等握手响应。
手工握手看清楚了些东西——先确认 HTTP 层是活的:
const s = net.createConnection(9222, '127.0.0.1');
s.on('connect', () => {
s.write('GET /json/version HTTP/1.1\r\nHost: 127.0.0.1:9222\r\n\r\n');
});
回来的是 HTTP/1.1 404 Not Found,Content-Type: text/html,body 为空。这不像 Chromium 的 DevTools 接口(那个 /json/version 会返回 JSON),但至少说明端口后面有个 HTTP 服务在。
再发一次 Upgrade 请求,就完全没有响应了:
GET /devtools/browser/c935e082-... HTTP/1.1
Host: 127.0.0.1:9222
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:
Sec-WebSocket-Version: 13
6 秒,0 字节。不是 403,不是 400,是沉默。这种沉默通常意味着服务端在等一个不会到来的状态,而不是拒绝你。
真正的原因藏在调试端点的语义里:同一时刻只接受一个客户端连接。当时已经有一个常驻代理占着这条连接,我这边再开一个,服务端既不拒绝也不回应,直接挂死。更麻烦的是这个状态有粘性——占位进程被杀掉之后,新连接依然不应答,必须在 edge://inspect/#remote-debugging 里把开关取消再重新勾上,会话才会重置。
顺带一个副作用:/json/version、/json/list 这些 HTTP 枚举接口在新版上是 404 的,只能从 DevToolsActivePort 文件拿端口和 ws 路径:
9222
/devtools/browser/c935e082-3d7d-4e93-b161-4c34bb1bcf58
所以别写"先查 /json/version 拿 webSocketDebuggerUrl"这种逻辑,它在 toggle 模式下必然失败。
坑二:编辑器在跨域 iframe 里,注入不进去
目标是往一个富文本编辑器里写正文。页面上看着是一整块,实际有两套编辑器,切换标签页会换成不同的 iframe:
Markdown 模式那个 iframe 是跨域的,contentDocument 直接给你 null,postMessage 也没有对端协议,等于写入路彻底堵死。能走的只有富文本模式,靠 TinyMCE 自己的 API:
window.tinymce.get('editor').setContent('
标题
正文
');
window.tinymce.get('editor').fire('change');
这里有个容易踩的细节:tinymce.editors 在某些版本上取不到,别用它判断实例是否存在,用 tinymce.get('editor') 更稳。
坑三:合成事件骗不过组件,得走真实输入
标题、摘要这类原生 好办,用原生 setter 改值再派发事件就行:
const el = document.getElementById('blogTitle');
const desc = Object.getOwnPropertyDescriptor(el.constructor.prototype, 'value');
desc.set.call(el, '标题');
el.dispatchEvent(new Event('input', { bubbles: true }));
但标签输入框不行。它是组件封装的下拉选择,值存在组件自己的状态里,DOM 上的 value 只是展示。我试过 value = 'xxx' 加 keydown Enter,页面上看起来有字,回车之后标签一个没加上,提交按钮依然是禁用态。
两条出路:
1. 真实键盘事件——走 CDP 的 Input.insertText / Input.dispatchKeyEvent,浏览器认为是真人在打字,组件会正常响应;
2. 绕开键盘——输入后等联想列表弹出来,点第一条。点击是允许的,问题只是你得等下拉渲染完成。
如果手上只有 HTTP 层的 eval 能力(没有 Input 域),还有第三个办法:document.execCommand('insertText', false, text)。它虽然被标记废弃,但在 Chromium 里仍然会触发真正的 input 事件,比直接改 value 靠谱得多。
结论:架构上只留一个客户端
三个坑指向同一件事:远程调试是独占资源,别让多个进程去抢。稳定跑了一阵的形态是这样:
Edge (9222) ← 常驻代理(唯一客户端,长连接) ← HTTP ← 业务脚本
业务脚本只跟代理说 HTTP,需要"看"就求值 DOM,需要"点"就发点击,需要"打字"就转成真实输入。代理崩了也比多客户端互抢好收拾——重新拉起来就行。
做浏览器自动化之前,先确认这五条:
- 调试端口是不是只有一个客户端在用
/json/*能不能用(新版本可能已关)- 目标编辑器是同源 iframe 还是跨域 iframe
- 表单字段是原生控件还是组件封装(组件封装的必须用真实输入)
- 提交按钮的启用条件(我这次就卡在"标签没加上 → 按钮不亮")
最后一条尤其容易被忽略:按钮不亮不代表点击逻辑写错了,多半是某个必填项还空着。
- 点赞
- 收藏
- 关注作者
评论(0)