从有道云笔记迁移到Obsidian:精确保留创建时间的自动化方案

📅 2026/8/3 15:21:03 👁️ 阅读次数 📝 编程学习
从有道云笔记迁移到Obsidian:精确保留创建时间的自动化方案

1. 从有道云到Obsidian:一次关乎“时间”的笔记迁徙

如果你和我一样,是个笔记重度用户,那么“迁移”这个词,大概率会像幽灵一样,在你使用某个笔记软件超过三年后,时不时地冒出来敲打你。这次,我决定把积累了近十年的有道云笔记,完整地搬到Obsidian。这不仅仅是一次简单的“复制粘贴”,核心诉求非常明确:我必须保留每一篇笔记的原始创建时间。这个看似简单的需求,在跨平台、跨格式的迁移中,却成了一个技术活。为什么创建时间如此重要?因为它是我个人知识脉络的“地层”,记录了某个想法、某段摘录最初诞生的那一刻,是回顾和梳理时不可或缺的坐标。而Obsidian,以其纯本地、Markdown优先、高度可定制的特性,成为了我心中理想的“数字花园”新址。但官方或社区常见的迁移工具,往往只关心内容转移,对文件的元数据,尤其是这个关键的“出生日期”,要么忽略,要么处理得相当粗糙。

所以,这篇文章记录的就是我如何解决这个问题的完整过程。它不是一篇泛泛而谈的“Obsidian入门”,而是聚焦于“从有道云笔记导出,并精确保留文件创建时间导入Obsidian”这一具体场景的实战手册。整个过程涉及文件格式转换、元数据操作、脚本编写和工具链整合,我会把每一步的原理、踩过的坑以及最终的自动化方案都摊开来讲。无论你是技术背景的开发者,还是希望更自主掌控数据的笔记爱好者,都能从中找到可复现的路径。

2. 迁移前的核心准备:理解数据与工具的边界

在动手之前,盲目操作只会导致数据混乱。我们必须先搞清楚两件事:有道云笔记给出了什么,以及Obsidian需要什么

2.1 有道云笔记的导出物剖析

有道云笔记的导出功能,在“批量导出”选项里,提供了两种主要格式:有道云笔记专用格式 (.note)HTML

  • .note格式:这是有道云自家的私有格式,一个.note文件实际上是一个压缩包,里面包含了笔记内容的XML描述、附件资源等。它的优点是信息最全,但对于外部工具极不友好,几乎无法直接使用。我们的迁移路径不会选择它。
  • HTML格式:这是本次迁移的唯一可行起点。当你选择导出为HTML时,有道云会为你生成一个文件夹,里面包含:
    • index.html:一个索引文件,列出了所有导出的笔记标题。
    • notes文件夹:里面是每一篇笔记对应的独立.html文件。
    • 一个resources文件夹:存放笔记中的所有图片、附件等。

关键点在于:这些导出的.html文件,其系统文件属性中的“创建时间”和“修改时间”,是否对应笔记的原始创建时间和最后修改时间?经过我的实测,在Windows和macOS系统下,导出的HTML文件的“创建时间”被设置为导出操作发生的时间,而不是笔记本身的原始时间。这是一个重要的认知,意味着我们不能依赖操作系统看到的文件属性,必须从文件内容里寻找时间戳。

幸运的是,打开任意一个导出的HTML文件,在<head>区域,通常能找到这样的元信息:

<meta name="created" content="2018-07-15T14:32:00" /> <meta name="modified" content="2023-11-20T09:15:00" />

这里的createdmodified就是我们要找的黄金数据。我们的核心任务,就是将这些HTML文件转换成Markdown(.md)的同时,把这两个时间戳精准地“刻录”到新文件上。

2.2 Obsidian 如何识别文件时间

