一份 Markdown 发十个平台:cspub 一稿多投

15 阅读1分钟

写技术博客的人多半都经历过这个循环:文章在本地 Markdown 里写得干干净净,往平台编辑器里一贴,图片全裂了。CSDN 对站外图有防盗链,<img> 直连外链图床返回的是 HTTP 200 + 0 字节——状态码挺健康,页面上只剩一个占位框。于是下载、上传、替换链接、重查排版,文章五分钟写完,图片折腾半小时。

这还只是一个平台。等你开始"一稿多投",CSDN、知乎、掘金、开源中国、百家号、博客园、腾讯云开发者社区、语雀、头条号、SegmentFault 思否,每家编辑器各有各的脾气:有的要传它自己的图床,有的悄悄剥掉站外图,有的接口必须带签名,有的不开浏览器根本发不出去。十个平台贴十遍,一个晚上就没了。

所以我把这件事做成了一个命令行工具:cspub。用华为仓颉写的,主链路零第三方依赖。

cspub publish 我的文章.md --platform csdn       # CSDN(Cookie + 网关签名,默认发草稿)
cspub publish 我的文章.md --platform zhihu      # 知乎专栏(Cookie 登录态,图片自动转知乎 CDN)
cspub publish 我的文章.md --platform baijiahao  # 百家号(Cookie 浏览器通道 / 开发者凭证)
cspub publish 我的文章.md --platform cnblogs    # 博客园(官方 MetaWeblog API,纯 HTTP)
cspub publish 我的文章.md --platform juejin     # 掘金(Cookie 两步流;图片外链优先,AtomGit 图床)
cspub publish 我的文章.md --platform oschina    # 开源中国(Cookie 直通站点接口;图片走它自带图床)
cspub publish 我的文章.md --platform tencent    # 腾讯云开发者社区(Cookie 零签名)
cspub publish 我的文章.md --platform yuque      # 语雀(官方 OpenAPI v2 + Token)
cspub publish 我的文章.md --platform toutiao    # 头条号(Cookie + X-CSRFToken)
cspub publish 我的文章.md --platform segmentfault  # SegmentFault 思否(/gateway 网关 + 页面取 Token)
cspub publish 我的文章.md --live                # 加 --live 直接发布,不带就是发草稿

下面把它怎么用、背后做了什么、以及哪些坑你得提前知道,一次说清楚。

cspub 发文数据流

一条命令背后是什么

publish 不是简单地把文件 POST 出去,背后是一条六个环节的流水线:

  1. 读 Markdown + front-matter(title / tags / categories / desc / draft / origin_url);
  2. 按平台自动转存图片——CSDN 外链图转 CSDN 图床(防盗链),知乎图转知乎 CDN(知乎会剥离站外图),头条号强制全量转存,开源中国用它自带图床;
  3. 把 Markdown 渲染成 HTML——各平台编辑器收的都是渲染后的 HTML,不是 Markdown 源文,直接发原文页面会显示字面 ## 和 ![]();
  4. 按平台鉴权发文:CSDN 走阿里云 API 网关风格的 HMAC-SHA256 签名(x-ca-* 请求头)+ Cookie,知乎走 Cookie 草稿接口三步流,语雀走官方 OpenAPI v2,博客园走 MetaWeblog,SegmentFault 走 /gateway 网关 + 从写文章页实时取 Token;
  5. 回显文章链接和 id(CSDN 支持用 --update <id> 原地更新)。

主链路全部用仓颉标准库 + stdx 实现——HMAC、Base64、HTTP、JSON 这些 stdx 恰好齐活,没有依赖树要维护,cjpm build 出来就是一个可执行文件。唯一的例外是百家号 Cookie 通道,需要一个 Playwright 伴生脚本(原因后面讲)。

装它:从源码构建

需要仓颉 SDK 1.2.0 + 配套 stdx:

git clone https://atomgit.com/qq8864/cspub.git
cd cspub

export CANGJIE_STDX=<你的 stdx>/dynamic/stdx
export PATH=$CANGJIE_HOME/bin:$CANGJIE_HOME/tools/bin:$PATH

cjpm build
# 产物:target/release/bin/main

直接跑二进制报 libstdx.encoding.json.so 缺失的话,补一下动态库路径就好,不用重编译:

export LD_LIBRARY_PATH=$CANGJIE_STDX:$CANGJIE_HOME/runtime/lib/linux_x86_64_cjnative:$CANGJIE_HOME/third_party/llvm/lib

Windows 侧仓库里有 ./scripts/winbuild.sh 交叉编译,产出 exe 加一个解压即用的自包含 zip。

配凭证:唯一麻烦的一步,我帮你省到点三下

十个平台的鉴权方式没有一个是统一的,这是整件事最烦的部分,我没法消除它,只能帮你省事。

