👁 51 次阅读

配图

一、先明确同步边界与数据主权
Notion AI 与 Obsidian 的双库同步,目标不是把整个 Notion 工作区镜像到本地,而是让『协作与 AI 加工层』和『本地检索与长期归档层』各司其职。Notion 侧负责多人协作、数据库属性、AI 摘要与改写;Obsidian 侧负责 Markdown 本地检索、双链、离线编辑和 Git 版本控制。同步对象应限定为一个或几个核心数据库,以及这些数据库关联的页面。AI 对话界面里的临时内容不应被当作同步源,只有保存为正文块、数据库属性或评论的内容才进入同步范围。每个页面在 Obsidian 中保留 notion_id、obsidian_path、last_hash 三个锚点,后续比对和回写都围绕它们进行。首次配置建议只读拉取,不开放回写,确认路径、编码和附件规则稳定后再扩大范围。敏感页面不要授权给集成,避免把不该落地的内容带到本地磁盘。

配图

二、方案选型:API 直连、Git 中转与插件组合
常见的双库同步有四种思路:自写脚本调用 Notion API、用 Obsidian Git 做版本控制、使用桥接插件、以及把 Notion 数据库当索引仅做 URI 跳转。下表按可控性、维护成本和适用场景做对比。
| 方案 | 数据流向 | 优点 | 风险与限制 | 推荐场景 |
|---|---|---|---|---|
| Notion API 加自写脚本 | Notion 页面与数据库转换为 Markdown,再写入 Obsidian;白名单字段可回写 | 规则透明、可审计、能定制冲突策略 | 需要处理块转换、限流和附件下载 | 字段稳定、有脚本维护能力的个人或小团队 |
| Obsidian Git 加手动导出 | Obsidian 侧用 Git 管版本,Notion 内容定期导出后放入镜像目录 | 版本清晰、误删可恢复 | 人工步骤多,双向能力弱 | 低频归档、以本地笔记为主 |
| 桥接插件或第三方工具 | 按插件规则双向搬运页面与块 | 配置少,上手快 | 复杂块容易丢失,依赖插件质量 | 非技术成员多、同步范围简单的团队 |
| Notion 数据库作为索引 | 只同步标题、链接和状态,不搬正文 | 轻量、冲突少 | 离线检索能力有限 | 做导航库、项目索引和任务入口 |
推荐主线是 Notion API 直连加 Git 版本控制,插件只做辅助。这样可以把『同步什么』『如何转换』『冲突怎么办』写进代码和映射库,而不是把关键逻辑藏在不可见的插件设置里。若团队没有脚本维护能力,可以先用索引方案降低风险,再逐步把正文迁移到脚本同步。
三、Notion 侧准备:集成、授权与映射库
先在 Notion 中创建内部集成,取得集成令牌,再到目标数据库或页面右上角的连接设置里授权该集成。注意不要只授权父页面却忘记数据库本身,否则脚本查询时会出现找不到对象的问题。接着建立一个同步映射数据库,字段至少包括:标题、notion_id、obsidian_path、last_hash、last_synced_at、sync_status、conflict_policy。notion_id 必须唯一,obsidian_path 使用相对路径,sync_status 可设为 clean、pull、push、conflict、paused,conflict_policy 可设为 manual、notion_win、obsidian_win。
环境变量建议放在项目根目录的 .env 中,由脚本加载,不要写进代码仓库。示例配置如下:
NOTION_TOKEN=secret_xxx NOTION_DATABASE_ID=xxx NOTION_VERSION=按集成文档填写 OBSIDIAN_VAULT=/absolute/path/to/vault
映射库的作用不是装饰,而是同步账本。每次成功拉取或回写后,脚本更新 last_hash 和 last_synced_at;发生异常时保留 sync_status 和错误摘要。这样即使换机器、换脚本版本,也能从账本恢复同步状态,而不是靠文件名猜测。
四、Obsidian 侧准备:目录、模板与版本控制
Obsidian 仓库建议分成固定目录:镜像区、AI 草稿区、收件箱、附件区和冲突区。示例结构如下:
vault/ 00_inbox/ 10_notion_mirror/ 20_ai_notes/ 90_attachments/ 99_conflicts/ .sync/
10_notion_mirror 存放从 Notion 拉取的页面,20_ai_notes 存放本地 AI 草稿,90_attachments 存放下载后的附件,99_conflicts 存放冲突副本。安装 Templater、Dataview 和 Git 相关插件后,为镜像文件建立统一 frontmatter,至少写入 notion_id、source、sync_status、last_hash。示例模板如下:
--- notion_id: xxx source: notion sync_status: clean last_hash: xxx ---
版本控制可以用 Git 做本地或私有远端备份。初始化命令示例:
cd /absolute/path/to/vault git init git add . git commit -m 'init vault'
建议忽略 .sync 临时目录和 Obsidian 工作区缓存,只提交正文、附件和配置。提交信息可以写成『sync: 页面标题』,方便回溯某次同步到底改了哪些文件。
五、字段映射与 Markdown 转换规则
字段映射要先定规则再写代码。标题属性可转成一级标题或 frontmatter 的 title;rich_text 转成 Markdown 文本;select 和 status 转成 tags;date 转成 updated;relation 转成链接列表;files 先下载到附件区,再写入本地相对路径。正文块方面,段落、标题、列表、引用、代码块和待办项可以稳定转换;toggle、synced block、database view、复杂 callout 等块要么降级为引用和链接,要么保留 notion_id 锚点并记录日志。遇到未知块时,不要静默丢弃,可以写入类似 的注释,同时在日志里标记未转换数量。
写文件时使用原子写入:先写临时文件,再替换目标文件,避免同步中断留下半截 Markdown。统一使用 UTF-8 和 LF 换行,路径要做 slug 化处理,过滤 ../ 和绝对路径,防止页面标题中的特殊字符造成路径穿越。附件文件名建议带上 page_id 前缀,避免同名覆盖。对于包含双链的 Obsidian 笔记,回写 Notion 时只处理白名单字段,不要试图把整个双链网络反向映射成 Notion 块。
六、同步脚本:拉取、比对、写回的最小实现
脚本的核心是先查询映射库或目标数据库,再把 Notion 页面读取为块树,转换成 Markdown 后写入 Obsidian。下面是一个最小示例,重点展示认证、查询和原子写入,不包含完整块转换器。
import os
import requests
from pathlib import Path
TOKEN = os.environ['NOTION_TOKEN']
DB = os.environ['NOTION_DATABASE_ID']
VERSION = os.environ['NOTION_VERSION']
VAULT = Path(os.environ['OBSIDIAN_VAULT'])
HEADERS = {'Authorization': 'Bearer ' + TOKEN, 'Notion-Version': VERSION}
def query_db():
url = 'https://api.notion.com/v1/databases/ + DB + '/query'
body = {'page_size': 100}
r = requests.post(url, headers=HEADERS, json=body, timeout=30)
r.raise_for_status()
return r.json()['results']
def safe_write(path, text):
tmp = path.with_suffix(path.suffix + '.tmp')
tmp.write_text(text, encoding='utf-8')
tmp.replace(path)
运行时把拉取、比对、写回拆成不同模式,先只读再写回:
python sync_notion_obsidian.py --mode pull python sync_notion_obsidian.py --mode push --dry-run python sync_notion_obsidian.py --mode sync
pull 模式只拉取 Notion 并生成 Markdown,push 模式只回写白名单字段,sync 模式先拉取再回写。每次运行都计算内容哈希,与映射库的 last_hash 比对;如果只有一边变化,就自动同步;如果两边都变化,就进入冲突流程。
七、Notion AI 内容如何进入 Obsidian
Notion AI 生成的摘要、改写、待办提取,只有在保存为页面正文块或数据库属性后,才会被 API 当作普通内容读取。不要把 AI 对话窗口当成同步源,也不要假设脚本能够还原对话上下文。更稳妥的做法是:在 Notion 中把 AI 结果保存到固定区块,例如『AI 摘要』属性或页面底部的摘要段落;同步脚本只搬运这些已落盘内容。若在 Obsidian 侧使用其他 AI 工具生成笔记,先写入 20_ai_notes,再通过白名单字段回写 Notion 属性。AI 生成内容建议在 frontmatter 中标记 ai_generated 和 review_status,回写前要求人工确认,避免把未经审核的推断写进团队数据库。
八、冲突处理:哈希、时间戳与人工兜底
只比较修改时间并不可靠,因为本机时钟、Notion 更新时间、Git 提交时间可能不一致。更稳的方案是用内容哈希加映射库状态判断。单边变化时自动同步;双边变化时在 99_conflicts 生成冲突副本,保留 Notion 版本和 Obsidian 版本,并写一份简短 diff 说明。删除操作不要自动传播,应先标记为待确认,再由人工决定是删除、归档还是恢复。冲突策略可以按数据库设置:manual 只生成副本不覆盖,notion_win 以 Notion 为准但保留本地副本,obsidian_win 回写白名单字段并记录审计日志。无论选择哪种策略,都不要自动删除冲突文件。
九、自动化运行、日志与监控
稳定运行后,可以用系统计划任务定时执行。首次运行仍建议只读,确认日志和文件路径无误后再开启写回。cron 示例:
*/15 * * * * cd /absolute/path/to/project && /usr/bin/python3 sync_notion_obsidian.py --mode sync >> sync.log 2>&1
日志至少记录时间、page_id、动作、哈希、结果和错误摘要。监控重点看连续失败次数、冲突文件数量、未转换块数量和附件下载失败数量。每次成功同步后执行 Git 提交,失败时不提交,避免把半成品写入版本历史。若触发 Notion API 限流,使用退避和队列,不要并发猛冲。
十、验收清单与常见故障
上线前按清单验收:集成已连接目标数据库而不是仅连接父页面;映射库字段完整且 notion_id 唯一;只读拉取能生成可解析的 frontmatter;未知块被记录而不是静默丢失;冲突副本可复现并能人工合并;Git 提交包含变更摘要;定时任务能写入日志;附件路径在 Obsidian 中可打开。常见故障包括:401 多为令牌无效或环境变量未加载;404 多为页面未共享给集成;429 多为触发限流,需要退避;中文乱码多为编码不统一;路径穿越多因未过滤特殊路径;复杂块丢失则需要降级为链接并记录 block_id。
十一、可执行建议
建议从一个核心数据库开始,先只读拉取,确认 frontmatter、目录结构和附件规则稳定;再打开少量白名单字段做回写;最后加入定时任务和 Git 提交。扩大同步范围前,在映射库把相关页面标记为 paused 做灰度,观察日志和冲突副本。团队多人使用时,把 Notion 侧作为协作主库,Obsidian 侧作为个人检索与离线编辑层,避免双方同时修改同一字段。保留 99_conflicts 和同步日志,不要自动删除;出现异常先停写回,再修脚本。