Obsidian 作为一个基于本地文件系统的软件,它显示笔记的“创建时间”和“修改时间”默认依赖于操作系统提供的文件元数据。在Windows上是文件的“创建日期”属性;在macOS/Linux上是文件的“ctime”(状态更改时间,通常近似创建时间)和“mtime”(修改时间)。

因此,我们的目标非常清晰:生成最终的.md文件后,必须使用程序化的方法,将我们从HTML中解析出来的createdmodified时间,分别写入到操作系统层面文件的“创建时间”和“修改时间”属性中。这样,当Obsidian打开这个文件夹时,它就能“看到”并展示出正确的时间线。

2.3 工具链选型与搭建

基于以上分析,我们需要一个处理管道:

  1. 格式转换:将HTML批量转换为Markdown。
  2. 内容清洗:清理转换后Markdown中残留的HTML标签、有道云特有的无用样式等。
  3. 时间元数据提取与写入:从源HTML提取时间,并写入目标.md文件的系统属性。

我选择的工具组合是pandoc+Python

  • pandoc:文档转换的“瑞士军刀”,能高质量地将HTML转换为Markdown,对代码块、表格、列表等格式支持良好。它是命令行工具,便于批量处理。
  • Python 3:用于编写自动化脚本,协调整个流程。它的ossubprocessBeautifulSoup4(用于解析HTML)、frontmatter(用于处理YAML头信息)等库非常适合这类文件操作。

环境准备步骤:

  1. 安装pandoc:访问 pandoc官网 下载并安装对应操作系统的版本。安装后,在终端输入pandoc --version确认安装成功。
  2. 安装Python 3:确保你的系统已安装Python 3.6及以上版本。
  3. 安装必要的Python库:打开终端或命令提示符,执行以下命令:
    pip install beautifulsoup4 python-frontmatter
    beautifulsoup4用于解析HTML提取时间,python-frontmatter用于方便地读写Markdown的YAML Front-Matter(虽然本次核心不依赖Front-Matter写入时间,但该库在文件操作上很方便)。

3. 实战迁移:从HTML到带时间戳的Markdown

假设你已经从有道云笔记导出了一个HTML文件夹,结构如下:

有道云笔记导出/ ├── index.html ├── notes/ │ ├── 第一篇笔记.html │ ├── 第二篇笔记.html │ └── ... └── resources/ └── (图片等附件)

我们在同一目录下创建一个Python脚本migrate.py,并按照以下步骤构建逻辑。

3.1 第一步:批量转换HTML为Markdown

首先,我们使用pandoc进行格式转换。一个基本的转换命令是:

pandoc "input.html" -f html -t markdown -s -o "output.md"

为了获得更好的Markdown兼容性(特别是针对Obsidian),我们可以添加一些选项,例如--wrap=none来防止pandoc自动换行(保持原有段落结构),--atx-headers使用#风格的标题。

我们在Python脚本中批量执行这个操作:

import os import subprocess from pathlib import Path # 定义路径 source_html_dir = Path("./有道云笔记导出/notes") target_md_dir = Path("./Obsidian笔记库") # 创建目标文件夹 target_md_dir.mkdir(parents=True, exist_ok=True) # 遍历所有HTML文件 for html_file in source_html_dir.glob("*.html"): md_file = target_md_dir / (html_file.stem + ".md") # 构建pandoc命令 # -f html: 输入格式为HTML # -t markdown: 输出格式为Markdown # --wrap=none: 不自动换行 # -s: 生成独立文件(包含必要的头部) cmd = ["pandoc", str(html_file), "-f", "html", "-t", "markdown", "--wrap=none", "-s", "-o", str(md_file)] try: subprocess.run(cmd, check=True, capture_output=True, text=True) print(f"转换成功: {html_file.name} -> {md_file.name}") except subprocess.CalledProcessError as e: print(f"转换失败 {html_file.name}: {e.stderr}")

运行这部分脚本后,你会得到一个包含所有Markdown文件的Obsidian笔记库文件夹。但此时的.md文件,其系统创建时间仍然是“现在”。

