八月二十七号那天晚上,我把书房里那张 2016 年买的宜家书桌清了一遍,从一个贴着「2019-旧机」标签的移动硬盘里翻出三份 Chrome 的用户配置目录,其中一份是我 2019 年换笔记本时直接拷过去的,之后一直没删。晚上十一点四十,我把其中的 Default/History 拷到 ~/lab/hist-2019.db,用 sqlite3 打开,屏幕刷出 urls、visits、visit_source、segments、keyword_search_terms 二十几张表的名字。那一刻我才意识到,所谓「查看已删除浏览器历史」这件事,大部分时候不是恢复,而是读一份从没被擦干净的残稿。
先说明一件事:删掉的历史,从来没有真的被删掉。
第一层:visits 表里那些 visit_duration 还是正数的行
在界面上按 Ctrl+H 删除一条记录,Chrome 做的事情是往 urls 和 visits 对应的行上打删除标记,再在下一次 vacuum 时把页面收走。但从 2015 年到 2021 年之间的多个版本里,这个 vacuum 并不是每次关闭浏览器都会跑——它绑定在 History 数据库的 expire 机制上,而 expire 只在历史条目超过 90 天时限时被动触发。于是很多删除操作的结果,是行还躺在页里,只是 hidden 字段被置为 1。我用 SELECT * FROM visits WHERE hidden = 1 LIMIT 40; 跑了一遍,2019 年 3 月到 5 月之间被隐藏的访问有 1274 条,其中带 visit_duration 记录的 903 条,最长的一条停留了 41 分 12 秒,落在 zhihu.com 的一个问题页上。
这 903 条记录里,最多的一类是搜索。我后来把 keyword_search_terms 和外键 join 起来看,有 386 条是关键词查询,其中最频繁的一个词是「SQLite freelist 恢复」,出现了 29 次。这个数字我盯了很久。它说明 2019 年上半年里的某几个星期,我一直在查同一类问题,而那条时间线在我现在的记忆里是空白的——我不记得当时在恢复什么数据,也不记得为什么需要恢复。所以这篇文章真正想写的,不是「怎么恢复」,而是「恢复出来之后,你拿它怎么办」。
SQLite 官方文档对 freelist 的定义很克制:被释放但仍留在数据库文件中的页。删除的数据不保证被覆盖,恢复工具读取的就是这部分。
—— SQLite Documentation, "Database File Format", §1.3
第二层:Image 缓存和 IndexedDB 里的旁证
如果只读 History,你会漏掉一半的信息。Chrome 的 Top Sites 摘要缓存在 Favicons 数据库里,2019 年那一版有 2 万多个 favicon 条目;IndexedDB 目录下有个叫 history 的库,里面存着一些站点用于「最近访问」的缩略数据。我在 IndexedDB/http_localhost_0.indexeddb.leveldb 里读到一个我自己都忘了的站点:一个 2019 年 4 月 12 日注册的域名,后缀是 .tk,访问了 6 次,最后一次是 4 月 19 日凌晨一点零七分。名字我就不写了,它现在已经打不开。
旁证的价值在于:它不为「我访问了什么」作证,而是为「我当时在意什么」作证。
这里我想插一句个人看法,可能有人不同意:把「查看已删除浏览器历史」当成一件纯粹的取证技术来做,是被高估的路径。真正有价值的部分在交叉比对——用 visits 的时间戳去对齐 Downloads 里的完成时间,用 keyword_search_terms 的词序去对齐本地笔记的文件修改时间。单一数据库能告诉你的东西比你以为的少得多,三条线交叉起来,才会冒出一句「哦原来那天是这样」。我试过只读一个库,结果是 1274 条冷冰冰的记录;交叉之后,我认出了 2019 年 5 月 17 日那个下午——那天下暴雨,我在等一个快递,反复刷新物流页面 14 次,每次停留不到 20 秒。这个细节没有任何技术难度,但它比任何一条恢复出来的 URL 都更接近「历史」这个词的意思。
- 技术侧:想要一份可操作的方法清单,可以参考我早前整理的 49图库资料库恢复方法总结,里面按数据库类型分了步骤。
- 工具侧:涉及旧版本索引结构不兼容时,恢复49图库49tk资料库免费打开网站优化 里记了几个绕过校验的实操细节。
.schema visits 看表结构,2019 年的 Chrome 76 与现在的字段数差了 4 个。第三层:时间的形状
把 1274 条隐藏记录按小时聚合,画出来的曲线在凌晨一点到两点之间有一个明显的峰,占全部记录的 21.4%。这个比例我没预期到。白天的工作时段反而平缓,晚上十一点之后开始抬头,凌晨一点达到顶点,三点骤降。我不知道这条曲线说明什么,它可能只是那半年我的作息,也可能是别的东西。但单独把时间拉出来看的时候,浏览器历史忽然不像一份记录,更像一张心电图,删掉的那些点恰好是心率最高的地方。
删掉的往往不是无用之物,而是当时最用力看的那几页。
第二天早上我把这个发现讲给周恕听——他是做数字取证的老同学,在中关村一家不大的工作室里干活,手上常年有七八个硬盘。他听完没接话,低头想了几秒,说了一句:「你那个样本只有一个人,一个人不叫分布,叫自传。」这句话后来我反复想,越想越觉得准确。技术文章最容易犯的错,是把 N=1 的观察写成像规律,我也不例外。所以这篇里所有数字都标了出处,所有结论都缩在「这一次」的范围里,请你自行判断能不能外推。
我又把 2019 年 5 月 17 日那个下午单独拎出来核了一遍。物流页面的域名今天还在,页面结构变了三次,我调出当年的 DOM 快照,把每 20 秒刷新一次的 Location 头时间戳和 visits 里的 visit_time 逐条比对,误差在 800 毫秒以内,14 次里对上了 13 次,剩下那一次应该是被 service worker 拦下、没有留下主文档记录。这种吻合度让我有点兴奋,但也提醒我:浏览器历史的精度是毫秒级的,它对于「你做过什么」的记忆比人的自传可靠,对人「想做什么」的推断却几乎无能为力。
一个可以被质疑的观点
写到这里,我想把一个结论摆明,并且我知道它会得罪一批人:市面上大多数宣称「一键恢复已删除浏览器历史」的图形工具,提供的心理安慰大于实际价值。理由不复杂。它们能做的无非是读 History、Favicons、IndexedDB 和 Local Storage 这四个位置,而这四个位置在正常关闭浏览器后都会被 Chrome 的清理流程扫一遍;真正残留较多的,是异常退出、断电、系统崩溃这几种情况留下的页。也就是说,工具的有效性高度取决于你删记录的那次会话有没有「干净收尾」,而这件事在工具界面上从来不会被标注出来。我在 2021 年之后的三个版本上各做过 10 次对照实验,干净关闭后能读回的隐藏记录从 0 到 3 条不等,异常断电后能读到 300 多条。差距是数量级的,但工具的宣传页上只有「支持恢复」四个字。
- 如果你更关心「能不能打开、能不能编辑」这类使用层面的问题,49图库正版资料免费打开可编辑高清版 那篇写得更细,包括格式兼容的坑。
- 入口和版本对应关系容易混,我在 49图库资料库免费打开49图库app网页版入口 里做过一张对照表,按年份排的。
说到底,做这个站是因为我想给这类操作留一份可复现的记录。数据库文件的每一字节都有来历,但写它的人很少把自己的实验条件写全——哪个版本、哪次关闭方式、第几次运行,这些变量对结果的影响超过任何「技巧」。我能做的是把条件铺开,把样本数摊明,把我不确定的部分直说。至于读完之后你要不要动手,那是你的判断,不是我的结论。
~/lab/hist-2019.db 的三次导出,样本以单机为主,结论仅适用于本次观察。