思源笔记迁移到 OneNote 完整教程

教程概述

前言

出这个帖子的原因,因为最早我用的是 oneonte 所以我的笔记全都在这里, 后来发现 oneonte 没有 ai 功能,也就是微软的副驾驶,因为我没有购买,而且公司也越来越多人用 wps。 所以打算换个笔记软件,可以总结文本软件,发现了思源笔记,整体用起来非常好用,但是同步功能让我很费脑子, 购买了缤纷云,充值后就非常好用了,

后来因为一些原因,我购买了微软 365,并且有微软的副驾驶 AI, 所以打算重新迁移回 oneonte,但是导出的 Markdown 格式让我无法迁移, 格式不互通, 中间花费 3 天时间,研究出了此方法。

如无必要,请继续使用思源笔记,因为折腾起来太麻烦了。 我刚开始使用思源笔记时候,就直接购买了功能特性,也是支持开发者,希望思源越来越好。

本教程帮助用户将思源笔记(SiYuan Notes)内容迁移到 Microsoft OneNote,步骤包括:

  1. 环境准备
  2. 安装 Python 及依赖
  3. 注册 Microsoft Azure 应用(获取 API 权限)
  4. 配置迁移脚本
  5. 执行迁移
  6. 注意事项

1. 环境准备

确保电脑满足以下条件:

  • 操作系统:Windows 10/11

  • Python:3.10 或以上

  • OneNote:已安装并可登录 Microsoft 账户

  • 思源笔记:导出的.zip 文件压缩包

    • PixPin_2025-08-25_10-55-18
    • 将其解压到本地文件夹(后续这个文件夹会用到)
  • 网络:可以访问 Microsoft Azure


2. 安装 Python 与依赖

2.1 安装 Python

  1. 访问 Python 官网
  2. 下载适用于 Windows 的 Python 3.10+
  3. 安装时勾选 Add Python to PATH
  4. 安装完成后,在命令行执行:
python --version

确认 Python 安装成功

2.2 安装依赖(可写自动化脚本)

保存以下代码为 install_dependencies.bat

@echo off
echo Installing Python dependencies...

REM 升级 pip
python -m pip install --upgrade pip

REM 安装 requests
pip install requests

REM 安装解析库
pip install beautifulsoup4 lxml

echo Dependencies installed successfully!
pause

运行方法:直接打开这个 bat 文件

install_dependencies.bat

3. 注册 Microsoft Azure 应用(获取 OneNote API 权限)

  1. 登录

  2. 在左侧菜单选择 Azure Active Directory应用注册新注册

    1. 填写应用信息:

      • 名称:SiYuanToOneNote(可自定义)
      • 支持账户类型:按照图片设置
      • 重定向 URI:留空
    2. PixPin_2025-08-25_10-47-55

  3. 点击 注册

  4. 记录 应用(客户端)ID ,后续脚本中会用到

    1. PixPin_2025-08-25_10-42-54
  5. 配置 API 权限:

    • 点击 API 权限添加权限Microsoft Graph委托的权限

      • PixPin_2025-08-25_10-49-21
    • 搜索框搜索 Notes.ReadWrite,点击 添加权限

      • PixPin_2025-08-25_10-50-18
    • 添加权限后,点击代表默认目录 授予管理员同意

      • PixPin_2025-08-25_10-51-13
  6. 点击 证书与机密新建客户端密钥,记录密钥值(只显示一次)

    1. PixPin_2025-08-25_10-43-40
  7. 点击身份验证,添加 URL 到移动和桌面应用程序,内容填写:http://localhost

    1. PixPin_2025-08-25_10-45-00
    2. 点击设置,按照我的设置方法
    3. PixPin_2025-08-25_10-46-22

以上信息在迁移脚本中使用,用于 OAuth2 登录和访问 OneNote API


4. 配置迁移脚本

桌面新建 txt 文本,复制以下代码,粘贴进去,保存为(.py)文件 注意查看代码内的配置选项

PixPin_2025-08-25_10-54-05

主要配置这两项。

import os
import re
import base64
import msal
import requests
import markdown
from bs4 import BeautifulSoup

# ---------------- 配置 ----------------
CLIENT_ID = "XXXXXXXXXXXXXXXXXXXXXXXXXX"   # 微软应用ID
TENANT_ID = "consumers"   # 个人账户必须用 "consumers"
SCOPES = ["Notes.ReadWrite"]

MD_ROOT = r"XXXXXXXXXXXXXXXXXXXXXXXXXXXX"  # 思源导出的 Markdown 根目录
NOTEBOOK_NAME = "思源迁移"  # OneNote 笔记本名,不要和已有重名

GRAPH_API = "https://graph.microsoft.com/v1.0"


# ---------------- 认证 ----------------
def get_token():
    app = msal.PublicClientApplication(CLIENT_ID, authority=f"https://login.microsoftonline.com/{TENANT_ID}")
    result = app.acquire_token_interactive(scopes=SCOPES)
    if "access_token" not in result:
        raise Exception(f"获取访问令牌失败: {result}")
    return result["access_token"]


# ---------------- OneNote 操作 ----------------
def get_or_create_notebook(token, notebook_name):
    url = f"{GRAPH_API}/me/onenote/notebooks"
    resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
    resp.raise_for_status()
    for nb in resp.json().get("value", []):
        if nb["displayName"] == notebook_name:
            return nb["id"]

    resp = requests.post(url, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
                         json={"displayName": notebook_name})
    resp.raise_for_status()
    return resp.json()["id"]


