知乎+掘金自动发布实战:Draft.js fiber chain、签名校验与 content_api 深度踩坑
2026/7/29 21:19:09 网站建设 项目流程

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

AI工具人PM 的实战笔记:这篇是发布实战系列的最后一篇。前两篇讲了架构和 CSDN,这篇专注知乎和掘金——一个用浏览器自动化绕过 Draft.js 的反自动化设计,一个用纯 API 驱动但需要逆向签名。


起因:两个平台的自动化方式完全不同

掘金和知乎看似都是"填表发文章",但它们的技术栈差异极大:

  • 掘金是 REST API 驱动,接口文档虽然不公开但可以通过抓包逆向
  • 知乎是基于 React + Draft.js 的单页应用,内容存储在 Immutable.js 数据结构中,DOM 里看不到正文

这就导致两种完全不同的自动化策略:直接调 API vs 控制真实浏览器


一、掘金:Cookie API 自动发布的坑

掘金不需要 OAuth,只需要sessionidcookie。两个接口搞定发布:

1. POST /content_api/v1/article_draft/create → 创建草稿 2. POST /content_api/v1/article/publish → 发布草稿

坑 1:摘要长度必须 50-100 字

掘金 API 对brief_content有硬性长度要求——少于 50 个字符直接返回错误。如果你的 Markdown 文章没有明确的摘要字段,需要从正文中提取第一段有意义的文字并补齐到 50 字以上。

def get_summary(fm, body): brief = fm.get("brief_content", "") if len(brief) < 50: # 从正文提取第一段 >20 字符的文本 for line in body.split("\n"): if line.strip() and not line.startswith(("#", ">")): brief = line.strip()[:80] break # 补齐到最小 50 字 while len(brief) < 50: brief += brief[:min(50 - len(brief), len(brief))] return brief

坑 2:category_id 类型陷阱

掘金 API 返回错误信息时err_msg: "success"err_no != 0。判断成功与否应该用err_no == 0,而不是检查err_msg的内容。

另外,创建草稿时category_id必须是字符串而非整数。很多开发者踩在这个上:

// ❌ 错误 { category_id: parseInt(categoryId) } // ✅ 正确 { category_id: categoryId.toString() }

坑 3:标签映射表

掘金要求至少一个标签,且tag_ids必须使用平台已有的标签 ID(不是标签名)。解决方案是维护一个映射表:

TAG_MAPPING = { "AI": 1692, "Python": 1624, "产品经理": 3246, "自动化工具": 7187, }

这个映射表可以用「掘金标签搜索 API」定期更新。


二、知乎:与 Draft.js 的搏斗

知乎的编辑器用的是 Facebook 的 Draft.js——一个专门为 React 设计的富文本编辑器。它的核心反自动化设计在于:内容不直接在 DOM 中

Draft.js 的数据流

用户输入 → EditorState 变更 → Immutable.js Collection → DOM 渲染 ↑ DOM 只是视图层 真正的数据不在 DOM 里

textarea[contenteditable]里填文本是无效的——编辑器下次渲染时会从 EditorState 重建 DOM,你的直接修改被覆盖。

正确方案:通过 React Fiber 定位状态节点

Draft.js 的核心操作是instance.update(newEditorState)。但要从 headless Playwright 调用它,必须先找到实例——而实例藏在 React Fiber chain 里。

步骤 1:找到 title textarea 的 React fiber

var ta = document.querySelector('textarea[placeholder]'); var fiberKey = Object.keys(ta).find(function(k){return k.startsWith('__reactFiber')}); var fiber = ta[fiberKey]; // 沿 fiber chain 上行,找到有 handleChangeTitle 方法的节点 var node = fiber; var target = null; while(node){ if(node.stateNode && node.stateNode.handleChangeTitle){ target = node.stateNode; break; } node = node.return; } if(target){ ta.value = '标题'; target.handleChangeTitle('标题'); // 核心:调用 React 内部方法 target.forceUpdate(); // 强制重新渲染 }

步骤 2:找到编辑器正文的 React fiber

