豆备 DOUBAK
← 开发日志

为什么是 WARC 和 JSON Schema,而不是 protobuf

上一版这个项目用 Go + Bazel + protobuf 写的。这一版全部推翻了,理由是 2040 年。

这个项目 2020 年 5 月就开始了。 前一版是 its-my-data/doubak: Go 写的命令行工具,Bazel 构建,数据存 protobuf。它能用,也真的抓下了几百 MB 的档案。 技术上没什么问题。

推翻它的是一个问题:2040 年,一个跟这个项目毫无关系的人拿到这份档案, 他能读懂吗?

protobuf 的字节,离开 .proto 就是垃圾

protobuf 的编码是纯二进制,字段名不在数据里,只有字段编号。 没有对应版本的 .proto 文件,那就是一坨不透明的字节—— 你甚至看不出里面有几个字段。而档案的生命周期比任何一个代码仓库都长。

NDJSON 不一样:一行一条记录,字段名就写在数据里, 十四年后拿 jq、拿 grep、拿任何一个文本编辑器都能读。 慢一点、大一点,但它不需要任何配套物就能被理解。对一个存档格式来说, 这个性质压倒性能。

所以规则是:从 schema 生成代码,绝不反过来。 schema 是规范文本的一部分,代码只是它的一个实现。

还有一个更直接的原因

扩展跑在 Manifest V3 上,而 MV3 的内容安全策略禁止 unsafe-eval。 protobuf.js 的动态模式正好需要它。也就是说,就算不谈 2040 年, 这条路在浏览器里本来也走不通。

WARC 保持原味

页面本身存成 WARC,一个 1996 年就开始用、2009 年成为 ISO 标准的格式, 互联网档案馆存了几千亿个页面用的就是它。关键决定是不往 WARC 里塞私货: 不自定义记录类型,不加豆备专有的头。

这样 ReplayWeb.page、pywb 这些现成工具 可以原封不动地打开一份豆备档案,不需要知道豆备是什么。 所有豆备特有的元数据——抓取意图、判定结果、覆盖率、连续性证明—— 都放在旁边的 index.ndjson 里。

代价是档案里有冗余(WARC 里有一份,index 里有一份摘要), 换来的是任何一方单独存在都有意义: 只有 WARC,你还能重放;只有 index,你还知道抓了什么。

零依赖,同一个理由

扩展本身也是零运行时依赖、零开发依赖、零构建步骤—— 用原生 ES 模块,类型写在 JSDoc 里,测试用 Node 自带的 node:test

三个理由叠在一起:这个项目的整个论点就是可审计和长寿; 扩展会碰到用户的登录会话,每一个依赖都是供应链攻击面; 以及——那些看起来需要库的事情,其实浏览器原生都有。 gzip 是 CompressionStream,摘要是 crypto.subtle, 而一条 WARC 记录说穿了就是几行文本头加一段负载。

结果是:仓库里的源码就是浏览器里跑的东西。 想审计它的人不需要先信任一条构建流水线。

← 更新:豆瓣自己报的数量不能用来证明抓全了