资讯详情

caveman实战:用Django模板构建极简静态博客

📅 2026/10/29 14:03:43 | 华诺云谱 👁 阅读
caveman实战:用Django模板构建极简静态博客
1. 为什么是caveman一个“原始”博客框架的定位与核心逻辑1.1 名字里的设计哲学第一次看到“caveman”这个名字我以为是某个复古游戏或者石器时代主题的营销项目。直到在一个技术社群里看到有人用它搭博客我才意识到这其实是一个极简风格的静态网站生成器。它的核心思路很直接不搞数据库、不搞后台管理、不搞复杂的插件体系把内容写作回归到最原始的文件夹加模板文件。这种“原始”不是简陋而是一种刻意打磨后的克制。caveman 构建在 Django 的模板引擎之上你写文章不是在编辑器里敲 Markdown而是直接写 HTML 模板文件。这一个特性决定了它的受众画像熟悉 Django 模板语法、想要一个轻量输出、又懒得折腾 Node.js 或 Go 工具链的人。我最初看到这个框架时的反应是“有必要吗”毕竟市面上的静态生成器一抓一大把。但在实际用了两个小项目之后我反而理解了这种“穴居人”式的选择——当你的内容形态足够固定、页面结构足够简单时背靠 Django 模板意味着继承、include、自定义 tag、模板变量这些能力开箱即用不需要额外学一套新的模板语法。你只需要会 Django 的模板写法整个生成逻辑对你就是透明的。1.2 与传统静态站点生成器差异点在哪把 caveman 和主流的 Hugo、Pelican、Jekyll 放在一起对比能一眼看出它的定位非常特殊。Hugo 强在构建速度和主题生态Pelican 强在 Python 原生和 Markdown 写作流程Jekyll 强在 GitHub Pages 原生支持。这些工具都遵循一个模式内容文件Markdown加主题模板经过渲染引擎生成 HTML。caveman 的逻辑走了一条不太一样的路。它的内容本身就是模板文件你不需要“内容”和“展示”两层分离而是两者合一。这样做的好处是自由度极高你可以为每个页面写完全不同的布局结构不需要照顾全局主题的抽象设计坏处是一旦页面多了重复的 HTML 结构会变多必须靠继承和 include 自行约束。这就像是从“装修好的精装房”回到“毛坯房自己动手刷墙”。精装房省事但毛坯房能让每一个角落都符合自己的实际需求。caveman 适合的项目类型非常明确个人博客、极客个人主页、内部文档站点、产品落地页。这些场景往往页面数量在几十个以内结构简单但需要高度定制化。如果你要做一个几百篇文章的大型内容站、需要标签分类动态筛选、需要全文搜索caveman 的“原始”模式会让你想砸键盘。它没有内置的 taxonomy 系统没有动态分页组件一切都要靠自己在生成逻辑里实现。所以选型时先掂量清楚体量和功能预期不要盲目追求极简。2. 快速上手五分钟跑通一个最小站点2.1 安装与最小化配置caveman 的安装过程非常普通直接通过 pip 拉到本地环境即可。我建议在任何项目里都先创建独立的虚拟环境避免和系统 Python 环境的包互相污染尤其是涉及 Django 版本依赖时。mkdir caveman_demo cd caveman_demo python -m venv venv source venv/bin/activate pip install django caveman装完后在项目根目录初始化一个 Django 工程。这一步可能会劝退一些习惯单文件工具的读者但实际上只有两条命令django-admin startproject config .接下来编辑config/settings.py在INSTALLED_APPS里加一行INSTALLED_APPS [ django.contrib.staticfiles, caveman, ]然后添加 caveman 自己的配置块。我习惯把站点相关配置集中放到一个字典里便于后续备份和迁移import os CAVEMAN { SITE_ROOT: os.path.join(BASE_DIR, content), BUILD_ROOT: os.path.join(BASE_DIR, build), BASE_TEMPLATE: base.html, GENERATORS: [ (pages, caveman.generators.pages), ], }这里的SITE_ROOT是内容源目录BUILD_ROOT是最终生成静态文件的输出目录。GENERATORS是构建流程中要执行的页面生成器后面会详细说。先记住一个原则caveman 把“页面定义”和“输出位置”看成两件事内容文件在源目录怎么组织构建后就基本怎么映射到输出目录。2.2 内容目录与基础模板搭建在项目根目录创建content目录然后新建一个base.html作为全局母版。这里我直接用 Django 的模板继承语法!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{% block title %}{% endblock %}/title link relstylesheet href/static/style.css /head body header a href/我的极简博客/a /header main {% block content %}{% endblock %} /main footer pPowered by caveman/p /footer /body /html注意我引用 css 时用了绝对路径/static/style.css。静态资源的处理有一个关键点caveman 构建时默认不会自动拷贝你项目里的静态文件到输出目录。所以我在content下面单独建了static文件夹然后在部署阶段用 Nginx 直接把这个文件夹指到站点的静态资源路径上。这种方式比折腾 caveman 的静态文件收集机制要省心得多。接着在content目录下创建一个首页文件index.html{% extends base.html %} {% block title %}首页{% endblock %} {% block content %} h1你好洞穴/h1 p这是一个用 caveman 生成的静态页面。/p {% endblock %}2.3 本地预览和构建输出caveman 提供了一个内置的开发服务器和 Django 的runserver类似会在本地起一个服务让你检查渲染效果python manage.py caveman serve浏览器打开http://127.0.0.1:8000就能看到首页。开发模式下改了模板文件会实时刷新这个体验和 Django 开发基本一致。确认效果没问题后构建正式静态文件python manage.py caveman build构建完成后build目录下会生成对应的index.html。用命令看一下输出结构find build -type f你会看到build/index.html。如果之前建了content/about.html构建后会是build/about/index.html。这个 URL 映射规则是 caveman 比较关键的机制后面的章节专门展开讲。3. 核心机制拆解从模板文件到静态页面的转换链路3.1 URL 与文件路径的映射规则对一个静态站点生成器来说最核心的问题就是源文件如何变成最终浏览器访问的 URL。不同的工具映射规则不同Hugo 支持丰富的 frontmatter 控制Jekyll 大致按文件名和目录结构生成固定路径。caveman 的规则极其朴素只有一条主逻辑content下的模板文件名和目录层级直接决定输出路径。我们可以把它理解为“一个模板文件对应一个展示页面”。比如content/index.html构建后就是站点的根路径/实际文件是build/index.htmlcontent/about.html构建后是/about/实际文件是build/about/index.htmlcontent/blog/first.html构建后是/blog/first/实际文件是build/blog/first/index.html这种“把.html文件名转换为目录加index.html”的处理方式让我们不需要额外的路由配置文件就能获得干净的 URL 结构。它天然避免了.html后缀暴露在浏览器地址栏也让 Nginx 部署时不需要任何 rewrite 规则。我在实际使用时发现这种映射规则有一个需要注意的地方你想生成一个纯文件资源、而不是一个 HTML 页面时不能直接把文件叫.html或放在会被当作模板的目录下。比如放一张图片、一个 favicon应该单独放到静态资源目录否则构建时会被奇怪地当成页面处理。3.2 生成器如何注入动态数据模板文件解决了“长什么样”的问题但博客首页通常要展示文章列表架构页面要展示项目卡片这些数据从哪来caveman 的答案是生成器Generators。从我的理解角度生成器是一个“在构建时运行的 Python 函数”。它在每次构建页面之前被调用负责把动态数据塞进模板上下文模板里用变量渲染这些数据。这种设计其实和 Django 的 view 逻辑如出一辙只是没有 HTTP 请求和响应过程纯粹是静态生成。先看一个最基础的例子。假设我们要给首页注入一个当前日期的变量import datetime def inject_date(): return { now: datetime.date.today(), }然后把这个函数配置到CAVEMAN的GENERATORS里CAVEMAN { GENERATORS: [ (pages, caveman.generators.pages), (date, myapp.generators.inject_date), ], }这是我一直推荐的命名方式生成器字段名就是模板中可用的变量前缀。上面的date会让模板里出现{{ date.now }}。生成器函数不仅限于返回一个普通字典。它还可以拿站点配置、扫描源目录、解析文件内容做更复杂的聚合操作。比如实现一个标记了tag: python文章自动归档的功能import os import re def scan_articles(site_root): articles [] for root, dirs, files in os.walk(site_root): for name in files: if not name.endswith(.html): continue path os.path.join(root, name) content open(path, encodingutf-8).read() match re.search(rtag: (\w), content) if match: articles.append({ title: re.search(rh1(.*?)/h1, content).group(1), path: os.path.relpath(path, site_root), tag: match.group(1), }) return articles然后配合模板循环就能在归档页面把文章列表渲染出来。当然真正的生产环境要处理 HTML 转义、相对链接等细节不过从这里可以看出生成器这个机制的上限很高。它本质上允许你“在模板里写逻辑在 Python 里写数据准备”动静结合灵活度远大于纯模板工具。3.3 模板继承和组件化复用说完动态数据再讲静态结构。Django 模板最强大的能力就是继承和 include。caveman 完整保留了这一点使得站点的公共头尾、侧边栏、卡片组件可以被抽离复用。我的习惯是把复用片段放到content目录下的partials文件夹并用前缀区分职责partials/header.html站点导航栏partials/sidebar.html侧边栏模块partials/article_card.html文章卡片然后在页面中引入{% include partials/article_card.html %}配合with参数传递变量{% include partials/article_card.html with articleitem %}这种组件化写法的收益在页面数量增多后会非常明显。维护一套公共组件的样式和结构远比复制粘贴二十个页面要好。尤其是调整间距、修改标签样式时改一处即可生效不会漏改。不过我踩过一个大坑caveman 的模板加载顺序和普通 Django 略有差异。它默认从SITE_ROOT下查找模板也就是说如果你写了{% extends base.html %}这个base.html必须能在content目录或 Django 的模板目录中找到。我一开始把base.html放在了项目根目录导致所有页面报模板找不到错误。解决办法很简单把母版模板也放进content目录即可。这类细节官方文档写得不显眼很容易被忽略。4. 进阶实操用 caveman 搭一个带标签归档的极简博客4.1 页面结构规划和导航设计理论讲完现在完整走一遍搭建流程。我们要做一个功能完整的极简博客包含首页、文章详情页、标签聚合页和关于页面。结构如下content/ ├── base.html ├── index.html ├── about.html ├── partials/ │ ├── header.html │ └── article_card.html ├── blog/ │ ├── first-post.html │ ├── second-post.html │ └── tags.html └── static/ ├── style.css └── favicon.ico导航栏每个页面都要展示所以抽到partials/header.htmlnav a href/首页/a a href/blog/first-post/文章/a a href/about/关于/a /nav在base.html中通过 include 引入{% include partials/header.html %}4.2 标签聚合与文章列表的实现标签页是最能体现生成器价值的地方。我们在content/blog/tags.html里放一个空壳模板预留循环结构{% extends base.html %} {% block title %}标签归档{% endblock %} {% block content %} h1标签归档/h1 ul {% for tag, article_list in tags.items %} li h2{{ tag }}/h2 ul {% for article in article_list %} lia href{{ article.url }}{{ article.title }}/a/li {% endfor %} /ul /li {% endfor %} /ul {% endblock %}接下来在myapp/generators.py里写一个标签聚合生成器。它要做三件事扫描源目录下所有 HTML 文件、从内容里提取标签信息、组装成以标签为键的文章列表字典。import os from collections import defaultdict def build_tag_archive(site_root): tags defaultdict(list) for root, _, files in os.walk(site_root): for name in files: if not name.endswith(.html): continue path os.path.join(root, name) with open(path, encodingutf-8) as f: content f.read() tag_match re.search(r!-- tags: (.*?) --, content) if not tag_match: continue tag_list [t.strip() for t in tag_match.group(1).split(,)] title_match re.search(rtitle(.*?)/title, content) article_rel os.path.relpath(path, site_root).replace(os.sep, /) article_url / os.path.splitext(article_rel)[0] / for tag in tag_list: tags[tag].append({ title: title_match.group(1) if title_match else name, url: article_url, }) return dict(tags)把这个生成器挂到配置里CAVEMAN { GENERATORS: [ (pages, caveman.generators.pages), (tags, myapp.generators.build_tag_archive), ], }然后在文章页面里通过 HTML 注释标记标签!-- tags: django, python -- {% extends base.html %} ...这种“HTML 注释作为元数据”的做法是受 caveman 极简哲学启发的妥协方案。它不需要 frontmatter 解析逻辑也不会影响浏览器渲染但能被生成器扫描读取。用起来清爽比引入 YAML 解析省事很多。4.3 部署到 Nginx 的完整步骤全部页面构建完成后部署反而成了最简单的环节。caveman 输出的是纯静态文件不依赖 Python 进程所以 Nginx 配置和普通静态站点没有区别。先把构建产物同步到服务器rsync -avz --delete build/ userserver:/var/www/caveman_site/然后配置一个 Nginx server 块server { listen 80; server_name example.com; root /var/www/caveman_site; index index.html; location /static/ { alias /var/www/caveman_site/static/; } location / { try_files $uri $uri/ $uri/index.html 404; } }这段配置里的try_files非常关键。因为 caveman 生成的 URL 没有.html后缀浏览器访问/blog/first-post/时Nginx 需要先查找/var/www/caveman_site/blog/first-post/目录再回退到目录下的index.html。try_files $uri $uri/ $uri/index.html正好覆盖了这三种情况。我实际部署时一开始漏了$uri/index.html这一段结果访问/blog/first-post/返回 404而直接访问/blog/first-post/index.html又正常。排查了半天才意识到是 try_files 写得不完整。如果你遇到类似问题先检查 Nginx 的这个配置项。5. 实战排查我踩过的坑与高频问题速查5.1 模板继承失效与相对路径陷阱使用 caveman 时最容易出问题的就是模板继承。症状非常典型页面内容空白、样式丢失、或者报出TemplateDoesNotExist错误。这类问题九成是模板查找路径配置错误。caveman 的模板查找机制和 Django 标准机制有个差异它把SITE_ROOT当作优先查找目录。也就是说你的base.html必须放在content目录下而不是 Django 项目的templates目录里。我当初把base.html放到项目根目录下一个叫templates的文件夹结果构建时提示找不到母版模板。后来把文件移动到了content/base.html页面立刻正常。这个折腾过程让我意识到读文档时不能想当然地认为“所有静态生成器都是同一套逻辑”。相对路径也是一个坑。如果你在一个子目录页面里引用了../static/style.css构建后生成的页面在/blog/first-post/下浏览器解析相对路径时会去找/blog/first-post/../static/style.css最终变成/blog/static/style.css而不是/static/style.css。解决办法是始终在模板里使用以/开头的绝对路径。5.2 页面死活不更新的缓存问题开发模式下改了模板内容刷新浏览器发现页面还是旧的。这个问题典型到几乎每个用 caveman 的人都会遇到。caveman 的serve命令并不是每次都全量重新构建所有页面它有简单的缓存机制在文件变更时才触发重新渲染。如果你的模板文件被修改了但关联的页面没变缓存可能没有命中导致页面保持旧版本。我的排查流程是这样# 先删除 build 目录强制全量重建 rm -rf build python manage.py caveman build全量构建后如果页面更新了说明是缓存问题而非代码问题。如果全量构建后仍然旧内容就要检查是不是浏览器缓存了按Ctrl F5强制刷新看看。开发过程中建议把它当成纪律来执行写完内容先构建再看不要过度依赖serve的自动刷新。5.3 其他高频问题与排查要领下面是我使用过程中收集到的高频问题汇总整理成表格方便对照问题现象可能原因解决思路中文乱码模板文件编码不是 UTF-8统一用 UTF-8 编码保存源文件文章列表里有重复项生成器扫描了构建目录build扫描时排除BUILD_ROOT路径页面没有样式静态资源路径用了相对路径改为/static/绝对路径引用标签页是空的元数据注释格式写错检查是否写了!-- tags: ... --且逗号为英文构建报模板不能继承base.html不在SITE_ROOT移动到content目录下首页点击文章跳 404Nginxtry_files配置不完整加上$uri/index.html回退遇到问题时建议按这个思路排查先确认源文件内容正确再检查生成器的扫描逻辑最后看部署层的路径配置。绝大多数情况下问题出在这三层中的某一个位置很少存在多个问题同时爆发的场景。如果你准备把旧站点迁移到 caveman我最后再给一条经验先写一个脚本扫描旧站所有文章的标题、链接、发布时间输出 CSV 清单再批量生成对应的模板文件。迁移之前先理顺内容架构远比迁移过程中反复修补更高效。这个习惯帮我节省了大量重复劳动也让你在动手迁移前对内容资产有一个整体把握。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