3.2 第二步:提取原始时间戳并修改文件属性

这是最核心的一步。我们需要从源.html文件中解析出<meta name="created"><meta name="modified">的内容,然后将这些时间设置为对应.md文件的系统时间。

这里有一个关键陷阱:操作系统(尤其是Windows)对“创建时间”这个属性的写入权限限制。在Python中,直接使用os.utime只能修改文件的访问时间和修改时间(atimemtime)。要修改“创建时间”(在Windows上),我们需要借助pywin32库(仅限Windows)或win32_setctime等跨平台兼容性稍差的库。为了追求方案的通用性和可靠性,我采用了以下策略:

核心策略:我们优先确保“修改时间”的绝对正确,因为它是笔记最后更新的真实记录。对于“创建时间”,我们通过一个“曲线救国”的方式在Obsidian中完美呈现:将原始创建时间写入Markdown文件的YAML Front-Matter中。Obsidian可以通过插件(如Dataview)或主题来读取和显示Front-Matter中的自定义字段,效果与系统创建时间无异,且更可控、可移植。

更新后的脚本逻辑如下:

  1. 解析HTML,获取时间:使用BeautifulSoup.html文件中抓取createdmodified的日期字符串。
  2. 处理Markdown文件: a.写入Front-Matter:在Markdown文件的开头插入YAML Front-Matter,包含createdupdated字段。 b.修改系统修改时间:使用os.utime将文件的系统“修改时间”设置为从HTML中解析出的modified时间。 c. (可选)对于Windows用户,可以尝试使用win32_setctime来设置创建时间,但这不是跨平台必须的。
import os import subprocess from pathlib import Path from datetime import datetime import frontmatter from bs4 import BeautifulSoup def parse_time_from_html(html_path): """从有道云导出的HTML文件中解析创建和修改时间""" with open(html_path, 'r', encoding='utf-8') as f: soup = BeautifulSoup(f.read(), 'html.parser') created_meta = soup.find('meta', attrs={'name': 'created'}) modified_meta = soup.find('meta', attrs={'name': 'modified'}) created_str = created_meta['content'] if created_meta else None modified_str = modified_meta['content'] if modified_meta else None # 将字符串转换为datetime对象,有道云格式通常是 ISO 8601 (如 2018-07-15T14:32:00) created_dt = datetime.fromisoformat(created_str.replace('Z', '+00:00')) if created_str else None modified_dt = datetime.fromisoformat(modified_str.replace('Z', '+00:00')) if modified_str else None return created_dt, modified_dt def set_file_mtime(file_path, dt): """设置文件的系统修改时间""" if dt: # 将datetime对象转换为时间戳(秒) timestamp = dt.timestamp() os.utime(file_path, (timestamp, timestamp)) # 同时设置atime和mtime为相同值 print(f" 已设置修改时间: {dt}") # 主流程 source_html_dir = Path("./有道云笔记导出/notes") target_md_dir = Path("./Obsidian笔记库") target_md_dir.mkdir(parents=True, exist_ok=True) for html_file in source_html_dir.glob("*.html"): md_file_name = html_file.stem + ".md" md_file_path = target_md_dir / md_file_name # 1. 转换格式 cmd = ["pandoc", str(html_file), "-f", "html", "-t", "markdown", "--wrap=none", "-s", "-o", str(md_file_path)] subprocess.run(cmd, capture_output=True) # 2. 解析时间 created_dt, modified_dt = parse_time_from_html(html_file) if os.path.exists(md_file_path): # 3. 读取现有的Markdown内容 with open(md_file_path, 'r', encoding='utf-8') as f: content = f.read() # 4. 创建或更新Front-Matter # 使用frontmatter库可以优雅地处理已有或没有Front-Matter的情况 post = frontmatter.loads(content) if created_dt: post['created'] = created_dt.isoformat() # 以ISO格式存储 if modified_dt: post['updated'] = modified_dt.isoformat() # 使用'updated'作为键,更语义化 # 5. 写回文件(包含Front-Matter) new_content = frontmatter.dumps(post) with open(md_file_path, 'w', encoding='utf-8') as f: f.write(new_content) # 6. 设置系统修改时间 if modified_dt: set_file_mtime(md_file_path, modified_dt) print(f"处理完成: {html_file.name} | 创建于: {created_dt} | 更新于: {modified_dt}") else: print(f"警告: 转换后的文件不存在 {md_file_path}")

