导读:本期聚焦于赵六创作的《Python爬虫如何抓取URL不变的分页数据?几种实用方案详解》,敬请观看详情。爬虫抓取分页数据时,最常见的情况是点击下一页后浏览器地址栏的URL完全不变,这时候直接用requests拼接页码参数的方式就失效了。本文围绕这类由前端动态加载的分页场景,介绍几种经过验证的解决思路:通过浏览器开发者工具分析Network面板中的Ajax请求、利用参数规律直接构造API请求、借助Selenium或Playwright模拟点击翻页按钮,以及结合哈希路由监听页面变化。文章还对比了各方案的适用场景、反爬风险与性能差异,并给出完整可运行的Python代码示例,帮助你针对不同网站选择最合适的抓取策略。

用Python写爬虫时,分页抓取几乎是绕不开的需求。理想情况下,网站的翻页是通过URL参数实现的,比如list?page=2,这种页面只需要简单修改参数就能遍历所有数据。但现实中大量网站采用了前后端分离架构,分页操作由JavaScript发起Ajax请求完成,点击下一页后地址栏的URL纹丝不动,页面内容却刷新了。这种URL不变的分页场景让不少初学者束手无策,本文将系统梳理应对这类页面的几种实用方案。

Python爬虫如何抓取URL不变的分页数据?几种实用方案详解

一、先弄清楚:为什么URL不变,数据却变了

要解决这个问题,首先要理解其背后的原理。传统的服务端渲染页面中,每一次翻页都是一次完整的HTTP请求,服务器返回新的HTML文档,URL自然会变化。而在前后端分离的网站中,页面首次加载时只返回一个“空壳”HTML,真正的内容是由JavaScript在后台向后端API请求数据,再动态渲染到DOM中的。

点击“下一页”按钮时,实际发生的事情是:JavaScript捕获点击事件,构造一个携带页码参数的Ajax请求发送给后端接口,拿到JSON或HTML片段后替换页面中的列表区域。整个过程没有触发浏览器导航,所以地址栏URL保持不变。有些网站虽然看起来URL变了,但变的只是#后面的哈希部分(如list.html#page=3),这种哈希根本不会发送到服务器,本质上和URL不变是同一类问题。

理解了这个机制,解决思路就清晰了:既然数据是通过某个接口传输的,那么找到这个接口,直接请求它,往往比模拟浏览器操作更高效。这就是下面要讲的第一种方案。

二、方案一:分析Ajax请求,直接构造API调用(推荐)

这是处理动态分页最优雅的方式。打开浏览器开发者工具(按F12),切换到Network(网络)面板,筛选XHR或Fetch请求,然后点击页面上的下一页按钮,观察新出现的请求。你会发现一个返回JSON数据的接口,请求参数中通常包含pagepageSize之类的字段。

找到接口后,用Python直接请求这个API即可。这种方式的优势非常明显:请求轻量、速度快、返回的是结构化数据、无需解析HTML,也不需要渲染JavaScript。下面是一个典型示例:

import requests

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    "Referer": "https://example target site/list.html"
})

def fetch_page(page):
    api = "https://example target site/api/article/list"
    params = {"page": page, "pageSize": 20}
    resp = session.get(api, params=params, timeout=10)
    resp.raise_for_status()
    return resp.json()

# 遍历前10页数据
for p in range(1, 11):
    data = fetch_page(p)
    items = data.get("data", {}).get("list", [])
    if not items:
        break  # 没有数据了就停止
    for item in items:
        print(item["title"])

需要注意的是,有些网站的接口参数不是简单的page=2,而是使用偏移量offset(等于(page-1)*pageSize),或者对参数做了加密签名。遇到加密参数时,可以在Network面板中查看请求头和表单数据,确认是否携带signtoken等字段,必要时需要分析前端JS代码或借助补环境技术还原加密逻辑。另外,如果接口是POST请求,把session.get换成session.post并传入datajson参数即可。

