今日问题与解决方案整理(Hexo Butterfly 主题博客搭建 + 网络安全笔记迁移)

一、Markdown 笔记批量迁移到 Hexo

1. 问题:本地笔记(D:\Blog\ 小迪学习笔记)迁移到 Hexo _posts 目录

  • 需求:批量迁移大量网络安全笔记,自动生成 Hexo 所需 Front-matter(避免手动修改),要求 title 用笔记文件名,categories/tags 固定为网络安全相关分类。

  • 解决方案:使用 Python 迁移脚本(自动添加 Front-matter + 复制文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import os
from datetime import datetime

# 配置路径(根据实际情况修改)
src_dir = r"D:\Blog\小迪学习笔记" # 本地笔记源目录
dst_dir = r"D:\BlogFile\source\_posts" # Hexo 文章存储目录(_posts)

# 若目标目录不存在,自动创建
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)

# 遍历所有 Markdown 笔记
for file in os.listdir(src_dir):
if file.endswith(".md"):
src_path = os.path.join(src_dir, file) # 原笔记路径
dst_path = os.path.join(dst_dir, file) # 迁移后路径

# 读取原笔记内容
with open(src_path, "r", encoding="utf-8") as f:
content = f.read()

# 自动生成 Front-matter(适配 Butterfly 主题)
title = os.path.splitext(file)[0] # 用文件名作为文章标题(去掉.md后缀)
date_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 生成当前时间作为发布时间
front_matter = f"""---
updated: 2025-09-16 15:50:31
title: {title}
date: 2025-09-16 13:44:53
categories:
- 你的种类
tags:
- 文章标签
---
"""

# 将 Front-matter 与笔记内容合并,写入目标文件
with open(dst_path, "w", encoding="utf-8") as f:
f.write(front_matter + content)

print(f"已迁移:{file}") # 打印迁移进度

print("所有网络安全笔记迁移完成!")
  • 使用步骤
  1. 将脚本保存为 migrate_notes.py,放在 D:\Blog 目录(与笔记源目录同级)。

  2. 打开 CMD 命令行,执行 cd D:\Blog 切换到脚本目录。

  3. 运行 python migrate_notes.py,等待脚本完成所有笔记迁移。

二、Hexo 构建(hexo g)报错解决

1. 问题 1:Nunjucks Error(无法解析 SSTI 代码中的 {{ }} 语法)

  • 报错信息:Unable to call class.base.subclasses, which is undefined or falsey。原因是笔记中 SSTI 渗透测试代码的 {{ }} 被 Hexo/Butterfly 主题的 Nunjucks 模板引擎误解析为模板语法。

  • 解决方案:如果代码块中含有 {{ }},必须用 ` 和 ` 包裹,避免模板引擎解析。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import os

# 配置 Hexo 文章目录
posts_dir = r"D:\BlogFile\source\_posts"

for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
file_path = os.path.join(posts_dir, filename)
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines() # 按行读取文章内容

new_lines = []
in_code_block = False # 标记是否处于 ``` 代码块内
code_block_content = [] # 暂存代码块内容

for line in lines:
stripped_line = line.strip()

# 检测代码块开始(``` 开头)
if stripped_line.startswith("```"):
if not in_code_block:
# 进入代码块,初始化暂存列表
in_code_block = True
code_block_content = [line]
else:
# 退出代码块,检查是否含 {{ }}
code_block_content.append(line)
in_code_block = False

# 若代码块含 {{,则用 {% raw %} 包裹
if any("{{" in content_line for content_line in code_block_content):
new_lines.append("{% raw %}\n") # 加 raw 开始标记
new_lines.extend(code_block_content) # 加代码块内容
new_lines.append("{% endraw %}\n") # 加 raw 结束标记
else:
new_lines.extend(code_block_content) # 不含 {{,直接添加

elif in_code_block:
# 处于代码块内,暂存内容
code_block_content.append(line)
else:
# 非代码块内容,直接添加
new_lines.append(line)

# 将处理后的内容写回文件
with open(file_path, "w", encoding="utf-8") as f:
f.writelines(new_lines)

print(f"已处理代码块:{filename}")

print("所有文章代码块 {{ }} 解析问题修复完成!")

