ARTICLE DETAIL

资讯详情

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

BlogLoom 分发 Skill 03:CSDN 渠道分发 Skill 设计思路与实现

BlogLoom 分发 Skill 03:CSDN 渠道分发 Skill 设计思路与实现 BlogLoom 分发 Skill 之 CSDN 设计思路与功能实现前言本篇是BlogLoom 分发 Skill 专栏的第 03 篇承接开篇《BlogLoom 分发 Skill 01博客一键分发 Skill 从 0 到 1》确立的「Playwright 主操作 CDP 底层增强 按渠道分层」通用骨架聚焦CSDN这一具体渠道。开篇讲的是跨渠道通用的技术选型、分层结构与统一 CLI 契约而 CSDN 作为第一个、也是最早跑通真实发布的渠道其 DOM 结构、弹窗交互与坑点都值得单独成篇。本篇把 CSDN 渠道的实现细节、关键机制与真机踩坑逐段拆开。项目地址https://github.com/changluya/BlogLoom Skill 目录https://github.com/changluya/BlogLoom/tree/master/skills/blogloom-publisher-skill一、背景为什么单独为 CSDN 做一个分发 Skill1.1、场景引入在开发 BlogLoom 的多渠道分发能力时我们遇到了一个非常具体的问题同一篇本地 Markdown 博客除了留在 BlogLoom 平台还要分发到 CSDN。CSDN 是很多 Java / 后端开发者最常打理的站外渠道。它不像自建平台那样提供稳定的发布接口而是典型的「前端富文本编辑器 私有发布协议」。手动搬运的痛点非常集中字段多且隐蔽标题默认是展示态、标签/分类专栏藏在发布弹窗里、首图要等异步加载改版频繁CSDN 前端迭代快选择器经常变容易「假成功」点了发布/删除实际没生效肉眼难发现。于是我们把「发布到 CSDN」这一整套重复点击沉淀成blogloom-publisher-skill里的一个独立渠道分发 Skill。1.2、为什么单独成篇而不是塞进开篇开篇《BlogLoom 分发 Skill 01》讲的是跨渠道通用的骨架Playwright CDP 选型、三层分层、统一 CLI 契约。而 CSDN 作为第一个、也是唯一先跑通真实发布的渠道其 DOM 结构、弹窗交互、坑点都值得单独成篇维度01 篇通用骨架本篇CSDN 专属定位所有渠道共用的地基CSDN 一个渠道的实现细节内容选型、分层、契约选择器、弹窗配置、坑与校验复用新增渠道照搬只服务 csdn 这一个 channel1.3、目标与范围一句话描述本篇目标把符合统一 SOP 的本地 Markdown用一句话分发到 CSDN且发布/删除结果可被机器校验绝不「假成功」。涉及的完整动作checkLogin/login/publishDraft/publish/delete/test publish。二、技术栈站在通用骨架之上2.1、Playwright 主操作 CDP 底层增强CSDN 分发 Skill 复用开篇确立的技术栈不另起炉灶层次技术在 CSDN 上的职责主操作Playwright可见浏览器、真实点击/输入/粘贴、进入编辑器、打开发布弹窗、逐项配置底层增强CDP复用登录态connectOverCDP、Input.insertText兜底富文本写入、读取 HttpOnly Cookie 判定登录页面结构selectors.js集中维护 CSDN 全部 DOM 选择器改版只改这里2.2、目录结构CSDN 渠道代码全部收敛在两个文件里职责单一scripts/channels/csdn/ ├── index.js # 业务流程登录判定、进编辑器、填字段、发布、删除 └── selectors.js # CSDN DOM 选择器集中维护改版只改这里selectors.js里一个关键点标签容器tagContainer已经是.mark_selection子选择器不要再带.mark_selection前缀否则container.locator(.mark_selection …)永远匹配不到后代。这是踩出来的坑。三、核心功能CSDN 分发 Skill 能做什么3.1、动作矩阵动作命令说明检测登录csdn checkLogin判定登录态返回是否已登录、是否出现滑块扫码登录csdn login打开浏览器扫码登录态持久化到独立 profile存草稿csdn publishDraft填标题/正文后保存草稿不走发布弹窗发布csdn publish完整走发布弹窗并最终发布返回文章链接删除csdn delete内容管理页定位后删除含二次确认与结果校验链路自测csdn test publish真实发布 → 立即删除验证链路且不留残留3.2、登录态检测checkLogin项内容核心原理用「首页登录入口文案」做主判定再用「真正的登录 Cookie」交叉验证避免把匿名态 Cookie 误当登录凭证执行细节流程① 进入首页https://www.csdn.net/→ ② 读.toolbar-btn-loginfun文案含「登录」则未登录 → ③ 读 Cookie 是否同时含UserTokenUserInfo→ ④ 探测是否出现滑块.verify-move-block→ ⑤ 返回{loggedIn, user, hasSlider, loginEntryVisible}详细原理与代码见 4.1。3.3、扫码登录login项内容核心原理打开有头登录页让用户扫码脚本轮询登录 Cookie 判定成功登录态持久化到独立 profile执行细节流程① 打开passport.csdn.net/login有头→ ② 用户扫码/输入账号可能含滑块→ ③ 每 2s 轮询 Cookie 是否含UserToken/UserInfo→ ④ 命中即成功默认最长 10 分钟→ ⑤ 登录态存到~/.blogloom-publisher/chrome-profile-csdn3.4、发布publish完整发布链路项内容核心原理进入编辑器 → 填标题/正文 → 打开发布弹窗逐项配置 → 点最终发布 → 抓取文章链接执行细节流程① 首页点「创作」进editor.csdn.net/md失败回退直开→ ② 先点.article-bar__title-display唤出隐藏 input 填标题 → ③ 清空正文模板按paste → cdp → keyboard写正文含图片时按数量估算等待 → ④ 点「发布文章」button.btn-publish打开弹窗 → ⑤ 依次配置标签/首图/摘要/分类专栏/类型/可见范围/创作声明 → ⑥--dry-run则返回DRY_RUN及publishConfig→ ⑦ 否则点弹窗底部发布按钮从成功弹窗a.success-modal-btn[href*/article/details/]取链接返回PUBLISHED3.4.1、发布弹窗内的逐项配置配置项核心原理执行细节流程标签标签输入框在点「添加文章标签」后才出现① 幂等打开选择框 → ② 清空残留标签 → ③ 逐个fill Enter≤10 个每次重新解析输入框→ ④ 关闭选择框 → ⑤ 回读.mark_selection_box_el_tag校验写入selectedTags/missingTags首图首图列表异步加载需等图片上传完再选① 等.img-selection-item img.select-cover可见 → ② 取第一张点击 → ③ 点确认.vicp-operate-btn→ ④ 返回coverSet摘要直接填充文本域① 定位.desc-box .el-textarea__inner→ ②fill(summary)分类专栏勾选项是隐藏 checkbox匹配用评分制① 定位「分类专栏」.form-entry→ ② 清空已选 → ③ 对每个专栏按评分匹配最优项 → ④ 用 DOMclick()触发隐藏 checkbox → ⑤ 关闭下拉 → ⑥ 回读.tag__item-box返回resolved/unresolved/selected文章类型单选按钮按 value 定位定位input[valueoriginal]点击原创可见范围单选按钮按 value 定位定位input[valuepublic]点击公开创作声明下拉 contains 匹配选项① 点.creation-statement-select触发 → ② 在选项里按「个人观点仅供参考」contains 匹配 → ③ 点击并返回选中项3.5、存草稿publishDraft项内容核心原理填标题/正文后直接点编辑器顶部「保存草稿」不走发布弹窗执行细节流程① 进编辑器填标题/正文 → ② 点「保存草稿」button.btn-save→ ③ 返回statusDRAFT_SAVED标签/摘要/分类等草稿阶段不设置后续发布时补3.6、删除delete项内容核心原理进内容管理页定位文章行 → 二次确认删除 → 回查该行已消失拒绝「假成功」执行细节流程① 进入mp.csdn.net/mp_blog/manage→ ② 按 id浏览链接/article/details/id或 title 定位行 → ③ 悬停行右侧「…」→ ④ 下拉点「删除」→ ⑤ 二次确认弹窗点「确定」→ ⑥ 回查该行已从列表消失 → ⑦ 返回DELETED未确认则DELETE_UNVERIFIED3.7、链路自测test publish项内容核心原理组合「真实发布 立即删除」验证链路可用且不留残留执行细节流程① 执行publish→ ② 拿到文章 URL/id → ③ 立即执行delete→ ④ 返回statusPUBLISHED_AND_DELETED3.8、CSDN 字段到渠道的映射SOP 字段CSDN 目标title文章标题tags逗号分隔取前 10文章标签columns逐个匹配已有专栏分类专栏articleSummary文章摘要![coverImg](url)封面/首图content去掉元数据块后的正文正文Markdown 源码四、核心功能设计思路与原理这一章是整篇的重点逐段拆解 CSDN 分发 Skill 的关键机制。4.1、登录态判定文案 Cookie 交叉验证问题怎么知道当前浏览器是否已登录 CSDN方案靠「首页登录入口文案」做主判定再用「真正的登录 Cookie」做交叉验证。asyncfunctioncheckLogin(page,ctx{}){// 1. 找到 .toolbar-btn-loginfun若文案含「登录」→ 未登录constloginFunpage.locator(SELECTORS.homeLoginFun).first();constloginText(awaitloginFun.innerText()||).trim();letloggedIn!loginText.includes(登录)loginText.length0;// 2. 交叉验证Cookie 是否含 UserToken UserInfoif(!loggedIn){constcookiesawaitctx.cookies(https://www.csdn.net);loggedIncookies.some(cc.nameUserToken)cookies.some(cc.nameUserInfo);}return{loggedIn,hasSlider:/* .verify-move-block 是否存在 */};}重点不能把uuid_tt_dd这类匿名 Cookie 当作登录凭证——CSDN 匿名态也会下发它会导致「假阳性」详见 5.1 的坑。4.2、进入编辑器与标题写入问题CSDN 的标题输入框是隐藏的怎么填原理标题默认显示为.article-bar__title-display【无标题】点击它之后才显示隐藏的input.article-bar__title。所以要先点展示态、唤出 input、再写入并回读校验asyncfunctionsetTitle(page,title){awaitclickIf(page.locator(SELECTORS.titleDisplay).first());// 点展示态唤出 inputconstinputpage.locator(SELECTORS.titleInput).first();awaitinput.waitFor({state:visible});awaitinput.fill(title);// 兜底fill 未生效则全选替换if((awaitinput.inputValue())!title){awaitpage.keyboard.press(${MOD}A);awaitpage.keyboard.type(title,{delay:10});}}4.3、正文写入清空 多策略粘贴问题CSDN 编辑器预置了欢迎模板且富文本粘贴可能被拦截怎么把 Markdown 正文可靠写入原理四步走——清空Cmd/Ctrl A全选Backspace删除默认欢迎内容多策略写入按paste → cdp → keyboard依次尝试——剪贴板粘贴最贴近真人优先失败用 CDPInput.insertText兜底绕过 paste 事件拦截最后键盘逐字回读校验每尝试一个策略就读回内容非空才算成功图片等待正文含图片时按图片数量估算等待时间给 CSDN 异步上传/解析图片留足时间否则发布弹窗的首图列表是空的。functionestimateImageWaitMs(content,{base2000,blockSize5,perBlock3000,max60000}{}){constcountcountImages(content);if(count0)return0;returnMath.min(baseMath.ceil(count/blockSize)*perBlock,max);}4.4、标签配置先打开选择框再填问题CSDN 的标签输入框不是一开始就可见的直接fill会找不到元素。原理标签输入框.el-autocomplete input.el-input__inner只有在点过「添加文章标签」.tag__btn-tag打开选择框之后才出现。所以流程是幂等打开选择框输入框可见即视为已打开清空草稿/上一次运行残留的标签逐个fill Enter每次重新解析输入框避免异步替换导致丢标签关闭选择框回读.mark_selection_box_el_tag校验实际生效的标签写入selectedTags / missingTags。4.5、分类专栏评分匹配 DOM click 勾选问题分类专栏有两层坑——勾选项是隐藏的 checkbox 打不到且「包含」匹配会误配宽泛子类。原理一匹配用评分制对每个已有专栏选项打分取最高分——精确相等100000 分已有专栏包含设置值10000 名称长度 分设置值包含已有专栏1000 名称长度 分。这样MavenGradle不会被宽松地误配到子类Maven。原理二勾选触发 DOM click分类专栏的勾选项是隐藏的input typecheckbox普通click()打不到看着选中了实际没生效。需要先判断checked避免反选再用evaluate((el) el.click())触发 DOM clickconstchkoption.locator(SELECTORS.categoryOptionCheckbox).first();constalreadyawaitchk.evaluate((el)!!(elel.checked));if(!already)awaitchk.evaluate((el)elel.click());// 关键DOM click最后回读.tag__item-box校验实际勾选结果返回resolved / unresolved / selected。4.6、封面/首图等异步加载再选第一张问题发布弹窗的「已有图片列表」是异步加载的点开弹窗马上找.img-selection-item经常是空的。原理因为 4.3 已经按图片数量估算过等待时间首图列表才有内容。取第一张作为首图点击后再点确认按钮.vicp-operate-btn返回coverSet: true/false。列表为空则跳过不影响正文发布。4.7、删除二次确认 结果校验拒绝「假成功」问题怎么保证删除真的生效而不是「看起来删了」原理进入内容管理页https://mp.csdn.net/mp_blog/manage→ 按 id/title 定位行 → 悬停行右侧「…」→ 下拉点「删除」→二次确认弹窗点「确定」→回查该行已从列表消失constremainingawaitfindManageRow(page,target).count();if(remaining0)return{status:DELETE_UNVERIFIED};return{status:DELETED};重点自动化最危险的错误不是「报错」而是「看起来成功」。所以每个关键动作后都要有回读校验。4.8、统一 CLI 契约与机器可读输出问题Agent / 脚本怎么稳定地调用并解析结果原理所有动作统一入口stdout 只输出一个 JSON日志走 stderr便于解析nodescripts/publisher.js csdn publish--file/abs/blog.md{ok:true,channel:csdn,action:publish,data:{status:PUBLISHED,url:https://blog.csdn.net/…/details/123,publishConfig:{tags:[Maven,spotless],selectedCategories:[项目管理工具,# MavenGradle],coverSet:true,creationStatement:个人观点仅供参考}},error:null}status约定PUBLISHED发布/DRAFT_SAVED草稿/DRY_RUN仅填写/DELETED删除并校验/DELETE_UNVERIFIED未确认。五、实战发布与链路自测5.1、干跑验证--dry-run发布是对外可见的写操作建议先干跑验证字段填写无误nodescripts/publisher.js csdn publish--file/abs/blog.md--dry-run它走完所有填写与弹窗配置但不点最终发布返回statusDRY_RUN与publishConfig生效的写入策略、标签回读、命中的专栏、封面是否设置等。据此判断各字段是否写入成功。5.2、正式发布nodescripts/publisher.js csdn publish--file/abs/blog.md默认有头模式发布全过程可见未登录时自动拉起浏览器让你扫码登录完成后继续。5.3、链路自测csdn test publishMock 能测逻辑但测不出「页面改版」。提供一个组合自测——真实发布 → 立即删除nodescripts/publisher.js csdntestpublish--file/abs/blog.md返回statusPUBLISHED_AND_DELETED用于验证分发链路是否可用且不残留内容。5.4、选择器维护CSDN 站点改版频繁。所有 DOM 选择器集中在scripts/channels/csdn/selectors.js改版时只改这里。改动后用--dry-run验证即可。六、踩坑与优化重点复盘以下是 CSDN 渠道真机跑出来的坑每一条都值得记住。6.1、坑一登录被「假阳性」最初用UserToken/UserInfo/uuid_tt_dd/cnt_w判定登录结果 CSDN匿名态也下发uuid_tt_dd导致未登录被误判为已登录随后编辑器直接跳到登录页。优化只认真正的登录凭证UserToken/UserInfo并在编辑器阶段判断是否被重定向到passport.csdn.net/login是则抛AUTH_REQUIRED。6.2、坑二标签输入框隐藏.el-autocomplete input.el-input__inner直接waitFor(visible)超时——它要先点「添加文章标签」打开选择框后才出现。优化先点.tag__btn-tag打开选择框再定位输入框写入。6.3、坑三分类专栏勾选无效分类专栏的勾选项是隐藏的input typecheckbox普通click()打不到。优化先判断checked避免反选再用 DOMclick()触发。6.4、坑四分类专栏误匹配设置值MavenGradle用「包含」反向匹配时被宽松匹配到子类Maven。优化匹配改为评分制精确 已有包含设置值 设置值包含已有实测正确命中二级项# MavenGradle。6.5、坑五首图列表异步加载刚点开发布弹窗就找首图项经常是空的。优化正文含图片时按数量估算等待时间给足图片上传/解析时间再点发布。6.6、坑六删除「假成功」CSDN 确认弹窗类名是.el_mcm-message-box带下划线前缀el_mcm而选择器写成.el-message-box导致「确定」根本没点到却返回了DELETED。优化修正确认按钮为.btn-msg-confirm并增加结果校验——回查列表该行必须消失否则返回DELETE_UNVERIFIED。重点回顾这些坑规律高度一致——CSDN 的元素要么隐藏、要么异步、要么类名带下划线。应对手段也只有两条先触发展示再定位、关键动作后回读校验。七、验证测试把不稳定关进笼子7.1、mock 单测用test/helpers/mock-page.js模拟 Playwrightlocator/click/fill/hover/filter/waitFor/evaluate等子集无需真实浏览器即可对每个工具做行为验证npmtest# 全部单测核心 所有渠道npmrun test:core# 仅核心CLI / markdown / channel 加载npmrun test:channels# 仅渠道csdn / gzh覆盖 CSDN 的checkLogin / enterEditor / setTitle / setContent / preparePublish / publish / saveDraft / deleteBlog等每一个脚本工具。7.2、真实链路自测csdn test publish执行「真实发布 → 立即删除」验证完整链路且不留残留内容。重点mock 单测守逻辑真实链路自测守改版两者缺一不可。八、总结CSDN 分发 Skill 是 BlogLoom 多渠道分发的第一块、也是踩坑最全的一块拼图。核心收获可以浓缩为登录判定要交叉验证别把匿名 Cookie 当登录凭证隐藏元素先触发再定位标题、标签输入框都要先点出展示态异步加载要估算等待图片越多等待越久匹配用评分制避免误配宽泛子类一切关键动作都要回读校验拒绝「假成功」。这套「Playwright 主操作 CDP 底层增强 一渠道一 SOP 选择器独立 回读校验」的 CSDN 方案可以直接复用到掘金、公众号等其他渠道。项目地址https://github.com/changluya/BlogLoom Skill 目录https://github.com/changluya/BlogLoom/tree/master/skills/blogloom-publisher-skill参考资料[1]. Playwright 官方文档Locators、Actions[2]. Chrome DevTools ProtocolInput.insertText / Network.getCookies[3]. Playwright connectOverCDP复用已有浏览器登录态整理者长路 创建时间2026.10.6 更新时间2026.10.6
返回列表