分页抓取:终止条件比循环更重要
分页的难点不是把页码加一,而是准确判断何时应该停止。错误的终止条件会漏抓数据,或反复请求最后一页。
1. 先识别站点的分页方式
常见方式包括:
- URL 查询参数:
?page=2、?offset=20&limit=20 - 路径参数:
/page/2/ - HTML 中的“下一页”链接
- 公开接口返回的
next、has_more、total等字段
优先使用页面或接口已经提供的“下一页”信息,而不是根据页面上看起来有多少条数据猜测总页数。
2. 以“下一页链接”为终止条件
下面例子仅展示控制流程;fetch_html 和 parse_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,便于中断后复查。
- 不要并发请求大量分页地址;按站点规则控制频率。