def get_or_create_section(token, notebook_id, section_name):
    url = f"{GRAPH_API}/me/onenote/notebooks/{notebook_id}/sections"
    resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
    resp.raise_for_status()
    for sec in resp.json().get("value", []):
        if sec["displayName"] == section_name:
            return sec["id"]

    resp = requests.post(url, headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
                         json={"displayName": section_name})
    resp.raise_for_status()
    return resp.json()["id"]


def create_page(token, section_id, title, content_html, resources=None, is_subpage=False):
    """
    在 OneNote 创建页面,支持插入图片(作为 multipart 上传)
    """
    url = f"{GRAPH_API}/me/onenote/sections/{section_id}/pages"

    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>{title}</title>
        {"<meta name='isSubPage' content='true'/>" if is_subpage else ""}
    </head>
    <body>
        <h1>{title}</h1>
        {content_html}
    </body>
    </html>
    """

    # 如果没有图片/资源,直接发 xhtml
    if not resources:
        resp = requests.post(url,
                             headers={"Authorization": f"Bearer {token}",
                                      "Content-Type": "application/xhtml+xml"},
                             data=html.encode("utf-8"))
    else:
        # multipart 请求,带图片
        multipart = {
            "Presentation": ("presentation.html", html, "application/xhtml+xml")
        }
        for rid, (fname, fcontent, mime) in resources.items():
            multipart[rid] = (fname, fcontent, mime)

        resp = requests.post(url,
                             headers={"Authorization": f"Bearer {token}"},
                             files=multipart)

    if not resp.ok:
        print("创建页面失败:", resp.text)
    return resp


# ---------------- Markdown 转 HTML,并处理图片 ----------------
def md_to_html(md_path):
    try:
        with open(md_path, "r", encoding="utf-8") as f:
            text = f.read()
        # markdown 转 html
        html = markdown.markdown(text, extensions=["extra", "tables", "fenced_code"])
        soup = BeautifulSoup(html, "html.parser")

        resources = {}
        # 处理 <img> 标签,把本地图片转成 OneNote 资源
        for idx, img in enumerate(soup.find_all("img")):
            src = img.get("src")
            if not src:
                continue
            img_path = os.path.join(os.path.dirname(md_path), src)
            if os.path.exists(img_path):
                rid = f"image{idx}"
                with open(img_path, "rb") as f:
                    data = f.read()
                mime = "image/png" if src.lower().endswith("png") else "image/jpeg"
                resources[rid] = (os.path.basename(src), data, mime)
                # 替换 src 为 oneNote:image resource
                img["src"] = f"name:{rid}"

        return str(soup), resources

    except Exception as e:
        print(f"读取 {md_path} 出错: {e}")
        return "", {}


# ---------------- 遍历文件夹并上传 ----------------
def process_directory(token, notebook_id, root_path):
    for section_name in os.listdir(root_path):
        section_path = os.path.join(root_path, section_name)
        if not os.path.isdir(section_path):
            continue

        section_id = get_or_create_section(token, notebook_id, section_name)
        print(f"创建分区: {section_name}")
        process_pages(token, section_id, section_path, level=0)


def process_pages(token, section_id, folder_path, level=0):
    entries = sorted(os.listdir(folder_path))
    for entry in entries:
        entry_path = os.path.join(folder_path, entry)

        if os.path.isdir(entry_path):
            folder_md = os.path.join(entry_path, f"{entry}.md")
            if os.path.exists(folder_md):
                content_html, resources = md_to_html(folder_md)
                create_page(token, section_id, entry, content_html, resources, is_subpage=(level > 0))
                print("创建页:", entry)

            process_pages(token, section_id, entry_path, level=level + 1)

        elif entry.endswith(".md"):
            title = os.path.splitext(entry)[0]
            content_html, resources = md_to_html(entry_path)
            create_page(token, section_id, title, content_html, resources, is_subpage=(level > 0))
            print("创建页:", title)


# ---------------- 主程序 ----------------
def main():
    token = get_token()
    notebook_id = get_or_create_notebook(token, NOTEBOOK_NAME)
    process_directory(token, notebook_id, MD_ROOT)


if __name__ == "__main__":
    main()

确保脚本可访问思源笔记导出的本地文件目录


5. 执行迁移

  1. 将思源笔记导出的文件放在脚本目录下
  2. 打开命令行,切换到目录:
cd C:\Users\YourName\Documents\SiYuanMigration
  1. 执行脚本:
python siyuan_to_onenote.py
  1. PixPin_2025-08-25_11-01-55
  2. 完成后登录 OneNote 检查笔记是否已成功同步

6. 注意事项

  • 备份数据:迁移前请备份思源笔记数据
  • 分批迁移:大数据量笔记建议分批处理
  • 编码问题:确保笔记文件 UTF-8 编码
  • Azure 权限:确保应用权限正确,并完成授权
  • 网络:迁移需要网络访问 OneNote API
  • 目前此代码仅支持个人微软账户,不支持公司账户

  1. 因为 oneonte 默认只支持,分区-页, 没有子页,子子页,如果需要有很多子子子子子页的用户,迁移后需要手动分类,

    1. 这个是你想要的。

      1. PixPin_2025-08-25_11-02-19
    2. 实际迁移后的样子

      1. PixPin_2025-08-25_11-02-49
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    28446 引用 • 119772 回帖
  • OneNote
    2 引用 • 5 回帖
1 操作
fux 在 2025-08-25 11:14:46 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...