用requests请求一个商品列表页,返回的HTML里只有几个空div,价格、标题、评论数统统不见踪影,但浏览器里明明显示得好好的。这个现象几乎每个写爬虫的人都遇到过。原因很简单:现代网站大量使用JavaScript在页面加载后再通过XHR请求从后端拉取数据,再用脚本填充到DOM里。requests只负责发HTTP请求,不执行JavaScript,自然拿不到这些内容。本文就来拆解这个问题,介绍如何定位XHR接口、直接请求数据,并结合BeautifulSoup完成解析。

一、先搞清楚:静态页面与动态加载页面的区别
传统的静态页面,所有内容都直接写在服务器返回的HTML源码里。你用requests拿到响应后,BeautifulSoup一顿解析就能提取出全部数据,这种方式简单可靠,也不容易被反爬针对。
而动态加载页面则不同。服务器返回的初始HTML往往只有一个页面骨架,里面嵌着JavaScript脚本。浏览器执行这些脚本后,会向服务器发送额外的异步请求(也就是XHR,XMLHttpRequest,或者基于fetch API的请求),拿到JSON或HTML片段后再动态插入页面。典型的标志是:在浏览器里右键查看源代码(Ctrl+U)时找不到你在页面上看到的数据,这就是判断一个页面是否动态加载的最快捷方法。
举个例子,某电商平台的商品列表,页面源码里搜不到商品标题,但在Network面板里却能找到一个返回商品数据的接口。遇到这种情况,直接解析初始HTML就是死路一条,必须换思路。
二、用开发者工具定位真实的XHR数据接口
Chrome开发者工具的Network面板是定位XHR接口的核心武器。操作步骤:按F12打开工具,切换到Network标签,筛选类型选择XHR或Fetch,然后刷新页面或触发滚动加载、点击翻页等操作,观察新出现的请求。
重点看这几个字段:请求的URL、请求方法(GET还是POST)、请求参数(Query String Parameters或Form Data)、以及响应内容(Response)。如果响应是一段JSON,里面恰好包含你要抓的数据,恭喜,接口找到了。
一个实用技巧是搜索法:在Network面板的搜索框里输入页面上看到的某个具体数据值(比如某个商品名),工具会帮你定位到包含这个值的请求,比逐个点击请求快得多。另外注意有些接口会带时间戳签名或token参数,这类参数需要进一步分析其生成逻辑,必要时可以先用Selenium拿到初始cookie和token,再切换到requests模式。
三、用Python直接请求接口并解析数据
找到接口后,用requests模拟这个请求即可。通常需要带上合适的请求头,尤其是User-Agent、Referer和Cookie,缺了可能直接返回403或空数据。下面是一个完整示例:
import requests
from bs4 import BeautifulSoup
import json
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Referer": "https://www.ipipp.com/list",
"Cookie": "sessionid=xxxx"
}
# 直接请求XHR接口,返回JSON数据
url = "https://www.ipipp.com/api/goods/list"
params = {"page": 1, "size": 20, "sort": "sales"}
resp = requests.get(url, headers=headers, params=params, timeout=10)
data = resp.json()
for item in data["result"]:
print(item["title"], item["price"])
如果接口返回的不是JSON而是HTML片段(有些网站习惯返回渲染好的局部HTML),这时候BeautifulSoup就派上用场了:
# 假设接口返回的是HTML片段
resp = requests.get(url, headers=headers, params=params, timeout=10)
soup = BeautifulSoup(resp.text, "lxml")
# 解析返回的HTML片段
for card in soup.select("div.goods-card"):
title = card.select_one("span.title").get_text(strip=True)
price = card.select_one("em.price").get_text(strip=True)
print(title, price)
两种形态的处理思路都一样:先看响应内容的结构,再决定用json模块还是BeautifulSoup。lxml解析器速度比默认的html.parser快不少,建议安装后优先使用。
四、XHR直连方案与Selenium的取舍
拿到接口后直连请求,优点非常明显:速度快、资源占用低、代码简单、稳定性好。一个requests请求几十毫秒就能完成,而Selenium启动一个浏览器实例要好几秒,内存占用也是数量级的差距。对于大规模抓取任务,能走接口就绝不要开浏览器。
但XHR方案也有短板。一是接口参数可能加密,分析成本高;二是接口没有文档,字段含义靠猜;三是反爬检测可能针对接口做频控和签名校验。而Selenium或Playwright模拟真实浏览器,能执行JavaScript、绕过大部分前端渲染问题,对加密参数的网站反而是更省事的选择。
实践中常见的组合策略是:用Selenium登录并获取cookie,再把cookie塞进requests的会话里做后续批量抓取;或者发现接口签名难以逆向时,退而求其次用headless浏览器渲染页面,再用BeautifulSoup解析渲染后的完整HTML。两者配合,往往能兼顾效率与可行性。
五、稳定性与合规的几点建议
抓取动态页面时,控制请求频率很重要。加上随机延时,比如每次请求间隔1到3秒,避免触发限流。使用Session对象复用TCP连接和cookie,能显著提升效率。遇到返回空数据或状态码异常时,做好重试与日志记录。
import time
import random
session = requests.Session()
session.headers.update(headers)
def fetch_page(page):
for attempt in range(3):
try:
resp = session.get(url, params={"page": page}, timeout=10)
if resp.status_code == 200:
return resp.json()
except requests.RequestException:
pass
time.sleep(random.uniform(1, 3))
return None
最后提醒一点:抓取前先查看目标网站的robots.txt和服务条款,遵守数据获取的边界,不抓取涉及个人隐私的数据,不对服务器造成过大压力。技术本身是中性的,合规使用才能走得长远。掌握XHR分析加BeautifulSoup解析这套组合拳,绝大多数动态页面的数据获取问题都能迎刃而解。
动态网页抓取BeautifulSoupXHR请求修改时间:2026-09-05 00:34:38