上架当晚,别人替我逮到了两个 bug
1.0.0 发出去几个小时,头两个 issue 就来了,都是 @Colafornia 报的。两个都不崩——一个多发了几次注定落空的请求,一个把诊断信息存进日志之后改了口。
这两个我自己撞不上,原因很朴素:我一直对着自己那个两千九百多条标记的账号在测。 报进来的第一个,是一条只有 8 个条目的游戏列表。
#5:8 条的列表,请求了 start=15 / 30 / 45
成因是两件事共用了一个阈值。停滞检测的判据是「连续 3 页无进展」, 而空页也走这条——于是三页全空、到第三页才收手。 对一个「每个请求都要算账」的工具来说,那是三次注定拿不到任何东西的请求。
修法不是把 3 改小,是把两件事分开,因为它们的证据强度不一样:
- 「整页都是重复」是正常现象。豆瓣的列表是头部插入的, 新条目会把旧条目往后面的页推,所以一整页重复什么都说明不了, 要连着 3 页才敢下结论。
- 「整页一条都没有」在列表中段说不通。 实测过的审查抑制是部分的——游戏/玩过声称 293 条、渲染 288 条, 第 7、14、17 页渲染 14/14/13 条,从没见过整页为空。
那为什么空页阈值不是 1、见空即停?因为「从没见过」是从一个账号上看到的, 而这个仓库在「从手上的样本推出一个封闭集合」这件事上已经错过四次。 取 2:一页空还继续走,连着两页空才收——多花一个请求, 换掉「万一中段真有整页为空就静默截断」那个不可恢复的结果。
实测:8 条的列表 4 个请求 → 3 个;60 条的正常列表一个请求都没少。
还有一处容易漏的:空页计数必须跟着断点存盘走。
不存的话,每次恢复都归零,而一场抓取要死很多次——「连着两页空」永远凑不满。
跟 seenIds 是同一个道理,那次踩过。
#6:同一条诊断,存进日志之后改了口
extractor_stale 是「豆瓣是不是改版了」的第一个信号,
是整套日志里最不该含糊的一条。而它实时看和存过之后看,说的不是一件事:
实时 这一页看得见 15 个条目,却一个条目都抽不出来
存过之后 这一页看得见 undefined 个条目,却一个时间都抽不出来
成因是 formatEntry 是个字段白名单,而这条事件用到的两个字段都不在上面。
undefined 只是难看,「时间」才是要命的那个:
原事件说的是 id 抽不出来,而缺失的字段一空,
那个三元判断就倒向了「时间」。
一条印着错误结论的诊断,比没有诊断更糟——它看起来像证据。
两头都改:白名单补上那两个字段;
而更要紧的是让 eventNote 在字段缺失时不许猜,
宁可说得笼统。判据也换了地方——不是「补上字段就算修好」,
而是「实时的那句话,与存过之后念出来的,必须一模一样」。
白名单漏字段是不报错的,下一个加事件类型的人照样会漏;现在会红。
第二天早上,第三处:导完之后那句话还挂在那儿
这一个是档案主人自己报的,形状和上面两个是一族:
17 份 · 462.9 MB · ⚠ 其中 1 份没有导出记录,浏览器里这一份可能是唯一的副本
——而这一份,刚刚导出去。成因是两条导出路径做法不一样: 整条链那条会去刷新用量和当前页,单份那条只记了一笔就完了。
这不是显示滞后的问题。用户刚把档案导到硬盘上,界面却仍然说它可能是唯一的副本—— 在一件与数据安全直接有关的事情上说了假话,而且方向正是最容易让人白导第二遍的那个。
修法不是在那一处补两行。把「记一笔」和「让界面跟上」合成一个
noteExported(),两条路径都只走它——
下一个写导出路径的人不会有机会忘记刷新,
而那正是这次出问题的方式。
顺带把那条「只在校验通过时才记已导出」的测试, 从「检查那一处调用」改成「每一个调用点都要有那道闸」—— 原来只查了一处,而问题恰恰出在另一处。
值得记下来的不是这三个 bug
是它们为什么由别人发现。三条全都需要一个和我的账号不一样的账号, 或者一次和我的习惯不一样的操作: 8 个条目的列表、一条抽取器失效的日志被翻回去看、单独导出一份而不是整条链。
这个项目的每一条选择器、每一个阈值都是对着一个真实账号量出来的, 这是它到今天为止最大的方法论优势,也是它最确定的盲区。 装的人多一个,样本就多一个。