一、背景:为什么单独为 CSDN 做一个分发 Skill
1.1、场景引入
在开发 BlogLoom 的多渠道分发能力时,我们遇到了一个非常具体的问题:同一篇本地 Markdown 博客,除了留在 BlogLoom 平台,还要分发到 CSDN。 CSDN 是很多 Java / 后端开发者最常打理的站外渠道。它不像自建平台那样提供稳定的发布接口,而是典型的「前端富文本编辑器 + 私有发布协议」。手动搬运的痛点非常集中:- 字段多且隐蔽:标题默认是展示态、标签/分类专栏藏在发布弹窗里、首图要等异步加载;
- 改版频繁:CSDN 前端迭代快,选择器经常变;
- 容易「假成功」:点了发布/删除,实际没生效,肉眼难发现。
blogloom-publisher-skill 里的一个独立渠道分发 Skill。
1.2、为什么单独成篇,而不是塞进开篇
开篇《BlogLoom 分发 Skill 01》讲的是跨渠道通用的骨架(Playwright + CDP 选型、三层分层、统一 CLI 契约)。而 CSDN 作为第一个、也是唯一先跑通真实发布的渠道,其 DOM 结构、弹窗交互、坑点都值得单独成篇:1.3、目标与范围
一句话描述本篇目标:把符合统一 SOP 的本地 Markdown,用一句话分发到 CSDN,且发布/删除结果可被机器校验,绝不「假成功」。涉及的完整动作:
checkLogin / login / publishDraft / publish / delete / test publish。
二、技术栈:站在通用骨架之上
2.1、Playwright 主操作 + CDP 底层增强
CSDN 分发 Skill 复用开篇确立的技术栈,不另起炉灶:2.2、目录结构
CSDN 渠道代码全部收敛在两个文件里,职责单一:selectors.js 里一个关键点:标签容器 tagContainer 已经是 .mark_selection,子选择器不要再带 .mark_selection 前缀,否则 container.locator('.mark_selection …') 永远匹配不到后代。这是踩出来的坑。
三、核心功能:CSDN 分发 Skill 能做什么
3.1、动作矩阵
3.2、登录态检测(checkLogin)
详细原理与代码见 4.1。
3.3、扫码登录(login)
3.4、发布(publish):完整发布链路
3.4.1、发布弹窗内的逐项配置
3.5、存草稿(publishDraft)
3.6、删除(delete)
3.7、链路自测(test publish)
3.8、CSDN 字段到渠道的映射
四、核心功能设计思路与原理
这一章是整篇的重点,逐段拆解 CSDN 分发 Skill 的关键机制。4.1、登录态判定:文案 + Cookie 交叉验证
问题:怎么知道当前浏览器是否已登录 CSDN? 方案:靠「首页登录入口文案」做主判定,再用「真正的登录 Cookie」做交叉验证。uuid_tt_dd 这类匿名 Cookie 当作登录凭证——CSDN 匿名态也会下发它,会导致「假阳性」(详见 5.1 的坑)。
4.2、进入编辑器与标题写入
问题:CSDN 的标题输入框是隐藏的,怎么填? 原理:标题默认显示为.article-bar__title-display(【无标题】),点击它之后才显示隐藏的 input.article-bar__title。所以要先点展示态、唤出 input、再写入并回读校验:
4.3、正文写入:清空 + 多策略粘贴
问题:CSDN 编辑器预置了欢迎模板,且富文本粘贴可能被拦截,怎么把 Markdown 正文可靠写入? 原理:四步走——- 清空:
Cmd/Ctrl + A全选,Backspace删除默认欢迎内容; - 多策略写入:按
paste → cdp → keyboard依次尝试——剪贴板粘贴最贴近真人(优先),失败用 CDPInput.insertText兜底(绕过 paste 事件拦截),最后键盘逐字; - 回读校验:每尝试一个策略就读回内容,非空才算成功;
- 图片等待:正文含图片时按图片数量估算等待时间,给 CSDN 异步上传/解析图片留足时间(否则发布弹窗的首图列表是空的)。
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 + 名称长度 分。
Maven&Gradle 不会被宽松地误配到子类 Maven。
原理二(勾选触发 DOM click):分类专栏的勾选项是隐藏的 <input type=checkbox>,普通 click() 打不到(看着选中了实际没生效)。需要先判断 checked 避免反选,再用 evaluate((el) => el.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 定位行 → 悬停行右侧「…」→ 下拉点「删除」→ 二次确认弹窗点「确定」 → 回查该行已从列表消失:
4.8、统一 CLI 契约与机器可读输出
问题:Agent / 脚本怎么稳定地调用并解析结果? 原理:所有动作统一入口,stdout 只输出一个 JSON(日志走 stderr),便于解析:status 约定:PUBLISHED(发布)/ DRAFT_SAVED(草稿)/ DRY_RUN(仅填写)/ DELETED(删除并校验)/ DELETE_UNVERIFIED(未确认)。
五、实战:发布与链路自测
5.1、干跑验证:--dry-run
发布是对外可见的写操作,建议先干跑验证字段填写无误:
status=DRY_RUN 与 publishConfig(生效的写入策略、标签回读、命中的专栏、封面是否设置等)。据此判断各字段是否写入成功。
5.2、正式发布
5.3、链路自测:csdn test publish
Mock 能测逻辑,但测不出「页面改版」。提供一个组合自测——真实发布 → 立即删除:
status=PUBLISHED_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 type=checkbox>,普通 click() 打不到。
- 优化:先判断
checked避免反选,再用 DOMclick()触发。
6.4、坑四:分类专栏误匹配
设置值Maven&Gradle 用「包含」反向匹配时,被宽松匹配到子类 Maven。
- 优化:匹配改为评分制(精确 > 已有包含设置值 > 设置值包含已有),实测正确命中二级项
# Maven&Gradle。
6.5、坑五:首图列表异步加载
刚点开发布弹窗就找首图项,经常是空的。- 优化:正文含图片时按数量估算等待时间,给足图片上传/解析时间,再点发布。
6.6、坑六:删除「假成功」
CSDN 确认弹窗类名是.el_mcm-message-box(带下划线前缀 el_mcm),而选择器写成 .el-message-box,导致「确定」根本没点到,却返回了 DELETED。
- 优化:修正确认按钮为
.btn-msg-confirm,并增加结果校验——回查列表该行必须消失,否则返回DELETE_UNVERIFIED。
七、验证测试:把不稳定关进笼子
7.1、mock 单测
用test/helpers/mock-page.js 模拟 Playwright(locator/click/fill/hover/filter/waitFor/evaluate 等子集),无需真实浏览器即可对每个工具做行为验证:
checkLogin / enterEditor / setTitle / setContent / preparePublish / publish / saveDraft / deleteBlog 等每一个脚本工具。
7.2、真实链路自测
csdn test publish 执行「真实发布 → 立即删除」,验证完整链路且不留残留内容。
重点:mock 单测守逻辑,真实链路自测守改版,两者缺一不可。
八、总结
CSDN 分发 Skill 是 BlogLoom 多渠道分发的第一块、也是踩坑最全的一块拼图。核心收获可以浓缩为:- 登录判定要交叉验证:别把匿名 Cookie 当登录凭证;
- 隐藏元素先触发再定位:标题、标签输入框都要先点出展示态;
- 异步加载要估算等待:图片越多等待越久;
- 匹配用评分制:避免误配宽泛子类;
- 一切关键动作都要回读校验:拒绝「假成功」。
项目地址:https://github.com/changluya/BlogLoom | Skill 目录:https://github.com/changluya/BlogLoom/tree/master/skills/blogloom-publisher-skill
参考资料
[1]. Playwright 官方文档(Locators、Actions) [2]. Chrome DevTools Protocol(Input.insertText / Network.getCookies) [3]. Playwright connectOverCDP:复用已有浏览器登录态整理者:长路 创建时间:2026.10.6 更新时间:2026.10.6