CSDN 自动发布全流程实战:从 CKEditor HTML 注入到创作页就地发布

CSDN 自动发布全流程实战:从 CKEditor HTML 注入到创作页就地发布

AI工具人PM 的实战笔记:前两篇讲了搭建发布实战和踩坑实录,这篇深入 CSDN 的最难环节------自动发布到 CSDN。你会发现它的坑比知乎和掘金加起来还多。


起因:为什么 CSDN 最难?

掘金有现成的 content_api(两接搞定),知乎虽然编辑器反自动化,但通过 webbridge 能绕过。CSDN 的问题在于三个叠加:

  1. 编辑器是 CKEditor 经典版 ------HTML 必须用 setData() API 注入,直接操作 DOM 不生效
  2. /edit/{id} 页面在 headless 下被风控重定向------自动化脚本存草稿后,跳转到编辑页会被拦截
  3. "发布博客"不是发布按钮 ------它只是展开文章设置面板,真正的发布按钮是 <span class="title">发布文章</span>

更复杂的是,CSDN 非会员每天只能发 3 篇。所以自动化脚本不仅要"能发",还要知道"今天还能不能发"。


一、CKEditor 正文注入:为什么直接填 DOM 不行?

CSDN 的编辑器用的是 CKEditor Classic(内嵌在 textarea 里的版本)。很多人尝试用 CSS selector 找到编辑器 DOM 然后设置 innerHTML,发现文字写进去了但保存后是空的。

原因:CKEditor 的渲染和保存是两个独立层。你改了 DOM,编辑器内部的数据模型没有同步。

正确做法 :调用 CKEditor 实例的 setData() API。

javascript 复制代码
// ❌ 错误:直接改 DOM
document.querySelector('.cke_wysiwyg_frame').innerHTML = html;

// ✅ 正确:通过实例 setData
window.CKEDITOR.instances.editor.setData(html);

在自动化脚本中,先检查实例是否存在,再执行 setData,最后验证注入结果(获取 getData().length):

python 复制代码
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

python 复制代码
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 模式下完全不成立。之前的方案在这个点上卡了很久。

正确解法:在创作页就地发布

既然跳转编辑页会被拦,那就不跳。在创作页完成所有步骤:

markdown 复制代码
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 文本,立即停止本轮后续发布,标记为额度不足:

python 复制代码
# 点击「发布文章」后等待 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 字符串:

  1. 登录 mp.csdn.net
  2. F12 → Application → Cookies → 查看所有 cookie
  3. 复制格式:uuid_tt_dd=xxx; UserName=xxx; UserInfo=xxx; UserToken=xxx; dc_session_id=xxx; ...
  4. 放入 .env 文件的 CSDN_COOKIE=

关键字段UserToken(鉴权)、dc_session_id(会话追踪)、p_uid(用户标识)、uuid_tt_dd(设备指纹)。缺少这些字段会导致自动登录失败。


六、实战成果

自动化 CSDN 发布的完整流程跑通后的效果:

  1. 在 Obsidian 写好 Markdown,放入 待发布/
  2. publish.py 解析 frontmatter → 提取标题/标签/摘要/CSDN cookie
  3. 启动 Playwright headless Chromium → 加载 cookie → 登录 mp.csdn.net
  4. 打开创作页 → 填充标题/标签/摘要 → CKEditor setData 注入正文
  5. 验证正文长度 → 点「保存草稿」→ 网络拦截抓文章 ID
  6. 同一页点「发布博客」展开设置 → 点「发布文章」
  7. 轮询校验跳转至 /creation/success/{id} → 构建公开 URL
  8. 回写 csdn_url + csdn_published_at → 归档到 已发布/csdn/

整个过程全程无 UI 交互,只需要说一句话。


下一篇预告:知乎 + 掘金自动发布实战------Draft.js fiber chain 和掘金 Content API 的深度踩坑


📚 这是「发布实战」系列的第 4 篇

  • 上一篇:《产品经理用 AI 实现三平台一键发布:从掘金/知乎/CSDN 的横向对比到统一方案》------讲了搭建和踩坑,这篇我们继续
  • 同柱推荐:
    • 产品经理用 AI 搭建内容发布实战------从零到全自动

    • 产品经理用 AI 实现掘金/知乎平台文章一键发布:完整踩坑实录

    • 知乎+掘金自动发布实战:Draft.js fiber chain、签名校验与 content_api 深度踩坑

相关推荐
_Jimmy_1 小时前
Tool Calling 与 Function Calling 区别
人工智能·python·langchain
逆风飞翔的小叔1 小时前
【Python基础】Python 流程控制语句使用详解
python·python 流程控制语句·python 流程控制·python 流程控制语句使用·python 流程控制语句详解
2601_963870221 小时前
基于Python的晋江文学城热门作品数据分析与可视化
开发语言·python·数据分析
llwszx1 小时前
【Java/Go后端手撸原生Agent(第七篇):Token预算管理 + 滑动窗口上下文裁剪】
java·后端·python·agent开发·上下文工程·上下文裁剪·滑动窗口裁剪
我怎么又饿了呀2 小时前
DataWhale—量化金融(task8 最大回撤 和 仓位管理)
python·金融·量化
Black_Rock_br2 小时前
打通 PyTorch Monarch 与 ROCm:单 Controller 架构的异构算力实战
人工智能·pytorch·python·开源
其实防守也摸鱼2 小时前
Kimi K3深度测评:长文本之外的真实力
运维·开发语言·网络·人工智能·python·学习·安全
weixin_BYSJ19872 小时前
springboot3家政平台小程序--附源码00904
java·javascript·spring boot·python·django·flask·php
风痕天际3 小时前
Pytorch开发教程1——CUDA安装
人工智能·pytorch·python