浏览器扩展 + CDP 调试实践:多平台文章同步的五个坑

需求背景

手上有一批技术文章需要在多个平台发布:CSDN、掘金、51CTO、开源中国、博客园。每个平台都要单独登录、单独排版、单独上传,一篇文章发五个平台要花掉将近一小时。

于是想做一个工具:本地写一份 Markdown,一条命令同步到全部平台。

调研下来,开源方案里比较成熟的是 Wechatsync------它本身是个 Chrome 扩展,2026 年的版本同时提供了 CLI。这篇文章记录实际落地过程中遇到的问题和解决办法,给同样想做多平台同步的人省点时间。

一、先搞清楚架构:CLI 不干活,扩展才干活

很多人第一次用会觉得奇怪:装了 CLI,为什么还要在浏览器里装扩展?

因为 Wechatsync 的设计是扩展干活、CLI 当桥:

bash 复制代码
本地 Markdown  ──>  CLI(WebSocket 服务端,监听 9527)
                          ↑
                    扩展主动连上来(携带 MCP Token)
                          ↓
              浏览器已登录的 Cookie / 页面操作
                          ↓
                   各平台草稿箱 / 文章接口

关键点有三个:

  1. CLI 只是本地 WebSocket 服务,它没有平台账号,也没有发布能力;
  2. 扩展必须主动连上来,而且要带着 Token 握手,这个 Token 在扩展弹窗里的「MCP 连接」开关里生成;
  3. 登录态全靠浏览器,所以必须用一个持久化的浏览器 profile,登录一次之后就不用重复扫码。

理解了这一点,后面所有问题都好定位:连不上扩展查 Token,发不出去查 Cookie,发出去没内容查编辑器。

二、坑一:本地调试端口被代理接管

第一个坑很隐蔽。CLI 起来了、扩展也连上了,但所有请求都返回 502。

排查过程:

bash 复制代码
# 直接看环境变量
env | grep -i proxy
# http_proxy=http://127.0.0.1:55560
# https_proxy=http://127.0.0.1:55560

本机开着代理软件(Clash 一类),它把 HTTP_PROXY 写进了环境变量。Node.js 的部分网络库处理本地回环地址的逻辑和其他请求不一致,访问 127.0.0.1:9527 的请求也被送进了代理,而代理不认识这个地址,直接返回 502。

解决办法:所有涉及本地端口的命令都要显式排除代理干扰。

bash 复制代码
# Windows / Git Bash
export NO_PROXY="127.0.0.1,localhost"
export no_proxy="127.0.0.1,localhost"

用 curl 测试时加 --noproxy "*"。这一条不解决,后面所有调试都是在错误的前提下瞎猜。

三、坑二:Playwright 的 CDP 连接被中断

为了用脚本操作浏览器页面,第一反应是 Playwright 的 connectOverCDP。但在 Windows + 沙箱环境下,进程会收到 SIGTERM 直接退出,日志里一行错误都没有。

换成裸 WebSocket 直连 CDP 协议就稳定了:

javascript 复制代码
const targets = await fetch('http://127.0.0.1:9333/json/list').then(r => r.json());
const page = targets.find(t => t.url.includes('目标页面'));
const ws = new WebSocket(page.webSocketDebuggerUrl);

await new Promise(res => ws.on('open', res));
ws.send(JSON.stringify({
  id: 1,
  method: 'Runtime.evaluate',
  params: { expression: 'document.title', returnByValue: true }
}));

启动浏览器时加 --remote-debugging-port=9333,就能通过 /json/list 拿到所有标签页的调试地址。

两个细节值得注意:

  • 方法名是 Runtime.evaluate,参数名是 expression ,写成 expr 会报 mandatory field missing;
  • 想拿到异步结果,用 awaitPromise: true,或者在页面里挂全局变量轮询,后者在部分老版本里更兼容。

四、坑三:CSDN 的编辑器不是 CodeMirror

这是最容易让人白忙半天的坑。CSDN 的 Markdown 编辑器看起来像 CodeMirror,但 DOM 里其实是一个 contenteditable 的 <pre>:

javascript 复制代码
// 找 .CodeMirror 是找不到的
document.querySelectorAll('.CodeMirror').length  // => 0

// 真正的编辑区
document.querySelector('pre.editor__inner')      // => 存在,contenteditable

既然是 contenteditable,直接赋值 innerText 是不会被框架识别的。可行的做法是全选内容后执行一次文本插入:

javascript 复制代码
const pre = document.querySelector('pre.editor__inner');
pre.focus();
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(pre);
sel.removeAllRanges();
sel.addRange(range);
document.execCommand('insertText', false, newContent);

