豆备 DOUBAK
豆备 Doubak 豆瓣备份器 · Douban Backup

扩展 v1.3.2 已发布

把你的豆瓣,备份到自己手里

这不是一个工具,而是一整条数据链路:把豆瓣抓下来、存成十几年后还打得开的档案、 解析、生成你自己的静态豆瓣、再导出到你想去的地方。 每一段都是独立的一块,可以单独审、单独换掉、也可以只用其中一段。

从豆瓣到一个能打开的网站,这条路现在整条通了,而且全在扩展里。 扩展在你自己的浏览器、用你自己的 IP 和节奏抓,页面原样存成标准 WARC; 再点一下「导出」,当场变成结构化数据、NeoDB 的导入包、或者一个可搜索、 有固定链接、完全离线也能看的个人存档站点—— 不用装任何别的软件。凭据和数据全程不离开你的设备。 长什么样不用想象——sample.doubak.com 就是这么来的。

需要 Chrome 或 Edge。两个商店提交的是同一份包——同一条 CI 打出来的, 所以装哪边都一样;Edge 也仍然能从 Chrome 应用商店装。 不想走商店的话,release 里那份 .zip 还是同一个包——注意它是商店的提交格式, 浏览器不能直接装 zip,要先解压再用开发者模式的「加载已解压的扩展程序」。 说明在这里

零依赖、零构建步骤——仓库里的源码就是浏览器里跑的东西。

为什么要有它

豆瓣没有导出。十几年的标记、评分、短评、广播和日记,只存在于一个你随时可能被登出的账号里; 而对于注册手机号已经停用的海外用户,一次短信验证就是永久失去。 现有的备份工具大多导出一份 CSV 或抓一份文本——等豆瓣的页面变了、条目被删了、图片挂了, 你手里那份东西已经没法还原当时到底看到了什么。

一个真的例子 短评绕开了敏感词,没有用

样张里有 8 个作品豆瓣已经删掉(7 个游戏、1 部电影)。 其中这一页《返校》——赤烛 2017 年那款台湾恐怖解谜游戏,今天在 NeoDB 上照样查得到, 只是豆瓣上打不开了。档案里还留着那段短评、四个标签「恐怖 台湾 解谜 历史」、 2017 年 1 月那两条广播,以及广播里冻住的 ★★★★☆ (标记本身没有分数,这颗星只在广播里)—— 「实在是一个政治题材的作品……可惜对政治题材十分不感冒」。

那段短评当年是刻意绕开敏感词写的:通篇只说「政治题材」, 具体是什么题材一个字没提。条目照样没了。 豆瓣为什么删它,页面上不会告诉你;能确定的只有一件事—— 消失的不是你写的那句话,是整个条目。 自我审查最多管得住自己写的那一半,而豆瓣删掉的是它自己那一半。

所以唯一没留住的,是它叫什么:页面上现在写着「未知作品」, 档案里那一格是空的。广播的正文是发出去那一刻冻住的, 而挂在广播下面那张作品卡不是—— 豆瓣是在你打开页面的那一刻现渲染它的,所以 2026 年抓到的 2017 年那条广播, 卡片上也已经是「未知作品」了。 上面那个名字是作者自己还记得,不是从档案里读出来的。

名字只有在条目还在的时候抓过才留得下。这就是「早抓一次」与「等哪天再说」之间的全部区别。

这件事从 2020 年就在做了。 前一版是个 Go 写的命令行工具, 古法编程,一行一行手敲出来的,现已归档;这一版是 vibe coding 写的,五天 112 次提交。 所以它更得让人读得懂:零依赖、零构建步骤、仓库里的源码就是浏览器里跑的东西—— 你不需要信任写它的人或模型,你可以直接读它。 重写的理由写在这篇日志里, 怎么写出来的写在这篇里。

和豆伴、豆坟有什么不一样

