前言

今天早上打算对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/朝夕见闻志⚡️ 的更多信息

订阅后即可通过电子邮件收到最新文章。