[Vibe Coding][DeepSeek]用大模型自动为博客生成分类与标签V3

V2 的四阶段流水线运行了几个月,分类体系依旧达不到理想状态:LLM 生成的中性命名缺少个人味道,类别粒度也和博客定位对不齐。V3 把 LLM 的职责从「生成体系」收缩到「从固定词表选标签」,类别分配改成零 API 的机械规则,用 taxonomy.json 固化 21 个类别和 239 个标签。本文记录这次推倒重来的完整过程:两级层级设计、四轮迁移 424 篇文章和踩到的几个坑。

为什么还要重写

V2 的四阶段流水线(stage1_extract_meta.pystage4)从 2025 年底运行了几个月。体系能用,但几个问题越来越明显:

  1. 分类由 LLM 半自动归纳,命名和粒度不受控制,实际导航效果一般
  2. 每篇新文章理论上要走一遍完整流水线,日常维护成本不低
  3. .ontology/ 缓存和四个 stage 的边界只存在于脚本里,体系演进没有单一入口
  4. LLM 生成的命名太中性,缺少个人博客的味道:同样一套分类换个博客也能用,体现不出这个博客的技术定位

2026 年 9 月决定推倒重来。这次的思路和 V2 相反:体系完全由人工设计,LLM 只负责最机械的一步——从固定词表里选标签

固定词表与机械分配

新方案的核心是 blog-taxonomy/taxonomy.json,唯一的体系事实源:

1
2
3
4
5
6
7
{
"categories": [
{"folder": "cv-image-classification", "name": "图像分类", "parent": "计算机视觉"},
{"folder": null, "name": "计算机视觉"}
],
"tags": ["Image Classification", "Loss Function", "..."]
}

两个配套脚本:

  • assign_post.py:新文章日常工具。类别按文章所在文件夹机械映射中文名(有 parent 时输出两级),零 API 成本;标签先尝试把现有标签映射到词表,能映射出 3 个以上就直接采用,不够才调用 LLM 从词表选题
  • refresh_taxonomy.py:年度体检。扫描文件夹和标签使用率,输出缺失文件夹、低频标签和 LLM 新词候选,结果写 taxonomy.review.json,绝不自动修改词表
1
2
3
4
5
# 处理指定文章(--dry-run 只预览不写回)
python blog-taxonomy/assign_post.py blog/source/_posts/xxx/新文章.md

# 年度体检(只读 + 提议,不自动修改词表)
python blog-taxonomy/refresh_taxonomy.py --no-llm

词表平时冻结。新文章只进词表选标签,年度体检后人工确认才动词表。这条规则保证体系不会被单篇文章悄悄改变。

两级层级与文件夹对应

类别支持两级路径,靠 parent 字段表达:

  • parent 的类别写入 categories: [父级, 子级]
  • 父级是虚拟类(folder: null),不允许直挂文章

最终体系是 15 个顶级类,其中 5 个两级父类:

父类 子类
计算机视觉 图像分类 46 / 图像检索 27 / 目标检测 29 / 视觉基础 14 / 视觉理解 10 / 视频理解与压缩 10
深度学习 优化与调度 10 / 训练技巧 14 / 正则化与初始化 6 / 卷积与实现 11 / CNN 理解与可视化 6
机器学习 分类器 14 / 神经网络 7 / 无监督与降维 2 / 泛化与正则化 2
模型压缩 知识蒸馏 7 / 模型剪枝 7
Z-归档 Jenkins 24 / OKR 7

文件夹名与类别名一一对应:cv-image-classificationdl-optimizationml-classifiers。文件夹就是类别的英文直译,assign_post.py 的类别映射因此完全机械,不依赖任何语义理解。

四轮人工重构记录

迁移分四轮完成,每轮先分析再动手,全部脚本化重写 front-matter

轮次 动作 范围
第一轮 类别更名(部署→工程实践、Agent→LLM 应用、目标分类→图像分类),Jenkins/OKR 归档到 Z-归档 109 篇
第二轮 计算机视觉收拢 6 个子类,模型压缩两级化,vision 拆成视觉基础/视觉理解 139 篇
第三轮 深度学习拆 5 子类、机器学习拆 4 子类,12 篇检测内容归位目标检测 84 篇
第四轮 论文汇总帖补录 3 篇论文,修复 6 篇综述帖的失效分类链接 7 篇

几个决策依据:

  1. 拆类门槛 15~20 篇:独立子类至少这个体量,边界再清晰也不拆
  2. 语义平行:同级类别不能是父子概念,「计算机视觉」和「图像分类」并列就是反例
  3. 术语以 CV 惯例为准:整图分类任务用「图像分类」,标签用 Image Classification

每轮迁移前先备份(.backup_taxonomy2/3/4),改写脚本对每篇文章断言旧值匹配才替换,保证没有误伤。

几个工程细节

分类页排序。NexT 分类页调用 list_categories(),默认按类别名排序,中文按 Unicode 码点比较。想让归档垫底,拼音 z 前缀没用(字母码点小于汉字),全角 U+FF3A)的码点大于所有常用汉字,所以父类名是「Z-归档」。

改名不破坏 URLhexo-abbrlink 源码里 abbrlink 由 title 计算(crc32),已有值且 force: false 时不会重算。文件夹随便改名、文章随便移动,只要不动 titleabbrlink,URL 完全稳定。顺带发现 _config.yml 里 abbrlink 的注释写错了:注释说基于文件名和创建时间,实际源码是 crc32.str(data.title)

DeepSeek 调用base_urlhttps://api.deepseek.com(没有 /v1),模型 deepseek-v4-pro。推理模型做机械任务要加 extra_body={"thinking": {"type": "disabled"}},否则可能返回空回复。

分类页 URL 变化。类别改名后旧分类页直接 404(比如 /categories/目标分类/),没有做重定向。分类页是次要入口,不值得为此维护 alias 插件。

小结

三次迭代,LLM 的职责一直在收缩:

  1. V1:LLM 端到端生成体系
  2. V2:人工定骨架,LLM 做分配
  3. V3:人工定体系,LLM 只从词表选标签

这个方向是对的。分类体系是作者意图的表达:博客定位、命名习惯、粒度偏好,这些决策不该外包给 LLM;标签选择才是语义识别问题,LLM 的价值恰好在这里。这次重构四轮迁移全站 424 篇文章,机械规则零 API 成本;对比 V1 时期上千次调用花不到 20 元,V3 把日常成本降到了接近零。

当然,这套方案成立的前提是词表本身设计得足够好。词表不好,机械规则只是把错误固化得更快,这正是 refresh_taxonomy.py 年度体检存在的意义。