运行这个脚本后,你的Obsidian笔记库里的每个.md文件都会:

  1. 内容正确(由pandoc保证)。
  2. 文件开头包含如下的YAML Front-Matter:
    --- created: 2018-07-15T14:32:00 updated: 2023-11-20T09:15:00 ---
  3. 文件的系统“修改时间”被设置为笔记的最后更新时间。

3.3 第三步:附件资源的处理

有道云导出的resources文件夹里存放着图片等附件。在HTML中,图片链接可能是相对路径或带有特定资源ID。经过pandoc转换后,这些链接通常会变成指向本地resources文件夹下文件的相对路径,例如![图片](resources/abc123.jpg)

你需要手动将resources文件夹整个复制Obsidian笔记库目录下。确保复制后的路径与Markdown文件中的相对引用路径匹配。通常,直接放在库的根目录或一个专门的assets文件夹内都是可行的,只要调整好相对路径即可。一个更稳妥的做法是在复制后,使用文本编辑器的“在文件中查找替换”功能,批量将resources/路径替换为你Obsidian库内设定的附件文件夹路径,比如assets/

4. 在Obsidian中完美呈现时间线

现在,我们有了一个包含正确Front-Matter时间戳和系统修改时间的笔记库。如何在Obsidian里利用它们呢?

4.1 使用Dataview插件动态查询与展示

Dataview是Obsidian的超级插件,它允许你使用类SQL的查询语法,基于笔记的元数据(包括Front-Matter)动态生成视图。

  1. 安装Dataview插件:在Obsidian设置中,进入“社区插件”,搜索并安装“Dataview”。

  2. 创建一个视图笔记:例如,创建一个名为笔记时间线.md的文件。

  3. 写入Dataview查询:在该文件中,你可以写入如下代码:

    ## 按创建时间排序的笔记列表 ```dataview TABLE created AS "创建时间”, updated AS “最后更新”, file.mtime AS “文件修改时间” FROM “” WHERE created SORT created DESC ```

这个查询会列出所有包含created字段的笔记,并按创建时间倒序排列。file.mtime显示的是我们之前设置的系统修改时间,可以和updated字段对比验证。

你还可以创建更复杂的视图,比如按年/月分组: ````markdown ## 笔记年鉴

```dataview TABLE WITHOUT ID link(file.link, file.name) AS “笔记”, updated AS “更新” FROM “” WHERE created SORT created DESC GROUP BY dateformat(created, “yyyy-MM”) AS “月份” ``` ````

4.2 利用主题或CSS片段直接显示

如果你希望创建时间直接显示在每一篇笔记的标题下方或侧边栏,可以:

  1. 寻找支持Front-Matter显示的主题:一些Obsidian主题(如Blue Topaz)内置了显示Front-Matter字段的选项。
  2. 使用CSS代码片段:创建一个.css文件(如show-frontmatter.css)放在你的Obsidian库的.obsidian/snippets/文件夹下,并在设置中启用它。CSS代码可以像这样:
    /* 在文档标题后显示创建时间 */ .inline-title:after { content: “创建于:” attr(data-created); font-size: 0.9em; color: var(--text-muted); margin-left: 1em; font-weight: normal; }
    但这需要主题或插件提供对应的数据属性支持,通常需要配合TemplaterDataview的内联字段功能来实现,复杂度较高。对于大多数用户,使用Dataview创建独立的“仪表盘”或“索引”页面是更简单有效的方式。

