用 Notion API 做完整备份:分页、关联截断和文件链接过期这几个坑
Notion 里的东西越攒越多,我一直担心哪天账号或服务出问题,数据就没了。于是写了个脚本,通过官方 API 把页面、数据库、附件全部导出成 Markdown 加原始 JSON。过程中踩了几个很容易让备份“看起来成功、实际缺数据”的坑,记录一下。
一、准备
在 Notion 的「我的集成」里新建一个内部集成,拿到密钥(只放环境变量,别写进代码),然后在需要备份的页面上点「连接」,把集成加进去。没有加连接的页面,API 是看不到的。
export NOTION_TOKEN=你的密钥
二、先搞清楚备份要拿什么
- 页面和数据库行:用 search 接口遍历。
- 页面正文:块(block)是树状的,需要递归取子块。
- 附件和图片:Notion 返回的是带签名的临时链接。
- 关联(relation)属性:指向其他页面。
三、坑 1:所有列表接口都要分页
search、查询数据库、取子块,每次最多返回 100 条,结果里有 has_more 和 next_cursor。只取第一页是最常见的漏数据原因。
def fetch_all(call, **kw):
out, cursor = [], None
while True:
r = call(start_cursor=cursor, page_size=100, **kw) if cursor else call(page_size=100, **kw)
out += r["results"]
if not r.get("has_more"):
return out
cursor = r["next_cursor"]
四、坑 2:关联属性超过 25 个会被截断
这个最隐蔽。页面对象里的 relation 属性,最多只直接给 25 个关联,多了会带一个 has_more 标记。要拿全,需要单独调用“获取页面属性”的接口,用属性 id 分页取:
GET /v1/pages/{page_id}/properties/{property_id}
我的数据里有些行关联了几十个页面,一开始导出的条数对不上,就是卡在这里。同理,rollup、people 等属性也可能有类似限制,统一走属性接口最稳。
五、坑 3:文件链接一小时左右就过期
API 给的文件地址带签名,过一段时间就失效。如果先把整个工作区的结构抓完再回头下载附件,后面的文件会 403。我的做法是:每取到一个页面的块,就立刻把里面的文件下载到本地,再把 Markdown 里的链接改成相对路径。
六、坑 4:别吞掉错误
请求超时、限流(429)、权限不足,都会让某些页面取不到。我加了三件事:每个请求设置超时;遇到 429 按 Retry-After 等待后重试;失败的页面写进一个错误清单,最后在报告里列出来,而不是默默跳过。
七、验证
备份完成后做对账:
- 用 API 统计的页面数、块数、文件数,和本地落盘的数量逐项比对;
- 随机抽几个页面,对比 Notion 里的显示;
- 保留一份原始 JSON,Markdown 转换有损时还能回头查。
我最后一次完整跑下来,页面、块、附件三项数量都能一一对上。
八、小结
备份最怕的是“以为备好了”。分页、关联截断、链接过期、静默错误,这四点处理好,结果才可信。另外要注意,评论需要在集成里单独勾选读取评论的权限,否则评论不会出现在备份里。
- 点赞
- 收藏
- 关注作者
评论(0)