叫「MewX的豆瓣存档」,以及多账号公开归档
不只是用户名与 UID:从数据规范、解析器到站点生成器,全链路沉淀用户昵称;支持账号封禁或未登录下的公开主页紧急抢救,并确立全局单任务互斥与凭据隔离底线。
在过去几版的豆备中,生成的离线静态站点首页顶部赫然印着一行大字:
154611037 的豆瓣存档(或者一个冷冰冰的拼音用户名 slug)。
对人来说,这是令人出戏的:我们在网络空间里被朋友呼唤、在书影音讨论区被认出的名字,
从来都不是那一串阿拉伯数字或 URL 路径,而是我们亲手填写的昵称。
「能不能直接叫『MewX的豆瓣存档』?」 一句看似简单的直觉需求,落到多仓库、跨链路的体系里,其实是一次系统性的演进。
三位一体的身份体系:UID、用户名与昵称
豆瓣的账号标识长期存在三种形态,各自承担完全不同的职责:
| 标识 | 形态 | 特点 | 在豆备中的职责 |
|---|---|---|---|
| 数字 UID | 154611037 |
全局唯一、终身不可变 | 档案链(chain)、增量比对与去重的唯一权威锚点。 |
| 用户名(Slug) | mewx |
可自定义、可注销再占用 | 构成个人主页 URL,用于定位与翻页入口路由。 |
| 昵称(Nickname) | MewX |
用户展示名、富文本、可频繁修改 | 人机可读标识;站点标题、NDJSON 导出与归档人称呼。 |
早先为了追求确定性,数据格式与解析器将数字 UID 奉为唯一真神,而丢弃了展示昵称。 现在我们为它补齐了完整的生命周期:
-
规范先行(
doubak-data-specs#5): 在 Bundle v1 规范与 Canonical NDJSON 规范中,正式加入account.nickname字段; 明确昵称为string | null,空值不假造。 -
抽取与增量合一(
doubak-data-parser#8): 从主页#db-usr-profile区域的<h1>文本或头像alt中稳定抽取昵称, 经过单趟 HTML 实体解码还原。在增量合并(mergeRecords)时,按数字 UID 将最新确认的昵称向下游稳定继承。 -
站点动态标题(
doubak-site-generator#5): 站点生成器按级联规则确定站点名:优先取--site-title显式指定值, 其次取档案中的<nickname>的豆瓣存档,无昵称时优雅回退为<username>的豆瓣存档, 最后兜底为「豆瓣存档」。同时在全站导航栏、页头与 HTML<title>中保持一致。
公开归档的起点:账号被封禁时的抢救生命线
在豆备最初的路线规划里,公开归档甚至根本不在优先级清单上。
早在今年 8 月,就有用户在 GitHub 上提了 Issue #2(请求加入备份他人账号的功能), 希望能备份其他豆友公开的书影音记录。当时我们的回复很直截了当: 「设计之初没有考虑这个功能,可能短期内不会加。」 彼时豆备的定位非常单纯:全心服务于个人登录态,把属于你自己的全部数据(包括仅自己可见的私密日记、状态与私密标记)原汁原味搬回家。
但转折点很快到来——或者说,豆瓣生态的现实给我们结结实实提了个醒: 如果用户的账号被封禁、停用,或者因为各种原因无法登录了呢?
Issue #19(依然需要支持备份未登录的账号,例如自己账号被封禁) 的提出,彻底重塑了这个设计的优先级。 经历过账号停用或被封的豆友都知道,账号一旦被封,你便彻底失去了登录后台的可能;但在很长一段时间内, 个人主页上公开展示的读过、看过、书评和公开日记依然可以被外部直接访问。 如果豆备死守「必须登录目标账号才能启动备份」的假设,那么在用户最绝望、最需要抢救自己多年心血的至暗时刻, 工具反而成了摆设。
支持无需登录的公开归档,不再只是「帮好友做镜像」的锦上添花, 而是变成了一根抢救被封禁或无法登录账号的唯一生命线。
为此,我们在扩展面板中加入了统合的账号输入能力:不仅支持数字 UID,还支持直接粘贴完整的个人主页 URL
(如 https://www.douban.com/people/mewx/)。扩展会自动探测主页、核实 UID 与昵称,
并为未登录账号或公开目标建立独立的档案集合。
凭证隔离:避免操作者会话污染
但在技术实现上,未登录与公开归档潜藏着一个极其隐蔽且危险的陷阱:操作者会话污染。
当你在日常使用的浏览器里登录着小号 A,去抢救被封的主号 B(或归档他人主页)时: 如果扩展发出的所有请求都自然带上浏览器里的登录 Cookie,豆瓣对某些作品详情页会返回小号 A 的「在读标记」、「短评打分」或仅好友可见内容。 最终产出的「B 的公开档案」,里面会悄悄混入「A 的个人私密数据」——这是一次灾难性的数据污染与隐私泄露。
为此,扩展引擎(doubak-extension)建立了操作者与目标账号的隔离契约:
在公开归档模式(visibility: 'public')下,请求作品目录、通用条目时主动剥离操作者的身份凭据,
并在 manifest 中显式声明 operator_user_id 与 visibility,
保证公开档案里只存真正的公开可见数据,绝不发生跨账号的身份串标。
铁律:绝不允许同时抓取多个账号
随着多账号支持的完善,有人问过:「既然我可以为不同的账号备份,那能不能开两个任务同时抓?」
答案在设计文档里被定为了不可逾越的绝对铁律:全局单抓取互斥,即使是不同账号也坚决不允许并发。
理由非常现实:
- 豆瓣的风控是以 IP、会话与设备特征综合衡量的。 即使在不同账号间切分任务,来自同一个浏览器实例的并发请求在豆瓣眼里就是翻倍的频率攻击。 自适应退避器(Pacer)好不容易维持的平稳节奏会被瞬间打乱。
- 风控关联风险。 如果账号 B 的抓取动作踩中了豆瓣的人机验证或软封锁,浏览器 Cookie 容器中的账号 A 极可能随之一同被标记甚至封号。保护用户的豆瓣资产安全,永远高于跑得快。
在代码实现上,全局互斥锁横跨 Service Worker 调度层、Offscreen 驱动层与面板 UI: 任何新抓取启动前,必须确认无任何处于活跃或未收尾状态的任务; 已有任务必须显式「收尾」或「中止」后,才允许开启下一个目标。
这一系列底层体系已全部合并进主线并在扩展中铺开。下一篇日志,我们将聊聊在实地抓取中踩到的另一个隐蔽坑: 为什么正常页面里的脚本源码会让抓取器如临大敌。