先说个常被混起来的事:「豆坟」和「豆伴」不是同一个东西豆伴是那个扩展; 豆坟是他们打包的一个改过的 Chromium 浏览器, 给装不了 Chrome 应用商店的 Windows 用户用的,装进去的还是豆伴。 所以「豆坟用不了」通常是那个浏览器的事,不是备份本身没救了—— 扩展本体照样可以装进你已经有的 Chrome 或 Edge。

豆伴比这个项目早很多年,很多人十年的豆瓣是靠它留下来的。 真正的分歧只有一个:存下来的是「从页面上读出来的东西」,还是「页面本身」

豆伴豆备
存下来的是 解析后的条目,存进扩展自己的库 页面原样,标准 WARC 档案
图片 不含(它的 README 写明「备份下载的内容不包括图片」,要联机才看得到) 一起抓(自己上传的照片、作品封面)
断网之后 图片打不开 整站可看,链接是固定的
导出 Excel 表格 结构化数据、NeoDB 导入包、可搜索的静态站点
浏览器 只支持 Chromium 内核 一样只支持 Chromium 内核

最后一行不是笔误。这个项目用 File System Access 和 offscreen 两个只有 Chromium 有的接口,所以 Firefox 和 Safari 现在都装不了,这一点上它不比豆伴强

什么时候豆伴更合适。 只想要一份能翻的清单、拉进 Excel 算一算,那它更直接,也不用琢磨 WARC 是什么。 这里多出来的那些——原样存档、图片、离线、固定链接——是为另一件事准备的: 等条目哪天被删了,你还想知道当时页面上到底写了什么。 上面那个《返校》的例子就是这么留下来的。

数据的一生

抓取是唯一不可逆的一步,别的都不是。所以整条链路被拆开成独立的几段: 先把页面原样存住,怎么解读它可以以后再说——重跑解析器不要钱,请人重爬一次不可能。 从抓取到能打开的网页,这条路现在整条通了, 成果就是 sample.doubak.com。 往外导出的口子也通了——NeoDB、Letterboxd、Goodreads 各要一种格式, 而它们跑的都是你今天抓下来的那份档案,什么时候导都不用重抓一次。

  1. 01

    抓取 已可用

    浏览器扩展,在你自己的设备上跑。唯一不可逆的一步,所以它先做完。

  2. 02

    档案 已可用

    WARC + 索引,格式独立成规范。别的东西全都能从它重建,它不能从别的重建。

  3. 03

    解析 已可用

    档案 → 条目、评分、短评、时间。纯函数、不联网,改错了重跑一遍就行。十份真实档案几秒跑完。

  4. 04

    结构化数据 已可用

    五个 NDJSON,jq 直接查。每次观察留一份修订,你写的东西和豆瓣的条目库分开存。

  5. 05

    出口 已可用

    生成静态站点,或者导出成 NeoDB、Letterboxd、Goodreads 的导入文件。走的时候不用求谁。

具体能做什么

抓取这一段 —— 现在就能用

  • 本地抓取 已可用

    你自己的浏览器、用你自己的 IP 和节奏抓,没有服务器参与,也没有账号体系。 登录凭据和会话 cookie 不离开设备。遇到验证码它会停下来等你,而不是重试。

  • 增量抓取 已可用

    第二次只抓上次之后新增的部分,沿着上一份档案记下的下界回溯。 多份档案连成一条链,完整性是整条链的属性,不是单份的。

  • 版本化抓取 已可用

    同一个页面在链上被抓到过几次就留几份,捕获列表里分得清「本次新增」和「又抓了一次」, 并能翻出跨档案的版本历史。至于把两次的差别说清是「又看了一遍」还是「改了短评」, 那是解析器逐字段比对的活,现在也能用了(见下一组)。

  • 连作品详情页一起抓 已可用

    不只抓你的标记列表,每一部标记过的电影、书、音乐、游戏、舞台剧的详情页也抓下来, 连封面一起。这是「一份能留一辈子的静态豆瓣」的前提—— 标题、年份、评分、简介、封面都在档案里, 以后生成个人站点时不需要回头再找豆瓣要一次,而那时候还找不找得到,谁也说不准。 代价是它占了一份档案九成的体积,所以单独成段存放,不想要的话一条命令就能删掉。

  • 原样存成 WARC 已可用

    网页存档的国际标准,互联网档案馆用的就是它。原始字节、原始响应头,不做任何清洗。 用 ReplayWeb.page 或 pywb 可以直接打开重放, 不需要豆备本身。

  • 连续性证明与覆盖率 已可用

    每条路线都说得清从哪抓到哪、中间有没有缺口,而不是含糊地说一句「已完成」。 豆瓣声称的数量也照记,因为两者对不上本身就是证据。

  • 导出到本地文件夹 已可用

    整条链导出,每份档案各占一个子目录,导出完再回读校验一遍。 档案是普通文件,放硬盘、放网盘、刻盘都行。

