kp-010
分页、列表与详情页的采集模式
前置知识
本文基于模型知识整理,建议核对官方文档(见 参考资料)。
一句话定义
整站采集的骨架是「列表页发现 URL → 详情页抽取字段」,翻页有页码、偏移、游标、内容寻址四种模式,每种都必须配明确的终止条件。
为什么重要
单页抓取只是玩具;真实数据集动辄千页。翻页规则与终止条件设计错误,要么漏数据(提前停)、要么死循环(无限请求触发风控)。列表/详情两级架构也是 kp-018 Scrapy 与 kp-022 分布式的天然分工单元。
前置知识
kp-007、kp-009。
核心概念
- 四种翻页模式:页码参数(
?page=N)、偏移量(offset/limit)、游标(cursor/next_token,只能顺着响应走)、内容寻址(sitemap / 归档接口,无翻页概念)。 - 终止条件:空结果、条数不足一页、无
hasMore标志、URL 重复出现。 - 两级架构:列表阶段产出 URL 队列,详情阶段消费队列抽取字段。
- 增量采集:按时间戳/自增 ID 只抓「上次之后的新内容」,配合 kp-021 的状态存储。
原理与机制
四种模式的循环骨架:
def paginate(url_builder, parser, max_pages=50):
page, seen = 1, set()
while page <= max_pages: # 双保险上限
url = url_builder(page)
if url in seen: # 环形翻页检测(翻页又翻回第一页)
break
seen.add(url)
items = parser(fetch(url))
if not items: # 终止: 空页
break
yield from items
if len(items) < PAGE_SIZE: # 终止: 不足一页
break
page += 1
polite_sleep() # 见 kp-017
列表→详情的两级流水:
列表阶段: page=1..N → 抽取 [detail_url, list_time, list_price]
↓ 产出 URL 队列(带来源与时间)
详情阶段: 逐个抓取 detail_url → 抽取全字段 → 落库
增量判断的通行做法:记住上次采集到的最大 ID/最新时间,新一轮只处理大于该值(或晚于该时间)的条目;对「热门内容会被顶上来」的站点,改用「ID 集合差集」。
实例或案例
books.toscrape.com 共 50 页:用页码模式抓完约 1000 本书。练习要点——写终止条件时发现该站第 51 页会返回 404,于是终止策略改为「捕获 404 即停止」,这与「空页停止」是同一思想的两种实现。
常见误区
- 误区一:
for page in range(1, 100)写死页数。 数据更新后要么多抓空页要么漏新页;终止条件必须来自响应本身。 - 误区二:忽略深翻页限制。 不少接口
page>100时静默返回第一页数据(造成重复)或直接报错;要么改用游标,要么按时间/分类切片绕过深翻页。 - 误区三:列表、详情混在一次循环里。 单线程串行逐条抓详情极慢,且中途崩溃前功尽弃;拆成「先收集队列,再消费队列」才能断点续抓(kp-021)。
自测题
- 游标翻页与页码翻页的本质差异?
答:游标的下一页地址由上一页响应给出,不可随机跳页但天然防漏防重;页码可跳页但需自行防环、防深翻页失效。
- 至少列出三种翻页终止条件。
答:空结果、条数不足一页、响应无 hasMore/next 游标、URL 重复(环形)。
- 为什么要列表、详情两级分离?
答:解耦发现与抽取,队列可持久化支持断点续抓与并发加速,也便于只重跑失败部分。
与其他知识点的关系
kp-017 约束翻页的请求节奏;kp-021 把 URL 队列升级为带状态的调度;kp-030 案例即本模式在电商场景的完整落地。
延伸阅读
本库 kp-021;常见开放数据接口(如公开 API 文档)中的分页参数说明章节。