编程打卡?来写个静态博客生成器吧。
编程打卡?来写个静态博客生成器吧。
一、问题描述
纯粹是觉得写教科书上的习题太过于无聊了,所以我想尝试写个静态博客生成器。我的博客(另一个)是完全用手写的静态网页,只使用了现学的一点点 HTML,CSS和一点点 JavaScript ,还有各种各样插入到内容中的图片文件,效果还不错?但当我要尝试编辑整个博客的一些样式的时候,痛苦的事情发生了。我不得不批量的进行替换,每一个页面文件,重复的HTML代码替换工作。当然,有自动化的工具帮助我们完成这些,比如常见于 Unix 中的 sed
命令,来批量替换文本,但还是好麻烦,我不得不去想如何才能准确的替换而不把 HTML 文件弄得一团糟。然后博客中有个目录页面,如果能随着文章的增加而自动更新目录界面,该有多好,此外用稍微有点复杂的 HTML 写文章实在是痛苦,于是我想到了,不如写个博客生成器吧?
听起来好困难,但是稍微想想的话,它的工作还是很容易的。首先从我们的一个目录中读取 .md 格式的文章,把它变成 HTML ,然后再插入到模板中。自然是选择 Python 作为工具,因为前者可以用 Python 中的 markdown 库来实现,后者可以用 jinja2 这个库来实现,问题一下子就变得简单许多了?虽然说有点偷懒,但它能解决问题,重复造轮子之类的事情我想意义不大。
大概只是写一个简单能跑的,更多还是要等待后续完善。
二、设计思路
- 读取配置文件,查找相关目录和模板,如果不存在则自动创建。
- 读取页面,读取文章
- 转换成 HTML,插入模板,输出到静态博客目录
三、程序流程图
四、编写
Python语言是一种简洁的语言,基本上可以看作可以直接执行的伪代码了。
创建目录
我们需要一个函数来创建目录,如果目录不存在则创建,存在则忽略
def mkdir(path): folder = os.path.exists(path) if not folder: os.makedirs(path) print("create " + path + " folder") else: print("found " + path + " folder")
启动
为程序做一些初始化的准备,包括创建相关目录配置,查找模板。
def init(): mkdir(os.getcwd() + "/article") mkdir(os.getcwd() + "/article/posts") mkdir(os.getcwd() + "/template") mkdir(os.getcwd() + "/styles") mkdir(os.getcwd() + "/static") if not os.path.exists(os.getcwd() + "/config.json"): with open(os.getcwd() + "/config.json", "w") as f: json.dump( { "title": "My Blog", "author": "My Name", "url": "https://example.com", "discription": "My Blog", "email":"", }, f, ) print("create config.json, please edit it first.") exit() with open(os.getcwd() + "/config.json", "r") as f: global config config = json.load(f) print("found config.json") mkdir(os.getcwd() + "/static/styles") mkdir(os.getcwd() + "/static/scripts") mkdir(os.getcwd() + "/static/res") mkdir(os.getcwd() + "/static/posts") if not os.path.exists(os.getcwd() + "/template/default.html"): print("no default.html template found, please create one.") exit() if not os.path.exists(os.getcwd() + "/styles/style.css"): print("no style.css found") else: copyfile(os.getcwd() + "/styles/style.css", os.getcwd() + "/static/styles/style.css") print("copy style.css to static/styles/style.css") global env env = Environment(loader=FileSystemLoader(os.getcwd() + "/template"))
全局变量env
用来初始化jinja2
的环境,告诉它,你要在这个目录里面查找模板嗷这样。
转换成HTML
接下来这个函数用来将文件批量转换成HTML,和其他一些信息组成一个dict
类型的数据,返回,来为下一步的渲染网页做准备。
def parseMD(page): with open(page, "r", encoding="utf-8") as f: content = f.read() html = markdown.markdown(content, extensions=extension) title = content.splitlines()[0].replace("#", "") data = { "title": title + " - " + config["title"], "content": html, "date": datetime.datetime.fromtimestamp(os.path.getmtime(page)).strftime("%Y-%m-%d"), } return data
渲染页面
这个函数包含三个参数,要渲染的页面路径,文件名和类型
def renderPages(path,filename,type): if os.path.exists(os.getcwd() + "/template/" + filename.replace(".md",".html")): print("found " + filename.replace(".md",".html") + " in template folder, using it.") template = env.get_template(filename.replace(".md",".html")) elif os.path.exists(os.getcwd() + "/template/" + type + ".html"): print("found " + type + ".html in template folder, using it.") template = env.get_template(type + ".html") else: print("no " + filename.replace(".md",".html") + " or " + type + ".html found, using default.html") template = env.get_template("default.html") with open(path.replace("article/","static/") + filename.replace(".md",".html"), "w",encoding='utf-8') as f: f.write(template.render(parseMD(path + filename))) print("created " + filename.replace(".md",".html"))
渲染目录根部的是页面
从article目录遍历,如果未找到index.md
,就新创建一个
def render(): if not os.path.exists(os.getcwd() + "/article/index.md"): with open(os.getcwd() + "/article/index.md", "w") as f: f.write("# Index") print("create index.md") for page in os.listdir(os.getcwd() + "/article"): if page.endswith(".md"): renderPages(os.getcwd() + "/article/",page,"page") for post in os.listdir(os.getcwd() + "/article/posts"): if post.endswith(".md"): renderPages(os.getcwd() + "/article/posts/",post,"post") print("done.")
五、完整代码
import os import markdown import json import datetime from jinja2 import Environment, FileSystemLoader from shutil import copyfile extension = { "markdown.extensions.extra", "markdown.extensions.codehilite", "markdown.extensions.toc", "markdown.extensions.tables", "markdown.extensions.fenced_code", } # 使用插件来确保 markdown 库可以正确解析文档中的表格和代码 def mkdir(path): folder = os.path.exists(path) if not folder: os.makedirs(path) print("create " + path + " folder") else: print("found " + path + " folder") def init(): mkdir(os.getcwd() + "/article") mkdir(os.getcwd() + "/article/posts") mkdir(os.getcwd() + "/template") mkdir(os.getcwd() + "/styles") mkdir(os.getcwd() + "/static") if not os.path.exists(os.getcwd() + "/config.json"): with open(os.getcwd() + "/config.json", "w") as f: json.dump( { "title": "My Blog", "author": "My Name", "url": "https://example.com", "discription": "My Blog", "email": "", }, f, ) print("create config.json, please edit it first.") exit() with open(os.getcwd() + "/config.json", "r") as f: global config config = json.load(f) print("found config.json") mkdir(os.getcwd() + "/static/styles") mkdir(os.getcwd() + "/static/scripts") mkdir(os.getcwd() + "/static/res") mkdir(os.getcwd() + "/static/posts") if not os.path.exists(os.getcwd() + "/template/default.html"): print("no default.html template found, please create one.") exit() if not os.path.exists(os.getcwd() + "/styles/style.css"): print("no style.css found") else: copyfile( os.getcwd() + "/styles/style.css", os.getcwd() + "/static/styles/style.css" ) print("copy style.css to static/styles/style.css") global env env = Environment(loader=FileSystemLoader(os.getcwd() + "/template")) def parseMD(page): with open(page, "r", encoding="utf-8") as f: content = f.read() html = markdown.markdown(content, extensions=extension) title = content.splitlines()[0].replace("#", "") data = { "title": title + " - " + config["title"], "content": html, "date": datetime.datetime.fromtimestamp(os.path.getmtime(page)).strftime( "%Y-%m-%d" ), } return data def renderPages(path, filename, type): if os.path.exists(os.getcwd() + "/template/" + filename.replace(".md", ".html")): print( "found " + filename.replace(".md", ".html") + " in template folder, using it." ) template = env.get_template(filename.replace(".md", ".html")) elif os.path.exists(os.getcwd() + "/template/" + type + ".html"): print("found " + type + ".html in template folder, using it.") template = env.get_template(type + ".html") else: print( "no " + filename.replace(".md", ".html") + " or " + type + ".html found, using default.html" ) template = env.get_template("default.html") with open( path.replace("article/", "static/") + filename.replace(".md", ".html"), "w", encoding="utf-8", ) as f: f.write(template.render(parseMD(path + filename))) print("created " + filename.replace(".md", ".html")) def render(): if not os.path.exists(os.getcwd() + "/article/index.md"): with open(os.getcwd() + "/article/index.md", "w") as f: f.write("# Index") print("create index.md") for page in os.listdir(os.getcwd() + "/article"): if page.endswith(".md"): renderPages(os.getcwd() + "/article/", page, "page") for post in os.listdir(os.getcwd() + "/article/posts"): if post.endswith(".md"): renderPages(os.getcwd() + "/article/posts/", post, "post") print("done.") def main(): init() render() main()
六、其他
这里实现了大概做到了将写好的markdown变成静态网站功能,不过工作还远没有结束,目前想到的等待解决的问题如下
- 博客中的图片资源还没有处理,一般来说还应该应该将图片分类存储在静态网页的对应文件夹中,除非你只使用图床。
- 每次运行都会重新渲染所有的页面,过于耗时,如果只是添加了文章而没有修改模板/其他文章,可以仅渲染添加的几篇。或许应该添加一个启动参数来让用户决定。
- 暂时没有去管CSS,因为我发现CSS真的很难,我自己也不懂。
- 好看好用的模板还在编写中,我还在学习一些jinja2的语法,简单写了个测试的页面,确认正常替换,也就只是这样。
以上
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!