抓一个分页列表时,数据可能正在变化。用 offset 翻页,新数据一插入,下一页就读到上一页读过的条目;删了前面的条目,后面的又被跳过。这篇用一个会同时新增和删除数据的本机服务器,对比四种分页方式的重复和漏抓。
(初始 1000 条数据,每页 20 条,约 50 页;每页之间客户端间隔 10 ms。"漏掉"=初始就有、整个抓取期间都没被删除、却从没返回过的数据条数;"重复"=返回的总条数减去不同的条数) 分页方式 请求页数 返回条数 重复 漏掉 期间新增/删除 偏移量分页,最新在前 51 1000 0 0 0 / 0 偏移量分页,最旧在前 51 1000 0 0 0 / 0 游标(键集)分页,最新在前 51 1000 0 0 0 / 0 服务器快照分页 51 1000 0 0 0 / 0
数据不变时四种方式没有区别,都是 51 次请求、1000 条。
分页方式 请求页数 返回条数 重复 漏掉 期间新增/删除 偏移量分页,最新在前 59 1148 148 0 151 / 0 (3 次的 重复/漏掉:[(149, 0), (148, 0), (147, 0)]) 偏移量分页,最旧在前 59 1142 0 0 146 / 0 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)]) 游标(键集)分页,最新在前 51 1000 0 0 131 / 0 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)]) 服务器快照分页 51 1000 0 0 135 / 0 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)])
最新在前的偏移量分页:新数据插在最前面,把已读过的条目向后推到后面的页里,于是下一页又读一遍,重复 148 条。最旧在前时新数据追加在末尾,没有重复、也没有漏,抓取还读到了新增的数据。
分页方式 请求页数 返回条数 重复 漏掉 期间新增/删除 偏移量分页,最新在前 46 884 0 59 0 / 120 (3 次的 重复/漏掉:[(0, 58), (0, 59), (0, 60)]) 偏移量分页,最旧在前 46 889 0 46 0 / 114 (3 次的 重复/漏掉:[(0, 54), (0, 46), (0, 52)]) 游标(键集)分页,最新在前 49 947 0 0 0 / 128 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)]) 服务器快照分页 51 1000 0 0 0 / 134 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)])
偏移量分页不论排序,前面有条目被删除,后面的条目前移一格,下一页的偏移量就跳过它们:漏掉 46 到 59 条。游标分页以最后一条 id 继续,不受影响;快照分页返回冻结时的数据(包括之后被删除的条目)。
分页方式 请求页数 返回条数 重复 漏掉 期间新增/删除 偏移量分页,最新在前 51 1000 60 0 138 / 138 (3 次的 重复/漏掉:[(53, 0), (60, 0), (58, 0)]) 偏移量分页,最旧在前 51 1000 0 45 137 / 137 (3 次的 重复/漏掉:[(0, 41), (0, 45), (0, 44)]) 游标(键集)分页,最新在前 49 950 0 0 122 / 122 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)]) 服务器快照分页 51 1000 0 0 134 / 134 (3 次的 重复/漏掉:[(0, 0), (0, 0), (0, 0)])
两种变化同时发生时,偏移量分页的结果更难预料(重复 60 或漏掉 45,取决于排序);游标和快照仍然既不重复也不漏掉。
读完第 2 页后,新增 3 条(id 101–103):
offset_new 第 2 页 [95, 94, 93, 92, 91],变化后的第 3 页 [93, 92, 91, 90, 89]
offset_old 第 2 页 [6, 7, 8, 9, 10],变化后的第 3 页 [11, 12, 13, 14, 15]
cursor 第 2 页 [95, 94, 93, 92, 91],变化后的第 3 页 [90, 89, 88, 87, 86]
读完第 2 页后,删除 3 条已读过的条目:
offset_new 第 2 页 [95, 94, 93, 92, 91],变化后的第 3 页 [87, 86, 85, 84, 83]
offset_old 第 2 页 [6, 7, 8, 9, 10],变化后的第 3 页 [11, 12, 13, 14, 15]
cursor 第 2 页 [95, 94, 93, 92, 91],变化后的第 3 页 [90, 89, 88, 87, 86]能用游标(按 id 或时间戳继续)就用游标;API 只给 offset 时,最旧在前加倍抓取校验(用抓到的 id 去重,并在结束后再扫一遍)可以减少风险。本实验没有测校验方案。