前言
今天早上打算对Memos进行升级(0.25.2—0.25.3),没想到一升级就出事。打不开了,于是又回滚。反复几次都失败。最后是放弃数据库的恢复,直接用最新版0.25.3直接成功部署。但之前毕竟记了好些笔记和备忘。我就开始了拯救之旅。将0.25.2的数据库直接恢复到0.25.3是不可能了,试了好几次,报错,我也不知道怎么修复,AI也不知道。索性我还部署了TriliumNotes的知识库,干脆引入得了。但db的数据库文件又怎么办呢?反正折腾了八九个小时,和Gemini斗智斗勇,两个一起折腾出了一条通案:先把db想办法弄成json文件,再想办法从json转为html,TriliumNotes支持html笔记导入。最终,虽然所有笔记合并成了一条大笔记。好歹我的笔记都回来了。我折腾不动了,让Gemini帮我写了篇教程。大家如果遇到类似情况,或者有需求,也可以用。我就纳闷儿,为什么Memos自己不弄一个笔记文件导出呢。
🚀 Memos 数据无缝迁移至 Trilium Notes 完整指南:SQLite 至 Trilium HTML 终极教程
Memos 是一款优秀轻量级的卡片式笔记工具,而 Trilium Notes 则是一款强大的自托管知识库软件。本指南将为您提供一套完整且成功的解决方案,将 Memos 数据库(SQLite)中的所有数据,无缝、准确地转换为 Trilium Notes 可识别的 HTML 导入格式,完美保留笔记内容、标签和时间戳。
🎯 目标
将 Memos 的 memos.db 文件,通过 Python 脚本转换为单个 Trilium 兼容的 HTML 文件,最终导入 Trilium Notes。
🛠️ 准备工作
在开始之前,请确保您具备以下环境和文件:
-
Memos 数据库文件:
memos.db文件。 - Python 环境: 安装了 Python 3 的操作系统(用于运行转换脚本)。
-
项目文件夹: 创建一个空文件夹(例如
MemosImport),用于存放所有文件和脚本。
第一部分:从 Memos 数据库提取 JSON 数据
Memos 默认将其数据存储在 SQLite 数据库文件中。我们首先需要从该数据库中提取所有 Memo 记录并将其导出为 JSON 格式。
1. 放置数据库文件
将您的 memos.db 文件(或其副本)放入项目文件夹中。
2. 创建数据库导出脚本
创建一个名为 export_db_to_json.py 的文件,并粘贴以下代码。该脚本负责连接数据库,并将字段名调整为后续转换脚本所需的大写开头格式(如 Id, Content, Created_TS)。
`import sqlite3
import json
import os
import argparse
import time
def extract_memos_from_db(db_path, output_json_path):
“””
连接 Memos SQLite 数据库,提取 Memo 数据,并导出为 JSON。
“””
if not os.path.exists(db_path):
print(f”❌ 错误: 找不到数据库文件 ‘{db_path}’。请检查路径。”)
return
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# 尝试查询 'memo' 或 'memo_entity'
table_name = ''
for name in ['memo', 'memo_entity']:
try:
cursor.execute(f"SELECT * FROM {name} LIMIT 1")
table_name = name
break
except sqlite3.OperationalError:
continue
if not table_name:
print("❌ 错误: 数据库中找不到 'memo' 或 'memo_entity' 表。请手动检查表名。")
conn.close()
return
print(f"✅ 找到 Memo 数据表: {table_name}")
# 提取字段并转换为大写开头格式
query = f"""
SELECT
id AS Id,
content AS Content,
created_ts AS Created_TS,
updated_ts AS Updated_TS
FROM {table_name}
WHERE row_status = 0;
"""
cursor.execute(query)
memos_data = [dict(row) for row in cursor.fetchall()]
conn.close()
if not memos_data:
print("⚠️ 警告: 数据库中未找到有效的 Memo 记录 (row_status=0)。")
return
# 导出为 JSON 文件
output_data = memos_data
with open(output_json_path, 'w', encoding='utf-8') as f:
json.dump(output_data, f, indent=4, ensure_ascii=False)
print(f"✅ 成功提取 {len(memos_data)} 条 Memo 记录并导出至 '{output_json_path}'。")
except Exception as e:
print(f"❌ 数据库操作失败: {e}")
if name == ‘main‘:
default_db = ‘memos.db’
default_output = ‘memos_extracted.json’
parser = argparse.ArgumentParser(
description="从 Memos SQLite 数据库中提取 Memo 记录并导出为 JSON 文件。"
)
parser.add_argument(
"db_file",
nargs='?',
default=default_db,
help=f"Memos 数据库文件路径 (默认为: {default_db})"
)
parser.add_argument(
"output_json",
nargs='?',
default=default_output,
help=f"输出 JSON 文件路径 (默认为: {default_output})"
)
args = parser.parse_args()
extract_memos_from_db(args.db_file, args.output_json)`
3. 运行导出脚本
打开命令行或终端,导航到您的项目文件夹,运行以下命令:
`Bash
python export_db_to_json.py`
执行成功后,您将在文件夹中得到一个名为 memos_extracted.json 的文件。
重点难点开始
Memos 是一款优秀轻量级的卡片式笔记工具,而 Trilium Notes 则是一款强大的自托管知识库软件。本指南将为您提供一套完整且成功的解决方案,将 Memos 数据库(SQLite)中的所有数据,无缝、准确地转换为 Trilium Notes 可识别的 HTML 导入格式,完美保留笔记内容、标签和时间戳。
🎯 目标
将 Memos 的 memos.db 文件,通过 Python 脚本转换为单个 Trilium 兼容的 HTML 文件,最终导入 Trilium Notes。
🛠️ 准备工作
在开始之前,请确保您具备以下环境和文件:
-
Memos 数据库文件:
memos.db文件。 - Python 环境: 安装了 Python 3 的操作系统(用于运行转换脚本)。
-
项目文件夹: 创建一个空文件夹(例如
MemosImport),用于存放所有文件和脚本。
第一部分:从 Memos 数据库提取 JSON 数据
Memos 默认将其数据存储在 SQLite 数据库文件中。我们首先需要从该数据库中提取所有 Memo 记录并将其导出为 JSON 格式。
1. 放置数据库文件
将您的 memos.db 文件(或其副本)放入项目文件夹中。
2. 创建数据库导出脚本
创建一个名为 export_db_to_json.py 的文件,并粘贴以下代码。该脚本负责连接数据库,并将字段名调整为后续转换脚本所需的大写开头格式(如 Id, Content, Created_TS)。
Python
`import sqlite3
import json
import os
import argparse
import time
def extract_memos_from_db(db_path, output_json_path):
“””
连接 Memos SQLite 数据库,提取 Memo 数据,并导出为 JSON。
“””
if not os.path.exists(db_path):
print(f”❌ 错误: 找不到数据库文件 ‘{db_path}’。请检查路径。”)
return
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# 尝试查询 'memo' 或 'memo_entity'
table_name = ''
for name in ['memo', 'memo_entity']:
try:
cursor.execute(f"SELECT * FROM {name} LIMIT 1")
table_name = name
break
except sqlite3.OperationalError:
continue
if not table_name:
print("❌ 错误: 数据库中找不到 'memo' 或 'memo_entity' 表。请手动检查表名。")
conn.close()
return
print(f"✅ 找到 Memo 数据表: {table_name}")
# 提取字段并转换为大写开头格式
query = f"""
SELECT
id AS Id,
content AS Content,
created_ts AS Created_TS,
updated_ts AS Updated_TS
FROM {table_name}
WHERE row_status = 0;
"""
cursor.execute(query)
memos_data = [dict(row) for row in cursor.fetchall()]
conn.close()
if not memos_data:
print("⚠️ 警告: 数据库中未找到有效的 Memo 记录 (row_status=0)。")
return
# 导出为 JSON 文件
output_data = memos_data
with open(output_json_path, 'w', encoding='utf-8') as f:
json.dump(output_data, f, indent=4, ensure_ascii=False)
print(f"✅ 成功提取 {len(memos_data)} 条 Memo 记录并导出至 '{output_json_path}'。")
except Exception as e:
print(f"❌ 数据库操作失败: {e}")
if name == ‘main‘:
default_db = ‘memos.db’
default_output = ‘memos_extracted.json’
parser = argparse.ArgumentParser(
description="从 Memos SQLite 数据库中提取 Memo 记录并导出为 JSON 文件。"
)
parser.add_argument(
"db_file",
nargs='?',
default=default_db,
help=f"Memos 数据库文件路径 (默认为: {default_db})"
)
parser.add_argument(
"output_json",
nargs='?',
default=default_output,
help=f"输出 JSON 文件路径 (默认为: {default_output})"
)
args = parser.parse_args()
extract_memos_from_db(args.db_file, args.output_json)
`
3. 运行导出脚本
打开命令行或终端,导航到您的项目文件夹,运行以下命令:
Bash
python export_db_to_json.py
执行成功后,您将在文件夹中得到一个名为 memos_extracted.json 的文件。
第二部分:JSON 文件转换为 Trilium HTML 格式
现在我们有了标准格式的 JSON 数据,接下来将其转换为 Trilium Notes 支持的 HTML 导入格式。
1. 创建转换脚本
创建一个名为 memos_to_trilium.py 的文件,并将以下优化后的代码粘贴进去。该脚本的关键在于:
- 将 Memos 的秒级 Unix 时间戳准确转换为 Trilium 所需的 ISO 8601 UTC 格式。
-
将 Memos 的
#标签从内容中移除,并转换为 Trilium Notes 的 Note 标签属性。 - 将 Markdown 基础语法转换为 HTML。
`import json
import re
import os
import argparse
import datetime
— FUNCTIONS —
def convert_unix_to_iso(timestamp):
“””
关键修复:将 Memos 的 Unix 时间戳 (秒级整数) 转换为 Trilium 要求的 ISO 8601 格式。
“””
try:
if timestamp is None:
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace(‘+00:00’, ‘Z’)
if isinstance(timestamp, (int, float)):
dt = datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc)
else:
raise ValueError("Unexpected timestamp format.")
# Trilium 偏好 Z 后缀表示 UTC 时间
return dt.isoformat().replace('+00:00', 'Z')
except Exception as e:
# 如果转换失败,则使用当前时间作为保障
print(f"时间转换错误: {e}. 采用当前 UTC 时间作为替代。")
return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')
def format_content(content):
“””
Converts Memos content (Markdown) into HTML.
“””
if content is None:
return “”
# 移除标签 (已在上一层处理)
content = re.sub(r'#([^\s#]+)', r'\1', content)
# Markdown Links [text](url)
content = re.sub(r'\[(.*?)\]\((.*?)\)', r'[\1](\2)', content)
# Basic Formatting (Bold, Italic, Strikethrough)
content = re.sub(r'\*\*(.*?)\*\*', r'**\1**', content)
content = re.sub(r'\*(.*?)\*', r'*\1*', content, flags=re.MULTILINE)
content = re.sub(r'~~(.*?)~~', r'\1', content)
# Newlines
content = content.replace('\n', '
‘)
# 简单内容清理和防止 XSS (这里只保留我们转换后的 HTML 标签)
content = content.replace('', '>')
content = content.replace('<br>', '
‘)
content = re.sub(r’(.?)‘, r’\2‘, content)
content = re.sub(r’(.?)‘, r’\1‘, content)
content = re.sub(r’(.?)‘, r’\1‘, content)
content = re.sub(r’(.?)‘, r’\1’, content)
return content.strip()
def create_note_xml(memo):
“””
生成单个 Memo 对应的 Trilium Note XML 结构。
“””
id_ = memo.get(‘Id’)
created_ts_unix = memo.get(‘Created_TS’)
# 时间戳转换
created_iso = convert_unix_to_iso(created_ts_unix)
updated_iso = created_iso
content = memo.get('Content', '')
# 标签提取
tags = re.findall(r'#([^\s#]+)', content)
# 从内容中移除标签,得到纯净内容
content_without_tags = re.sub(r'#([^\s#]+)', r'', content).strip()
# 标题生成: 取第一行非空内容
title = content_without_tags.split('
‘)[0].split(‘\n’)[0].strip() or f”Memos Memo #{id_}”
if len(title) > 64:
title = title[:61] + ‘…’
title = title.replace(‘
‘, ‘ ‘).replace(‘\n’, ‘ ‘)
# --- XML Structure Generation ---
xml_parts = []
# Note Tag Start
xml_parts.append(f'')
# Content
formatted_body = format_content(content_without_tags)
xml_parts.append(f'{formatted_body}')
# Attributes
xml_parts.append('')
# Trilium Tags (标签属性)
for tag in tags:
xml_parts.append(f'')
# 原始 Memos ID (方便追踪)
xml_parts.append(f'')
xml_parts.append('')
# Note Tag End
xml_parts.append('')
return '\n'.join(xml_parts)
def convert_memos_to_trilium_html(input_file, output_file):
“””
主函数: 读取 Memos JSON 并生成 Trilium HTML 导入文件。
“””
if not os.path.exists(input_file):
print(f”Error: Input file not found at ‘{input_file}'”)
return
try:
with open(input_file, 'r', encoding='utf-8') as f:
memos_data = json.load(f)
except json.JSONDecodeError as e:
print(f"Error decoding JSON from file: {e}")
return
if not isinstance(memos_data, list):
print("Error: JSON 文件结构不是预期的列表格式。请确保第一步成功。")
return
# 排序:按创建时间排序,保持笔记的先后顺序
memos_data.sort(key=lambda m: m.get('Created_TS', 0))
# --- HTML Structure Start ---
html_content = [
'',
'',
'',
' ',
' Memos Import',
'',
'',
# Trilium 导入的根笔记 (作为所有 Memos 的容器)
'',
'',
'',
f'',
'',
''
]
# --- Add all Memo Notes ---
for memo in memos_data:
try:
xml_note = create_note_xml(memo)
html_content.append(xml_note)
except Exception as e:
print(f"处理笔记 ID {memo.get('Id', '未知')} 时出错: {e}")
# --- HTML Structure End ---
html_content.extend([
'',
'',
'',
''
])
final_output = '\n'.join(html_content)
print(f"写入输出文件: {output_file}...")
try:
os.makedirs(os.path.dirname(output_file) or '.', exist_ok=True)
with open(output_file, 'w', encoding='utf-8-sig') as f:
f.write(final_output)
print(f"✅ 成功转换为 Trilium HTML 文件: '{output_file}'。")
except Exception as e:
print(f"写入输出文件出错: {e}")
— ENTRY POINT —
if name == ‘main‘:
parser = argparse.ArgumentParser(description=”将 Memos JSON 导出文件转换为 Trilium HTML 导入文件。”)
parser.add_argument(“input_file”, nargs=’?’, default=”memos_extracted.json”, help=”Memos JSON 导出文件路径 (默认: memos_extracted.json)”)
parser.add_argument(“output_file”, nargs=’?’, default=”memos_trilium_import.html”,
help=”输出 Trilium HTML 文件路径 (默认: memos_trilium_import.html)”)
args = parser.parse_args()
convert_memos_to_trilium_html(args.input_file, args.output_file)`
2. 运行转换脚本
在终端/命令行中,运行以下命令:
`Bash
python memos_to_trilium.py`
执行成功后,您将在当前文件夹中获得一个名为 memos_trilium_import.html 的文件。
第三部分:导入至 Trilium Notes
这是最后一步,将生成的 HTML 文件导入到您的 Trilium Notes 知识库中。
1. 导入操作步骤
- 打开您的 Trilium Notes 应用程序或 Web 界面。
- 在您希望导入笔记的位置(通常是根节点),右键点击。
- 选择 “导入笔记 (Import note)”。
- 在弹出的对话框中,导入格式 (Import format) 选择 “HTML”。
-
点击 “选择文件 (Choose file)”,上传您刚刚生成的
memos_trilium_import.html文件。 - 点击 “导入 (Import)”。
2. 导入结果验证
导入完成后,您将看到一个名为 Memos Import - 导入于 XXXX-XX-XX 的父笔记。展开该笔记,您会发现:
💡 提示: 导入后,如果您想对所有 Memos 笔记进行批量管理,可以直接在 Trilium 中选择
Memos Import父笔记,使用 笔记克隆 (Clone Note) 或 批量操作 (Batch Operations) 功能进行统一调整。
希望这篇完整的教程能帮助您和其他网友顺利完成数据迁移!
了解 ~/jintaoblog/朝夕见闻志⚡️ 的更多信息
订阅后即可通过电子邮件收到最新文章。
