使用 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 pymysql
import json
import os
import 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 官方未来如何发展,只要保留了数据库文件,你的文字资产就永远不会丢失。
温馨提示: 建议定期运行此脚本进行数据备份,确保你的博客内容安全无忧。