先说结论:用 cookie-export 提取,别 F12 手抄。

登录态里最关键的那几个字段——知乎的 z_c0、掘金的 sessionid、CSDN 的 UserToken——多半是 httpOnly,document.cookie 根本读不到;F12 手抄还容易漏字段、少分号,抄完发现 401,再回去猜哪个字段错了。

cookie-export 工具界面

cookie-export 是我在 cj-tauri 仓库里写的桌面小工具,流程就三步:内嵌浏览器登录一次 → 点提取(按 cspub 各平台的字段硬需求逐项校验,缺哪个直接点出来,比如「已提取,但缺必需字段:UserToken」)→ 点导出,写进 ~/.cspub/config.json(chmod 600,合并写,不会覆盖其他平台的凭证)。全程本机运行,不上报任何 cookie。

不想用工具也行,环境变量和 config.json 二选一,环境变量优先:

export CSPUB_CSDN_COOKIE='<浏览器 F12 复制的 Cookie 整串>'
export CSPUB_ZHIHU_COOKIE='<须含 z_c0>'
export CSPUB_JUEJIN_COOKIE='<须含 sessionid>'
export CSPUB_OSCHINA_COOKIE='<须含 oscid>'

cspub whoami csdn      # 校验凭证是否就位(只验格式不联网)

写进 ~/.bashrc 时 Cookie 整串务必用单引号包裹(里面有分号和空格)。几个特例记一下:

  • 博客园不用 cookie,走官方 MetaWeblog:后台开启「允许博客客户端访问」,拿博客名、登录名和访问令牌(不是登录密码),填 CSPUB_CNBLOGS_BLOG_ID / USERNAME / TOKEN 三个变量;
  • 语雀也走官方 Token:去设置页生成,namespace 取知识库 URL 的两段(user/repo,写一段一定 404);
  • 腾讯云的 skey 里的 * 原样保留,不要 URL 编码;tencentTagIds 必填,空了接口回 -9993,工具会本地先拦下;
  • 掘金想要最顺的图片链路,配一个 CSPUB_ATOMGIT_TOKEN(AtomGit 个人访问令牌),本地图自动传 AtomGit 图床、正文嵌外链,不再依赖浏览器。

一篇文章长什么样

front-matter 就五个字段,都很直白:

---
title: 文章标题            # 必填
tags: 仓颉, 工具           # 逗号/顿号分隔,最多 5 个(API 上限)
categories: 编程
desc: 摘要一行             # 不写就从正文剥 Markdown 取前 200 字
draft: true                # 缺省 true;publish 加 --live 可覆盖
---

正文 Markdown……

发文前先预演:check / html / imgs

这三个命令都不联网,是我的"发文前安检":

cspub check   文章.md   # 解析 front-matter + 渲染统计(标题数、图片数、代码块行数)
cspub html    文章.md   # 打印渲染后的 HTML,肉眼确认排版
cspub imgs    文章.md   # 预演图片去向:rehost / skip / BLOCKED

imgs 值得单独说。它会列出正文里每张图会被如何处理:rehost(要转存)、skip(平台图床或 data:,跳过)、BLOCKED(内网/回环地址,拦截)。发出去才发现图裂了,和发之前就知道,是两种完全不同的心情。

然后就一条命令:

cspub publish 文章.md --platform csdn                 # 默认发草稿
cspub publish 文章.md --platform csdn --live          # 直接发布
cspub publish 文章.md --platform csdn --update <id>   # CSDN 原地更新

注意 --platform 要放在子命令后面,写前面会报「未知命令或参数不足」。另外建议默认走草稿——先去后台看一眼排版,没问题再 --live。

十个平台的脾气

同样是"发文章",十家的实现路径完全不同。这张表是实测出来的,发之前值得扫一眼:

平台鉴权方式图片策略你要知道的脾气
CSDNCookie + HMAC-SHA256 网关签名外链图强制转 CSDN 图床站外图直连 200+0 字节;每天限 1 篇(草稿同算)
知乎Cookie 三步流转知乎 CDN知乎直接剥离站外图,不转存就是裂图
掘金Cookie 两步流外链优先(AtomGit 图床)接受外链图,配了 AtomGit Token 全程不用浏览器
开源中国Cookie 直通站点接口它自带图床正文提交 Markdown 服务端自己转,别预渲染;tags 平台自动提取
百家号Cookie 浏览器通道 / 开发者凭证伴生脚本处理唯一必须开浏览器的:React 发布按钮需完整事件链,直调 API 缺 CSRF 必 500
博客园MetaWeblog 访问令牌接口原生处理最省心的一家,纯 HTTP,零浏览器
腾讯云纯 Cookie 零签名转腾讯云图床字段语义全是坑:sourceType 必须 1,原创不能带 sourceDetail,专栏必须是自己已加入的
语雀OpenAPI v2 + TokenMarkdown 原样提交少数真有官方 API 的,反而最省事
头条号Cookie + X-CSRFToken强制全量转存头条 CDN任何非头条域名图都让整篇被拒(7115 图片uri非法);标题 5~30 字
SegmentFaultCookie + 页面实时取 Token需公网 URLToken 从写文章页 __NEXT_DATA__ 现取,不用手工填

