传统的爬虫库(如 requests + BeautifulSoup)只能获取网页的初始 HTML 源代码,如果页面的内容是通过 JavaScript 动态加载的(比如点击“加载更多”后出现的内容,或者数据是通过 API 请求异步获取的),requests 就拿不到这些数据。
解决这个问题主要有两大主流方案:
- 模拟浏览器(无头浏览器):使用工具(如 Selenium, Playwright)来控制一个真实的浏览器(如 Chrome 或 Firefox),浏览器会执行 JavaScript,渲染出完整的页面,然后我们从中提取数据,这是最通用、最强大的方法。
- 直接调用 API:分析网页的 JavaScript 代码,找到它用来加载数据的 API 接口,我们直接在 Python 中模拟发送这个 API 请求,获取 JSON 数据,这种方法更快、更高效,但需要一些逆向工程的技巧。
下面我将详细介绍这两种方法,并提供完整的代码示例。
使用 Selenium 模拟浏览器(推荐入门)
Selenium 是最流行的浏览器自动化测试工具,它也可以用来爬取由 JavaScript 渲染的页面,它会打开一个浏览器窗口(可以设置为“无头模式”即后台运行),访问网页,等待页面加载完成,然后你就可以像操作真实浏览器一样定位和提取元素。
步骤 1:安装必要的库
你需要安装 selenium 和一个 浏览器驱动,驱动是 Selenium 用来控制浏览器的“桥梁”。
# 安装 selenium 库 pip install selenium # 安装浏览器驱动 (以 Chrome 为例) # 1. 确保你已安装 Google Chrome 浏览器 # 2. 下载对应版本的 ChromeDriver: https://googlechromelabs.github.io/chrome-for-testing/ # 3. 将 chromedriver.exe (Windows) 或 chromedriver (Mac/Linux) 放到你的项目目录或系统 PATH 中 # (或者使用 webdriver-manager 自动管理驱动,强烈推荐!) pip install webdriver-manager
步骤 2:编写 Python 代码
下面是一个使用 webdriver-manager 自动管理驱动的完整示例,它会爬取 https://quotes.toscrape.com/js/ 这个专门用来练习 JS 爬虫的网站。
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# --- 配置浏览器选项 ---
chrome_options = Options()
# 1. 无头模式:在后台运行,不弹出浏览器窗口
chrome_options.add_argument("--headless")
# 2. 禁用 GPU 加速,在某些系统上可以避免问题
chrome_options.add_argument("--disable-gpu")
# 3. 窗口大小
chrome_options.add_argument("--window-size=1920,1080")
# --- 初始化 WebDriver ---
# 使用 webdriver-manager 自动下载并管理驱动
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
try:
# --- 访问目标网页 ---
print("正在访问网页...")
url = "https://quotes.toscrape.com/js/"
driver.get(url)
# --- 等待和提取数据 ---
# 方法一:等待元素出现(推荐)
# 使用 WebDriverWait 显式等待,比 time.sleep() 更智能、更稳定
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
print("等待页面加载完成...")
# 等待至少一个 quote 元素被加载
wait = WebDriverWait(driver, 10) # 最多等待10秒
quotes = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "quote")))
print(f"找到 {len(quotes)} 条名言。")
# 遍历提取数据
for quote in quotes:
text = quote.find_element(By.CLASS_NAME, "text").text
author = quote.find_element(By.CLASS_NAME, "author").text
print(f"文本: {text}")
print(f"作者: {author}\n")
# 方法二:简单粗暴的等待(不推荐,仅用于演示)
# time.sleep(5) # 等待5秒,给 JS 足够的时间渲染
# quotes = driver.find_elements(By.CLASS_NAME, "quote")
# print(f"找到 {len(quotes)} 条名言。")
finally:
# --- 关闭浏览器 ---
print("任务完成,关闭浏览器。")
driver.quit()
代码解释:
- 配置选项:
Options用于配置 Chrome 浏览器,--headless是关键,它让浏览器在后台运行。 - 初始化
WebDriver:ChromeDriverManager()会自动检测你的 Chrome 版本并下载对应的驱动,省去了手动下载和配置的麻烦。 driver.get(url):命令浏览器打开指定 URL。- 等待与定位:
WebDriverWait和expected_conditions:这是 Selenium 的核心功能,它会一直等待,直到某个条件(比如某个元素出现)被满足,或者超时,这比time.sleep()更可靠,因为它不会在页面还没加载好时就执行下一步。By.CLASS_NAME:通过 CSS 类名来定位元素,这是最常用的定位方式之一。
- 提取数据:使用
find_element或find_elements在已定位的父元素(如quote)中查找子元素,并获取其.text属性。 driver.quit():非常重要!确保在最后关闭浏览器,释放资源。
直接调用 API(更高效)
很多时候,前端 JavaScript 会通过 fetch 或 XMLHttpRequest (XHR) 请求一个后端 API 来获取数据,这个 API 通常返回的是 JSON 格式的数据,直接、干净,没有多余的 HTML 标签。
步骤 1:分析网页,找到 API
- 在目标网页上右键,选择“检查”打开开发者工具。
- 切换到 “网络”(Network) 标签页。
- 勾选 “保留日志”(Preserve log) 和 “禁用缓存”(Disable cache)。
- 刷新页面。
- 在网络请求列表中,寻找类型为
XHR或Fetch的请求,并且响应内容是 JSON 格式的,你可以通过点击请求,在“响应”(Response)或“预览”(Preview)标签页查看。
以 https://quotes.toscrape.com/js/ 为例,我们可以在网络标签页发现一个名为 quotes?page=1 的请求,它的响应正是我们需要的 JSON 数据。
步骤 2:编写 Python 代码
一旦找到了 API,我们就可以用 requests 库直接请求它。
import requests
import json
# --- 找到的 API URL ---
# 注意:这个 URL 可能包含查询参数来控制分页等
api_url = "https://quotes.toscrape.com/api/quotes?page=1"
try:
print(f"正在请求 API: {api_url}")
# 发送 GET 请求
response = requests.get(api_url)
# 检查请求是否成功
response.raise_for_status() # 如果请求失败 (404, 500), 会抛出异常
# 解析 JSON 数据
data = response.json()
print(f"成功获取数据,当前页面: {data['page']}, 总页数: {data['pages']}")
# 提取并打印名言
for quote in data['quotes']:
text = quote['text']
author = quote['author']
print(f"文本: {text}")
print(f"作者: {author}\n")
except requests.exceptions.RequestException as e:
print(f"请求 API 失败: {e}")
except json.JSONDecodeError:
print("响应内容不是有效的 JSON 格式。")
代码解释:
requests.get():向 API 发送一个 HTTP GET 请求。response.raise_for_status():检查 HTTP 状态码,如果是 4xx 或 5xx 错误,则抛出异常。response.json():将响应内容直接解析为 Python 字典或列表,非常方便。- 数据提取:直接操作 Python 字典来获取数据,比解析 HTML 简单得多。
总结与对比
| 特性 | Selenium (模拟浏览器) | 直接调用 API |
|---|---|---|
| 原理 | 控制真实浏览器,执行 JS 渲染页面 | 直接向服务器发送数据请求 |
| 优点 | 通用性强,能处理任何 JS 渲染逻辑;能处理复杂的用户交互(点击、输入、滚动)。 | 速度快,资源消耗小;数据格式纯净(JSON),易于解析;更稳定,不易受前端代码变动影响。 |
| 缺点 | 速度慢,资源消耗大(需要启动浏览器);配置相对复杂;网页结构变动可能导致爬虫失效。 | 需要逆向工程,找到 API 接口;可能遇到反爬机制(如 API 的鉴权、签名);API 可能随时被网站修改或废弃。 |
| 适用场景 | - 页面内容由复杂的 JS 逻辑生成,难以找到 API。 - 需要模拟用户登录、点击、拖拽等交互。 - 爬取 SPA(单页应用)网站。 |
- 网站数据通过 API 加载,且 API 容易发现。 - 对爬取速度和效率要求高。 - 只需要获取特定数据,不关心页面渲染效果。 |
最佳实践建议
- 优先尝试 API 方法:这是最高效、最稳定的方式,花几分钟在开发者工具里找找 API,通常会有惊喜。
- API 失败再使用 Selenium:如果找不到合适的 API,或者 API 有复杂的反爬机制(如动态生成的 token),再使用 Selenium。
- 结合使用:有时,你可能需要先用 Selenium 登录或完成某个操作,拿到登录后的
cookie或token,然后再用requests去请求需要这些认证信息的 API。
希望这个详细的指南能帮助你掌握 Python 爬取 JavaScript 渲染页面的技术!
