使用 Python 脚本备份 Halo 博客文章
前言
对于使用 Halo 2.x 版本的博主来说,数据备份一直是个核心问题。不同于 Halo 1.x 或 WordPress 等传统博客系统将文章明文存储在 posts 表中,Halo 2.x 采用了一种更现代但对普通用户不太友好的”对象模型”存储方式。
当你打开数据库试图进行人工备份时,你可能会发现找不到文章表,只看到一张巨大的 extensions 表。其实,你的所有文章都序列化后存储在这张表的 data 字段(BLOB 类型)中。
本文将提供一个 Python 自动化脚本,绕过 Halo 后台,直接从 MySQL 数据库的底层 extensions 表中提取数据,并将所有文章批量还原为通用的 Markdown 文件。这不仅是数据容灾的最佳手段,也是迁移到 Hexo、Hugo 或 Typecho 的完美方案。
适用场景
- Halo 博客无法启动,需要紧急导出文章
- 计划从 Halo 迁移至静态博客(Hexo/Hugo),需要批量 Markdown 文件
- 希望将所有文章在本地硬盘保留一份”纯文本”格式的冷备份
准备工作
在运行脚本之前,请确保你具备以下环境:
- Python 3.x - 确保本机已安装 Python 环境
- MySQL 连接信息 - 你需要知道数据库的 IP 地址、端口、用户名、密码以及 Halo 的数据库名称
- 安装依赖库 - 我们需要
pymysql来连接数据库
打开终端或命令行(CMD/PowerShell),执行以下命令安装依赖:
pip install pymysql核心脚本
创建一个名为 halo_backup.py 的文件,将下方代码完整复制进去。
该脚本的核心逻辑如下:
- 连接 MySQL 数据库
- 筛选
extensions表中name字段包含/registry/content.halo.run/posts/的记录(即文章数据) - 读取
data字段的二进制流(BLOB),并将其解码为 JSON 格式 - 解析 JSON,提取标题、正文、发布时间、Slug 等元数据
- 生成带有 Front Matter(头部元数据)的 Markdown 文件并保存
import pymysqlimport jsonimport osimport re
# ================= 配置区域 (请修改此处) =================DB_CONFIG = { 'host': '127.0.0.1', # 数据库地址 (本地填 127.0.0.1,远程填服务器IP) 'port': 3306, # 数据库端口 'user': 'root', # 数据库用户名 'password': 'your_password',# 数据库密码 (请替换为真实密码) 'database': 'halo', # Halo 的数据库名 'charset': 'utf8mb4'}
# 备份文件保存的文件夹名称OUTPUT_DIR = './halo_posts_backup'# =======================================================
def clean_filename(title): """ 清洗文件名:将无法作为文件名的字符替换为下划线 """ return re.sub(r'[\\/*?:"<>|]', '_', title)
def export_halo_posts(): # 1. 创建备份目录 if not os.path.exists(OUTPUT_DIR): os.makedirs(OUTPUT_DIR) print(f"[-] 已创建备份目录: {os.path.abspath(OUTPUT_DIR)}")
print("[-] 正在连接数据库...")
connection = None try: # 2. 建立数据库连接 connection = pymysql.connect(**DB_CONFIG)
with connection.cursor() as cursor: # 3. 执行查询 # Halo 2.x 的文章数据存储在 extensions 表中 # Key 规则为: /registry/content.halo.run/posts/{UUID} print("[-] 正在查询文章数据...") sql = "SELECT name, data FROM extensions WHERE name LIKE '/registry/content.halo.run/posts/%'" cursor.execute(sql) results = cursor.fetchall()
total_count = len(results) print(f"[-] 数据库中发现 {total_count} 篇文章,准备开始解析...")
success_count = 0
for row in results: name_key = row[0] blob_data = row[1] # 获取二进制数据 (longblob)
try: if not blob_data: continue
# 4. 解码:将二进制数据转为 JSON 字符串 json_str = blob_data.decode('utf-8') post_data = json.loads(json_str)
# 5. 提取核心字段 # Halo 2.x 数据结构通常包含 spec 和 metadata spec = post_data.get('spec', {}) metadata = post_data.get('metadata', {})
title = spec.get('title', '未命名文章') content = spec.get('content', '') # 正文 (Markdown源码) create_time = metadata.get('creationTimestamp', '') slug = spec.get('slug', clean_filename(title))
# 6. 拼接 Markdown 内容 # 使用 Front Matter 格式,通用性最强 md_content = f"""---title: "{title}"date: {create_time}slug: {slug}id: {name_key}---
{content}""" # 7. 写入文件 file_name = f"{clean_filename(title)}.md" file_path = os.path.join(OUTPUT_DIR, file_name)
with open(file_path, 'w', encoding='utf-8') as f: f.write(md_content)
print(f"[√] 成功导出: {file_name}") success_count += 1
except Exception as e: print(f"[x] 解析失败: {name_key} - {str(e)}")
print(f"\n" + "="*40) print(f"备份任务完成!") print(f"共扫描: {total_count} 篇") print(f"成功导出: {success_count} 篇") print(f"文件路径: {os.path.abspath(OUTPUT_DIR)}") print(f"="*40)
except pymysql.MySQLError as e: print(f"\n[ERROR] 数据库连接失败: {e}") print("请检查脚本顶部的 DB_CONFIG 配置是否正确。") finally: if connection: connection.close()
if __name__ == '__main__': export_halo_posts()使用教程
第一步:配置数据库信息
用文本编辑器(如 VS Code, Notepad++)打开 halo_backup.py,找到代码顶部的 DB_CONFIG 区域:
DB_CONFIG = { 'host': '127.0.0.1', 'port': 3306, 'user': 'root', 'password': '这里填你的密码', 'database': 'halo', 'charset': 'utf8mb4'}注意事项:
- 如果你是在服务器上运行脚本(连接本机数据库),host 填
127.0.0.1 - 如果你是在本地电脑运行脚本连接远程服务器,host 填服务器 IP,并确保服务器防火墙开放了 3306 端口
第二步:运行脚本
在文件所在目录打开命令行,输入:
python halo_backup.py第三步:查收备份
脚本运行只需几秒钟。运行结束后,你会看到如下提示:
[-] 已创建备份目录: .../halo_posts_backup[-] 正在连接数据库...[-] 数据库中发现 25 篇文章,准备开始解析...[√] 成功导出: Docker部署教程.md[√] 成功导出: Python学习笔记.md...备份任务完成!成功导出 25 篇。打开脚本目录下的 halo_posts_backup 文件夹,你所有的文章都已经安静地躺在那里了。
原理分析与总结
为什么只用一张 extensions 表就能恢复数据?
Halo 2.0 借鉴了 Kubernetes 的 CRD(自定义资源定义)设计理念。在数据库层面,它不再为每种资源创建独立的表,而是将数据统一存储。
- Key:
/registry/content.halo.run/posts/文章ID - Value: 一个完整的 JSON 对象,包含了文章的所有信息
这种设计虽然增加了直接通过 SQL 查询数据的难度,但数据的完整性极高。只要掌握了本文介绍的 JSON 解析方法,数据主权就永远掌握在你手中。无论 Halo 官方未来如何发展,只要保留了数据库文件,你的文字资产就永远不会丢失。
温馨提示: 建议定期运行此脚本进行数据备份,确保你的博客内容安全无忧。