解析与生成这一段 —— 也能用了

  • 解析成结构化数据 已可用

    档案 → 四个 NDJSON(标记 / 作品 / 广播 / 长文),纯函数、不联网,jq 直接查。 每次观察留一份修订,逐字段比对,所以「改了评分」和「改了短评」不会混成同一件事。 喂一个装着全部档案的目录就行,不用你挑哪条链—— 实测合并的结果恰好是并集,挑任何一条都会丢东西。

  • 生成你自己的豆瓣网站 已可用

    一条命令从档案生成一个可搜索、有固定链接的个人存档站点, 成品就是 sample.doubak.com重建全程离线、零网络请求,双击 index.html 就能看,不用起服务器。 真正的产物是 Markdown + YAML front matter,Hugo / Astro / Eleventy / Jekyll 都能接, 自带的那个骨架只有五个文件,删掉换主题就行。

  • 发布成一个网站 已可用

    npm run deploy 把成品铺进一个仓库,GitHub Pages 直接就能发。 发布前有一次预演,明确列出哪些内容会变成公开的—— 第三方内容默认排除,而且旧文件会被清干净,不留下「链接还在、数据已经没了」的幽灵页。

  • 导出到 NeoDB / Letterboxd / Goodreads 已可用

    一条命令产出三家的导入文件,不联网——上不上传、什么时候传,都不影响你的档案。 NeoDB 收得最全:实测 2950 条标记里 2943 条能带走,连短评、标签、评分和书评一起; 剩下 7 条是豆瓣已经删掉、档案里连链接都没留下的作品, 那 7 条会单列出来告诉你,而不是混在导入失败里。 Letterboxd 只收电影、Goodreads 只收书,能带走多少、带不走什么,同样一条条数给你看。 反过来绝不成立:档案永远存无损的超集,这三个都是向外的有损适配器。 NeoDB 这一路已经真的导进去验过,导完长这样: neodb.social/users/doubak ——跟 样张站是同一份档案的两种去处。

    NeoDB 那一档现在默认出它自己的 NDJSON 归档格式 (旧的 CSV 还在,要显式要):多带走豆列不挂作品的日记、每条记录各自的可见性, 以及一条从广播还原出来的状态时间线—— 想看 → 在看 → 看过,每一步带着当天的日期和当天打的星, 豆瓣自己只留最后一次。 NDJSON 这一路也真导过了(2026-08-25,整份档案 17305 条记录,0 条失败), 导完长这样:neodb.social/users/immewx ——想看 1098 · 在看 71 · 看过 1774,电影 1473 · 剧集 638 · 游戏 598 · 图书 145 · 音乐 84 · 舞台剧 5,连同 712 个标签、6 份豆列和 2519 条状态历史。 导完之后还从 NeoDB 自己的导出功能把数据取回来逐字段对过一遍: 2979 个条目全部对上,每一类记录一条不差。

