爬虫跑了几个小时被杀掉,重启后是从头来还是接着来?丢了多少、重复了多少?这篇让真实的爬虫进程在随机时刻被 SIGKILL,对比五种保存状态的做法:放内存、两种 JSON 快照、两种 SQLite 提交方式。
只放内存(没有持久化):0.54 秒;状态文件 0.0 MiB JSON 快照,每 25 个 URL 直接覆盖写:1.12 秒;状态文件 29.3 MiB JSON 快照,每 25 个写临时文件 + fsync + 原子改名:1.14 秒;状态文件 29.3 MiB SQLite(WAL),每抓完一个 URL 提交一次:0.55 秒;状态文件 29.5 MiB SQLite(WAL),每 25 个 URL 批量提交一次:0.65 秒;状态文件 29.5 MiB
300 个 100 KB 的响应,状态文件 29 MiB。JSON 快照每 25 个 URL 重写一次整个文件,耗时从 0.54 秒涨到 1.12 秒;SQLite 每抓完一个 URL 提交一次只是 0.55 秒。把整体状态当一个文件反复重写,代价随数据量增长。
设计 状态文件损坏 重复抓取(平均) 重复抓取(最少/最多) 遗漏 只放内存(没有持久化) 0 / 20 112 0 / 203 0 JSON 快照,每 25 个 URL 直接覆盖写 1 / 20 36 7 / 206 0 JSON 快照,每 25 个写临时文件 + fsync + 原子改名 0 / 20 27 12 / 32 0 SQLite(WAL),每抓完一个 URL 提交一次 0 / 20 7 0 / 8 0 SQLite(WAL),每 25 个 URL 批量提交一次 0 / 20 18 7 / 32 0
只放内存,进度全丢,平均重复 112 个(最多 203,看杀在哪儿)。JSON 直接覆盖写在写到一半时被杀,文件被截断,20 次里有 1 次读不出来,只能从头开始。原子改名(写临时文件、fsync、再改名)不会损坏。SQLite 每个 URL 提交一次,重启后最多重复抓取在途的那 8 个。
JSON 快照,每 25 个 URL 直接覆盖写:207/175, 207/175, 157/150, 207/175, 223/200, 215/200 JSON 快照,每 25 个写临时文件 + fsync + 原子改名:206/175, 181/150, 181/150, 207/175, 182/150, 207/175 SQLite(WAL),每抓完一个 URL 提交一次:66/59, 44/38, 70/62, 111/103, 112/104, 59/51 SQLite(WAL),每 25 个 URL 批量提交一次:73/50, 165/150, 57/50, 165/150, 143/125, 122/100
已提交的状态不会丢;没提交的会重新抓。所有设计的“遗漏”都是 0,但“重复”从 7 到 112 不等。要做到不重复,需要把“抓取”和“保存”做成同一个事务,或让下游对重复幂等。