ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BlogLoom 分发 Skill 02:公众号(gzh)渠道分发 Skill 设计思路与实现

BlogLoom 分发 Skill 02:公众号(gzh)渠道分发 Skill 设计思路与实现 BlogLoom 分发 Skill 之公众号gzh设计思路与功能实现面向对象参与 BlogLoom 多渠道分发设计、需要理解或扩展第三方渠道 Skill 的开发者。对应实现目录skills/blogloom-publisher-skill/scripts/channels/gzh/、references/channels/gzh/sop.md。前言本篇是BlogLoom 分发 Skill 专栏的第 02 篇承接开篇《BlogLoom 分发 Skill 01博客一键分发 Skill 从 0 到 1》确立的「Playwright 主操作 CDP 底层增强 按渠道分层」通用骨架聚焦**微信公众号gzh**这一具体渠道。开篇讲的是跨渠道通用的技术选型、分层结构与统一 CLI 契约而公众号的编辑器形态与 CSDN 完全不同——不识别 Markdown、发布配置繁琐、群发还要管理员扫码。本篇把公众号渠道的设计思路、关键机制与真机踩坑逐段拆开供后续扩展更多渠道时参考。项目地址https://github.com/changluya/BlogLoom Skill 目录https://github.com/changluya/BlogLoom/tree/master/skills/blogloom-publisher-skill一、背景1.1、要解决的问题在把 BlogLoom 的本地 Markdown 分发到 CSDN 之后我们紧接着要打通第二个渠道微信公众号。公众号是内容分发绕不开的阵地但它和 CSDN 有着本质不同的编辑器形态CSDN 的编辑器原生识别 Markdown直接粘贴 Markdown 源码即可排版公众号的编辑器是ProseMirror 富文本不识别 Markdown 源码——直接粘贴 Markdown 只会得到一堆带#、*、反引号的纯文本。除此之外公众号还有一套极其繁琐的发布配置首图要「从正文选择」、声明原创要勾「我已阅读」、群发要管理员扫码……每一个环节都是真人才能稳定点对的操作。1.2、为什么单独成篇上一篇CSDN讲的是「编辑器原生吃 Markdown、靠弹窗逐项配置」的模式公众号这篇讲的是完全不同的三个难点维度CSDN公众号gzh编辑器内核Markdown 原生识别ProseMirror 富文本不认 Markdown正文写入直接粘贴 Markdown 源码先用 md.doocs 转成带样式 HTML 再粘贴封面发布弹窗选已有图片第一张编辑页底部「从正文选择」 图片上传校验发表弹窗点发布即可群发弹窗 二次确认 管理员扫码终态PUBLISHEDSUBMITTED已提交需扫码/审核1.3、设计目标目标含义正文排版不丢用 md.doocs 把 Markdown 渲染成公众号样式富文本粘贴后格式保留发布配置齐全首图 / 摘要 / 原创 / 合集 / 创作来源 / 赞赏一个不少能自测不留残留testModedraft自测走「发布草稿 → 删除」避免自动群发/审核机器可判读发表后进入扫码/审核流程终态定为SUBMITTED而非PUBLISHED二、技术栈公众号渠道同样基于「Playwright 主操作 CDP 底层增强」的通用骨架但在渠道层多了一个关键文件scripts/channels/gzh/ ├── index.js # 业务流程登录判定、进编辑器、写正文、发布配置、发表、删除 ├── selectors.js # 公众号 DOM 选择器集中维护改版只改这里 └── format.js # 正文样式转换Markdown → 公众号富文本md.doocs.org核心差异集中在format.js它解决了公众号「不认 Markdown」的根本矛盾。三、核心功能3.1、动作矩阵动作命令说明检测登录gzh checkLogin判定登录态账号名 真实会话 Cookie 交叉验证扫码登录gzh login打开后台扫码登录态持久化存草稿gzh publishDraft填标题/作者/正文 底部配置后保存草稿发表gzh publish完整走发布配置并触发发表群发需扫码删除gzh delete草稿箱定位后删除含确认与结果校验链路自测gzh test publish发布草稿 → 删除testModedraft3.2、登录态检测checkLogin项内容核心原理用「后台账号名」做主判定再用「真实会话 Cookie」白名单交叉验证避免把未登录态下发的wxuin/ua_id误当登录凭证执行细节流程① 进入后台首页mp.weixin.qq.com/cgi-bin/home→ ② 出现.weui-desktop_name账号名则已登录 → ③ 否则读 Cookie 是否含slave_sid/slave_user/data_ticket→ ④ 都没有则视为未登录 → ⑤ 返回{loggedIn, user, cookieNames, loginEntryVisible, reason}详细原理与代码见 4.2。3.3、扫码登录login项内容核心原理未登录时首页出现#jumpUrl登录入口点击进入扫码页扫码成功后会话 Cookie 持久化到独立 profile执行细节流程① 进入后台首页 → ② 有#jumpUrl则点击进入扫码登录页 → ③ 用户扫码 → ④ 脚本轮询slave_sid/slave_user/data_ticket判定成功 → ⑤ 登录态存到~/.blogloom-publisher/chrome-profile-gzh3.4、正文样式转换format.js项内容核心原理公众号编辑器ProseMirror不识别 Markdown先用 md.doocs.org 把 Markdown 渲染成带样式的 HTML 富文本再粘贴回编辑器执行细节流程① 同一浏览器上下文新开转换页md.doocs.org→ ② 清空默认内容把 Markdown 粘贴进.cm-content→ ③ 先把「图注」设为「不显示」避免图片下方渲染图注文字→ ④ 点「复制」把富文本写入剪贴板 → ⑤ 关闭转换页 → ⑥ 回主编辑页ModV粘贴详细原理与代码见 4.1。3.5、进入编辑器enterEditor项内容核心原理公众号编辑器藏得深且点「文章」可能新开标签页需监听新页面并切换上下文执行细节流程① 进入后台首页 → ② 点「内容管理」→「草稿箱」→ ③ 点「新的创作」→「文章」→ ④ 用context.waitForEvent(page)监听若新开标签页则切到新页面 → ⑤ 等.ProseMirror编辑器就绪 → ⑥ 判断是否被重定向到/loginpage/是则抛AUTH_REQUIRED3.6、标题 / 作者写入setTitle / setAuthor项内容核心原理标题/作者是 contenteditable/ProseMirrorfill不一定可靠用键盘输入最稳作者必须先填否则原创弹窗「我已阅读」无法勾选执行细节流程① 定位标题.title-editor__input .ProseMirror兜底#title→ ② 全选清空 → ③keyboard.type逐字输入 → ④ 回读校验作者同理写#js_author_area #author→ ⑤ 回读校验兜底3.7、正文写入setContent项内容核心原理优先用 md.doocs 样式粘贴粘贴后处理微信「内容结构检测」确认框回读为空则回退原始写入策略执行细节流程① 可选把公众号名片插入正文顶部 → ② 调 3.4 样式转换 → ③ 点正文编辑器ModV粘贴 → ④ 循环点「继续插入」处理内容结构检测框 → ⑤ 回读正文长度 → ⑥ 为 0 则回退writeContent([paste,cdp,keyboard])3.8、发布配置项配置核心原理执行细节流程首图封面面板按正文顺序增量渲染需等图片上传完再取第一张并校验上传成功① 底部定位#js_cover_area→ ② 悬停「从正文选择」→ ③ 轮询面板.appmsg_content_img_item数量达正文图总数 → ④ 取第一张 → ⑤ 「下一步」「完成」→ ⑥ 校验预览区出现background-image失败重试一次摘要直接填充文本域超长自动裁剪① 定位#js_description_area textarea→ ② 按 conf 上限默认 120 字slice→ ③fill(text)原创微信自定义组件首次常不响应需多轮重试 触发 DOM click 勾协议① 点「原创」→ ② 点「文字原创」→ ③ 反复对label触发 DOM click 直到「我已阅读」isChecked()→ ④ 「确定」→ ⑤ 校验弹窗关闭失败则关弹窗下一轮再试最多 4 轮赞赏需先声明原创点开关会弹「赞赏设置」选账户勾协议确定① 等#js_reward_setting_area可见 → ② 点开关 → ③ 弹窗选「赞赏作者」 首个赞赏账户 勾协议 → ④ 确定 → ⑤ 校验isRewardOn整体加超时保护付费按产品要求不开启跳过保留默认关闭合集匹配同名合集不存在则跳过不创建① 点#js_article_tags_area标签 → ② 弹窗遍历.select-opt-li匹配同名 → ③ 点「确认」创作来源下拉 contains 匹配文案① 点#js_claim_source_area描述 → ② 在选项里按「个人观点仅供参考」匹配 → ③ 点「确认」3.9、发表publish项内容核心原理发表可能触发群发需管理员扫码因此终态定为SUBMITTED而非PUBLISHED执行细节流程① 点底部「群发」.mass_send→ ② 弹窗 1非群发则关闭群发开关点确认 → ③ 弹窗 2二次确认「继续发表」→ ④ 等待成功 toast → ⑤ 命中返回PUBLISHED否则返回SUBMITTED并提示在浏览器完成扫码确认3.10、存草稿 / 删除功能核心原理执行细节流程存草稿草稿场景也要先配封面/摘要/原创/合集/创作来源再保存① 关闭残留弹窗 → ② 调发布配置 → ③ 点「保存为草稿」button:has-text(保存为草稿)→ ④ 返回DRAFT_SAVED删除进草稿箱定位文章卡片删除后确认浮层回查卡片消失① 进「内容管理 → 草稿箱」→ ② 按 appmsgid/标题定位.weui-desktop-card→ ③ 悬停 → ④ 点行内「删除」→ ⑤ 确认浮层点「删除」→ ⑥ 轮询回查卡片消失 → ⑦ 返回DELETED未确认则DELETE_UNVERIFIED3.11、渠道专属参数透传augmentPayload项内容核心原理核心 CLI 不感知渠道业务--key value原样透传由渠道augmentPayload组装优先级「CLI 元数据 extra conf」执行细节流程① 解析--author/--wechat-name/--collection/--group-send→ ② 与blog.extra、conf逐级取优先值 → ③ 返回{author, wechatName, collection, groupSend}供发布/配置使用四、核心功能设计思路与原理这一章逐段拆解公众号渠道最核心的机制。4.1、正文样式转换Markdown → 公众号富文本重点问题公众号编辑器不识别 Markdown直接粘贴只会得到纯文本排版全丢。方案参照已跑通的auto-sync-blog用在线排版工具md.doocs.org先把 Markdown 渲染成带内联样式的 HTML复制该富文本再粘贴回公众号编辑器。asyncfunctionconvertMarkdownToGzhHtml(page,markdown){// 1. 在同一浏览器上下文新开一个转换页转换完即关闭不影响主编辑页constconverterawaitcontext.newPage();awaitconverter.goto(https://md.doocs.org/,{waitUntil:domcontentloaded});consteditorconverter.locator(.cm-content);awaiteditor.waitFor({state:visible});// 2. 清空默认内容粘贴 Markdown走剪贴板避免超长文本逐字输入的性能问题awaitconverter.keyboard.press(${MOD}A);awaitconverter.keyboard.press(Backspace);awaitconverter.evaluate((v)navigator.clipboard.writeText(v),markdown);awaitconverter.keyboard.press(${MOD}V);// 3. 点「复制」把渲染后的富文本写入剪贴板awaitconverter.locator(button:has-text(复制)).click();return{converted:true};}关键点同上下文新开页转换页与主编辑页共享 Cookie/登录态且转换完close()不污染主流程走剪贴板而非逐字输入长文逐字输入有性能问题剪贴板粘贴一次到位复制前先把「图注」设为「不显示」md.doocs 默认会在图片下方渲染图注文字公众号正文不需要需先在右侧样式面板选中「不显示」。图注隐藏的实现细节样式面板默认收起看不到「图注」区块时先点右上角「样式」展开用 XPath 先锁定「图注/Caption」标题再取其父区块内的「不显示/None」按钮避免误点其他区块的同名按钮选中态用border-primaryclass 校验constcls(awaithideBtn.getAttribute(class))||;return/border-primary/.test(cls);4.2、登录态判定账号名 真实会话 Cookie问题公众号登录凭证复杂怎么避免「假阳性」原理双保险判定——进入后台首页出现账号名.weui-desktop_name→ 已登录否则看真正的会话 Cookieslave_sid/slave_user/data_ticket——它们仅在登录后下发都没有则视为未登录。重点不能用wxuin/ua_id判定——未登录态也会下发会假阳性。登录凭证白名单只放LOGIN_COOKIE_NAMES [slave_sid, slave_user, data_ticket]。4.3、进入编辑器内容管理 → 草稿箱 → 新的创作问题公众号编辑器藏得深且「文章」可能新开标签页打开。原理路由链路——后台首页 → 内容管理 → 草稿箱 → 「新的创作」→ 「文章」。点「文章」时用context.waitForEvent(page)监听新标签页若新开则切换到新页面继续操作constnewPageawaitPromise.all([context.waitForEvent(page,{timeout:15000}).catch(()null),addBtn.click({timeout:8000}).catch((){}),]).then(([np])np);if(newPage)editorPagenewPage;同时判断是否被重定向到登录页/loginpage/是则抛AUTH_REQUIRED。4.4、标题 / 作者写入标题编辑器是 contenteditable/ProseMirrorfill不一定可靠用键盘输入最稳全选清空 →keyboard.type逐字输入 → 回读校验。作者栏#js_author_area #author是必须先填的——不填作者后续原创弹窗的「我已阅读」就无法勾选。同样全选替换 回读校验兜底。4.5、正文写入样式粘贴 回退兜底asyncfunctionsetContent(page,markdown,ctx){// 1. 可选把公众号名片插入正文顶部if(profileName)awaitinsertProfileCard(page,profileName);// 2. 优先用 md.doocs 样式转换constconvertedawaitconvert(page,markdown);// 3. 粘贴进 ProseMirrorawaitpage.keyboard.press(${MOD}V);// 4. 处理微信的「内容结构检测」确认框 → 点「继续插入」才真正写入awaitconfirmInsertDialog(page);// 5. 回读长度为 0 则回退为原始 Markdown 写入策略if(length0)returnawaitwriteContent(page,editor,markdown,[paste,cdp,keyboard]);}重点微信粘贴外部富文本后会弹「内容结构检测」确认框必须点「继续插入」否则内容不会真正写进正文。同时保留原始writeContent兜底样式转换失败时仍能发布。4.6、首图从正文选择 上传校验问题公众号封面面板按正文顺序增量渲染图片没传完时第一张并不是正文首图。原理setCover(page, firstBox, expectedImages)——传正文图片总数轮询面板 item 数量直到达到期望值或连续若干秒不再增长再取第一张asyncfunctionwaitForContentImages(page,panel,expected,timeoutMs45000){while(Date.now()deadline){constcountawaitpanel.locator(.appmsg_content_img_item).count();if(countexpected)returncount;if(countlast){stable;if(stable4count0)returncount;}elsestable0;lastcount;awaitpage.waitForTimeout(1000);}}然后「下一步」→「完成」最后校验封面是否真上传成功预览区出现background-image且非空 url失败重试一次asyncfunctionisCoverUploaded(page){conststyle(awaitpreview.getAttribute(style))||;returnstyle.includes(background-image)!style.includes(url());}4.7、原创声明类型 勾协议多重试问题微信的自定义组件首次打开常不响应且「我已阅读」勾选失败会导致声明不生效。原理chooseOrigin采用多轮重试——最多 4 轮每轮打开弹窗 → 点「文字原创」→ 反复勾「我已阅读」直到isChecked()为真 → 确定 → 校验弹窗是否已关闭失败则关弹窗下一轮再试for(leti0;i6!checked;i){awaitagreeLabel.evaluate((el)el.click());checkedawaitagreeInput.isChecked();}勾选对label触发 DOM click重试直到真勾上。4.8、发表群发弹窗 二次确认 扫码问题公众号发表可能触发群发需管理员扫码终态如何界定原理publish流程——点底部「群发」→ 弹窗 1非群发则关闭群发开关→ 确认 → 弹窗 2二次确认「继续发表」→ 等待成功 toast。群发需要扫码给出一段确认窗口返回returnpublished?{status:PUBLISHED,url:}:{status:SUBMITTED,url:,message:已提交发表如需群发请在浏览器中完成管理员扫码确认};重点公众号发表后可能进入扫码/审核所以终态是SUBMITTED而不是PUBLISHED通过channel.successStatuses [SUBMITTED]声明避免机器误判失败。4.9、渠道专属参数透传公众号的--author/--wechat-name/--collection/--group-send等参数核心 CLI不感知统一--key value原样透传给渠道。渠道用augmentPayload组装优先级CLI 参数 元数据 extra conf 默认author:opts.author||params.author||extra.author||extra.username||CONF.author||,wechatName:opts.wechatName||extra.wechatName||CONF.wechatName||,groupSend:/* opts → params → extra → conf 逐级 */五、实战发布与链路自测5.1、存草稿推荐首测nodescripts/publisher.js gzh publishDraft--file/abs/blog.md--author长路--wechat-name长路Java草稿场景也会先配置封面/摘要/原创/合集/创作来源configureOnDraft: true再保存草稿。5.2、正式发表nodescripts/publisher.js gzh publish--file/abs/blog.md--wechat-name长路Java默认有头模式可见全过程未登录自动拉起登录发表后若需群发则在浏览器完成管理员扫码。5.3、链路自测gzh test publish公众号自测用草稿模式避免自动群发/审核nodescripts/publisher.js gzhtestpublish--file/abs/blog.md返回statusDRAFT_SAVED_AND_DELETEDchannel.testModedraft控制。六、踩坑与优化重点复盘6.1、坑一直接粘贴 Markdown 变纯文本公众号编辑器不识别 Markdown直接粘贴只得到纯文本#、*、反引号全暴露。优化用 md.doocs 先把 Markdown 渲染成带样式的 HTML 再粘贴。6.2、坑二粘贴后内容没进去内容结构检测微信粘贴外部富文本后弹「内容结构检测」确认框不点「继续插入」内容不会写入正文。优化confirmInsertDialog循环点「继续插入」并回读正文长度为 0 则回退原始写入策略。6.3、坑三登录「假阳性」未登录态也会下发wxuin/ua_id用它们判定会把「未登录」误判为「已登录」。优化登录凭证白名单只放slave_sid/slave_user/data_ticket。6.4、坑四首图选错/选空封面面板按正文顺序增量渲染图片没传完时第一张不是正文首图。优化传图片总数轮询面板 item 数量直到达到期望值再取第一张并校验上传成功失败重试一次。6.5、坑五原创声明不生效微信自定义组件首次打开不响应「我已阅读」勾选失败导致声明不生效。优化多轮重试 对label触发 DOM click反复校验isChecked()直到真勾上。6.6、坑六发表终态判断发表后可能进入扫码/审核返回PUBLISHED会导致机器误判失败。优化终态定为SUBMITTEDsuccessStatuses: [SUBMITTED]发表后给出扫码确认窗口。重点公众号的坑集中在「内容不进去」和「状态假成功」两类应对手段是「回读校验」「重试」「兜底回退」。七、验证测试7.1、mock 单测用test/helpers/mock-page.js模拟 Playwright无需真实浏览器即可验证npmtest# 全部单测核心 所有渠道npmrun test:channels# 仅渠道csdn / gzh覆盖 gzh 的checkLogin / enterEditor / setTitle / setAuthor / setContent / preparePublish / publish / saveDraft / deleteBlog / augmentPayload等每一个脚本工具。7.2、真实链路自测gzh test publish走「发布草稿 → 删除」验证完整链路且不触发群发、不留残留。八、总结公众号渠道的引入验证了「按渠道分层」这套骨架的可扩展性复用 Playwright CDP、登录判定、CLI 契约等通用能力只在channels/gzh/里新增渠道专属逻辑format.js样式转换、augmentPayload、testMode、successStatuses。核心收获编辑器不认 Markdown 就转 HTMLmd.doocs 渲染 剪贴板富文本内容不进就回退兜底回读校验 原始写入策略状态不进就定 SUBMITTED扫码/审核流程不该被当成失败一切关键动作都要回读校验拒绝「假成功」。项目地址https://github.com/changluya/BlogLoom Skill 目录https://github.com/changluya/BlogLoom/tree/master/skills/blogloom-publisher-skill参考资料[1]. md.doocs.orgMarkdown 公众号排版工具[2]. Playwright 官方文档Locators、Actions、Page Context[3]. Chrome DevTools ProtocolInput.insertText / Network.getCookies[4]. Playwright connectOverCDP复用已有浏览器登录态整理者长路 创建时间2026.10.6 更新时间2026.10.6
返回列表