我把「自动发文章到掘金」做成了一个 Agent 技能:juejin-skills
仓库地址:github.com/liuleihang/... (MIT,Node ≥ 18,发布脚本零依赖)

我平时写技术文章的工作流是:本地 Markdown 写作 → 贴到掘金编辑器 → 手动传图 → 手动贴标签 → 点发布。一篇文章的「搬运」环节要花好几分钟,而且图片一张张拖上传最烦。
于是我做了这件事:把「发文章到掘金」封装成一个 Agent 技能------在 WorkBuddy、Claude Code、Cursor 这些 AI Agent 里说一句「把 ./article.md 存为掘金草稿,分类前端,标签 Vue.js」,它就自己完成登录态检查、标签 id 解析、图片上传、正文改写、建草稿,最后把草稿链接递给我确认。
这篇文章复盘实现过程中的几个关键点:非官方接口的抓包适配、字节 ImageX 三段式图床上传、以及「草稿优先」的安全设计。
整体结构
Agent Skill 的结构很简单:一份 SKILL.md 描述能力与工作流(Agent 据此自主调用),加一组零依赖的 Node 脚本:
bash
juejin-skills/
├── SKILL.md # 技能定义:frontmatter + 工作流 + API 参考
├── README.md
└── scripts/
├── config.mjs # 配置集中管理(~/.juejin/config.json 可覆盖)
├── login.mjs # Playwright 弹浏览器人工登录,Cookie 落盘
├── check.mjs # 会话体检
├── tags.mjs # 分类/标签全量翻页查询与缓存
├── upload-images.mjs # ImageX 三段式传图 + 正文改写
└── publish.mjs # frontmatter 解析 + 自动传图 + 草稿/发布
设计上有两条硬约束:
- 发布脚本零依赖 ------只用 Node ≥ 18 的原生
fetch和crypto,只有login.mjs需要 Playwright(首次登录时引导安装); - 凭据外置 ------Cookie 只存
~/.juejin/session.json,绝不入库、绝不出现在日志里。
关键点一:非官方接口的抓包适配
掘金没有公开 API 文档,接口只能抓包 + 实测。过程中踩到的「文档根本没有、只能实测才知道」的坑:
旧接口下线 。网上能搜到的教程里图片上传用的是 POST /images_upload/v1/create------实测已经 404 下线 了。标签查询的 query_category_tags 同样下线,返回「请求路由不存在」,现行的翻页接口换成了 query_tag_list。
cate_id 参数是摆设 。query_tag_list 带 cate_id 过滤参数,但实测根本不过滤,只能全量翻页拉回来(约 700+ 个标签)自己缓存匹配:
js
// {cursor:"0", limit:100} 翻页,data 元素形如 {tag_id, tag:{tag_name}}
// cate_id 实测不过滤,只能全量拉取后本地缓存
所以 tags.mjs 的策略是全量翻页一次,缓存到 ~/.juejin/tag-ids.json,之后按名查 id 走本地缓存。但缓存不能一劳永逸------掘金随时可能新增或改名标签,陈旧缓存会让发布流程卡死在「找不到标签」。所以缓存文件记录了拉取时间,超过 7 天(可配置)自动重新全量刷新;发布时如果标签解析未命中,也会先强制刷新自愈一次,仍然找不到才报错。
摘要长度是硬校验 。brief_content 不在 50--100 字之间接口直接拒绝。脚本会在缺失或超限时自动补齐(截取正文前 N 字),但更建议在 frontmatter 里显式写 brief。
永远原样上报 err_no 。非官方接口随时可能再变,脚本对 err_no != 0 只做原样报告,不猜测、不盲目重试------把判断权留给上层(Agent 或人)。
关键点二:字节 ImageX 三段式图床上传
图片上传是整个技能里最复杂的一环。掘金编辑器现在走的是字节 ImageX 的三段式链路:
ini
1. GET /imagex/v2/gen_token?aid=2608&uuid=<19位数字>&client=web
(带 Cookie)→ 拿到 STS 临时凭证
2. ApplyImageUpload(imagex.bytedanceapi.com)
?Action=ApplyImageUpload&Version=2018-08-01&ServiceId=73owjymdk6
→ AWS SigV4 签名请求,SignedHeaders=x-amz-date;x-amz-security-token
→ 返回 StoreUri / StoreInfos(含 Auth 串)/ uploadHost
3. POST https://<uploadHost>/<StoreUri>
头:authorization: <StoreInfos[0].Auth>、Content-CRC32: <8位hex>
→ 直传文件内容
4. CommitImageUpload(同一个 SessionKey)
→ 偶发 201007 bad gateway,脚本自动重试一次
5. GET /imagex/v2/get_img_url?...&img_type=private
→ data.main_url 即编辑器同款签名 URL,写进正文即可
三个值得记录的细节:
AWS SigV4 要自己签 。第 2 步的 ApplyImageUpload 不是普通 JSON 接口,是 AWS 风格签名请求。Credential 形如 AK/<yyyymmdd>/cn-north-1/imagex/aws4_request,用 Node 原生 crypto 手撸 HMAC 链即可,不需要引 AWS SDK。
缺 CRC32 头会报 MismatchChecksum 。第 3 步直传必须带 Content-CRC32 头(8 位 hex,小端序的 IEEE CRC32),否则服务端返回 4007 校验失败。这是全网几乎搜不到答案的坑,对照编辑器请求才定位到。
上传完的 URL 是 img_type=private 的签名 URL ,写进 mark_content 之后掘金渲染层会动态重签,所以不用担心签名过期------实测发布后图片全部 HTTP 200。
publish.mjs 会自动扫描正文里的行内图片 ,逐张走上面这条链路,把返回的 CDN URL 原地改写进 Markdown。任何一张失败就中止发布------宁可发不出去,不发一篇带死链的文章。
关键点三:草稿优先 + 双标志互锁
让 Agent 替你发文章,最大的风险是「它理解错了但已经发出去了」。所以安全设计放在第一位:
css
默认行为:node publish.mjs article.md → 只建草稿,返回草稿链接
公开发布:node publish.mjs article.md --publish --yes → 缺一不可
- 草稿优先 :Agent 的工作流是「先建草稿 → 把链接给用户看格式 → 用户明确确认 → 才允许加
--publish --yes」; - 双标志互锁 :
--publish和--yes必须同时出现,互锁写在脚本里而不是提示词里------提示词可能被改,代码不会被; - 单次一篇:一次调用只处理一个文件,不做无人值守批量发布(频控与风控风险)。
这个思路对所有「让 AI 代替我执行对外发布类操作」的场景都适用:把危险动作拆成两段,中间必须有人确认,且确认机制放在代码层。
怎么用
把仓库 clone 到本地,放进任意 Agent 的技能目录(Windows / macOS / Linux 均可,发布脚本只依赖 Node ≥ 18),首次使用时说「登录掘金账号」完成一次扫码登录(Cookie 约 30 天有效),之后:
| 你说 | 它做 |
|---|---|
| 「把 ./article.md 存为掘金草稿,分类前端,标签 Vue.js」 | 体检会话 → 查标签 id → 传图 → 建草稿 → 给你链接 |
| 「把这篇文章里的本地图片上传到掘金图床」 | 只跑传图 + 正文改写 |
| 「确认无误,发布这篇草稿到掘金」 | 公开发布(你会看到它复述标题/分类/标签) |
脚本也可以脱离 Agent 单独用,比如只传图:
bash
node scripts/upload-images.mjs article.md # 输出 article.juejin.md
node scripts/upload-images.mjs article.md --in-place # 原地改写(自动留 .bak)
后续功能待办
当前版本只做了文章发布这一条链路。技能包的定位是「掘金社区技能包」,以下功能按优先级排队中:
| # | 功能 | 状态 | 说明 |
|---|---|---|---|
| F1 | 文章发布(含本地图片上传) | ✅ 已开源 | 本文内容 |
| F2 | 掘金热榜 | 📋 规划中 | 按分类拉热门文章榜;只读操作,无需登录 |
| F3 | 掘金沸点发布 | 📋 规划中 | 短内容 + 话题 + 配图(复用 ImageX 链路);发布前回显预览确认 |
| F4 | 掘金签到 | 📋 规划中 | 每日签到领矿石,幂等设计(已签不重签) |
| F5 | 文章下载(单篇) | 📋 规划中 | 掘金文章 → 本地 Markdown,保留代码块与元数据 |
| F6 | 文章批量下载 | 📋 规划中 | 备份某用户的全部文章;限速防频控 + 断点续传 |
仓库在此,欢迎 star / issue / PR:github.com/liuleihang/...