Obsidian 笔记一键转换发布为 Jekyll 博客
Obsidian 是一款功能强大且灵活的知识管理和笔记软件,与 Jekyll 这一轻量级静态博客框架的结合,既能保留 Obsidian 的网状知识关联优势,又能借助 Jekyll 的高效编译能力快速生成标准化博文。
Obsidian 笔记自动转换为 Jekyll 博客一文介绍了如何把挑选出的 Obsidian 笔记转换成 Jekyll 博文保存在本地的 Jekyll 仓库中,并推送到 github/gitee,并通过webhook 部署到自己的博客服务器上。本文将在此基础上,介绍如何零成本全自动构建一站式内容生产体系。整体流程如下:
- 用 GitHub Pages 和 Jekyll 搭建静态博客站点
- 在 Obsidian 笔记中用 md 写笔记
- 挑选需要作为博文发布的笔记,通过
quick add插件的 Macro 脚本把元数据写入博文清单文件 - 运行 python 脚本,将对应的笔记转换成 Jekyll 博文并保存在本地的 Jekyll 仓库中并推送到 GitHub
用 GitHub Pages 搭建静态博客
GitHub 搭建博客最主流的框架是 Hugo、Jekyll、Hexo 。这里选用的是 Jekyll 的Chirpy 主题搭建博客,该主题提供了 chirpy-starter 的模板,对新手非常友好,不需要本地安装 ruby 等 Jekyll 所需要的环境,只需要把博文的 markdown 文件放到 _posts 目录,推送到 GitHub 后会自动执行 Actions 任务。详细操作参见官方文档Getting Started | Chirpy
图床
在 Obsidian 笔记中用 md 写笔记时会插入图片,通常是在 Obsidian 中配置附件目录,图片保存在本地的附件目录中,但是要把笔记发布到博客中时,这样的处理就需要额外处理图片路径,因此可以选择图床。网络上的图床方案有很多,这里选用 Cloudflare R2 和 WebP Cloud 搭建免费图床,详细操作参考从零开始搭建你的免费图床系统(Cloudflare R2 + WebP Cloud)一文。
挑选笔记写入博文清单
Obsidian 笔记自动转换为 Jekyll 博客一文介绍了用一个单独的元数据笔记文件记录哪些笔记要转化为博文以及转化过程中需要使用的信息,但并没有描述如何自动化的生成/更新这个元数据笔记文件。我们可以借助 Quick Add 插件的 Macro 脚本功能自定义脚本读取笔记信息写到记录博文元数据的清单文件中。这里暂定清单文件名称为 Posts_to_Jekyll,并参照 QuickAdd docs 定义一个名为 WritePostMetadata.js 的脚本文件。
module.exports = {
entry: async (params, settings) => {
const { quickAddApi,app } = params;
// 获取当前活动的文件
const activeFile = app.workspace.getActiveFile();
if (!activeFile) {
console.error('No active file found.');
return;
}
// 获取当前文件的frontmatter
const frontmatter = app.metadataCache.getFileCache(activeFile)?.frontmatter
// 获取当前文件的名称
const fileName = activeFile.basename; // 获取文件名(不含扩展名)
if(activeFile.path.indexOf(settings["blogsFolder"]) < 0) return;
// 获取当前文件的创建时间
const fileCreationTime = frontmatter.created[0] || new Date(app.workspace.getActiveFile().stat.ctime).toLocaleString().replaceAll("/","-"); // 格式化为 YYYY-MM-DD
// 获取当前文件的修改时间
const filemodifyTime = new Date(app.workspace.getActiveFile().stat.mtime).toLocaleString().replaceAll("/","-"); // 格式化为 YYYY-MM-DD
// 获取当前文件的标签
const fileTags = frontmatter?.tags || [];
// 格式化要插入的内容
const content = `## [[${fileName}]]\n`+
`\`\`\`yaml\n`+
`title: ${fileName}\n`+
`date: ${fileCreationTime}\n`+
`mtime: ${filemodifyTime}\n`+
`categories: [${fileTags[0]}]\n`+
`tags: [${fileTags.filter(item => item != 'blog').join(', ')}]\n`+
`\`\`\` \n`;
// 获取或创建 list 文件
let listFile = app.vault.getAbstractFileByPath(settings["PostMetadata"]);
if (!listFile) {
return `${listFile} is not exist`;
}
let metaContent = await app.vault.read(listFile);
let reg = new RegExp(`(\\#\\# \\[\\[(`+ fileName +`)\\]\\]\n(.+\n){3}mtime:(.+)\n(.+\n){3})`,`g`);
if(!reg.test(metaContent)){
// 将内容插入到 list 文件的末尾
await app.vault.append(listFile, content + '\n');
}
else{
if(RegExp.$4.trim() != filemodifyTime){
const newContent = metaContent.replaceAll(reg, content);
await app.vault.modify(listFile,newContent);
}
}
},
settings: {
name: "Post_to_Jekyll configuration",
author: "czwy",
options: {
"PostMetadata": {
type: "dropdown",
description: "The path of Metadata file which records the article information to be saved to jekyll.",
defaultValue: "000-Index/Posts_to_Jekyll.md",
options: app.vault.getAllLoadedFiles().filter(item => item.extension=="md").map(item => item.path),
},
"blogsFolder": {
type: "dropdown",
description: "blogs folder.",
defaultValue: "",
options: app.vault.getAllFolders().map(item => item.path),
},
}
},
};
脚本分为 entry 和 settings 两部分, entry 是主要的业务逻辑:读取当前活动(打开的)笔记,读取笔记名称、创建时间、修改时间、标签等元数据,按照既定格式写到Posts_to_Jekyll,如果Posts_to_Jekyll没有该笔记元数据,则直接添加到末尾,如果已存在该元数据,则比较修改时间,如果修改时间不一致,则修改对应的元数据信息。
settings 是接收Quick Add 插件 Macros 脚本的设置信息,这里定义了博文类笔记保存的目录 blogsFolder 和博文元数据的清单文件 PostMetadata,在配置 Macros 时可以根据实际情况自己选择目录和文件。

