资讯详情

Caveman内容管线:用纯文本和脚本实现极简可靠的内容管理

📅 2026/10/29 16:01:23 | 华诺云谱 👁 阅读
Caveman内容管线:用纯文本和脚本实现极简可靠的内容管理
我自己的内容管线代号叫 Caveman核心思路就四个字能省则省。我用了三年多从最初的 20 篇文章写到现在 80 多篇期间换过笔记软件、换过写作工具、试过各种静态站生成器最后留下的却是这套看上去最原始的流程——纯文本文件加一个构建脚本外加一台 Ubuntu 服务器没了。今天这篇文章我想把这个项目的完整思路、关键决策、实操步骤和踩过的坑全部摊开讲一遍。它不是什么高端架构也不是什么最佳实践的集大成者而是一套刻意保持原始、但足够可靠的内容管理方案。如果你正在被各种工具绑架、被复杂的构建流程反复折腾或者只是想找一个长期不用操心的内容承载方式这篇文章应该能给你一些真正能落地的参考。我会把为什么这样选、每一步怎么做的细节都讲清楚包括那些常规文档里不会写的坑。1. Caveman 到底是什么——项目背景与核心思路拆解1.1 名字的由来从穴居人到极简主义工作法穴居人给人的印象通常是用石头当工具、用火把照明。我把这套方案叫 Caveman其实就是自嘲在别人用各种云文档、知识库、可视化编辑器的时候我像是回到了原始时代把所有东西都存成最简单的 Markdown 文件发布靠一个几百行的脚本管理靠命令行——但恰恰是这个回到原始时代的选择让我的内容管理体验前所未有的轻松。这个想法的萌芽大概在三年前。当时我维护着一个个人内容频道文章分散在三个地方草稿在笔记软件里配图在云盘里发布用的是一个重量级的建站程序数据库和版本还经常对不上。有一次想找一篇半年前写过的文章翻遍了三个平台才凑齐文字和图片那一刻我意识到工具并没有让我更高效反而让我的内容变成了碎片。所以我给自己定了一条规矩所有内容必须能用最简单的文本形式存在目录结构就是分类体系文件名就是检索依据任何操作都必须在五步以内完成。这套方案后来被我命名为 Caveman提醒自己永远不要被工具的复杂度牵着走。它是一个内容管理方案也是一种工作哲学先保证内容和数据的绝对可靠再去谈功能、美观和自动化。1.2 它解决了什么痛点工具爆炸与维护成本失控过去两年我身边越来越多朋友陷入一种状态电脑里装了十几个内容工具手机上有四五个笔记 App写作之前先要纠结开哪个工具同步了没有会员到期了怎么办。这其实是典型的工具过度选择症。我的 Caveman 方案对这个问题的回答非常直接不选了全部用文件本身来承载。用这套方案之前我最大的痛点是维护成本。这里说的维护不只是技术维护还包括工具磨合——比如某个在线服务的编辑器改版了、某个软件的导出格式变了、某个平台的免费额度不够了这些都会打断内容生产的节奏。换成纯文本之后这些烦恼基本消失因为 Markdown 文件几乎不受任何平台绑架。哪怕再过十年我也确信还能打开这些文件因为纯文本是最通用的格式没有之一。另一个痛点是写内容的时间远少于维护工具的时间。以前我发布一篇带排版的文章要点很多次鼠标、调整格式、处理图片发布过程本身就可能花掉半小时。现在我的流程是打开文本写内容写完跑一个脚本等待三秒钟然后页面就更新了。整个过程几乎没有任何中间环节也就不存在出错的环节。1.3 适用边界什么时候该用 Caveman什么时候不该Caveman 不是万能的我必须把它的适用范围说清楚免得有人照搬之后觉得踩坑。这套方案最适应的是个人内容管理、轻量级资讯站、文档仓库以及那些需要十年二十年持续维护长期资料的项目。它的核心优势是稳定、可迁移、零平台依赖适合一个人或者两三个人维护的内容规模。但如果你需要多人实时协作、需要复杂的权限系统、需要工作流审批那 Caveman 模式就不适用了。我自己也保留了在线文档用于团队协作但那些是作为临时协作空间重要资料最终都会归档成文件。这里的原则很简单协作归协作存档归存档两者要用不同的工具但存档一定要落在最稳的载体上。所以这个项目适合的读者画像很明确你是一位内容创作者、小型站长或者有大量资料需要长期管理的人并且你受够了今天这个工具停服、明天那个格式不兼容的折腾。2. 核心细节解析Caveman 模式的五条关键原则2.1 单一数据源任何内容只存一份Caveman 的第一条原则是单一数据源。在我的整个内容管线里所有文字内容只存在于 source 目录下的 Markdown 文件中网页本身是构建出来的产物服务器上的数据库也只是缓存不是权威来源。这样的设定带来了一个巨大的好处我永远不必担心哪个版本是最新的。为了让这套原则真正落地我在目录结构上做了严格的约定。文章一律按照日期-英文短标题.md的格式命名比如 2025-06-01-caveman-guide.md。日期负责排序短标题负责语义识别整个目录打开就是一张按时间排列的索引根本不需要额外的管理界面。配图也遵循同样的逻辑统一放在和文章同名的目录下面用相对路径引用打包归档的时候不会出现图片和文字分离的情况。我把这套结构类比成家里的收纳每件东西有唯一的固定位置用完之后放回原位就永远不会找不到。很多内容管理的问题本质上不是工具不够聪明而是没有建立唯一归属的纪律。Caveman 用最朴素的方式解决了这个问题成本几乎为零。2.2 最小依赖能自己控制的不交给框架Caveman 的第二条原则是最小依赖。很多静态站生成器本身非常强大但它们会引入大量的模板语法、插件体系、配置项。我用过其中一款之后发现光是为了调整一个页面的布局就需要去翻模板文档而插件之间的版本兼容问题更是家常便饭。最后我决定不就一个简单页面吗我自己写脚本生成一百多行代码足矣。这个决定看起来很原始但它带来的收益非常直接。我的构建脚本只有一个 Python 文件没有第三方库只依赖 Python 标准库再加上系统自带的 shell 命令。这意味着无论我换到哪台电脑完全可以保证脚本能跑起来不会出现环境装不上依赖版本冲突这类问题。对个人内容站来说功能少不是缺陷反而意味着你需要排查的东西少了很多。有人可能会问我这样是不是太折腾了我反而认为这恰恰是 Caveman 最省心的地方。因为脚本是我自己写的每行代码的作用我都清楚出了问题五分钟就能定位。以前用重型框架的时候出了问题可能要花一晚上查文档、搜 issue那种维护体验才是真正的折腾。2.3 可预见性手动但确定比自动但随机强我见过太多人追求全自动化结果自动化本身成了最大的不稳定因素。定时任务莫名其妙没跑、API 变更导致对接失败、自动化脚本产生了错误但不自知——这些情况在自动化的世界里太常见了。Caveman 的选择是保持人肉驱动的核心流程只在真正单调的地方自动化。具体来说我写文章的过程完全是手动的新建一个文本文件写好内容保存。唯一的一个自动化环节是构建命令它会读取所有文章、生成索引、产出静态页面。这个构建过程是确定性的也就是说同样的输入永远产生同样的输出。我不会为了让发布少点一下鼠标而引入一个常驻后台服务或者触发器因为多一个常驻进程就多一个出错的点。这背后的逻辑像一个生活化的类比向你寄信你宁愿每次自己去邮局亲手寄也不愿在门口放一个写着请把这封信带走的信箱因为你担心信箱哪天被当成废品收走了。自动化当然有价值但应该用在真正繁琐的地方而不是为了自动化而自动化。2.4 物理备份把云同步当辅助而不是救命的唯一依靠我对云同步一直有一个看法它是便利工具不是安全方案。很多人觉得数据存在云盘里就万事大吉了但实际会遇到某个网盘功能下线账号被误封同步冲突覆盖了旧版本等情况。Caveman 的备份思路非常原始每隔一个季度我会把整个内容目录打包成一个压缩文件然后拷贝到一块闲置的移动硬盘上这块硬盘平时不插电、不联网放在柜子里。有人会觉得这样做太老土了但我实测下来这恰恰是最可靠的备份方式。我在移动硬盘上放着三个历史季度备份加一份最新覆盖备份任何一个备份坏了我还有另外几个版本可用。云盘作为辅助手段依然保留但它只是我随时取用文件的方式而不是我唯一的数据保险。这一点非常关键当你的全部内容就是几个文件夹里的文本文件时备份这件事就变得极其简单你甚至不需要专门的备份软件直接复制粘贴即可。2.5 简单纪律一条约定比十次培训管用Caveman 的最后一条原则是用约定代替功能。我做这套系统的过程中最大的体会是约束人的不是软件的功能边界而是清晰的规则。比如我给自己定了一条铁律永远只改 source 目录下的内容public 目录是生成物手动去改它属于违规操作如果需要改页面样式那就改模板文件而不是去改已经生成的 HTML。这些规则如果用功能去强制实现需要做权限控制、操作审计之类的设计复杂度会直线上升。但对我来说一页纸的 README 就够了。我甚至练就了一个习惯每当发现自己想给系统加一个功能的时候先停下来问自己这是不是一条规则就能解决的问题能靠规则解决的绝不靠功能解决。这种纪律感才是 Caveman 模式能够长期运转的真正内核。3. 实操过程从零搭建一套 Caveman 内容生产管线3.1 场景设定与需求梳理在进入具体的搭建步骤之前先明确目标我要搭建一个个人内容站点文章的规模预计在几十篇到几百篇之间内容以文字为主、配图为辅需要有一个列表页和独立的文章详情页还要支持简单的标签归类。至于评论、搜索、数据分析这些功能目前全部不要留到以后有明确需要时再加。技术栈的选择上我刻意保持克制Python 标准库负责生成静态页面一个 bash 脚本负责部署到服务器。不需要数据库、不需要 Node.js、不需要任何 Web 框架。整个系统在本地构建完成之后把生成好的静态文件直接上传到服务器的 Web 目录里由服务器的 Nginx 负责对外提供服务。这个架构本质上就是构建 同步两步走简单到不能再简单。如果放在以前我可能会先折腾一套自动化部署流程但在 Caveman 模式下我的选择是本地构建、手动确认、同步上线。这三个步骤每一步都有肉眼可见的反馈完全可掌控。3.2 目录结构的设计与命名规则先看服务器的目录结构这是我的全部家当site/ ├── source/ # 唯一内容源 │ ├── 2025-06-01-caveman-guide.md │ ├── 2025-05-20-backup-strategy.md │ └── assets/ │ ├── 2025-06-01-caveman-guide/ │ │ └── cover.jpg │ └── 2025-05-20-backup-strategy/ │ └── folder-tree.png ├── templates/ │ ├── index.html # 列表页模板 │ ├── post.html # 文章详情页模板 │ └── rss.xml # 订阅源模板 ├── scripts/ │ ├── build.py # 构建脚本 │ └── deploy.sh # 部署脚本 └── public/ # 输出目录可随时删除重建 ├── index.html ├── 2025-06-01-caveman-guide/ │ └── index.html └── rss.xml这里有两个设计细节值得多说一句。第一个是文章的目录名直接采用日期-短标题格式这样的好处是浏览器地址栏里的 URL 具有天然的可读性搜索引擎也更容易理解网页内容。第二个是配图目录和文章建立了一一对应的关系这样做的好处非常明显以后想归档某篇文章时只需要把 .md 文件和同名图片目录放一起打包绝对不会出现图片素材找不到的尴尬。3.3 构建脚本的核心逻辑与代码讲解构建脚本是整个系统里唯一的技术难点但它的逻辑其实非常朴素扫描 source 目录解析每篇文章开头的元信息然后用模板把文章渲染成 HTML再生成列表页和订阅源。我用的是 Python 标准库里的 pathlib、re、datetime 和 html没有引用任何第三方包。直接看核心代码总行数大约 120 行左右#!/usr/bin/env python3 # -*- coding: utf-8 -*- from pathlib import Path import re, html SOURCE_DIR Path(source) PUBLIC_DIR Path(public) TEMPLATE_DIR Path(templates) def parse_meta(text): 从 Markdown 文件头部提取 title/date/tag 信息 meta {} meta_match re.search(r^---\n(.*?)\n---, text, re.S) if meta_match: for line in meta_match.group(1).splitlines(): key, _, value line.partition(:) meta[key.strip()] value.strip() text text[meta_match.end():].strip() return meta, text def render_markdown(text): 极简的 Markdown 渲染只处理段落、标题、列表、代码块 lines text.splitlines() html_parts [] for line in lines: if line.startswith(### ): html_parts.append(fh3{html.escape(line[4:])}/h3) elif line.startswith(## ): html_parts.append(fh2{html.escape(line[3:])}/h2) elif line.startswith(- ): html_parts.append(fli{html.escape(line[2:])}/li) elif line.startswith(): html_parts.append(precode) elif line.strip() : html_parts.append(p/p) else: html_parts.append(fp{html.escape(line)}/p) return \n.join(html_parts) def build_all(): posts [] for md_file in sorted(SOURCE_DIR.glob(*.md), reverseTrue): text md_file.read_text(encodingutf-8) meta, content parse_meta(text) slug md_file.stem post { title: meta.get(title, slug), date: meta.get(date, slug[:10]), tag: meta.get(tag, 随笔), slug: slug, } post[html] render_markdown(content) posts.append(post) # 生成文章详情页 post_dir PUBLIC_DIR / slug post_dir.mkdir(parentsTrue, exist_okTrue) template (TEMPLATE_DIR / post.html).read_text(encodingutf-8) (post_dir / index.html).write_text( template.replace({{TITLE}}, post[title]) .replace({{DATE}}, post[date]) .replace({{CONTENT}}, post[html]), encodingutf-8) # 生成列表页 index_template (TEMPLATE_DIR / index.html).read_text(encodingutf-8) post_items \n.join( flia href{p[slug]}/{p[title]}/a span{p[date]}/span/li for p in posts) (PUBLIC_DIR / index.html).write_text( index_template.replace({{POSTS}}, post_items), encodingutf-8) if __name__ __main__: build_all()这段代码最关键的部分是 parse_meta 函数它用正则从文章头部的两行---中间提取标题、日期和标签。为什么要自己实现而不是直接用现成的静态站工具因为我的需求只有这三个字段一个正则就够用。渲染函数我故意写得非常简单只支持标题、列表、代码块和段落这样每个元素长什么样我都心里有数不会出现为什么这里多了个标签这类黑魔法问题。发布和部署脚本则更简单就是把 public 目录同步到服务器#!/bin/bash # 构建并部署 python3 scripts/build.py rsync -avz --delete public/ userserver:/var/www/caveman/rsync 的--delete参数会让服务器上已经不存在于本地的文件被删除保证两边内容一致。这里有个细节需要注意rsync 中的--delete参数必须搭配-a使用否则可能执行的不是预期效果而且它采用增量传输策略实测部署 80 篇文章的整站只需要几秒钟。唯一的前提是本地和服务器都安装并配置好了 SSH 免密登录。3.4 日常维护流程新增一篇文章只要三步Caveman 系统运行起来之后日常维护流程简单到可以变成肌肉记忆。新增一篇文章只需要三步在 source 目录复制一个已有的文章模板把头部改成新的标题、日期和标签然后填写正文保存后在终端里运行一次构建脚本确认本地预览没有问题之后执行部署脚本页面就更新了。整个过程熟练之后不会超过三分钟。我之所以强调复制已有模板而不是新建空白文件是因为模板里已经有固定的头部格式复制后只需要改三行减少了记忆负担。另一个细节是source 目录下的所有文件都使用 UTF-8 编码绝对不用 GBK 或者带 BOM 的格式。这个问题我踩过一次坑Windows 下用记事本编辑过的文件默认可能带 BOM会导致生成页面首行出现不可见字符后来我统一换成了 VS Code并把默认编码改成 UTF-8 才算彻底解决。关于图片处理我的做法也很原始不做任何压缩优化直接放原图。因为我的站点以文字为主图片素材本来就少不需要自动化处理。如果哪天真有大量图片需求我会加一个独立脚本做批量压缩但它不会是内容生产流程的必经环节。Caveman 模式的维护原则就一句话把流程压到最短让每一步都有确认的机会。4. 常见问题与排查技巧实录4.1 极简方案的天花板内容量大了还能用吗有人可能会问这套方案目前确实简单但内容量到几百篇、几千篇的时候还撑得住吗我给出的回答是基于实际经验的如果内容在千篇以内纯文本加构建脚本的方案完全够用。我的内容站点现在有 80 多篇文章构建一次只需要两三秒。哪怕涨到一千篇构建时间预计也不会超过三十秒依然在可接受范围内。真正需要警惕的其实是另一个问题——归档扩散。当文章特别多时source 目录里的文件会迅速变多单靠目录浏览会显得吃力。我的应对方式很简单把文件按照年份拆分子目录比如 source/2025/ 和 source/2024/。构建脚本只需要加一行 glob 路径匹配就能继续正常工作。迁移成本也就是移动文件而已因为文件名里的日期信息已经足够定位不需要维护任何元数据。超过一千篇这种情况我会建议正式引入一个轻量级数据库或搜索组件。但即便到了那个阶段纯文本文件仍然应该是数据的主存储格式数据库只是索引随时可以从文件重新生成。这就是 Caveman 方案最有价值的地方它的天花板不是方案本身的上限而是你愿意投入的维护精力。4.2 中文处理与编码问题的几个坑我遇到过的最典型的一个坑就是 BOM 问题。系统刚跑起来不久我生成了几篇文章上传之后打开页面发现每篇文章开头都有一个很诡异的空行查看源码才发现是 BOM 字符。这个问题的本质是编辑器保存时加了不可见的字节序标记网页解析器不认识它就把残留内容当成文本输出了。排查起来倒是不难用命令行工具查看文件头的二进制内容能明显看到EF BB BF这三个字节。解决的方式是在编辑器设置里把编码统一成 UTF-8 without BOM并在构建脚本中加入一层防护读入文件后自动去 BOM。另一个和中文相关的问题是 URL 编码。早期我为了做 SEO 优化试图把文章名变成中文结果发现有些服务器对中文路径的处理并不友好而且分享链接时那些百分号转义看着很糟心。后来我干脆统一改成英文短标题中文标题只放在文章的元信息里。这样既保持了地址栏的干净也避免了兼容性问题。还有一个容易被忽略的细节是换行符。Windows 的换行是\r\nLinux 上是\n。如果你在 Windows 上编辑文件、在 Linux 上构建可能会出现页面显示错乱或者模板拼接异常。解决方案也简单要么你自己写的模板兼容两种换行要么把所有内容格式统一比如在构建脚本里把\r\n全局替换成\n。我的选择是题目都不大但要养成一个固定习惯所有源文件都保持 Unix 换行。4.3 协作与迁移Caveman 系统如何与团队配合我这里说的协作主要是两三个人共同维护一个站点的场景。Caveman 的极简特性对协作其实非常友好因为它的核心就是一堆文本文件。我们只需要约定一个共同的代码仓库或者一个网盘的同步目录大家各自编辑不同的文件合并时基本上不会发生冲突。如果哪天真冲突了找一部最简单的文本合并工具就能解决而不会像掉进某些可视化编辑器同步冲突的黑洞里。迁移则是 Caveman 的另一大优势。因为所有内容都是干净清晰的 Markdown 文件格式和命名都有规范所以任何时候你想把它换到某个现代化框架或者内容管理系统上几乎不用做任何数据清洗。我实测过把 source 目录导入某个主流静态站生成器只需要改一下文档头部的字段名几分钟就能全部转完。坚持内容纯文本化这个习惯是未来一切可能迁移的最大底气。4.4 问题排查速查表问题现象可能原因排查与解决方式页面第一行有多余空行文件带有 BOM 标记检查文件头EF BB BF统一用 UTF-8 无 BOM 保存脚本里做防御性过滤构建出来的文章没有样式templates 模板引用路径错误检查模板中 CSS 路径使用相对路径并用本地浏览器打开 output 验证图片无法显示图片文件名或路径大小写不一致确认 source 中的文件路径与文章引用完全一致注意服务器对大小写敏感部署后新内容未更新rsync 路径或同步命令语法问题检查 rsync 源路径带有/的含义测试时先加--dry-run脚本报 utf-8 解码错误某篇文件的编码不是 UTF-8用命令行工具扫描 source 目录定位非 UTF-8 文件后用编辑器统一转码列表页文章顺序不对排序逻辑或文件名日期格式不统一归一化所有文件名的日期格式为YYYY-MM-DD调整脚本的排序参数个别页面 404服务器未配上默认文档或目录缺少 index.html确认每个文章目录都生成 index.html 并且服务器支持目录索引这张表并不复杂但都是我在实操中真实遇到过的。Caveman 系统的调试思路其实和它的设计理念一脉相承一次只查一个变量先看文件有没有、再看内容对不对、最后看路径通不通几乎不需要引入任何高级调试手段。我个人在长期使用中的体会是当一个系统足够简单的时候问题排查的难度会呈指数下降。框架和工具越多未知的变量就越多出问题时的排查范围也就越大。Caveman 把所有变量压缩到最小剩下的每一个变量都是你亲手放进去的修复它们对你来说几乎不需要思考。最后再分享一个小技巧定期把整个 source 目录打包成一个 tar 文件并保存到离线存储设备压缩包命名带上打包日期。这听起来过分简单但它是整个 Caveman 方案里最可靠的一环。因为所有内容都是纯文本文件压缩率很高一年几十篇文章可能也就几 MB 大小。你甚至可以把它放到一张老旧的存储卡里。原始的手段往往就是最不会失效的手段。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