execCommand 虽然已经标记为废弃,但在 contenteditable 场景下依然是兼容性最好的方案------它走的是浏览器的编辑命令通道,框架能收到真实的 input 事件。

对照着看,其他平台的编辑器又是另一套:

平台 编辑区 可靠方案
CSDN pre.editor__inner(contenteditable) 全选 + execCommand('insertText')
掘金 textarea / 富文本双模式 切 Markdown 模式后直接写 value
博客园 独立编辑器 走 API 提交 HTML

没有一套通用的填内容方案,每个平台都得单独适配。

五、坑四:点击没反应?先查有没有东西盖着

调试中最耗时的场景:按钮明明定位到了,脚本点了,但没有任何请求发出。

这次的原因是一层登录态校验的浮层:

javascript 复制代码
const e = document.elementFromPoint(x, y);
// => DIV.passport-login-mark2

浮层在页面上不可见(或闪烁一下),但它把点击事件全吞了。后来养成一个习惯:每次点击前先验证落点。

javascript 复制代码
const btn = [...document.querySelectorAll('button')]
  .find(b => b.innerText.trim() === '发布文章');
const r = btn.getBoundingClientRect();
const hit = document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2);
const safeToClick = hit === btn || btn.contains(hit);

顺带一个经验:这类页面上的模态框(模板库、审核提示、新手引导)会不定时弹出,Escape 键是最可靠的关闭方式,比去找那个位置飘忽的关闭按钮省事得多。

六、坑五:批处理脚本里的中文

写了个 .cmd 双击启动浏览器,内容是 UTF-8,注释用中文。结果黑框一闪就消失,浏览器根本没起来。

原因:Windows 命令行按 GBK 读文件,UTF-8 的中文注释变成乱码,可能把后续的启动命令一起吃掉。

结论:.cmd / .bat 里不要写中文注释 ,或者第一行加 chcp 65001 并确保文件无 BOM 保存。纯英文注释是最省事的做法。

七、还有一个认知问题:草稿不等于发布

同步工具普遍只做到「存草稿」,因为各平台的发布接口大多不对外开放,而且一次性发到所有平台,出错的影响面太大。

实测下来各平台行为还不统一:

  • CSDN、掘金:进草稿箱,需要人工确认后发布;
  • 51CTO:部分情况下直接提交为待审核状态;
  • 博客园:走 API 时拿到的是草稿,但接口对 Cookie 完整性要求高。

所以同步完必须回各平台后台核对实际状态,不能只看 CLI 的返回值。

八、这套方案值不值得做

如果只发一两个平台:不值得,手动更快。

如果发三个以上平台,并且长期有内容产出:值得。核心收益不是省下那几十分钟,而是发布流程标准化------排版不会再因为平台不同而走样,错发、漏发、重复发的问题基本消失。

维护成本也要有预期:平台前端改版、Cookie 策略变严、编辑器换实现,都会让某个适配器失效。所以选择有社区维护的方案,比自己从零写一套更划算。

最后提醒一句:工具只解决「发出去」,不解决「发出去能活」。内容本身的合规性、平台社区调性是否匹配,这些是工具管不了的,得自己在选题阶段就想清楚。

相关推荐
子兮曰1 天前
Chrome 权限元素实战:144 与 151 的渐进增强写法
前端·javascript·chrome
Frag0ut1 天前
Chrome 156 新特性完整指南:2026 最新功能与开发实践
chrome·谷歌浏览器·浏览器开发·chrome 156·webgpu\·css random·indexdb
afumu3 天前
把五个网页版 AI 拉进同一个群聊:不用 API key 的多 Agent 编排
人工智能·chrome·typescript
其实防守也摸鱼5 天前
个人学习 Pwn 思路:从基础到漏洞利用的进阶路径
大数据·前端·人工智能·chrome·算法·安全·网络安全
李长渊哦6 天前
Qoder 自定义子智能体(Subagent)学习笔记
chrome·笔记·学习
来让爷抱一个7 天前
Linux 磁盘 100% 但 du 查不出东西,五种情况逐个排
linux·网络·chrome
不在逃避q7 天前
Chrome 扩展安全攻防实录:从攻击面到防御实践
前端·chrome·安全
10岁的博客8 天前
Chrome 扩展安全攻防实录:从权限模型到真实漏洞复盘
前端·chrome·安全
其实防守也摸鱼9 天前
Chrome 扩展安全攻防实录:从攻击面到防御实践
开发语言·前端·网络·chrome·安全·web安全·php