2. 问题 2:unknown block tag: endraw( 成对 / 嵌套错误) - **报错原因**:正文中误加多余的 {% raw %}/{% endraw %}(如表格、列表中),或 {% raw %} 未完整包裹代码块导致标签不成对。 - **解决方案**:用脚本清理正文中多余的 raw 标签,仅保留代码块内的有效 raw 标记: {% raw %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import os

# 配置 Hexo 文章目录
posts_dir = r"D:\BlogFile\source\_posts"

for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
file_path = os.path.join(posts_dir, filename)
with open(file_path, "r", encoding="utf-8") as f:
lines = f.readlines()

new_lines = []
in_code_block = False
code_block_content = []

for line in lines:
stripped_line = line.strip()

# 跳过正文中单独的 raw 标签(非代码块内)
if stripped_line in ("{% raw %}", "{% endraw %}"):
continue

# 处理代码块
if stripped_line.startswith("```"):
if not in_code_block:
in_code_block = True
code_block_content = [line]
else:
code_block_content.append(line)
in_code_block = False

# 含 {{ 的代码块加 raw 包裹
if any("{{" in c for c in code_block_content):
new_lines.append("{% raw %}\n")
new_lines.extend(code_block_content)
new_lines.append("{% endraw %}\n")
else:
new_lines.extend(code_block_content)
elif in_code_block:
code_block_content.append(line)
else:
new_lines.append(line)

# 写回处理后的内容
with open(file_path, "w", encoding="utf-8") as f:
f.writelines(new_lines)

print(f"已清理多余 raw 标签:{filename}")

print("所有文章 raw 标签错误修复完成!")
{% endraw %}

三、Hexo 图片路径适配问题

1. 问题:本地 Windows 路径(C:\Users…\image.png)无法显示

  • 原因:Hexo 生成的是静态网页,无法访问本地电脑的绝对路径(如 C:\Users\…\image.png),仅支持项目内相对路径或网络图片 URL。

  • 解决方案:用脚本自动迁移本地图片到 Hexo 图片目录,并替换 Markdown 中的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import os
import shutil
import re

# 配置路径
posts_dir = r"D:\BlogFile\source\_posts" # Hexo 文章目录
hexo_img_dir = r"D:\BlogFile\source\img" # Hexo 图片存储目录(自动创建)
# 匹配 Typora 默认的本地图片路径(格式:C:\Users\用户名\...\image-时间戳.png)
img_path_pattern = re.compile(r'C:\\Users\\[^\\]+\.*\\image-\d+\.png')

# 若 Hexo 图片目录不存在,自动创建
if not os.path.exists(hexo_img_dir):
os.makedirs(hexo_img_dir)

# 遍历所有文章,处理图片
for filename in os.listdir(posts_dir):
if filename.endswith(".md"):
file_path = os.path.join(posts_dir, filename)
with open(file_path, "r", encoding="utf-8") as f:
article_content = f.read()

# 提取文章中所有本地图片路径
local_img_paths = img_path_pattern.findall(article_content)
if local_img_paths:
for local_img_path in local_img_paths:
# 提取图片文件名(如 image-20250701191610806.png)
img_filename = os.path.basename(local_img_path)
# 复制本地图片到 Hexo 图片目录
shutil.copy2(local_img_path, os.path.join(hexo_img_dir, img_filename))
# 替换文章中的路径为 Hexo 相对路径(/img/图片名.png)
article_content = article_content.replace(local_img_path, f'/img/{img_filename}')

# 将修改后的内容写回文章
with open(file_path, "w", encoding="utf-8") as f:
f.write(article_content)

print(f"已处理图片:{filename}(共迁移 {len(local_img_paths)} 张)")
else:
print(f"无本地图片:{filename}")

print("所有文章图片路径适配完成!")
  • 关键说明
  1. 脚本仅匹配 Typora 默认截图路径(C:\Users...\image-xxxx.png),若图片路径不同,可修改 img_path_pattern 正则表达式。

  2. 迁移后图片存储在 Hexo 项目的 source/img 目录,文章中路径变为 /img/xxx.png(Butterfly 主题可直接识别)。

四、其他关键问题

1. 问题:Butterfly 主题 cover 字段不填的影响

  • 答案

    • 不填 cover:文章列表页(首页 / 分类页)的文章卡片无自定义封面,显示主题默认背景(无报错,功能完全正常)。
    • 填 cover:需配置 cover: /img/xxx.webp(图片需放在 source/img 目录),列表页和文章详情页顶部会显示自定义封面,提升视觉效果;若需统一封面,可在主题配置文件中设置默认封面。

五、核心工具 / 命令总结

操作场景 关键命令 / 工具
Hexo 文章预览 hexo clean && hexo g && hexo s(清理缓存→生成静态文件→启动本地服务器,访问 http://localhost:4000)
Hexo 文章部署 hexo d(需提前配置 GitHub Pages/Gitee Pages 等部署目标)
Markdown 笔记迁移 上述 migrate_notes.py 脚本
代码块 {{ }} 解析修复 上述 fix_code_raw.py 脚本
本地图片路径适配 上述 migrate_imgs.py 脚本
批量转换 WebP 图片(可选) Windows 批处理脚本 toWebp.bat(依赖 Google cwebp 工具)