分页
一、为什么要分页
想象一个新闻网站,数据库里有 10000 条 新闻。当你打开 https://example.com/news 这个页面的时候,网站不会把 10000 条一次性全倒给你,原因很现实:
-
网页一下子加载 10000 条,浏览器会卡死;
-
服务器一次性吐 10000 条,数据库压力太大;
-
用户屏幕也就那么大,一次看几十条就够了,剩下的都是浪费流量。
于是网站定了个规矩:每次只给你 20 条,看完再翻下一页。 这个"每次 20 条"就是所谓的 一页(page)。
所以"页"根本不是网页本身的天然结构,而是后端把一大堆数据切成了均匀的若干份,一份叫一页:
第 1 页:第 1~20 条新闻 第 2 页:第 21~40 条新闻 第 3 页:第 41~60 条新闻 ... 第 500 页:第 9981~10000 条新闻
你在浏览器里点"下一页""2""3""...""500",或者往下滚动加载出更多内容------本质都是在说:"把第 2 页那 20 条给我""把第 3 页那 20 条给我"。
那爬虫为什么要操心翻页?
这就是关键了。你写爬虫时请求的,永远只是某一页。
假如你只发一次请求 https://example.com/news,服务器默认只把第 1 页那 20 条返回给你。程序解析完,你以为抓到了新闻,其实 10000 条里只拿到了 20 条,剩下的 9980 条你压根没碰过。
所以爬虫要拿到全部数据,就必须模拟人不停点"下一页"这个动作 :抓完第 1 页,再请求第 2 页,再请求第 3 页......直到没有更多数据为止。这就是"翻页"在爬虫里的含义------不是页面在翻,是你的程序在一页一页地把数据分批要回来。
二、爬虫里常见的 4 种翻页方式
它们本质上是同一个问题:网站怎么告诉你"还有下一页"?你怎么拿到下一页?
| 翻页方式 | 下一页在哪里 | 爬虫怎么做 |
|---|---|---|
| URL 参数分页 | ?page=1、?offset=20 |
自己拼 URL,循环请求 |
| 下一页链接分页 | [下一页]() |
解析页面,取 href,再请求 |
| API 分页 | JSON 接口返回数据 | 直接调 API,按参数或 cursor 翻页 |
| 无限滚动 / Load More | 滚动后触发 AJAX 请求 | 先找真实接口,找不到再模拟滚动 |
1、URL 参数分页
URL 参数分页是爬虫最常见的一种翻页方式。它的核心思路很简单:每一页对应一个 URL,只是 URL 里的参数在变化。程序只要按规律修改这些参数,就能像人点"下一页"一样,一页一页把数据抓完。
两种常见形式
1. 页码式分页:page
/news?page=1 /news?page=2 /news?page=3
这里的 page 表示"第几页"。程序只需要从 page=1 开始,每次把页码加 1,直到没有数据、没有下一页链接,或者达到你设定的最大页数。
程序一圈圈跑,就是在替你把"下一页、下一页、下一页......"这一下午要点几百次的鼠标,自动按了几百次。
2. 偏移量分页:offset + limit
/news?offset=0&limit=20 /news?offset=20&limit=20 /news?offset=40&limit=20
这里有三个关键参数:
| 参数 | 含义 | 示例 |
|---|---|---|
limit |
每页返回多少条 | 20 表示一页 20 条 |
offset |
跳过前面多少条 | 0 表示从第 1 条开始 |
page |
第几页 | page=3 表示第 3 页 |
它们之间有一个很常用的换算关系:
offset = (page - 1) * limit
比如每页 20 条:
第 1 页:
offset = 0第 2 页:
offset = 20第 3 页:
offset = 40
所以 page=3&limit=20 和 offset=40&limit=20 本质上指向同一片数据。
爬虫里怎么处理?
通常做法是写一个循环,不断修改 URL 参数:
javascript
for page in range(1, 101):
url = f"https://example.com/news?page={page}"
# 请求、解析、存储
或者用 offset 方式:
javascript
limit = 20
for offset in range(0, 2000, limit):
url = f"https://example.com/news?offset={offset}&limit={limit}"
# 请求、解析、存储
需要注意的两个问题
一是性能问题 。offset 很大时,后端数据库可能需要跳过大量记录,查询会变慢。 二是数据漂移问题。如果数据在翻页过程中被新增或删除,下一页可能会出现重复或遗漏。
所以写爬虫时,除了改参数翻页,还要做好去重、异常处理和最大页数限制。
2、下一页"链接分页
页面上没有 ?page=2 这种明显规律,但有一个按钮或链接:
javascript
<a href="/news?page=2">下一页</a>
这时不要硬猜 page+1,而是:
解析当前页 ↓ 找到“下一页”链接 ↓ 取出 href ↓ 请求这个新 URL ↓ 继续解析
这种方式更稳,因为有些网站的页码可能不连续,比如:
/news/2026/10 /news/2026/09 /news/2026/08
它不是 page=1,2,3,而是按时间、分类、ID 生成链接。所以爬虫要跟着链接走,而不是假设页码一定连续。
3、API 分页
很多网页看起来是普通页面,但数据其实来自 JSON 接口。比如浏览器里看到新闻列表,但 HTML 源码里几乎没有数据。
常见接口形式是:
javascript
/api/news?page=2&size=20
/api/news?offset=20&limit=20
还有一种叫 cursor 分页:
javascript
{
"items": [...],
"nextCursor": "abc123"
}
下一页请求变成:
javascript
/api/news?cursor=abc123
cursor 不是页码,不能自己猜。它通常是服务器返回的一个"位置标记",可能编码了最后一条数据的 ID、时间戳或排序位置。
爬虫处理方式是:
请求接口 ↓ 解析 JSON ↓ 保存 items ↓ 取出 nextCursor ↓ 用 nextCursor 请求下一页 ↓ nextCursor 为空时停止
cursor 分页适合数据量大、数据会变化、需要稳定顺序的场景;缺点是没法直接跳到"第 100 页"。
4、无限滚动 / Load More
有些页面没有页码,也没有"下一页"按钮。用户往下滚动,数据就一批一批加载出来。
它的常见表现是:
页面滚动 ↓ 浏览器发出 AJAX / Fetch 请求 ↓ 返回 JSON 数据 ↓ 前端把数据渲染到页面上
这时不要一上来就用 Selenium 或 Playwright 模拟滚动。更高效的做法是:
打开 F12 ↓ 切到 Network 面板 ↓ 筛选 XHR / Fetch ↓ 滚动页面或点“加载更多” ↓ 找到返回目标数据的接口 ↓ 用 requests 直接请求这个接口
只有当接口有签名、加密参数、复杂校验,实在绕不过去时,才考虑用 Playwright / Selenium 模拟浏览器行为。
三、分页终止条件:防止爬虫"无限跑
分页最怕的就是 while True 不加刹车。只要没有明确的终止条件,爬虫就可能一直请求空页面、重复页面,甚至把自己跑死。
常见停止条件可以分成几类:
| 类型 | 判断方式 | 适用场景 |
|---|---|---|
| 链接消失 | 没有 next 链接 / 下一页按钮消失 |
页码分页、下一页链接分页 |
| 数据为空 | 返回 items 为空 |
API 分页 |
| 数据不足 | 返回数量 < pageSize |
偏移量分页 |
| 游标结束 | nextCursor == null |
cursor 分页 |
| 安全上限 | 达到 max_pages 或 max_articles |
所有场景兜底 |
| 重复检测 | 连续若干页没有新增数据 | 动态列表、无限滚动 |
其中 安全上限 尤其重要。比如设置 max_pages = 20 或 max_articles = 500,即使其他判断失效,也能保证爬虫不会无限循环。
四 、两个工程坑
1. 第一页和后续页可能不一样
很多网站第一页是:
/news
从第二页开始才变成
/news?page=2
/news?page=3
所以不要想当然认为第一页一定是 page=1。更稳的做法是:
-
先正常请求第一页;
-
从第二页开始再按分页规则构造 URL;
-
或者优先跟随页面里的
next链接,而不是硬拼页码。
2. 分页过程中要去重
新闻网站更新时,新文章可能插入第一页,原来的第二页数据就会整体后移。这样同一篇文章可能在两页里都出现。
所以常用 canonical URL 或详情页 URL 做去重依据:
seen_urls = set()
for article in articles:
if article.url in seen_urls:
continue
seen_urls.add(article.url)
save(article)
如果页面本身有 ``,也可以把它作为规范 URL;但在爬虫去重里,更常见的做法是用详情页的唯一 URL 判断是否已经采集过。
五、分页分析检查顺序
遇到一个列表页,可以按这个顺序判断:
页面有没有明确页码?
↓
URL 是否随翻页变化?
↓
有没有 Next 链接?
↓
F12 Network 里有没有分页 API?
↓
是 page / offset / cursor 哪一种?
↓
什么时候停止?
↓
如何防重复?
这套顺序本质上是在问三件事:
-
下一页在哪里?
-
怎么判断已经翻完了?
-
怎么保证不重复抓?