还没做的 —— 路线图上的

  • Letterboxd / Goodreads 真导一次 计划中

    NeoDB 那两条路(CSV 和 NDJSON)都真导进去验过了, 这两家还没有。教训是同一句:「我理解对了他们的代码」和 「他们真的收下了」是两件事——NeoDB 那两次上传各抓到过一个本地测试碰不到的问题。 在导过之前,这两家的说法只能是「符合已记录的格式」。

  • 从别处导入 计划中

    IMDb、Letterboxd、Goodreads、Trakt 都有原生导出,所以对它们只需要文件导入, 不需要爬虫。扩展是专门针对「豆瓣没有导出」这一件事的答案。

  • 外部链接补全 可选

    补上 TMDB / Wikidata 交叉链接和多语言标题。跑一次、永久缓存, 下游任何东西都不许依赖它——一个重建时需要联网的存档, 等于把这个项目要消灭的依赖又请了回来。

样张

生成出来的网站长什么样,不用想象 —— sample.doubak.com 就是拿作者自己的豆瓣数据跑完整条流水线生成的,十几年的标记、广播和短评。

  • 2,940条标记
  • 3,394条广播
  • 3,045张图片,全部本地
  • 0个外部请求

最后那个 0 是重点:页面里没有一个 src 指向站外。 封面和自己上传的图都从档案里导了出来 —— 一份要联网、而且要豆瓣还在才能看的备份,不叫备份。 整个仓库下下来双击 index.html 就能浏览,不需要服务器。

站点用的是生成器自带的那个最小骨架(五个文件)。真正的产物是 Markdown + YAML front matter, 所以换任何一个现成的 Hugo 主题只要删掉 layouts/, 换 Astro / Eleventy / Jekyll 也行 —— 每一个现成的主题生态都是它的模板库。

样张不含任何第三方内容:抓取时就不抓别人的回复、关注列表与豆邮, 转发进来的别人的广播按 data-uid 过滤掉,别人上传的图也不抓。
本项目早期的设计判断是拿前代命令行工具抓的一份 782 MB 档案验证的(2022–2024,20 个批次),包括 「豆瓣自己报的数量不能用来证明抓全了」这一条。

不让你的账号出事

被封号是不可接受的结果,不是可以容忍的风险。所以这件事是靠结构解决的,不是靠「小心一点」。

  • 信任边界就是你的设备。服务端抓取被评估过并否决了——那会让这个项目本身变成威胁模型, 还会造出一个所有用户共享的全局抓取预算。
  • 看内容,不看状态码。豆瓣的封禁页也返回 HTTP 200。 每个响应都要过一遍判定(正常/被封/验证码/掉登录/已删除/软 404),判不出来的一律算失败。
  • 遇到验证码是暂停,不是错误。插件会停下来通知你, 你在正常标签页里解完(它和你共用同一个会话),它再用一个廉价探针确认一下,然后放慢速度继续。 在软封禁上重试,正是把限流升级成封号的经典做法。
  • 会话过期是停机条件,不是可重试的错误。

抓取不会失败,只会停下来

一场抓取要几个小时,中途一定会碰上点什么:验证码、掉登录、断网,或者你自己想关掉浏览器。 这些都不是故障。每抓到一页就落一次盘,所以无论停在哪儿, 在那之前的每一页都已经在档案里了。

  • 停下来的十几种理由,没有一种会让你损失已经抓到的东西。 验证码、软封锁、掉登录、断网、手动暂停、关掉浏览器——都是暂停,回来点「继续」就接着走。 连写盘出错也不例外:修复的方向永远是向后截断,被截掉的那一条会在续抓时重新抓一遍,重复是免费的。
  • 抓到一半的档案,解析器照常读得出来。 manifest.json 只在收尾时写一次,所以没收尾的那份没有它——但索引和页面都在, 其中每一条判定为「正常」的捕获都是真实观测。标记、广播、作品、日记、豆列照常解析出来。
  • 导出到磁盘之后,以后还能再导回来。 档案就是一个文件夹。抓到一半就导出的那种同样能导回扩展里,然后接着抓。
  • 缺的是「抓全了」的证据,不是内容。 覆盖率与连续性证明在收尾时才写,所以没收尾的那份证明不了某条路线已经抓全, 也当不了下一次增量抓取的基准。少掉的是这个。
  • 个别页面确实可能抓不下来——那是唯一一种真的「失败」, 而它是逐页的:哪几页、什么原因,都会单独列出来。可以重试,也可以确认按现状收尾; 受影响路线的水位线不会推进,下次仍会重走那一段。

