CSDN 自动发布全流程实战:从 CKEditor HTML 注入到创作页就地发布
AI工具人PM 的实战笔记:前两篇讲了搭建发布实战和踩坑实录,这篇深入 CSDN 的最难环节——自动发布到 CSDN。你会发现它的坑比知乎和掘金加起来还多。
起因:为什么 CSDN 最难?
掘金有现成的 content_api(两接搞定),知乎虽然编辑器反自动化,但通过 webbridge 能绕过。CSDN 的问题在于三个叠加:
- 编辑器是 CKEditor 经典版——HTML 必须用
setData()API 注入,直接操作 DOM 不生效 /edit/{id}页面在 headless 下被风控重定向——自动化脚本存草稿后,跳转到编辑页会被拦截- "发布博客"不是发布按钮——它只是展开文章设置面板,真正的发布按钮是
<span class="title">发布文章</span>
更复杂的是,CSDN 非会员每天只能发 3 篇。所以自动化脚本不仅要"能发",还要知道"今天还能不能发"。
一、CKEditor 正文注入:为什么直接填 DOM 不行?
CSDN 的编辑器用的是 CKEditor Classic(内嵌在 textarea 里的版本)。很多人尝试用 CSS selector 找到编辑器 DOM 然后设置 innerHTML,发现文字写进去了但保存后是空的。
原因:CKEditor 的渲染和保存是两个独立层。你改了 DOM,编辑器内部的数据模型没有同步。
正确做法:调用 CKEditor 实例的 setData() API。
// ❌ 错误:直接改 DOM
document.querySelector('.cke_wysiwyg_frame').innerHTML = html;
// ✅ 正确:通过实例 setData
window.CKEDITOR.instances.editor.setData(html);
在自动化脚本中,先检查实例是否存在,再执行 setData,最后验证注入结果(获取 getData().length):
html_content = markdown.markdown(md_content, extensions=["fenced_code", "tables", "codehilite"])
inject_result = page.evaluate("""(html) => {
if (!window.CKEDITOR || !window.CKEDITOR.instances?.editor) {
return 'CKEditor not ready';
}
window.CKEDITOR.instances.editor.setData(html);
return 'injected';
}""", html_content)
# 验证注入成功
content_len = page.evaluate("() => window.CKEDITOR.instances.editor.getData().length")
assert content_len > 50, f"编辑器内容长度为空: {content_len}"
二、保存草稿:从网络响应中提取文章 ID
保存草稿的坑不是"点不到按钮",而是保存后不跳转。旧方案假设保存后会跳转到草稿管理页或 URL 变化,但实际上页面停留在创作页,唯一的线索是 saveArticle 接口的 JSON 响应体中的 article_id。
用 Playwright 的网络拦截抓 ID
captured_ids = []
def _capture_response(resp):
url_lower = str(resp.url).lower()
if resp.status in (200, 201) and "mp.csdn.net" in url_lower:
try:
body = resp.json()
data = body.get("data") or {}
aid = data.get("article_id") or data.get("id") or data.get("postId")
if aid and isinstance(aid, int) and aid > 0:
captured_ids.append(str(aid))
except Exception:
pass
page.on("response", _capture_response)
save_btn = page.get_by_role("button", name="保存草稿")
save_btn.click()
page.wait_for_timeout(8000)
saved_id = max(captured_ids, key=lambda x: int(x))
⚠️ 注意:save 接口的响应不一定是 JSON
部分情况下 CSDN 的 save 接口返回的是 true/false 布尔值而非对象。此时 resp.json() 会抛异常进入 except——这不代表失败,只是格式不同。关键看 saveArticle 接口是否正常返回了 article_id。
三、致命坑:/edit/{id} 页面被风控重定向
这是最容易让人误以为"发布功能没问题"的地方——存草稿和抓 ID 都是通的,但导航到 https://mp.csdn.net/mp_blog/article/edit/{id} 页面时,headless Playwright 会被 CSDN 的风控重定向到创作中心首页,返回一个「返回首页(7s)」的倒计时页面。
这意味着整个「存草稿 → 跳编辑页 → 点发布」的经典流程在 headless 模式下完全不成立。之前的方案在这个点上卡了很久。
正确解法:在创作页就地发布
既然跳转编辑页会被拦,那就不跳。在创作页完成所有步骤:
1. 填标题/标签/摘要 ✓
2. CKEditor setData 注入正文 ✓
3. 点「保存草稿」✓
4. 从 saveArticle 响应抓 ID ✓
5. 在同一页点「发布博客」→ 展开文章设置面板 ✓
6. 点「发布文章」→ 触发实际发布请求 ✓
7. 轮询校验:等待 URL 跳转到 /creation/success/{id} ✓
关键发现:创作页的 「发布文章」 按钮不在 发布博客 那个位置——它是工具栏 div.pub-btn > div.btn-slot > div.top-toolbar-box > span.title 里的 <span class="title">发布文章</span>。发布博客 只是展开右侧的内联「文章设置」面板(可见范围/原创声明/封面/标签)。
四、每日发文上限检测
CSDN 非会员每天最多发 3 篇文章。超出后点击「发布文章」不会报错,而是弹出一个确认框「当前已达发文上限,可开通会员提升额度」。
在自动化脚本中,点击「发布文章」后需要捕获页面提示。如果检测到包含"发文上限"或"提升额度"的 toast/dialog 文本,立即停止本轮后续发布,标记为额度不足:
# 点击「发布文章」后等待 3s,捕获任何弹窗提示
toast = page.evaluate("""() => {
const msgs = [...document.querySelectorAll('[class*="message"]')]
.map(e => (e.innerText||'').trim())
.filter(Boolean).slice(0, 6);
return msgs;
}""")
joined = " ".join(toast)
if "发文上限" in joined or "开通会员" in joined:
return "CSDN 发布失败: 已达每日发文上限"
配额管理:在批量发布场景下,发布脚本启动时先查询一次今日已发数(通过遍历管理页面中含今天日期的条目),限制本轮最大可发篇数。
五、Cookie 配置要点
CSDN 的 cookie 比掘金和知乎多得多(因为全站共用一个登录态)。你需要从浏览器导出一份完整的 cookie 字符串:
- 登录 mp.csdn.net
- F12 → Application → Cookies → 查看所有 cookie
- 复制格式:
uuid_tt_dd=xxx; UserName=xxx; UserInfo=xxx; UserToken=xxx; dc_session_id=xxx; ... - 放入
.env文件的CSDN_COOKIE=行
关键字段:UserToken(鉴权)、dc_session_id(会话追踪)、p_uid(用户标识)、uuid_tt_dd(设备指纹)。缺少这些字段会导致自动登录失败。
六、实战成果
自动化 CSDN 发布的完整流程跑通后的效果:
- 在 Obsidian 写好 Markdown,放入
待发布/ publish.py解析 frontmatter → 提取标题/标签/摘要/CSDN cookie- 启动 Playwright headless Chromium → 加载 cookie → 登录 mp.csdn.net
- 打开创作页 → 填充标题/标签/摘要 → CKEditor setData 注入正文
- 验证正文长度 → 点「保存草稿」→ 网络拦截抓文章 ID
- 同一页点「发布博客」展开设置 → 点「发布文章」
- 轮询校验跳转至
/creation/success/{id}→ 构建公开 URL - 回写
csdn_url+csdn_published_at→ 归档到已发布/csdn/
整个过程全程无 UI 交互,只需要说一句话。
下一篇预告:知乎 + 掘金自动发布实战——Draft.js fiber chain 和掘金 Content API 的深度踩坑
📚 这是「发布实战」系列的第 4 篇
- 上一篇:《产品经理用 AI 实现三平台一键发布:从掘金/知乎/CSDN 的横向对比到统一方案》——讲了搭建和踩坑,这篇我们继续
- 同柱推荐:
- [产品经理用 AI 搭建内容发布实战——从零到全自动]
- [产品经理用 AI 实现掘金/知乎平台文章一键发布:完整踩坑实录]
- [知乎+掘金自动发布实战:Draft.js fiber chain、签名校验与 content_api 深度踩坑]