跳转至

分页抓取:终止条件比循环更重要

分页的难点不是把页码加一,而是准确判断何时应该停止。错误的终止条件会漏抓数据,或反复请求最后一页。

1. 先识别站点的分页方式

常见方式包括:

  • URL 查询参数:?page=2?offset=20&limit=20
  • 路径参数:/page/2/
  • HTML 中的“下一页”链接
  • 公开接口返回的 nexthas_moretotal 等字段

优先使用页面或接口已经提供的“下一页”信息,而不是根据页面上看起来有多少条数据猜测总页数。

2. 以“下一页链接”为终止条件

下面例子仅展示控制流程;fetch_htmlparse_items 应分别负责请求与解析。

from urllib.parse import urljoin


def crawl_pages(start_url: str) -> list[dict]:
    url: str | None = start_url
    seen: set[str] = set()
    all_items: list[dict] = []

    while url is not None and url not in seen:
        seen.add(url)
        html = fetch_html(url)
        soup = make_soup(html)
        all_items.extend(parse_items(soup, url))

        next_link = soup.select_one("a[rel='next']")
        href = next_link.get("href") if next_link else None
        url = urljoin(url, href) if href else None

    return all_items

seen 是必要的保护:某些站点可能错误地让“下一页”指回当前页,或形成循环链接。

3. 页码方式也要设置上限和异常检查

当站点只提供页码参数时,除了“本页为空”外,还应设置业务上合理的最大页数,防止程序因页面模板异常而无限运行。

for page in range(1, 101):
    html = fetch_html("https://example.com/list", params={"page": page})
    items = parse_items(make_soup(html), page_url="https://example.com/list")
    if not items:
        break
    save_items(items)

“本页没有项目”只适用于站点明确以空结果表示结束的情况。若站点可能临时返回空列表、验证码页或错误页,先检查状态、内容类型和页面标题,再决定是否结束。

4. 防止重复与漏抓

  • 为每个项目使用稳定主键(如公开 ID 或规范 URL)去重。
  • 每页记录项目数;突然从正常数量变为 0 时记录日志。
  • 保存最后成功页和来源 URL,便于中断后复查。
  • 不要并发请求大量分页地址;按站点规则控制频率。

参考

评论