将 Obsidian 笔记转换为 Jekyll 博文
Obsidian 笔记自动转换为 Jekyll 博客一文介绍了 Obsidian 笔记转换为 Jekyll 博文时需要处理的一些细节:博文日期、图片处理、链接处理、Callouts 转换为 Prompts,并提供了Python 脚本文件。在我日常笔记应用中会使用到 wiki 链接[[]] 和嵌入文本块![[]],因此在原有脚本基础上增加了这两类语法的处理。
处理嵌入文本块
嵌入文本块分为全文嵌入和部分嵌入,其语法如下:
![[xxx]]
![[xxx#yyy]]
![[xxx#^yyy]]
示例中 xxx 是嵌入文本的标题,#后边是指定的文本块,如果以 ^ 开头,则是一个文本块,可以理解为一个段落 paragraph,否则表示一个标题及该级标题下所有内容。
全文嵌入的情况,只需通过正则表达式去除 front-matter 信息。
return re.sub(r'---\n.*?\n---\n','',md_content,flags=re.DOTALL)
部分嵌入文本块时,通过 MarkdownIt 的 SyntaxTreeNode 解析笔记,然后查找类型为 paragraph 且以 ^yyy 结尾的节点,读取该节点内容。
filtered = list(map(lambda r:r,filter(lambda node: node.type == "paragraph" and ''.join([child.content for child in node.children if child.type == 'text' or child.type == 'inline']).endswith(target), root.children)))
if len(filtered) == 1:
return '\n'+'\n'.join([child.content for child in filtered[0].children if child.type == 'text' or child.type == 'inline']).strip(target) + '\n'
else:
return ''
部分嵌入标题及该级标题下所有内容时,通过 MarkdownIt 的 SyntaxTreeNode 解析笔记,然后遍历节点,找到匹配的标题时记录标题层级以及标题的行号作为起始行,然后继续遍历节点,直到找到下一个同级标题,并记录行号,将上一行作为结束行,然后读取起始行和结束行之间的内容。
start_line = -1
end_line = -1
in_target_section = False
level = -1
in_target_section = False
for node in root.children:
if node.type == "heading":
title = ''.join([child.content for child in node.children if child.type == 'text' or child.type == 'inline'])
if title.strip() == target:
level = node.tag.replace('h', '') # 提取标题级别
in_target_section = True
start_line = node.map[0] # 起始行号
continue
# 遇到其他二级或更高标题时结束
if in_target_section and int(level) <= 2:
end_line = node.map[1] - 1 # 结束行号(前一行的末尾)
break
if start_line != -1:
lines = md_content.split('\n')
end_line = end_line if end_line != -1 else len(lines)
return '\n'+ '\n'.join(lines[start_line:end_line]).strip()+'\n'
return ""
需要注意的是,提取的嵌入式文本可能也嵌入了其他的笔记,因此需要递归执提取。详细的脚本代码见czwy/obsidian-to-jekyll: A simple python script that converts Obsidian notes to Jekyll themes, and deploy to github pages.
处理 wiki 链接
首先需要说明的是,这里介绍的 wiki 链接处理思路局限性非常大,只是将[[]]的内容转换为 <a>标签,链接的文本必须是也作为博客发布的笔记,否则 Github 执行 Action 时会因为找到不链接导致构建失败。处理的脚本如下:
def process_obsidian_links(self):
"""format url"""
def sanitize_slug(string: str) -> str:
pattern = regex.compile(r'[^\p{M}\p{L}\p{Nd}]+', flags=regex.UNICODE)
slug = regex.sub(pattern, '-', string.strip())
slug = regex.sub(r'^-|-$', '', slug, flags=regex.IGNORECASE)
return slug
"""replace [[**]] to Tag <a>"""
def process_title(title, head, alias):
return f"<a href=\"/posts/{sanitize_slug(title.lower())}/{head or ''}\">{(alias or title).replace('|','')}</a>"
lines = self.content.splitlines()
new_lines = []
for i in range(len(lines)):
# include obsidian links
urls = re.finditer(r"\[\[(.*?)(\#.*?)?(\|.*?)?\]\]", lines[i])
newline = ""
pos = 0
for url in urls:
newline += lines[i][pos:url.start()] + process_title(url.group(1),url.group(2),url.group(3))
pos = url.end()
lines[i] = newline + lines[i][pos:]
self.content = '\n'.join(lines)
一键发布博文
前面介绍了自动生成博文元数据清单,以及转换博文的 python 脚本,接下来需要让 Obsidian 在更新完博文元数据清单后执行 python 脚本。这里还是定义 Macros 脚本并使用 Node.js 的child_process模块执行 python 脚本。
module.exports = {
entry: async (params, settings) => {
const { quickAddApi,app,obsidian } = params;
const { exec } = require('child_process');
const { promisify } = require('util');
const fs = require('fs');
const path = require('path');
const os = require('os');
const execAsync = promisify(exec);
try {
let listFile = app.vault.getAbstractFileByPath(settings["PythonScript"]);
const scriptPath = path.join(app.vault.adapter.basePath,listFile.path);
const setEncoding = process.platform === 'win32' ? 'chcp 65001 > nul && ' : '';
const execPath = settings["execPath"] || "python";
const params = settings["parameters"];
const command = `${setEncoding}"${execPath}" -u "${scriptPath}" ${params}`;
const { stdout, stderr } = await execAsync(command, {
timeout: 30000,
encoding: 'utf8',
env: {
...process.env,
PYTHONIOENCODING: 'utf-8'
}
});
new obsidian.Notice(stdout || stderr || '代码执行完成,无输出',3000);
return stdout || stderr || '代码执行完成,无输出';
} catch (error) {
return `执行错误:${error.message}`;
}
},
settings: {
name: "Post_to_Jekyll configuration",
author: "czwy",
options: {
"PythonScript": {
type: "dropdown",
description: "The path of python script",
defaultValue: "088-Template/Script/obsidian_to_jekyll.py",
options: app.vault.getAllLoadedFiles().filter(item => item.extension=="py").map(item => item.path),
},
"execPath": {
type: "text",
defaultValue: "",
placeholder: "Placeholder",
description: "the path of python",
},
"parameters": {
type: "text",
defaultValue: "-w",
placeholder: "Placeholder",
description: " arguments for Script.",
},
}
},
};
entry 是 Node.js 执行 python 脚本的逻辑, settings 用于配置 python 脚本的路径,python程序的路径,以及脚本接收的参数。参数说明如下:
- -w:把笔记转换为Jekyll 博文并保存在本地的 Jekyll 仓库中
- -c:提交修改
- -p:把修改push到GitHub

至此,主要工作都已完成,接下来就是组合 Macros 脚本,在QuickAdd的设置界面中添加一个名为Post-to-Jekyll的 macro,然后在Post-to-Jekyll的设置中的User Scripts中依次选用WritePostMetadata.js和execPython.js,并在脚本中间插入 100ms 的等待。

当写完博文需要发布时,只需要打开要发布的博文,用Ctrl+P调出命令列表,执行Post-to-Jekyll命令(也可以为该命令配置快捷键)就可以一键发布博文到 GitHub Pages 了。
Obsidian 笔记一键转换发布为 Jekyll 博客的更多相关文章
- 搭建jekyll博客
使用jekyll将markdown文件生成静态的html文件,并使用主题有序的进行布局,形成最终的博客页面. 特点 基于ruby 使用Markdown书写文章 无需数据库 可以使用GitHub Pag ...
- Github pages + jekyll 博客快速搭建
Github pages + jekyll 博客快速搭建 寻找喜欢的模版 https://github.com/jekyll/jekyll/wiki/sites http://jekyllthemes ...
- (转) OpenCV学习笔记大集锦 与 图像视觉博客资源2之MIT斯坦福CMU
首页 视界智尚 算法技术 每日技术 来打我呀 注册 OpenCV学习笔记大集锦 整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的 ...
- 我的Jekyll博客
我在GitHub Page上托管的Jekyll博客地址:http://lastavenger.github.io/
- 在Jekyll博客添加评论系统:gitment篇
最近在Github Pages上使用Jekyll搭建了个人博客( jacobpan3g.github.io/cn ), 当需要添加评论系统时,找了一下国内的几个第三方评论系统,如"多说&qu ...
- Word直接发布新浪博客(以Wo…
原文地址:Word直接发布新浪博客(以Word 2013为例)作者:paulke2011 注意:这篇博客直接由Word 2013发出!这虽然也算是一个教程,但更多的是一个试验品. 老早就知道Word有 ...
- [转载]Word直接发布新浪博客(以Word 2013为例)
原文地址:Word直接发布新浪博客(以Word 2013为例)作者:paulke2011 注意:这篇博客直接由Word 2013发出!这虽然也算是一个教程,但更多的是一个试验品. 老早就知道Word有 ...
- Jekyll博客添加Valine评论
Jekyll博客添加Valine评论 关于github搭建jekyl博客,在这里不做过多描述,详情参考: 百度搜索关键字:github搭建jekyll博客 官网:https://www.jekyll. ...
- 在GitLab pages上快速搭建Jekyll博客
前一段时间将我的Jekyll静态博客从github pages镜像部署到了 zeit.co(现vercel)上了一份,最近偶然发现gitlab pages也不错,百度也会正常抓取,于是动手倒腾,将gi ...
- Docsify+腾讯云对象存储 COS,一键搭建云上静态博客
最近一直在想如何利用 COS 简化静态博客的搭建过程.搜了很多的静态博客搭建过程,发现大部分的静态博客都要通过编译才能生成静态页面.功夫不负有心人,终于让我找到了一个超简洁博客的搭建方法. 效果预览 ...
随机推荐
- Solution -「GLR-R4」大暑
\(\mathscr{Description}\) Link. 这里有兔以前写的另一个题意,大家可以参考着看看. 你有两个坐标集合 \(X,Y\),\(X=\{(0,y)\mid y\in ...
- JS播放m3u8
JS播放m3u8: 利用 hls.min.js <!DOCTYPE html> <html> <head> <title>HLS Video Playb ...
- 一点区块链资料-copy
1. 场景描述 (1)今天找资料,无意中看到15年底-16年初弄的关于区块链的资料,当时写了个交流汇报区块链的ppt,感觉挺好的,共享下,希望能帮助朋友们理解区块链. (2)背景:15年底,老板从朋友 ...
- spring-springMVC-总结列表
Spring 的优良特性 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API 控制反转:IOC--Inversion of Control,指的是将对象的创建权交给 Spri ...
- Redis常用命令手册
http://c.biancheng.net/redis_command/ Redis客户端(client)命令 Redis 提供了一些操作客户端(client)的命令,比如查询所有已连接到服务器的客 ...
- 项目PMP之八项目质量管理
项目PMP之八--项目质量管理 一.定义:以执行组织的名义支持过程的持续改进活动 核心理念:兼顾项目管理和可交付成果两方面 质量影响程度(代价由大到小):客户发现缺陷 > 交付前检测和纠正缺 ...
- 【Docker】---Docker入门篇(1)
Docker入门篇 简单一句话: Docker 是一个便携的应用容器. 一.Docker的作用 网上铺天盖地的是这么说的: (1) Docker 容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得 ...
- drools 规则引擎和 solon-flow 哪个好? 规则引擎 solon-flow 简明教程
前言 做电子政务的项目时,经常会有大量的业务逻辑变更,但其实里面的业务改动,其实就是一些业务逻辑变动. 而程序员编写的代码也没有任何技术含量,跟着式样书逐字逐句的翻译就行.大量的 if/else 判断 ...
- 0帧起手将腾讯混元大模型集成到Spring AI的全过程解析
在前面,我们已经为大家铺垫了大量的知识点,并深入解析了Spring AI项目的相关内容.今天,我们将正式进入实战环节,从零开始,小雨将带领大家一步步完成将第三方大模型集成到Spring AI中的全过程 ...
- VScode中C/C++调试文件配置
VScode中C/C++调试文件配置 //launch.json { "version": "2.0.0", "configurations" ...