kp-010

分页、列表与详情页的采集模式

核心 ≈ 30 分钟 分页翻页列表页详情页增量采集

前置知识

本文基于模型知识整理,建议核对官方文档(见 参考资料)。

一句话定义

整站采集的骨架是「列表页发现 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)。

自测题

  1. 游标翻页与页码翻页的本质差异?

答:游标的下一页地址由上一页响应给出,不可随机跳页但天然防漏防重;页码可跳页但需自行防环、防深翻页失效。

  1. 至少列出三种翻页终止条件。

答:空结果、条数不足一页、响应无 hasMore/next 游标、URL 重复(环形)。

  1. 为什么要列表、详情两级分离?

答:解耦发现与抽取,队列可持久化支持断点续抓与并发加速,也便于只重跑失败部分。

与其他知识点的关系

kp-017 约束翻页的请求节奏;kp-021 把 URL 队列升级为带状态的调度;kp-030 案例即本模式在电商场景的完整落地。

延伸阅读

本库 kp-021;常见开放数据接口(如公开 API 文档)中的分页参数说明章节。

相关知识点

学习进度