Selenium 获取动态 JSON 数据:从网页渲染到数据提取的完整指南**
在当今的 Web 开发和数据采集领域,动态网页已成为主流,许多网站不再直接在 HTML 中硬编码所有数据,而是通过 JavaScript 动态加载,JSON (JavaScript Object Notation) 是一种非常常见的数据交换格式,当我们需要使用 Selenium 这样的自动化测试工具来获取这些动态加载的 JSON 数据时,直接解析静态 HTML 是行不通的,本文将详细介绍如何使用 Selenium 获取动态 JSON 数据,涵盖从基本原理到实际代码示例的完整流程。
理解动态 JSON 加载的机制
我们需要明白为什么直接获取 HTML 无法得到动态 JSON,当浏览器访问一个现代网页时:
- 初始 HTML 加载:服务器返回一个基础的 HTML 文档。
- JavaScript 执行:浏览器加载并执行 HTML 中引用的 JavaScript 文件。
- AJAX/Fetch 请求:JavaScript 代码会向服务器发送异步请求(通常是 AJAX 或 Fetch API)来获取额外的数据,这些数据往往以 JSON 格式返回。
- DOM 更新:获取到的 JSON 数据会被 JavaScript 解析,并动态更新到页面的 DOM 结构中,或者直接用于某些交互逻辑。
Selenium 虽然可以模拟浏览器行为,但它默认获取的是初始加载完成的 HTML,JSON 数据是通过异步请求获取的,那么我们需要等待这个请求完成,并想办法捕获这个请求的响应,而不是等待整个页面的所有视觉效果都完成。
获取动态 JSON 的核心思路
使用 Selenium 获取动态 JSON 数据,主要有以下几种核心思路,其中第一种(直接监听网络请求)通常是最高效和推荐的:
-
监听并捕获网络请求(推荐):
- 原理:在 Selenium 加载页面时,监听浏览器发出的网络请求,当检测到目标 URL(即返回 JSON 数据的那个 API 接口)的请求完成时,直接获取其响应内容。
- 优点:无需等待 JavaScript 渲染到 DOM,数据获取速度快,且不受页面元素变化影响,直接获取原始数据。
- 适用场景:JSON 数据是通过明确的 URL(通常是 AJAX/Fetch 请求)获取的。
-
等待元素出现后,从 DOM 中提取 JSON 字符串:
- 原理:JavaScript 将 JSON 数据解析后,以某种形式(如
<script>标签的文本内容、<div>的data-*属性、或直接渲染为可见文本)嵌入到了 DOM 中,那么我们可以 Selenium 的等待机制,等待该元素出现,然后提取其中的 JSON 字符串并解析。 - 优点:适用于那些将数据直接嵌入 DOM 的页面。
- 缺点:依赖 DOM 结构,如果页面重构,代码可能失效;需要额外的解析步骤;可能等待时间较长。
- 原理:JavaScript 将 JSON 数据解析后,以某种形式(如
-
执行 JavaScript 代码获取数据(谨慎使用):
- 原理:JSON 数据已经被 JavaScript 加载到某个全局变量中,我们可以通过 Selenium 的
execute_script()方法直接执行 JavaScript 代码来获取这个变量的值。 - 优点:直接,如果数据在全局变量中,获取非常快。
- 缺点:高度依赖页面的 JavaScript 实现,可维护性差;无法直接获取未暴露到全局变量的数据。
- 原理:JSON 数据已经被 JavaScript 加载到某个全局变量中,我们可以通过 Selenium 的
实践方法详解与代码示例
监听并捕获网络请求(以 Chrome DevTools Protocol 为例)
这是最强大、最可靠的方法,Selenium 4+ 版本对 Chrome DevTools Protocol (CDP) 提供了良好支持,我们可以利用它来监听网络请求。
步骤:
- 启动浏览器时,启用 CDP。
- 监听 'Network.responseReceived' 事件,获取每个响应的详细信息。
- 判断响应的 URL 是否是我们目标 JSON 数据的 URL。
- 如果是,则获取该响应的 body(即 JSON 数据)。
- 处理完成后,可以停止监听。
代码示例 (Python):
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import json
# 目标 JSON API 的 URL (请替换为实际的 URL)
TARGET_JSON_URL = "https://example.com/api/data" # 示例URL
def get_json_via_network_monitor(url):
# 设置 Chrome 的 desired capabilities 以启用网络监控
caps = DesiredCapabilities.CHROME
caps["goog:loggingPrefs"] = {"performance": "ALL"} # Selenium 4 之前版本可能需要这样设置
# 对于 Selenium 4+,可以直接通过 options 启用性能日志
# options = webdriver.ChromeOptions()
# options.set_capability("goog:loggingPrefs", {"performance": "ALL"})
# driver = webdriver.Chrome(options=options)
driver = webdriver.Chrome(desired_capabilities=caps)
driver.get(url) # 访问包含动态加载 JSON 的页面
# 获取性能日志条目
logs = driver.get_log('performance')
json_data = None
for entry in logs:
log = json.loads(entry['message'])['message']
# 检查是否为网络响应
if log['method'] == 'Network.responseReceived':
response_url = log['params']['response']['url']
# 检查是否是目标 URL
if TARGET_JSON_URL in response_url: # 使用 in 是为了避免 URL 带有查询参数等问题
requestId = log['params']['requestId']
# 获取响应体
try:
# 使用 Network.getResponseBody 获取响应内容
# 注意:Selenium 4+ 推荐使用 CDP directly 或其他方式,get_log 在某些情况下可能无法直接获取 body
# 这里提供一个更通用的思路,实际可能需要结合 CDP 的 execute_cdp_command
# 以下代码块需要根据 Selenium 版本和实际 CDP 命令调整
# 对于 Selenium 4+, 可以尝试:
# body = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': requestId})['body']
# 但更常见的是在 responseReceived 时记录,然后等待 loadingFinished 时获取 body
# 这里简化处理,实际项目中可能需要更健壮的逻辑
print(f"Found target response for URL: {response_url}")
# 由于直接从日志获取 body 比较复杂,这里我们假设可以从某个地方获取到
# 实际应用中,可能需要使用 CDP 的 execute_cdp_command 来精确获取
#
# response = driver.execute_cdp_cmd("Network.getResponseBody", {"requestId": requestId})
# json_data = json.loads(response['body'])
# break
print("注意:此处获取响应体的逻辑需要根据 Selenium 版本和 CDP 命令进一步完善。")
# 作为示例,我们假设已经获取到了 JSON 字符串
json_data = '{"key": "value", "number": 123}' # 模拟获取到的 JSON
break
except Exception as e:
print(f"Error getting response body: {e}")
driver.quit()
return json_data
# 使用示例
# 假设我们访问的页面会通过 TARGET_JSON_URL 加载 JSON
# page_url = "https://example.com/page-that-loads-json"
# json_result = get_json_via_network_monitor(page_url)
# if json_result:
# print("获取到的 JSON 数据:")
# print(json.dumps(json.loads(json_result), indent=2, ensure_ascii=False))
# else:
# print("未找到目标 JSON 数据或获取失败。")
print("请替换 TARGET_JSON_URL 和访问的 page_url 为实际值,并完善获取响应体的 CDP 命令部分。")
重要提示:Selenium 对 CDP 的支持在不断发展,上述代码中获取响应体的部分可能需要根据你使用的 Selenium 版本和浏览器进行微调,Selenium 4+ 提供了 execute_cdp_command 方法,可以更直接地调用 CDP 命令。
等待元素出现后从 DOM 中提取
JSON 数据被渲染为 <script> 标签的内容:
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
import json
driver = webdriver.Chrome()
driver.get("https://example.com/page-with-json-in-script")
try:
# 假设 JSON 数据在一个 id 为 "data-json" 的 script 标签内
script_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "data-json"))
)
json_string = script_element.get_attribute("textContent")
json_data = json.loads(json_string)
print("从 DOM 中获取的 JSON 数据:")
print(json.dumps(json_data, indent=2, ensure_ascii=False))
except Exception as e:
print(f"从 DOM 获取 JSON 失败: {e}")
finally:
driver.quit()
执行 JavaScript 代码获取数据
JSON 数据在全局变量 window.myAppData 中:



还没有评论,来说两句吧...