在使用Python进行网页数据抓取时,经常会遇到用requests、urllib等库获取的网页源码,和浏览器F12开发者工具中看到的内容存在差异的情况,这本质上是网页的数据加载和渲染模式不同导致的。要搞清楚这个问题,首先需要了解服务端模板直出和前端JS渲染两种常见的网页内容生成方式。

两种网页内容生成模式的核心区别
服务端模板直出
服务端模板直出是传统的网页生成方式,服务器接收到用户请求后,会先在后端将页面的HTML结构、动态数据全部拼接完成,生成一个完整的HTML字符串,再直接返回给浏览器。浏览器拿到响应后,只需要解析HTML、加载CSS和JS即可完成页面展示,不需要再额外请求数据接口。
这种模式下,用Python的HTTP库直接请求网页URL,获取到的源码和浏览器F12看到的HTML内容是一致的,因为所有内容都已经包含在初始响应中。
前端JS渲染
前端JS渲染模式下,服务器返回的初始HTML只是一个空壳,只包含基础的页面结构和JS、CSS的引入标签,没有实际的业务数据。浏览器加载完初始HTML后,会执行页面中的JS代码,由JS向服务端的数据接口发起请求,获取动态数据后再通过DOM操作把数据插入到页面中,最终形成用户看到的完整页面。
这种模式下,Python的HTTP库请求得到的是初始的空壳HTML,自然和浏览器F12看到的、经过JS渲染后的完整页面内容不一样。
如何区分两种渲染模式
可以通过以下两种简单的方法快速判断目标网页使用的是哪种渲染方式:
- 查看网页初始响应的内容:在浏览器中打开目标网页,按F12打开开发者工具,切换到Network面板,勾选Disable cache,刷新页面,找到第一个HTML类型的请求,查看Response内容。如果Response中已经包含了需要抓取的业务数据,就是服务端模板直出;如果Response中只有基础结构,没有业务数据,就是前端JS渲染。
- 禁用浏览器JS后访问页面:在浏览器设置中禁用JavaScript,然后访问目标网页。如果页面只显示基础框架,没有业务数据,说明是前端JS渲染;如果页面依然显示完整内容,说明是服务端模板直出。
源码不一致的排查步骤
当遇到Python获取的源码和F12看到的不一致时,可以按照以下步骤排查:
第一步:确认请求URL和请求头是否正确
首先要检查Python请求时使用的URL是否和浏览器访问的URL一致,有些网页的移动端和PC端URL不同,返回的内容也会有差异。同时检查请求头是否携带了必要的参数,比如User-Agent,部分服务器会校验请求头,返回不同的内容。
以下是基础的Python请求示例:
import requests
url = "https://ipipp.com/test_page"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers)
print(response.text) # 打印获取到的源码
第二步:判断是否为前端JS渲染
按照前面提到的区分方法,确认网页是否为前端JS渲染模式。如果是JS渲染,那么直接请求URL无法获取到完整数据,需要找到JS请求的数据接口。
第三步:定位数据接口
在浏览器F12的Network面板中,切换到XHR或者Fetch分类,刷新页面,查看JS发起的请求,找到返回目标数据的接口。查看该接口的请求URL、请求方法、请求参数、请求头,然后在Python中模拟该请求即可获取到数据。
以下是模拟数据接口请求的示例:
import requests
# 数据接口的URL,从Network面板中获取
api_url = "https://ipipp.com/api/get_data"
params = {
"page": 1,
"size": 10
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://ipipp.com/test_page"
}
response = requests.get(api_url, params=params, headers=headers)
print(response.json()) # 打印接口返回的数据
第四步:处理特殊情况
如果网页有反爬机制,比如接口参数加密、需要携带Cookie才能访问,就需要进一步分析JS的加密逻辑,或者携带浏览器请求时的Cookie进行请求。如果是需要执行复杂JS才能生成内容的网页,也可以使用selenium、playwright等工具模拟浏览器运行,等待JS渲染完成后再获取页面源码。
以下是使用playwright获取JS渲染后页面源码的示例:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://ipipp.com/test_page")
# 等待页面渲染完成,可根据实际情况调整等待条件
page.wait_for_load_state("networkidle")
html_content = page.content()
print(html_content)
browser.close()
两种模式的适用场景对比
为了让大家更清晰地理解两种模式的差异,以下是两者的多维度对比:
| 对比维度 | 服务端模板直出 | 前端JS渲染 |
|---|---|---|
| 初始响应内容 | 包含完整页面内容和数据 | 仅包含基础页面结构,无业务数据 |
| 数据加载时机 | 服务端完成数据拼接后返回 | 浏览器加载JS后发起请求获取数据 |
| Python直接请求效果 | 可获取完整源码 | 仅获取空壳HTML |
| SEO友好度 | 友好,搜索引擎可直接抓取内容 | 不友好,需要额外做SSR优化 |
| 适用场景 | 内容固定、对SEO要求高的页面 | 交互复杂、数据动态更新的页面 |
在实际的爬虫开发中,遇到源码不一致的问题时,先按照上述步骤判断渲染模式,再选择对应的数据获取方案,就能高效解决大部分数据抓取异常的问题。