var root = document.querySelector('.DraftEditor-root'); var fiberKey2 = Object.keys(root).find(function(k){return k.startsWith('__reactFiber')}); var fiber2 = root[fiberKey2]; // 上行查找有 memoizedProps.editorState 的节点 var instance = null; var node2 = fiber2; while(node2){ if(node2.memoizedProps && node2.memoizedProps.onChange && node2.memoizedProps.editorState){ instance = node2.stateNode; break; } node2 = node2.return; } if(instance){ // 构造新的 EditorState 并更新 var Constructor = instance.props.editorState.constructor; var newEs = Constructor.createWithContent(emptyCs); instance.update(newEs); // 同时更新 DOM(字数统计从 DOM 读取) var editor = document.querySelector('[contenteditable="true"].public-DraftEditor-content'); if(editor){ editor.innerHTML = '<div data-contents="true">...</div>'; editor.dispatchEvent(new Event('input',{bubbles:true})); } }

坑 4:发布按钮 disabled 的条件

知乎发布按钮在以下情况下会被 disabled: - 标题为空 - 正文字数不足(约 10 个字以下) - 图片上传未完成

所以在调用instance.update()后,还需要确保 DOM 中的public-DraftEditor-content也被更新——字数统计是从 DOM 读取的,不是从 EditorState。

坑 5:两步发布流程

知乎的发布需要两次点击: 1. 第一次点「发布」→ 创建草稿(URL 变为/p/xxx) 2. 第二次点「更新」→ 真正公开发布

中间需要有 1-2 秒等待,否则第二个请求可能因为 API 未就绪而失败。


三、为什么知乎不用 Playwright?

很多人会问:"既然 CSDN 用 Playwright 成功了,知乎为什么不也用?"

因为 Playwright 启动的是全新的 Chromium 实例,没有任何登录态。知乎有严格的登录检测(x-zse-93/x-zse-96签名),逆向这些签名的成本极高。

webbridge 的优势:控制的是你已登录的真实浏览器,Cookie 天然可用,无需处理签名。代价是慢(需要 JS 注入 + 等待渲染),但对于知乎这种反自动化的编辑器来说,它是唯一可靠的方案。


四、统一调度器设计

三个平台的代码可以统一为一个调度器:

from lib.platforms import juejin, zhihu, csdn results = {} if platform == "juejin": results["juejin"] = juejin.publish(title, body, tags, brief, cookie) elif platform == "zhihu": results["zhihu"] = zhihu.publish(title, body, tags, brief, cookie) elif platform == "csdn": results["csdn"] = csdn.publish(title, body, tags, brief, cookie) # 统一归档逻辑 for plat, url in results.items(): if url.startswith("http"): archive(plat, url)

每个平台的发布函数签名完全一致(title, markdown_content, tags, brief, cookie),上层无需关心底层实现。这就是适配器模式的威力。


写在最后

这是「发布实战」系列的最后一篇。我们从搭建管线开始,到踩坑实录,再到三平台横向对比、CSDN 深度实战,终于来到了知乎+掘金的实战。

回头看这套系统的价值不在于"自动化"本身,而在于它解决了产品经理的核心痛点:知道的东西有价值,但分享成本太高。当你把发布成本降到一句话的时间,你才有精力专注于创作。

工具人 PM 的第二篇实战,讲的是「我怎么用 AI 跑通从灵感到发布的完整闭环」。


这是「发布实战」系列的完结篇。下一篇系列预告:写作管线——从灵感到内容的 AI 加工流程。


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

  • 上一篇:《CSDN 自动发布全流程实战:从 CKEditor HTML 注入到创作页就地发布》——讲了搭建和踩坑,这篇我们继续
  • 同柱推荐:
  • 产品经理用 AI 搭建内容发布实战——从零到全自动
  • 产品经理用 AI 实现掘金/知乎平台文章一键发布:完整踩坑实录
  • [产品经理用 AI 实现三平台一键发布:从掘金/知乎/CSDN 的横向对比到统一方案]

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询