每天自动发一篇博客:零手动日更的 GitHub Actions + Claude 流水线实战
想象一下,你每天早上醒来,博客里已经多了一篇新文章,而你昨晚连编辑器都没打开。这不是魔法,而是一条我每天都在跑的自动化流水线。
它的原理很简单:用 GitHub Actions 定时触发,从一个文本队列里取出主题,让 Claude 写好草稿,再自动推送到 Shopify 店铺。整个过程 90 秒左右,完全不需要人工介入。
从一段纯文本队列开始
整个系统有一个「指挥中心」,就是放在仓库根目录的 topics.txt。每行是一条待写主题,格式是「标题|写作角度」。
How I Auto-Publish a Blog Article Per Day|workflow walkthrough, queue file, dedup, API
Why I Switched My Image Pipeline to [Magnific](https://referral.magnific.com/mQMIvsh)|upscaling tests, before/after counts
有了它,你可以一次头脑风暴出两个月的内容。我现在就常备 60 条主题,未来两个月的文章都安排得明明白白。
GitHub Actions 定时触发
触发任务的是一段 YAML 配置。下面的 cron 表示每天 UTC 时间 06:00 执行,换算成中欧时间大概是早上 7 点到 8 点之间。
on:
schedule:
# 每天 UTC 06:00 自动运行
- cron: "0 6 * * *"
workflow_dispatch: # 同时支持手动点击触发
workflow_dispatch 这行很重要,它让你在 Actions 面板里点一下就能立即跑一遍。想插队发热点文章时,不用等定时任务。
这里有两个小提示:
- GitHub 的免费额度对公开仓库是不限时长的。整个发布流程跑下来只要 90 秒左右,一个月 30 次,总共也就 45 分钟,完全在免费范围内。
- GitHub 的 cron 并不精准,高峰期可能延迟 15 分钟。对博客发布来说完全无所谓,如果你需要精确到秒,才需要考虑自建 Runner。
去重:别让自己重复自己
自动化发布最可怕的不是写不出,而是重复发。如果连续两天标题几乎一样,读者信任会瞬间崩塌。
所以生成内容前,脚本会先拉取 Shopify 里所有已发布文章的标题,和队列里的第一条做模糊匹配。
// 拉取博客上已有的所有文章标题
const existing = await fetchAllArticleTitles();
// 从队列第一行提取标题部分(pipe 前面)
const candidate = queue[0].split("|")[0];
// 使用相似度算法检查是否重复
const tooSimilar = existing.some(t =>
similarity(t.toLowerCase(), candidate.toLowerCase()) > 0.72
);
if (tooSimilar) {
// 如果太相似,打印日志并从队列移除
console.log("Skipping duplicate:", candidate);
queue.shift(); // drop it
fs.writeFileSync("topics.txt", queue.join("\n"));
process.exit(0); // try again tomorrow
}
这里的 similarity 函数用的是最基础的「单词重叠率」:把两个标题拆成单词,看共同词占总不重复词的比例。作者经过 40 组旧标题测试,发现 0.72 是甜点——低于 0.6 会误杀,高于 0.85 会漏网。
发现重复后,脚本不会报错崩溃,而是把这条主题移出队列、保存文件、正常退出。第二天它会自动取下一个主题,绝不会卡住整条流水线。
另外,在发给 Claude 的 Prompt 里,我也会塞进去最近 10 篇文章的标题,明确告诉它不要写重复内容。两层保险,200 多篇下来只出现过两次「擦边球」,而且都被标题去重挡在了生成之前。
一次 API 调用,一篇 1800 字草稿
大家最好奇的部分来了:文章到底是怎么生成的?
答案是:一次请求,一次到位。没有 Agent 循环,没有工具调用,没有链式逻辑。
我把一个非常长的 System Prompt 塞给 Claude,里面规定了文章结构(TLDR 摘要、四个 H2、总结)、字数目标、语气规则、禁用词,以及必须插入的内部链接。User Message 则只有短短一行:主题 + 角度。
const res = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.CLAUDE_KEY, // 从环境变量读取 API Key
"anthropic-version": "2023-06-01", // Anthropic API 版本
"content-type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5", // 使用的模型名称
max_tokens: 4096, // 最大生成令牌数
system: SYSTEM_PROMPT, // 包含格式与风格指令的系统提示
messages: [{ role: "user", content: topicLine }] // 用户输入:仅主题与角度
})
});
API Key 放在 GitHub Secrets 里,运行时注入环境变量,代码仓库里永远看不到明文。
拿到草稿后,我还会做一次「守门」校验:
- 字数是否达标?低于下限就跳过,明天再试。
- 清除不合规的标点、货币格式。
- 扫描禁用短语。
大约有 4% 的生成会在第一次校验时被拦住,几乎都是因为字数不够。我的策略是不自动重试——重试既烧钱又可能无限循环,而队列够长,跳过一天完全不影响。
在 Prompt 里,我会附上一份内部文章列表,Claude 每次都会自动挑选至少 3 个相关链接插进正文。光是这一项优化,就显著提升了读者在网站的停留时间。
推送到 Shopify:最后一公里的自动化
草稿通过校验后,最后一步是调用 Shopify Admin REST API,把文章直接写进指定博客。
你需要准备三样东西:店铺域名、博客 ID,以及一个拥有 write_content 权限的 Admin API Token。
博客 ID 只需查一次,硬编码在代码里即可,它基本不会变。
// 向 Shopify 指定博客创建新文章
await fetch(`https://${SHOP}/admin/api/2024-01/blogs/${BLOG_ID}/articles.json`, {
method: "POST",
headers: {
"X-Shopify-Access-Token": process.env.SHOP_ADMIN_KEY, // Shopify 管理员 Token
"Content-Type": "application/json"
},
body: JSON.stringify({
article: {
title, // 文章标题
body_html: markdownToHtml(draft), // 将 Markdown 转为 Shopify 需要的 HTML
tags, // 文章标签
published: true // true 表示直接发布;false 则为草稿
}
})
});
注意 published: true。如果你想先审阅再发布,就改成 false。我最开始的三周就是跑 false,每天手动检查一遍,确认没有边缘 case 之后,才放心改成 true。
因为 Shopify 存的是 body_html 而不是 Markdown,所以推送前要用一个轻量库把 Markdown 转成 HTML。顶部的 TLDR 摘要块则以原始 HTML 形式注入,配合主题样式就能显示成漂亮的摘要框。
发布成功后,脚本会把已发布的主题从 topics.txt 里移除,并把更新后的文件提交回仓库。GitHub Actions 的 Token 拥有 contents: write 权限,所以它可以自己完成这个 Git 提交,保证下次运行时队列状态是正确的。
如果还要把文章分发到社交媒体,我只要把新文章的 URL 丢进 Buffer,后续的 Twitter/LinkedIn 排期也会自动完成。从定时触发到文章上线再到社媒分发,全程不需要人参与。
给你的建议
这套工作流每天只花几美分的 API 费用和几乎可以忽略的 GitHub Actions 分钟数。但真正的难点不是接口调用,而是guardrails(护栏):
- 去重检查:守住内容质量的第一道门。
- 字数校验:拦住太薄的草稿。
- 队列文件:把一次头脑风暴变成两个月的弹药。
如果你想亲自落地,建议按这个顺序来:
- 先建好
topics.txt,把想写的主题一次性丢进去。 - 在 Shopify 上开一个新博客,获取博客 ID。
- 先跑
published: false的草稿模式,每天人工审稿,持续两周。 - 根据你自己的历史标题,调整相似度阈值(别照搬我的 0.72)。
- 完全信任后,再把开关拨到
published: true。
复制适合你场景的代码,忽略不需要的部分,明天早上你就可以在睡梦中把文章发出去了。如果你想深入了解如何把 Claude 接入生产环境,可以参考 Claude Blueprint 里的完整架构。
