豆备 DOUBAK

开发日志

记录开发历程、关键技术决策以及踩过的坑。 具体代码改动可参阅 Git 提交历史。

  • 断点恢复只记下了未完成的,漏掉了已经抓过的

    开跑后重载扩展,自动恢复的增量抓取把历史所有作品详情和封面重新抓了一遍。真凶是 checkpoint 为了紧凑只记了未完成队列,而把历史条目的跳过名单留在了上一次的内存里。顺带聊聊刚发布的 v1.5.0 与 v1.5.1:相册、小组,以及站点生成器的跟进。

  • 同一张海报,换个服务器地址,档案里就多一条「改过」

    111 条作品修订是凭空的,两版之间只差一个字符。而去修它之前先重量了当初记下的两个数字——两个都是错的,其中一个还把问题的性质说反了。

  • 桌面浏览器放行了规范禁止的东西,于是那条路看起来是通的

    安卓 Edge 上一按开始就抛出一句规范原文。是我上一版引入的:两条分支都用了动态加载,而只有一条需要——桌面 Chrome 开了口子,所以本机全绿、CI 全绿、真浏览器里跑得好好的。

  • Firefox 能抓、能存,就是交不出一个文件夹

    「Firefox 的存储接口不太一样,不好移植」这句判断对了一半,而且指的是错的那一半:存储那一格原样可用,卡住的是把字节交到磁盘上那一步。

  • 他不是没登录,是豆瓣把他送去了手机版

    安卓 Edge 上一按开始就是「无法判断登录状态」。拒绝本身是对的——错的是那句话把人指向了一条修不好的路:他会一遍遍重新登录,去修一个不存在的登录问题。

  • 一个复选框,三处静默

    它本该默认藏着,却从第一天起就露在外面——三次检查都通过了,三次问的都是「代码里写了吗」,没有一次问「用户看到的是什么」。最后是一张顺手贴来的截图破的案。

  • 他花了一个小时锁日记

    一条长毛象抱怨:日记导进 NeoDB 之后全公开了。其实一直能改——只是那个办法要把 2961 条标记连坐,贵到没人会付。

  • 一句注释拦不住漂移

    同一份档案,命令行导出 2966 条标记,扩展导出 2967 条——合并逻辑写在了「读文件」那一侧,而扩展读的是浏览器里的存储。那个函数的注释写着「照抄它的定义,不要另发明」,而漂的是上游。

  • 豆瓣把它锁了,不是你把它藏了

    一篇日记显示为「仅自己可见」,豆瓣在页面中明确标注了违规理由。归档虽保留了正文,却漏掉了「内容被平台封禁」这一关键事实——而初版方案差点让归档在本地也替平台顺水推舟地进行隐匿。

  • 那七个字不是最长的动作词,是「在第一个链接前刹住」

    广播动作的七字上限看着像量过最长的那个词,实际是在第一个链接前停下——而链接里装的正是这句话的宾语。放宽到 18 能接住漏掉的 33 条,代价是给其中 11 条写进垃圾。

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

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

  • 全绿,只是没在查

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

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

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

  • 重标一次,2018 年那条就没了

    把一部电影删掉重标,站点上 2018 年的短评、标签、那条广播的链接和封面一起消失。而 git 里零新增、零删除——这种消失是看不见的。

  • v1.3.2,以及为什么格式版本跳到了 1.4

    两组修复正式发布,并说明 bundle/1.4 格式调整——仅增补了一个可选字段。文末附有版本兼容矩阵,明确指出具体受限的组合情况。

  • 档案是全的,声明是错的

    抓取报「有 1 处缺口」,点了重试,抓完还是「有 1 处缺口」——而那张图明明就在档案里,542163 字节。缺口只增不减,唯独这一种缺口能被自己证伪。

  • 响应头到了,不等于这次请求完了

    一次抓取卡了 8494 秒。超时只盖住了半次往返——而它能卡这么久,是因为另外三道保护各自堵住了自救的一段。

  • 名字里一个关键词都没有

    商店标题里一个关键词都没有,而这一格改不了后台——它取自 manifest.json。顺带发现自家英文 README 把两组不同的「8」并成了一句。

  • 页脚写着「零外部请求」,而那一版有 15 页在联网

    接上老档案重新生成,样张站上 15 个页面把豆瓣的图片地址印了出来。规矩在同一份代码里写过四遍,一条测试正钉着这个 bug,而告警说了实情却没说后果。

  • 一个目录里十份档案,只读了一份

    扫描器取了第一个匹配的清单,剩下九份、573 条捕获无声消失——而校验器只验了那一份,然后说「通过」。同一天还有个形状一模一样的:unknown 不是账号编号。

  • 校验器一直都在,只是没人跑

    校验器功能完备且运行仅需数秒,但因 24 份真实档案中有 4 份持续报错,导致此前始终未被纳入常规流程。动手修复前通过实测比对,排除了两条貌似合理但并不成立的先验推测。

  • 接上 2022 年的老档案:多的不是记录,是二十个月的编辑史

    前代命令行工具 2022–2024 存下 782 MB 页面。转成档案接进来之后,记录只多了 7 条——而「改过不止一次」的标记从 8 条变成 147 条。

  • 把下游搬进扩展,靠的是抄而不是重写

    解析、导出、建站现在扩展里点几下就能做完。做的时候差点重写一遍图片判定——三处全错,三处都不报错。

  • 把代码删光之后,证据反而更硬了

    上游说「这个 PR 只做合并」,那个子类就没有存在的理由了。删到一行生产代码都不剩,而它的证据比之前更强。

  • 自己跟自己对账,永远是平的

    整份档案 17305 条真导进了 NeoDB。本地测试全绿,导进去第一眼就看见一个把同一件事写成两行的 bug——因为对账的两边都是自己写的。

  • 广播里存着一段豆瓣自己都不留的历史

    NeoDB 的维护者问能不能出 NDJSON。换过去之后多带走的不只是豆列——3411 条广播拼出一条豆瓣自己已经不显示的状态时间线。

  • 真导一次才知道

    NeoDB 导出真的导了一次。读源码读对的那一处得到了唯一的实证,而一条注定失败的记录暴露出一个不该有的默认行为。

  • 放错目录的四份档案

    四份档案一直没被喂进解析器。并进去多出 43 个豆瓣改过名的作品,也让两条绿了几周的断言变红——两条都是断言错了。

  • 走的时候不用求谁

    导出到 NeoDB、Letterboxd、Goodreads 做完了。原先写着「要先和 NeoDB 维护者谈」——那是把 API 当成唯一入口时的判断。

  • 抓完之后,先让你看见自己的东西

    扩展 1.2.0 档案面板新增「查看内容」功能。核心提取逻辑直接复用解析器模块而非重复实现——编写 220 行胶水适配代码的意义在于保证业务逻辑仅有一处真实源头。

  • 验一验:能不能读将来的档案

    规范给读者立过两条义务,而在此之前没有任何东西验证过它们。以及过程中两次写出了恒真的断言。

  • 私密的那一份,目录页上也得带锁

    豆列渲染进站点。以及一份名单抄在三个入口里,加东西时漏改两处——不报错,只是少了几十页。

  • 豆列:值钱的不是清单,是你写在每条上的那句话

    扩展 1.1.0 开始抓豆列。以及一份豆列被别的豆列翻到头给停掉了——一个真的静默截断路径。

  • 上架当晚,别人替我逮到了两个 bug

    三处全都需要一个和我不一样的账号、或者一次和我不一样的操作。装的人多一个,样本就多一个。

  • 1.0.0,以及上架

    装法从「clone 整个仓库」变成点一下。以及四条在浏览器里从来就没成立过的测试,是 Node 一直宽松才让它们绿着。

  • 「导出之后可以放心删掉」——唯有支持导入恢复,这项承诺才算成立

    导入功能正式实现。为了使其成为第二条合法的归档写入路径,我们重新审视了「只允许单一写入路径」这一架构原则所守护的核心边界。

  • 打了分的广播,正文一律没抓到

    2200 条只抽到 789 条,漏掉的 1411 条每一条都带评分。以及广播里那颗星:一份豆瓣自己都不保留的评分变化史。

  • 又名是搜索里最值钱的一栏

    记得《重返沉默之丘》而不记得《寂静岭2》的人,没有又名就什么都搜不到。而按裸斜杠切开会存下 176 个不存在的片名。

  • 两个整整齐齐的零

    音乐 84 条标记 0 条短评,舞台剧 5 条也是 0 条。一句告警都没有——因为「这条本来就没写短评」是合法的。

  • 一按「开始抓取」就报错:那个 API 在这里根本不存在

    档案里写着「这是哪份代码抓的」。那个数字曾经有三份互不认识的副本,全都写死成 0.0.1。

  • 双击 index.html,那就是你的豆瓣

    站点生成器做完了,sample.doubak.com 上线。以及一个把用户自己写的字悄悄吃掉的 bug。

  • 解析器能跑了,以及两个只有换种跑法才会露出来的 bug

    豆瓣改演员表被记成「你编辑了这条标记」,页脚的浏览计数变成了编辑历史——两个都不报错,产出看起来完全正常。

  • 照片永远不会在增量抓取里出现

    增量只看水位线以上,而照片是从广播页面派生的。补抓那一趟:重读 176 个已存页面,抓下 121 张图,零个页面请求。

  • 最不可替代的那批字节:自己上传的图

    广播附图有三种写法,一个都猜不出来;以及第四次犯同一个错——从手上的样本推出一个封闭集合。

  • 公开测试开始:豆备 0.0.1

    能跑什么、不能跑什么,以及为什么在还缺一块的时候就开放测试。

  • 档案里第一次有了真的图片

    作品封面进档案,bundle 格式随之升到 1.1:surface 多了一个 asset。

  • 一堆档案不等于一条链

    增量抓取上线,以及一个把四次独立全量误接成一条链、把完整性说成「已验证」的 bug。

  • 豆瓣的封禁页也返回 200

    拿 6341 个真实页面校准判定器,以及豆瓣把「我的广播」改名成「我的动态」那天。

  • 豆瓣自己报的数量,不能用来证明抓全了

    同一次抓取里,电影的计数是准的,游戏的计数少了 5 个——而且缺口在列表中间。

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

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