4.3 关于“文件创建时间”的最终解决方案

经过上述流程,我们实现了:

  • 系统修改时间:100%准确,对应笔记的最后更新日期。
  • 创建时间:以高保真、可移植的方式存储在Front-Matter中,通过Dataview可以完美查询、排序和展示。

如果你极度执着于操作系统的“创建时间”属性,在Windows上可以补充安装win32_setctime库 (pip install win32-setctime),并在脚本中添加如下函数和调用:

import win32_setctime def set_file_ctime_windows(file_path, dt): """仅限Windows: 设置文件的系统创建时间""" if dt: timestamp = dt.timestamp() win32_setctime.setctime(file_path, timestamp) print(f" 已设置创建时间 (Windows): {dt}") # 在主循环中,在设置mtime后调用 if created_dt and os.name == 'nt': # 'nt' 代表 Windows set_file_ctime_windows(md_file_path, created_dt)

请注意:此方法仅适用于Windows,且需要管理员权限可能不是必须的,但操作系统的文件系统可能会在某些情况下(如文件移动)重置此时间。因此,将核心时间数据保存在文件内容(Front-Matter)中,是我推荐的、更健壮的方案。

5. 迁移后的整理与优化建议

完成基础迁移后,你的Obsidian知识库已经具备了正确的时间维度。接下来可以进行一些优化,让它更好用。

5.1 清理与格式化Markdown

pandoc转换的Markdown可能包含一些冗余的空格、残留的无关div标签或奇怪的空行。你可以编写一个简单的Python脚本进行后处理,例如使用正则表达式清理过多的换行,或者确保图片链接格式符合Obsidian的偏好(比如使用![[图片名.jpg]]的双链嵌入格式,但这需要附件在库内且文件名唯一)。

一个更简单的方法是使用Obsidian社区插件Linter。安装后,它可以批量对笔记进行格式化,比如标准化标题格式、清理空白字符、管理YAML Front-Matter等。但在使用前,请务必先对库进行备份,并在少量笔记上测试规则。

5.2 构建新的知识结构

有道云笔记的文件夹结构可以直接平移到Obsidian中作为文件夹使用。但Obsidian的威力在于“双向链接”和“图谱”。现在你可以:

  1. 打破文件夹壁垒:利用[[双链]]连接不同文件夹下的相关笔记。
  2. 添加标签:在Front-Matter或正文中使用#标签,为笔记添加多维分类。
  3. 建立MOC(内容地图):创建一些索引笔记,使用Dataview自动聚合某个主题下的所有笔记,形成动态目录。

5.3 处理迁移中的常见问题

  • 公式转换错误:如果笔记中包含LaTeX公式,pandoc可能转换不完美。检查转换后的公式,手动调整$...$$$...$$的边界。可以在pandoc命令中添加--mathjax选项尝试优化。
  • 表格格式错乱:复杂表格可能在转换后失去对齐。需要在Obsidian中手动微调,或者考虑使用HTML表格(Obsidian也支持渲染简单的HTML)。
  • 代码块语言识别:pandoc可能无法识别所有代码块的语言。转换后检查代码块,补充正确的语言标识符,如pythonjavascript等,以便Obsidian进行语法高亮。
  • 附件链接失效:这是最常见的问题。严格按照第3.3节操作,并确保在Obsidian中“设置 -> 文件与链接 -> 附件文件夹路径”配置正确,或者使用相对路径时,所有笔记和附件的相对位置保持不变。

整个迁移过程,从导出、转换、注入时间戳到最终整理,我花了大约一个周末的时间处理了上千条笔记。虽然需要一些技术准备和脚本调试,但换来的是一份完全属于自己、时间脉络清晰、可无限扩展的数字知识资产。当你第一次在Obsidian的图谱视图中,看到按真实创建时间分布的知识节点时,那种对个人数字历史的掌控感,会觉得这一切的折腾都是值得的。