从零开始实现一个属于自己的静态博客
为什么产生这个想法
其实,之前也有弄过个人博客站点,主要是想用来记录平时工作的一些文档,但最后终究是为了搭建而搭建,最开始使用的wordpress这种带后台的建站工具,但是个人不是很喜欢在后台编辑器里面写文章,最后不了了之,再之后又使用hexo这种静态博客生成工具,直接将自己本地的md格式文档直接生成静态博客,从这个角度来说确实挺适合我这种,但是一直都没有找到一个自己觉得满意的主题,最后依旧作罢。最近又萌生了搭建博客的想法,结合前面两次的经验,想想还是自己写一个,以后如果有调整样式也更得心应手些。
博客的产生
- 规划
由于平时的文档都是使用md格式写的,所以打算直接将md文档进行解析来生成博客。但是考虑到平时文档存放基本是在一个目录内无序存放,可能在某个子文件夹内,也可能在根目录下,所以不打算使用子文件夹来做分类目录,所有需要的“分类”、“标签”、“时间”等信息都放在markdown文档的Front Matter里面。这也是唯一的格式要求(主要考虑博客的分类和标签功能)
下面是一个参考示例,写作markdown文档的最开头处
---
title: Front Matter示例,写在markdown文档的最开头处
date: 2024-11-15
categories: web建站
tags: ['个人博客']
---
-
主要功能分析与实现
-
首先考虑如何将markdown文档的YAML Front Matter部分和内容部分分离
首先检查内容是否以 --- 开头,这是 YAML Front Matter 的标准标识符。如果是,它会在内容的第三个字符后(排除第一个--- )找到第二个 ---,从而确定 Front Matter 的结束位置。然后,它提取 --- 之间的部分作为 YAML 数据,并将其解析为字典。最后,它将 YAML Front Matter 部分提取出来,使用 PyYAML 库的 safe_load 函数将 front_matter 部分解析为 Python 字典。safe_load 是一个安全的解析方法,它能防止执行可能有害的 YAML 内容,yaml.safe_load(front_matter) 返回解析后的字典和剩余的正文内容。如果没有找到有效的 Front Matter,函数会返回一个空字典和原始内容,功能实现函数如下
import yaml
def parse_front_matter(content):
"""
content参数为读取的markdown文件原始内容
解析 YAML Front Matter,分离Front Matter与内容部分(body)
"""
if content.startswith('---'):
end_index = content.find('---', 3)
if end_index != -1:
front_matter = content[3:end_index]
body = content[end_index + 3:]
#返回front matter信息和内容
return yaml.safe_load(front_matter), body
#没有front matter信息则返回空字典和内容
return {}, content
- 考虑内容部分的格式转换,转换成html格式进行渲染
依赖markdown库,使用markdown.markdown() 函数,将 Markdown 格式的文本转换为 HTML。这个函数接受多个参数,其中第一个是要转换的 Markdown 内容(md_content),第二个是一个扩展列表(extensions=extensions),用来启用特定的 Markdown 扩展。CodeHiliteExtension扩展为代码块提供语法高亮。FencedCodeExtension扩展支持使用围栏代码块语法(即用 ````` 包裹的代码块)。
import markdown
from markdown.extensions.codehilite import CodeHiliteExtension
from markdown.extensions.fenced_code import FencedCodeExtension
def convert_markdown_to_html(md_content):
"""将 Markdown 内容转换为 HTML"""
extensions = [CodeHiliteExtension(), FencedCodeExtension()]
return markdown.markdown(md_content, extensions=extensions)
- 遍历文档目录,收集文章信息数据
有了前面的两步,现在可以成功的将一个markdown文档里的数据进行拆分了,这样就可以将目录内的所有文档的数据提取出来组合成一个文档数据的列表,方便后续其他渲染函数使用,这里我组合了三种数据,posts收集文章信息数据(包括标题、其它元素据、文章内容),tags收集元数据里的标签信息,categories收集分类信息,实现函数如下:
# 配置目录
POSTS_DIR = 'posts'
OUTPUT_DIR = 'output'
def get_by_metadata_process_posts():
'''
获取文章信息
包括文章、标签、分类
'''
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
tags = {}
categories = {}
posts = []
for root, dirs, files in os.walk(POSTS_DIR):
for filename in files:
if filename.endswith('.md'):
file_path = os.path.join(root, filename)
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
metadata, body = parse_front_matter(content)
title = metadata.get('title', filename.replace('.md', ''))
html_content = convert_markdown_to_html(body)
# 收集文章信息
posts.append({'title': title, 'html_content': html_content, 'metadata': metadata})
# 更新标签
for tag in metadata.get('tags', []):
tags.setdefault(tag, []).append(title)
# 获取子目录作为分类
categories.setdefault(metadata.get('category', '未分类'), []).append(title)
#category = os.path.basename(root) # 获取当前目录的名称
#categories.setdefault(category, []).append(title)
# 获取一级子目录作为分类
# relative_path = os.path.relpath(root, POSTS_DIR) # 获取相对路径
# category = relative_path.split(os.sep)[0] # 获取一级子目录
# categories.setdefault(category, []).append(title)
# 生成 search.xml 文件,后续搜索功能需要使用
generate_search_xml(posts)
return posts, tags, categories
- 使用jinja2模板渲染生成html文件
渲染生成html文件之前,创建一个html模板,这里介绍首页的生成,新建一个templates目录存放所有的模板文件,template/index.html为首页模板文件,下面是我的首页模板文件
{% extends "base.html" %}
{% block title %}各自花开{% endblock %}
{% block banner%}
<div class="banner container banner-content">
<div>
<div class="photo-wall">
<div class="photo-item">
<span class="photo-text">各</span>
</div>
<div class="photo-item">
<span class="photo-text">自</span>
</div>
<div class="photo-item">
<span class="photo-text">花</span>
</div>
<div class="photo-item">
<span class="photo-text">开</span>
</div>
<!-- Add more photo items as needed -->
</div>
</div>
<div>
<h1>凡事需三思,谨言慎行</h1>
<p>对世界有看法,对生活有态度,让花成花,让树成树,认可任何一种观点和习惯</p>
</div>
</div>
{% endblock %}
{% block content %}
<div class="content">
<h3>最近更新 <span class="new-icon">New</span></h3>
<ul>
{% if contents %}
<div class="content-list">
{% for content in contents %}
<span>
<img src="../static/img/文字_text.png" class="content-icon">
<li><a href="article/{{ content["title"] }}.html">{{ content["title"] }}</a> {{ content["metadata"]["date"] }}</li>
</span>
{% endfor %}
</div>
{% endif %}
</ul>
</div>
{% endblock %}
{% block sidebar %}
<aside class="sidebar">
<!-- 作者介绍 -->
<div class="sidebar-section author-intro">
<h3>关于作者</h3>
<div class="author-info">
<p>一个内心有想法但却随性,期望改变但最终随遇而安的矛盾存在</p>
<p>博客文章同步更新微信公众号【各自花开】,期待您的关注~</p>
<img src="../static/img/qrcode_1280.jpg" alt="作者头像" class="author-avatar">
</div>
</div>
<!-- 热门标签 -->
<div class="sidebar-section tags">
<h3>热门标签</h3>
<ul class="tag-list">
{% for tag in tags %}
<li><a href="./tags/{{ tag }}.html">{{ tag }}</a></li>
{% endfor %}
</ul>
</div>
<!-- 推荐文章 -->
<div class="sidebar-section recommended">
<h3>分类</h3>
<ul class="recommended-list">
{% for category in categories %}
<li><a href="./categories/{{ category }}.html">{{ category }}</a></li>
{% endfor %}
</ul>
</div>
</aside>
{% endblock %}
首页渲染函数,执行该函数会在OUTPUT_DIR目录下生成一个首页index.html文件
# 设置模板文件夹路径
template_dir = 'templates'
# 创建一个模板加载器
loader = FileSystemLoader(template_dir)
# 创建 Jinja2 环境
env = Environment(loader=loader)
def generate_index_page(posts,tags=None,categories=None):
"""生成首页"""
# 加载模板
index_template = env.get_template('index.html')
# 默认值为空列表
if tags is None:
tags = []
if categories is None:
categories = []
# posts[:5] 显示5条
html = index_template.render(contents=posts,tags=tags, categories=categories)
output_file = os.path.join(OUTPUT_DIR, 'index.html')
with open(output_file, 'w', encoding='utf-8') as file:
file.write(html)
print(f"Generated {output_file}")
其它页面比如文章页、标签页等参照首页的生成方式去渲染,当所有页面都渲染生成之后,就可以部署了,这样我的博客就建成了,最后给大家展示一下我自己写的博客吧