所以「抓了一半」的那份不要删。 真正会让你丢东西的只有一件事:没抓。广播删掉不留任何痕迹, 没抓到就是永久丢失——而已经抓到的那一半,是不可替代的。

它刻意不抓什么

备份的是的东西。别人的内容不在范围里——这既是隐私边界,也是一条工程边界: 每一条社交关系都是无界的入口。

不抓为什么
别人在你广播下的回复 属于社交外围。实测广播列表页只带回复的容器和计数,不带内容,所以今天也没有顺手抓到第三方内容。
关注/粉丝/黑名单 同上。关注列表指向的用户又有自己的关注列表,这个入口没有边界。
豆邮 一个豆瓣信箱里很大一部分是全站系统广播,不可替代的那一小部分埋在可替代的噪音里。
别人的头像、CSS/JS/字体 不是你的数据,也不影响档案离线可读。

如果以后要把档案发布成个人网站,第三方内容默认排除,发布前有一次「到底哪些会公开」的预演。

现在能跑到什么程度

v1.0 已经发布。抓取已经对着真实豆瓣跑通了: 一次 4 小时 16 分的全量抓取,5880 条捕获,其中 2925 个作品详情页、2918 张封面, 零失败;八份成链的档案全部通过规范校验。

只有一个账号跑过。这个账号没有相册、日记也只有三篇——很多分支从来没被 真实数据碰过。已知的坑几乎都是换一种跑法才暴露的(中途重载扩展、开着「重抓 作品详情页」),所以请把它当成「多一份备份」,暂时不要当成唯一那一份。

路线状态说明
广播✅ 能跑优先级最高:发出去就不能编辑,且删掉不留痕迹
标记列表(书/影视/音乐/游戏/舞台剧)✅ 能跑看过/在看/想看,含标记日期与短评
作品详情页✅ 能跑由标记列表派生,含封面图;单独成段,不想要一条命令就能删
增量抓取✅ 能跑沿链回溯,标出改动与已失效的条目
导出与回读校验✅ 能跑整条链导出,每份各占一个子目录
日记 / 影评书评✅ 能跑列表页上只有截断的摘要,所以正文页单独抓一遍;两种日记 URL 形状都认
用户上传的图片✅ 能跑广播附图与日记正文内嵌图,只取本人上传的原图——转发进来的是别人的,不存
豆列✅ 能跑只抓自己编的。值钱的不是清单,是每个条目上自己写的评语——实测 134 个条目里 62 条有。私密豆列在生成的站点上带一把锁
相册⬜ 未做实测这个账号一个相册都没有,暂时没有样本可量
中断续导✅ 能跑导出被打断后再导一次,只补缺的那些;没有进度文件,目的地目录本身就是进度
解析成结构化数据✅ 能跑原始档案(bundle)→ 结构化数据(canonical):标记、作品、广播、长文、豆列,带修订历史
生成静态网站✅ 能跑结构化数据 → Markdown + 图片 → HTML,见样张

在 README 里看完整的现状表 →

最缺的是别人的数据

到今天为止,这条链路只在一个账号上完整跑过。那个账号没有相册、 只有三篇日记、六个豆列。很多分支从来没被真实数据碰过—— 而豆瓣十几年里的页面结构差异,是坐在屋里想不出来的: 这个项目已经四次从太小的样本里推出过错的结论 (最近这次是漏了一个图床域名)。

所以如果你愿意帮忙,按你能接受的程度挑一档就好。 第一档最有用,而且几乎没有隐私成本——绝大多数 bug 光靠它就能定位。

01 日志与覆盖率