判断何时停止翻页也是细节之一。常见做法有三种:一是事先知道总页数,直接控制循环范围;二是接口返回中包含totalPagehasMore字段,据此判断;三是返回数据为空列表时退出循环,上面的代码采用了第三种,最通用。

三、方案二:用Selenium或Playwright模拟浏览器翻页

当接口参数被深度加密、难以破解时,模拟浏览器操作是最省心的兜底方案。思路很直接:让程序控制真实浏览器,找到页面上的“下一页”按钮,点击它,等待新数据加载完成,再提取内容,如此循环。

下面用Selenium演示完整流程,Playwright的写法类似且更现代化,推荐优先尝试:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
driver.get("https://example target site/list.html")

wait = WebDriverWait(driver, 10)

while True:
    # 等待数据列表加载完成
    wait.until(EC.presence_of_all_elements_located(
        (By.CSS_SELECTOR, ".article-item")))
    items = driver.find_elements(By.CSS_SELECTOR, ".article-item h3")
    for item in items:
        print(item.text)

    # 判断是否还有下一页
    next_btn = driver.find_element(By.CSS_SELECTOR, ".next-page")
    if "disabled" in (next_btn.get_attribute("class") or ""):
        break
    next_btn.click()

driver.quit()

这个方案的关键在于两处:一是翻页后必须等待数据渲染完成再提取,否则会拿到旧数据或空数据,上面代码用WebDriverWait配合显式条件等待解决;二是准确判断“下一页”按钮何时变为不可点击状态,通常按钮会加上disabled类名或属性,具体需要查看目标网站的HTML结构。

模拟浏览器方案的缺点也很明显:资源占用高、速度慢、容易被检测。如果需要抓取成千上万页数据,这种方式效率会很低。一个折中技巧是“半自动模式”——用Selenium完成登录获取Cookie,再把Cookie传给requests去请求那个Ajax接口,兼顾效率与可行性。

四、方案三:应对哈希路由与无限滚动两种变体

除了点击式翻页,还有两种容易混淆的变体需要单独说明。第一种是哈希路由分页,URL类似list.html#/page/3,哈希部分变了但页面不刷新。这种情况下仍然优先分析Ajax接口,因为哈希只是前端路由的标记,数据获取逻辑和普通Ajax分页没有区别。如果一定要模拟,监听hashchange事件对应的处理逻辑即可,但通常没必要。

第二种是无限滚动(瀑布流)加载,页面向下滚动时自动加载更多内容,连“下一页”按钮都没有。应对策略依然是先看Network面板:滚动时触发的Ajax请求中,offsetpage参数会递增,找到规律后直接构造请求循环抓取。如果用Selenium,则通过driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")模拟滚动,配合显式等待判断数据条数是否增加来决定是否继续。

无论哪种变体,核心方法论是一致的:数据一定通过网络请求传输,先观察请求,再决定是直接复现请求还是模拟用户行为。把这个思路内化后,绝大多数动态加载页面都不再是难题。

五、方案对比与选型建议

三种方案各有适用场景,简单对比一下:requests直连API的方式速度最快、资源消耗最小,是首选,前提是接口参数可分析、无复杂加密;Selenium或Playwright模拟操作最稳妥、适用面最广,能绕过大多数加密问题,但速度慢且环境依赖重;哈希路由与无限滚动的处理则是在前两种方案基础上的具体技巧延伸。

实际项目中还有一个不容忽视的因素是反爬。频繁请求接口容易触发限流或封IP,建议在请求之间加入随机延时(如time.sleep(random.uniform(1, 3))),控制抓取频率,必要时使用代理IP池。同时务必遵守目标网站的robots协议和服务条款,控制抓取规模,只获取自己需要的数据,做一个有分寸的爬虫开发者。

最后总结一句:遇到URL不变的分页页面,不要急于上重型工具,先打开F12看一眼Network面板,很多看似复杂的问题,背后只是一个等待你发现的简单JSON接口而已。

Python爬虫分页抓取Ajax请求修改时间:2026-09-01 19:12:39

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。