有意思的规律是:越是"大厂"的平台,接口越反直觉。CSDN 的签名看着唬人,其实逻辑很干净;百家号反过来,接口看着简单,实际上非走浏览器不可。

图片:真正费劲的地方,工具替你扛了

如果说发文接口是"一个平台一套鉴权",图片就是"一个平台一套生态"。cspub 的策略是按平台分流,但底线是共用的:

  • 内网、回环、链路本地地址直接拦截——防止把本机服务当图源发出去;
  • 下载外链图拒绝 30x 重定向——重定向可能把请求带到没检查过的主机;
  • 单张上限 12 MiB;
  • best-effort:单张失败保留原链接并打 ⚠⚠ 提示,不阻断发文。写文章已经够累了,不该因为图床抽风丢一次草稿。

想要更严格,设 CSPUB_STRICT_IMAGES=1,转存后仍有未处理的本地图就中止发布(返回码 1)。

小技巧:正文里的图片写本机绝对路径最稳(跳过下载直接上图床);相对路径按当前工作目录解析。代码围栏里的 ![]() 是示例文本,自动跳过。

排查发文链路问题先开 CSPUB_DEBUG=1,stderr 会打请求体积、上游状态码和响应首段。

三个踩过的坑,值得单独说

其一:JSON 字段必须按类型取。 CSDN 图床签名响应里的 customParam.isAudit 是 JSON 数字,我用只认字符串的函数去取拿到空串,把空的 x:isAudit 发给华为 OBS,OBS 回的是 HTTP 200 + 0 字节——看起来像请求被网关吞了,其实是回调拿不到字段。改法是按 JsonKind 分派取值,数字、字符串、布尔各走各的。这个坑教育我:"HTTP 200"和"成功"之间可以隔着一整个空文件。

其二:UTF-8 截断不能按字节切。 摘要截断必须判断首字节 (b & 0xC0) != 0x80 才算字符边界,否则把汉字切成半个,文章里一串乱码。行内渲染同理,要按区间整体转码,逐字节 append 会把汉字拆成非法序列。

其三:CSDN 的证书链很怪。 它的根是 Certum,一个小众 CA。实测仓颉 stdx 的 TLS 在两代版本上、默认模式和 CustomCA 模式(内嵌逐字节一致的 Certum 根)都报 unknown CA,而系统 curl 验同一条链是通过的——怀疑和 stdx 动态加载 OpenSSL 的行为有关。所以我留了 --insecure(TrustAll)逃生口,但正式发文请先试默认模式,失败再加,别默认带上。

这些坑在仓库 docs/开发文档与踩坑记录.md 里都有完整的"现象 → 排查铁证 → 修复方案 → 复用配方"记录,另外还有两份按症状索引的 skill 文档:cspub-publish(操作手册)和 cspub-troubleshoot(踩坑速查)。

已知限制,先告诉你

  • 表格、脚注、任务列表这些扩展 Markdown 不渲染(原样进段落),内置渲染器只覆盖标题/段落/列表/引用/代码围栏/图片/链接/行内代码/加粗——拿不准就先 cspub html 目验;
  • --update 只有 CSDN 支持,其他平台改内容得删了重发;
  • 开源中国"发布成功 ≠ 立刻公开":接口回了 id,公开页可能还在审核(新号首篇大概率人工审核),而且它的 SPA 外壳对任意 id 都回 200,curl 判不出文章死活,要用浏览器渲染判断;
  • CSDN 每日 1 篇的限额是平台规则不是工具 bug,错误码 400300012 出现就换自然日;
  • Windows 只验证了交叉编译产物,发文链路没在真机 Windows 上跑过。

最后

cspub 对我来说不只是个效率工具,它也是个用仓颉练手网络编程的真实项目——从 HMAC 签名逐字节对账、TLS 信任、UTF-8 字节语义,到十套平台鉴权,每一处都是真刀真枪踩出来的。开发过程本身是在我的另一个项目 cjh(一个用仓颉写的终端 Coding Agent)里完成的,工具先给自己用上了。

如果你也在一稿多投,欢迎拿去用、提 issue,或者把某个平台的适配改得更顺手。

相关链接:

许可证 MIT,随手拿去用。