Notion 备份到底怎么做的

备份,就是向一个从未为备份设计的 API 连续发出许多请求。只有清楚这个 API 会给什么、不会给什么,你才能拿到一份可以信任的副本。

先从一个 token 说起

Notion 用集成来决定谁能读什么。你创建一个内部集成,它能读的,只有你共享给它的页面和数据库:工作区里别的内容读不到。你不共享,它就什么都读不到。token 用来标识这个集成。

这就是 NotionManager 使用的全部访问模型。没有 Notion 密码,没有能读取一切的工作区级密钥,也读不到你没有共享的页面。集成能读什么,由共享列表决定;Notion 对任何其他工具执行的,也是同一条规则。你在 Notion 里撤销这个集成,之后每一次运行都会立刻失败。

接着由 API 决定能做到什么

Notion API 面向 block 和页面,是一个分页的读取接口。它按页返回结果,每页最多一百条,并限制每秒能发多少请求。它能完整表示的,就完整返回。它无法表示的,只报告这个 block 的类型,不会凭空补出内容。

分页、调用限制和无法返回的内容,决定了任何 Notion 备份的设计。所以备份不是一次请求,而是一次有节奏的遍历。在大型工作区上,它可能要花上几分钟,十几分钟,甚至更多时间。NotionManager 提供动态的进度监控,让你随时知道备份的进度。

Notion 也在不断更新 API,以修复缺陷,提供新能力或者完善数据组织。NotionManager 基于最新版本的 API 构建,确保拿到尽可能多的信息完善备份完整性。

备份复制的数据模型

Notion 里的一切都由 block 组成。一段正文、一个标题、一个待办、一个折叠块、一行表格,都是 block。页面本身也是 block,只是它包含其他 block,所以你在 Notion 里读到的一切都是一棵 block 树。备份按同样的方式读取这棵树:从一个节点开始,读取它的子节点,重复下去。

数据库是页面的容器。它的内容放在一个或多个数据源里,数据源的每一行都是一个页面,这个页面的属性由该数据源定义。视图本身不存放数据。它定义的是数据如何查看。看哪个数据源,用什么筛选和排序,以表格、看板、日历还是画廊呈现。Notion API 会返回视图的关键结构,不包含样式。

NotionManager 如何读取一个工作区

一次遍历,自顶向下,直到 API 不再返回任何结果。

  1. 1

    从你共享的内容开始

    integration token 暴露你共享给它的顶层页面和数据库。这些就是遍历的基础。

  2. 2

    把每一项完整读完

    每个根都连同属性和 block 一起取回,一页一页读到分页结束,确保无遗漏。

  3. 3

    顺着子节点往下

    路上遇到的每个子页面都会依次递归取回,直到没有更多的子页面。

  4. 4

    重建结构,再读它

    NotionManager 把读到的内容重新渲染为页面、数据库和关联。得到就是一个只读的,感官和 Notion 原生风格类似的可离线翻阅的副本,而不是一堆 JSON。

API 不会做的事,以及实际会发生什么

Notion API 的限制NotionManager 的策略
API 对请求有调用限制,导致更长的备份时间请求按节奏发出,遇到限制则等待,确保备份的完整性。
有些 block 类型,API 不返回它的内容保留所有可以读取的原始信息。渲染的时候明确标出来,所以缺口是看得见的,而不是无声的
视图的复杂样式NotionManager 复制视图背后的数据,并根据属性重建视图。尽可能模拟 Notion 的行为
没有变更流:API 说不出「这三个页面从周二起变了」暂时无法提供有效的增量、差分备份支持,每次备份都需要遍历所有的页面和 block。但是如果一个 block 从未变化,则不会多占用磁盘空间。外部媒体文件也是去重后保留,确保磁盘空间的有效使用
不能回写:API 无法替你恢复工作区无法直接支持备份的恢复。宣称可恢复的备份都是完全重建,而且有很大的限制,如 id 变更,关系丢失等。NotionManager 设计上不支持恢复操作
没有共享给集成的内容什么都不会有。副本恰好就是你共享的那组页面。NotionManager 支持进一步的选择,如选择共享页面中的一部分。但是无法超出这个范围

用 NotionManager 来备份

创建一个集成,把一个页面或数据库共享给它,然后看着第一份副本出现。

接下来可以看

常见问题

NotionManager 需要我的 Notion 密码吗?

不需要。它使用 integration token,而这个集成只能读取你明确共享给它的内容。整个过程不涉及任何工作区级别的凭据。

为什么一次备份要花几分钟,甚至更多?

首先是 Notion API 调用速度的限制非常严格,加速备份的任何努力都会导致并发调用超出而限流;其次是每次备份都是全量的,尽管数据可能不需要重新下载,都是每一个页面,每一个 block 都需要完全遍历才能确保备份的完整性。

它能只备份变化的部分吗?

基于目前的 Notion API 提供的能力,无法可靠地获取自从上次备份之后的所有变更清单。所以每次备份都是全备,但是 NotionManager 会智能地确保任何重复的数据只保留一份。

API 没有描述的 block 会怎样?

NotionManager 会记录这个 block 声明的类型和它在树中的位置。所以缺口在可翻阅的副本里看得见,而不是页面中间一个无声的洞。