面板「日志」页可以整份复制出来:抓过哪些 URL、重试、停机原因、错误。 里面没有你的短评、日记正文和图片。 覆盖率页那张表同样有用——尤其是「豆瓣声称 100 条、实际抓到 95 条」这种对不上的情况。

02 某一条路线的少量页面

如果你有相册,或者很老的广播(2010 年前后的),那正是一个样本 都没有的地方——相册至今没做,就是因为手上的账号里一个都没有。 豆列也还想要:已经做了,但只对着六份校准过,而它有好几种形态 (纯书签夹、几百条的大清单、条目是日记或广播的)。几页就够,不用给全部。

03 一整份档案

最有用,也最私密。发之前请先知道它装着什么:你的短评、 日记与影评全文、自己上传的图片原图、你的用户名,以及那些页面上的一切。 档案是普通文件,段可以整段删掉(catalog-* 是作品详情页,删了不影响其他)。

怎么发,以及会被怎么处理

  • 能公开说的问题走 GitHub issue—— 首选,因为撞上同一个问题的人能看到。
  • 涉及你自己数据的走邮件:[email protected]
  • 收到的数据只用来修 bug 和补样本,不公开、不转给第三方; 你随时可以要求删掉,说一声就行。
  • 永远不要发登录凭据。档案里没有 cookie,扩展也不会把它写进去。 如果有人以任何名义找你要豆瓣的 cookie 或密码,那不是我们。

还有一件不用交出任何数据、但一样有用的事: 告诉我们界面上哪句话让你产生了错误的理解。 这个项目里「说得比事实更强」一直是当 bug 修的—— 把四次独立抓取说成一条完整的链就是这么发现的。

最近的开发日志

  • 出处这枚角标,改了三版

    整行时间戳做成链接太重;缩成一个箭头又白占掉一整行,每条广播 35 px。第三版才落到时间戳右端——而第四版是因为漏了首页:那 3493 枚量过的角标,全在广播页里。

  • 全绿,只是没在查

    四个检查都写了,也都写得不错,没有一个在该响的地方响:一个长在别的仓库里,一个长在已经不存在的路径后面,一个长在永远有常驻项的清单上,还有一个长在最后那句话前面。

  • 同一份档案放了两处,出处就多了九千条

    下载文件夹里同一份档案躺了两遍,逐字节相同。产出的记录一条不差——错的是出处:2933 条标记声称同一次抓取看见过它两次。

全部开发日志 →

项目由几个仓库组成

每一块都能单独看、单独审、单独换掉。 档案格式独立成一个仓库并单独版本化,因为它是唯一一个改错了就没法补救的东西。

仓库做什么状态
doubak-extension 浏览器扩展。抓取,产出原始档案(bundle)。整个项目的入口都在这里。 v1.3.2
doubak-data-specs 档案格式的规范文本与 JSON Schema。整个项目的基石。 bundle 1.3
doubak-data-parser 原始档案 → 结构化数据(canonical)。纯函数,不联网。 能跑了
doubak-site-generator 结构化数据 → Markdown + 图片 → 有固定链接的个人存档站点。 能跑了
doubak-site-generator-sample 用作者自己的数据跑完整条流水线生成的样张,就是 sample.doubak.com 在线
doubak-export-adapters 结构化数据 → NeoDB / Letterboxd / Goodreads 的导入文件。纯函数,不联网。 能跑了
doubak-import-adapters 别的抓取工具存下来的页面 → 原始档案(bundle)。于是那些旧数据能走完同一条流水线。 能跑了

还有 doubak-data-enricher(可选的外部链接补全)。 下游全部可以晚几年再写,因为抓取已经完成了 —— 解析器是一个对已抓页面的纯函数,什么时候写都来得及。 2020 年那一版 its-my-data/doubak(Go 命令行)已归档, 但它抓下来的档案没有白费:本项目的实测结论几乎都来自那批数据,而现在 doubak-import-adapters 能把它们直接转成档案接进来——实测 782 MB、7353 个页面、2022 到 2024 的四次抓取, 补回 20 个月豆瓣自己也不保存的编辑史。