从零开发一个掘金自动发布 Skill,并上架 Clawhub
本文记录了一次完整的 Skill 开发旅程:从一句「帮我创建一个可以自动发布文章到掘金的 skill」开始,到最终成功上架 Clawhub,全程真实还原每一个关键决策和踩坑过程。
背景:为什么要做这个 Skill?
我日常运营一个 AI 资讯账号,每天需要将 Markdown 格式的文章发布到多个平台,包括微信公众号、小红书、掘金等。其中微信公众号和小红书已经有现成的 Skill 可以用,但掘金没有。
每次发布掘金都要:
- 打开浏览器,登录掘金
- 进入编辑器,粘贴 Markdown 内容
- 手动设置分类、标签、摘要、封面
- 点击发布
这套流程重复且机械,非常适合自动化。于是决定自己动手,开发一个 juejin-publisher Skill。
第一步:研究掘金 API
开发自动发布工具,首先要搞清楚掘金的发布接口。掘金没有公开的开发者 API 文档,但可以通过浏览器抓包来分析。
抓包分析
打开掘金编辑器,按 F12 进入开发者工具,切换到 Network 标签,然后正常发布一篇文章,观察请求:
发现关键的两个接口:
① 创建草稿
bash
POST https://api.juejin.cn/content_api/v1/article_draft/create
请求体(JSON):
json
{
"category_id": "6809637773935378440",
"tag_ids": ["6809640445233070098"],
"title": "文章标题",
"brief_content": "摘要,50-100字",
"edit_type": 10,
"mark_content": "# Markdown 正文...",
"cover_image": "",
"html_content": "deprecated",
"link_url": "",
"theme_ids": []
}
② 发布草稿
bash
POST https://api.juejin.cn/content_api/v1/article/publish
请求体:
json
{
"draft_id": "7xxxxxxxxxxxxxx",
"sync_to_org": false,
"column_ids": [],
"theme_ids": []
}
鉴权方式
掘金使用 Cookie 鉴权,只需在请求头中带上登录后的 Cookie 即可。Cookie 有效期约 30 天,其中最关键的字段是:
ini
sessionid=69c4b5312172d146beea98ddfabf5bd6
注意事项
edit_type: 10代表 Markdown 模式(富文本模式为0)brief_content必须在 50-100 字之间,否则接口报错tag_ids是数组,category_id是字符串
第二步:设计 Skill 结构
参考已有的 wechat-mp-publisher Skill 的目录规范,设计如下结构:
bash
skills/juejin-publisher/
├── SKILL.md # Skill 主文档(含 frontmatter 元数据)
├── README.md # 快速说明
├── _meta.json # 本地元数据
├── example.md # 文章格式示例
├── juejin.env.example # 配置文件模板
├── scripts/
│ ├── publish.py # 核心发布脚本
│ └── query_tags.py # 标签 ID 查询工具
└── references/
├── category_ids.md # 常用分类 ID 参考
└── tag_ids.md # 常用标签 ID 参考
设计原则:
- 核心逻辑用 Python 标准库实现,零依赖安装
- 支持 Markdown frontmatter,让文章自带元数据
- 提供 配置文件模板,降低上手门槛
- 附带 标签查询工具,解决 tag_id 难以记忆的问题
第三步:编写核心发布脚本
3.1 配置加载
从 juejin.env 文件读取 Cookie 和默认配置:
python
def load_config():
"""从 juejin.env 加载配置"""
config = {}
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("#") or "=" not in line:
continue
# 支持 export KEY="VALUE" 和 KEY="VALUE" 两种格式
line = line.removeprefix("export").strip()
key, _, val = line.partition("=")
val = val.strip().strip('"').strip("'")
config[key.strip()] = val
return config
3.2 Markdown 解析
支持从文章头部的 YAML frontmatter 中读取标题、摘要、封面、分类、标签:
python
def parse_markdown(filepath):
"""解析 Markdown 文件,提取 frontmatter 和正文"""
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
meta = {}
body = content
# 提取 YAML frontmatter
fm_match = re.match(r"^---\s*\n(.*?)\n---\s*\n", content, re.DOTALL)
if fm_match:
fm_text = fm_match.group(1)
body = content[fm_match.end():]
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"').strip("'")
# 从正文提取标题(取第一个 # 标题)
if "title" not in meta:
title_match = re.search(r"^#\s+(.+)$", body, re.MULTILINE)
if title_match:
meta["title"] = title_match.group(1).strip()
return meta, body.strip()
3.3 自动生成摘要
掘金要求摘要 50-100 字,脚本自动处理:
python
def generate_brief(meta, body, min_len=50, max_len=100):
"""生成符合掘金要求的摘要(50-100字)"""
if "description" in meta:
brief = meta["description"]
if min_len <= len(brief) <= max_len:
return brief
if len(brief) > max_len:
return brief[:max_len]
# 从正文提取纯文本生成摘要
plain = re.sub(r"```.*?```", "", body, flags=re.DOTALL)
plain = re.sub(r"[#*`>\[\]!]", "", plain)
plain = re.sub(r"\s+", " ", plain).strip()
return plain[:max_len]
3.4 API 调用封装
使用 Python 内置的 urllib 实现 HTTP 请求,无需安装 requests:
python
def api_post(path, data, cookie):
"""发送 POST 请求到掘金 API"""
url = f"https://api.juejin.cn{path}"
payload = json.dumps(data).encode("utf-8")
headers = {
"Content-Type": "application/json; charset=utf-8",
"Cookie": cookie,
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...",
"Referer": "https://juejin.cn/",
"Origin": "https://juejin.cn",
}
req = urllib.request.Request(url, data=payload, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read().decode("utf-8"))
3.5 完整发布流程
python
def main():
# 1. 解析命令行参数
args = parse_args()
# 2. 加载 Cookie 配置
config = load_config()
cookie = config["JUEJIN_COOKIE"]
# 3. 解析 Markdown 文件
meta, body = parse_markdown(args.file)
title = meta.get("title", "无标题")
brief = generate_brief(meta, body)
# 4. 创建草稿
draft_id = create_draft(title, body, brief, category_id, tag_ids, cover_image, cookie)
# 5. 发布(除非指定 --draft-only)
if not args.draft_only:
article_id = publish_draft(draft_id, cookie)
print(f"🎉 文章已发布: https://juejin.cn/post/{article_id}")
使用示例
Markdown 文章格式(支持 frontmatter):
markdown
---
title: 我的技术文章标题
description: 这里是文章摘要,建议 50-100 字,会显示在文章列表中
cover: https://example.com/cover.jpg
category_id: "6809637773935378440"
tag_ids: "6809640445233070098,6809640408797167623"
---
# 正文内容开始
这里是 Markdown 正文...
命令行调用:
bash
# 基本发布
python3 scripts/publish.py article.md
# 指定分类和标签
python3 scripts/publish.py article.md \
--category "6809637773935378440" \
--tags "6809640445233070098"
# 仅创建草稿
python3 scripts/publish.py article.md --draft-only
第四步:编写 SKILL.md 规范文件
Skill 的核心是 SKILL.md,它既是使用文档,也是 AI 理解该 Skill 的"说明书"。文件头部需要包含 frontmatter 元数据:
yaml
---
name: juejin-publisher
version: 1.0.0
license: MIT
description: 掘金文章自动发布技能。通过掘金官方 API(Cookie 鉴权),
支持将 Markdown 文章一键发布到稀土掘金平台,支持设置分类、标签、摘要和封面图。
metadata:
openclaw:
emoji: "⛏️"
category: publishing
clawdbot:
emoji: "⛏️"
requires:
bins: ["python3", "curl"]
install: []
---
关键字段说明:
name:Skill 的唯一标识,发布到 Clawhub 后即为 sluglicense:必填,Clawhub 发布时服务端会校验此字段(踩坑记录见下方)metadata.clawdbot.requires.bins:声明运行时依赖的系统命令
第五步:发布到 Clawhub
5.1 安装 clawhub CLI
bash
npm install -g clawhub
安装完成后验证:
bash
clawhub --version
# 0.7.0
5.2 登录
由于是在无 UI 的 Linux 服务器上操作,使用 Token 登录模式:
bash
clawhub login --token clh_xxxxxxxxxxxxxxxxxx
Token 获取方式:登录 clawhub.ai → Settings → API Tokens → 创建新 Token。
5.3 发布 Skill
bash
clawhub publish /data/workspace/skills/juejin-publisher --version 1.0.0
5.4 踩坑:acceptLicenseTerms 报错
第一次发布时遇到报错:
swift
Error: acceptLicenseTerms is required
原因:Clawhub 服务端要求 payload 中包含 acceptLicenseTerms: true 字段,但 CLI(v0.7.0)的 publish.js 脚本并没有在 payload 中传递这个字段。
解决方案: 修改 CLI 源码,在 payload 中补充该字段:
bash
# 定位 publish.js 文件
find /usr/local/node/lib/node_modules/clawhub/dist/cli/commands/ -name "publish.js"
# 使用 sed 在 payload 对象中插入字段
sed -i 's/form.set('\''payload'\'', JSON.stringify({/form.set('\''payload'\'', JSON.stringify({ acceptLicenseTerms: true,/' \
/usr/local/node/lib/node_modules/clawhub/dist/cli/commands/publish.js
修改后重新发布,成功!
另一个解决方案 (更优雅):在 SKILL.md frontmatter 中添加 license: MIT 字段,部分版本的 CLI 会据此自动处理。
5.5 发布成功
yaml
✅ Skill published successfully!
Name: juejin-publisher
Version: 1.0.0
Version ID: k97d5hg42cnk2yhzgjswhx8jc982n3nv
Publisher: @devilWwj
现在任何人都可以通过以下命令安装使用:
bash
clawhub install juejin-publisher
完整目录结构
bash
skills/juejin-publisher/
├── SKILL.md # Skill 主文档 + AI 使用说明
├── README.md # 快速说明
├── _meta.json # 本地元数据
├── example.md # 文章格式示例(含 frontmatter)
├── juejin.env.example # 配置文件模板(需复制为 juejin.env)
├── scripts/
│ ├── publish.py # 核心发布脚本(~200行,纯标准库)
│ └── query_tags.py # 标签 ID 查询工具
└── references/
├── category_ids.md # 常用分类 ID 速查表
└── tag_ids.md # 常用标签 ID 速查表
常用分类 ID 速查
| 分类名称 | category_id |
|---|---|
| 前端 | 6809637767543259144 |
| 后端 | 6809637769959178254 |
| AI | 6809637773935378440 |
| 工具 | 6809637771511070734 |
| Android | 6809635626879549454 |
| iOS | 6809635627209637895 |
总结与思考
这次 Skill 开发的整体流程:
需求分析 → 抓包研究 API → 脚本实现 → Skill 规范封装 → 上架 Clawhub
几个关键经验:
-
掘金 API 不难逆向:浏览器抓包 + 观察请求体,基本上两个接口就能搞定发布全流程
-
摘要长度是坑 :
brief_content必须 50-100 字,太短或太长都会报错,自动生成摘要时要处理边界情况 -
零依赖设计更健壮 :用 Python 标准库的
urllib代替requests,避免在不同环境中出现依赖缺失的问题 -
Clawhub 发布需要
license字段 :SKILL.mdfrontmatter 中必须声明license,否则服务端校验不通过 -
Skill 的价值在于可复用:封装好的 Skill 不仅自己用,还能分享给社区,一次开发多人受益
相关链接
- 掘金编辑器:juejin.cn/editor/draf...
- Clawhub:clawhub.ai
- juejin-publisher Skill:
clawhub install juejin-publisher
如果你也在做内容自动化,欢迎交流 👋