网页内嵌 JSON 数据的提取方法可以概括为四步:先在源代码或 Network 面板定位数据,读取对应的 script 文本或接口响应,使用标准 JSON 解析器转换为对象,最后检查关键字段并与页面内容交叉验证。静态页面可使用浏览器控制台或 requests + BeautifulSoup;数据由 JavaScript 动态加载时,应优先查找 Fetch/XHR 接口,确有必要时再使用 Playwright。

网页内嵌 JSON 数据的提取方法:定位、解析与验证实操教程

网页内嵌 JSON 数据是指网页为了传递结构化信息,将符合 JSON 语法的数据放入 HTML 的 <script> 标签、JSON-LD 标签或前端框架状态容器中。它与普通 JavaScript 代码不同:严格 JSON 只能包含对象、数组、字符串、数字、布尔值和 null,不能包含变量赋值、函数、注释、undefined 或尾随逗号。

一、先判断 JSON 数据位于哪里

步骤 1:检查网页源代码

  1. 打开目标页面,按 Ctrl+U 查看服务器返回的原始 HTML。
  2. Ctrl+F 搜索页面上的唯一内容,例如商品 ID、完整标题、价格或接口字段名。
  3. 继续搜索 application/jsonapplication/ld+json__NEXT_DATA____NUXT__ 等标记。
  4. 如果目标字段存在于源代码中,可直接使用 JavaScript 或 Python 解析,无须启动浏览器自动化工具。

步骤 2:检查渲染后的 DOM

F12 打开开发者工具,在 Elements 面板中搜索目标字段。Elements 显示的是浏览器执行 JavaScript 后的 DOM,可能与 Ctrl+U 看到的原始 HTML 不同。若数据只出现在 Elements 中,说明它可能由前端脚本动态插入。

步骤 3:检查 Fetch/XHR 接口

  1. 进入 Network 面板,选择 Fetch/XHR 分类。
  2. 清空现有记录后刷新页面,必要时执行翻页、搜索或展开详情等操作。
  3. 逐个检查请求的 Preview 和 Response,搜索目标字段。
  4. 确认请求 URL、请求方法、查询参数、响应格式,以及是否依赖 Cookie 或授权信息。

如果接口响应已经是 JSON,直接处理接口通常比解析渲染后的 HTML更稳定。但调用接口仍需遵守网站服务条款、访问频率限制和适用法律,不应绕过登录、验证码或其他访问控制。

二、使用开发者工具手动提取 JSON

步骤 1:找到目标 script 标签

在 Elements 面板中重点检查以下形式:

<script type="application/json" id="page-data">
  {"id": 123, "name": "示例商品"}
</script>

<script type="application/ld+json">
  {"@context": "https://schema.org", "@type": "Product"}
</script>

先确认标签内部以 {[ 开始,并且内容不含变量赋值和函数调用。类似 window.__STATE__ = {...}; 的文本属于 JavaScript 赋值语句,不能把整段内容直接交给 JSON.parse

步骤 2:复制原始文本

只复制 script 标签内部的文本,不要包含开始标签、结束标签或开发者工具生成的省略符号。也可以在控制台读取文本,以避免手动选取不完整:

copy(document.querySelector('#page-data').textContent)

步骤 3:格式化并验证

将文本交给浏览器的 JSON.parse、Python 的 json.loads 或可信的本地 JSON 工具。涉及账号、订单或个人信息时,不要把原始数据提交给未知的在线格式化网站。

三、使用 JavaScript 提取 script 中的 JSON

场景 1:提取单个 application/json 标签

const node = document.querySelector('script[type="application/json"]');

if (!node) {
  throw new Error('未找到 application/json 脚本');
}

const raw = node.textContent.trim();
const data = JSON.parse(raw);
console.log(data);

textContent 用于读取标签中的原始文本。不要使用 innerHTML,因为它可能返回 HTML 序列化后的内容,增加实体编码和转义处理的复杂度。

场景 2:页面存在多个 JSON 标签

const results = [...document.querySelectorAll('script[type="application/json"]')]
  .map((node, index) => {
    try {
      return { index, value: JSON.parse(node.textContent.trim()) };
    } catch (error) {
      console.warn(`第 ${index + 1} 个脚本解析失败`, error);
      return null;
    }
  })
  .filter(Boolean);

const target = results.find(({ value }) =>
  value && typeof value === 'object' && value.productId
);

console.log(target?.value);

筛选时应依据稳定的业务字段、标签 ID 或数据类型,不要依赖“第一个 script”或固定下标,因为页面模板调整后脚本顺序可能改变。

场景 3:提取 JSON-LD

const jsonLdItems = [...document.querySelectorAll('script[type="application/ld+json"]')]
  .flatMap(node => {
    try {
      const value = JSON.parse(node.textContent.trim());
      return Array.isArray(value) ? value : [value];
    } catch (error) {
      console.warn('JSON-LD 解析失败', error);
      return [];
    }
  });

const products = jsonLdItems.filter(item =>
  item && item['@type'] === 'Product'
);

console.log(products);

JSON-LD 还可能使用 @graph 包装多个实体。遇到这种结构时,需要继续遍历 @graph,再按 @typenameurlsku 筛选。

场景 4:提取 Next.js 页面状态

const node = document.querySelector('#__NEXT_DATA__');
if (!node) {
  throw new Error('未找到 __NEXT_DATA__');
}

const nextData = JSON.parse(node.textContent);
const pageProps = nextData?.props?.pageProps;
console.dir(pageProps);

props.pageProps 是常见位置,但并非固定标准。应先查看完整对象层级,再确定业务字段路径。

四、使用 Python 批量提取网页内嵌 JSON

步骤 1:安装依赖

python -m pip install requests beautifulsoup4

步骤 2:请求网页并解析目标标签

import json
import requests
from bs4 import BeautifulSoup

url = "https://example.com/page"
headers = {
    "User-Agent": "Mozilla/5.0"
}

response = requests.get(url, headers=headers, timeout=20)
response.raise_for_status()

# requests 通常会根据响应头判断编码;仅在乱码时再人工核对。
if not response.encoding or response.encoding.lower() == "iso-8859-1":
    response.encoding = response.apparent_encoding

soup = BeautifulSoup(response.text, "html.parser")
node = soup.select_one('script[type="application/json"]')

if node is None:
    raise ValueError("未找到目标 JSON script")

raw = node.get_text(strip=True)
data = json.loads(raw)
print(data)

raise_for_status() 可及时发现 403、404 和服务器错误,timeout 可防止请求长期阻塞。批量任务还应增加有限次数重试、请求间隔、缓存和错误日志。

步骤 3:提取所有 JSON-LD 并展开 @graph

import json

entities = []

for node in soup.select('script[type="application/ld+json"]'):
    raw = node.get_text(strip=True)
    if not raw:
        continue

    try:
        parsed = json.loads(raw)
    except json.JSONDecodeError as exc:
        print(f"JSON-LD 解析失败: {exc}")
        continue

    values = parsed if isinstance(parsed, list) else [parsed]
    for value in values:
        if isinstance(value, dict) and isinstance(value.get("@graph"), list):
            entities.extend(value["@graph"])
        else:
            entities.append(value)

products = [
    item for item in entities
    if isinstance(item, dict) and item.get("@type") == "Product"
]

for product in products:
    print(product.get("name"), product.get("offers"))

步骤 4:提取 Next.js 数据

node = soup.select_one('#__NEXT_DATA__')
if node is None:
    raise ValueError("未找到 __NEXT_DATA__")

next_data = json.loads(node.get_text(strip=True))
page_props = next_data.get("props", {}).get("pageProps", {})
print(page_props)

步骤 5:按字段筛选多个候选脚本

candidates = []

for node in soup.select('script[type="application/json"]'):
    raw = node.get_text(strip=True)
    if not raw:
        continue

    try:
        value = json.loads(raw)
    except json.JSONDecodeError:
        continue

    if isinstance(value, dict) and "productId" in value:
        candidates.append(value)

print(candidates)

限定 script[type="application/json"] 通常比扫描所有 script 更可靠。只有无法确定标签类型时,才扩大搜索范围,并为每个候选项记录标签 ID、类型和解析错误。

五、处理动态页面、Next.js 和 Nuxt 数据

场景 1:requests 返回的 HTML 没有目标字段

requests 不执行 JavaScript。如果浏览器中能看到数据,但响应 HTML 中没有,应先检查 Network 面板。找到 JSON 接口后,可在获得授权且符合使用规则的前提下复现必要的请求参数。

场景 2:必须执行 JavaScript 才能得到数据

无法从初始 HTML 或独立接口取得数据时,可以使用 Playwright读取渲染后的 DOM:

from playwright.sync_api import sync_playwright
import json

url = "https://example.com/page"
selector = 'script[type="application/json"]'

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(url, wait_until="domcontentloaded", timeout=30000)
    page.locator(selector).first.wait_for(state="attached", timeout=15000)

    raw = page.locator(selector).first.text_content()
    if not raw:
        raise ValueError("目标脚本为空")

    data = json.loads(raw)
    print(data)
    browser.close()

相比始终等待 networkidle,等待明确的 script 或业务元素通常更稳定,因为部分网站会持续发送统计和长连接请求。目标来自接口时,也可以监听满足 URL 或内容类型条件的响应。

场景 3:Nuxt 状态不是严格 JSON

Nuxt 页面可能把状态放在 window.__NUXT__=... 或框架生成的脚本中,其中可能包含 JavaScript 表达式,不能默认使用 json.loads。优先寻找 Nuxt 提供的标准 JSON payload、页面接口或可直接解析的数据脚本。不要对不可信页面内容使用 evalexec 或同类动态执行方式。

六、非标准 JSON 与解析异常的处理方法

1. 文本前后存在赋值语句

例如 window.__STATE__ = {...};。应先确认对象主体本身是否为严格 JSON,并根据稳定的前缀边界提取。不要简单使用贪婪正则从第一个 { 截取到最后一个 },因为字符串内部、后续脚本或嵌套结构都可能导致误截取。

六、非标准 JSON 与解析异常的处理方法

2. HTML 实体与 JSON 转义混在一起

&quot; 属于 HTML 实体,\" 则可能是 JSON 字符串内部的合法转义。应先保留原始文本并确认编码层级。不要全局替换反斜杠或引号,否则可能破坏 URL、换行和字符串边界。

3. 出现注释、单引号或尾随逗号

这些内容说明文本不是严格 JSON。优先寻找同页的标准 JSON 标签或网络接口;如果必须处理非标准格式,应使用与实际语法匹配的解析器,并限定可信输入,而不是连续执行字符串替换。

4. 中文乱码

依次检查 HTTP Content-Type 中的 charset、HTML 的 meta charset 和实际字节编码。response.apparent_encoding 只能作为辅助判断,不应在所有网站上无条件覆盖服务器声明的编码。

5. 返回 403、429 或空页面

这通常与权限、登录状态、访问频率、地区限制或自动化访问策略有关。应降低频率、缓存结果、设置明确的 User-Agent,并优先使用官方 API 或获得授权的数据源。不要尝试绕过验证码、鉴权或访问限制。

七、验证提取结果是否可靠

步骤 1:验证 JSON 语法

确保原始文本可以被 JSON.parsejson.loads 完整解析。解析成功只代表语法有效,不代表数据正确或完整。

步骤 2:验证关键字段和类型

required_fields = ["id", "name", "url"]
missing = [
    field for field in required_fields
    if field not in data or data[field] in (None, "")
]

if missing:
    raise ValueError(f"缺少关键字段: {missing}")

if not isinstance(data["id"], (str, int)):
    raise TypeError("id 类型不符合预期")

字段验证应区分“字段不存在”和“字段值为 0 或 false”。因此不要在所有场景中只使用 if not data.get(field) 判断缺失,否则合法的零值可能被误报。

步骤 3:与页面可见内容交叉核对

随机抽取记录,对比 JSON 与页面展示的名称、价格、日期、库存或 ID。若结果不一致,应检查缓存、地区版本、登录状态、A/B 测试、货币单位和前端二次计算。

步骤 4:验证数量、唯一性和完整性

  • 统计总记录数、成功解析数和失败数。
  • 计算唯一 ID 数量,检查重复记录。
  • 统计关键字段的空值比例。
  • 检查分页、懒加载或接口返回的总数是否已经全部覆盖。
  • 对同一 URL 重复采样,确认关键字段是否稳定。

步骤 5:保存可追溯信息

批量提取时建议记录页面 URL、抓取时间、HTTP 状态码、数据来源、定位器、响应摘要、解析异常和最终记录数。页面结构发生变化后,这些信息可以帮助判断问题来自请求、定位规则还是字段结构。

八、根据场景选择提取方案

  • 少量一次性提取:使用开发者工具定位、复制并格式化。
  • 静态 HTML 批量处理:使用 requests + BeautifulSoup + json
  • JSON-LD:选择 script[type="application/ld+json"],展开数组或 @graph 后按 @type 筛选。
  • Next.js:先检查 #__NEXT_DATA__,再查看页面接口和其他框架 payload。
  • Nuxt:先判断状态脚本是否为严格 JSON;不是时优先查找标准 payload 或接口。
  • 动态加载数据:优先分析 Fetch/XHR,确实依赖浏览器执行环境时再使用 Playwright。
  • 长期稳定采集:优先选择官方 API、公开数据文件或获得授权的接口。

九、总结

网页内嵌 JSON 数据的提取,关键不是直接搜索大括号,而是先确定数据来源和语法类型。对于严格 JSON,可读取 script 的 textContent 并交给标准解析器;对于动态数据,应先查找 Fetch/XHR;对于 Next.js、Nuxt 等框架数据,则要根据实际 payload 结构选择解析方式。提取完成后,还应验证字段、类型、数量、唯一性以及与页面展示的一致性,才能确认得到的是可用数据,而不只是可解析文本。

常见问题

网页源代码中没有 JSON,还能提取吗?

可以。先在开发者工具的 Network 面板筛选 Fetch/XHR,刷新页面并检查接口响应。如果数据必须在浏览器执行 JavaScript 后才生成,再使用 Playwright 等浏览器自动化工具读取渲染后的 DOM或监听响应。

如何提取网页中的 JSON-LD?

选择所有 script[type="application/ld+json"] 标签,读取 textContent 后使用 JSON.parse 或 json.loads 解析。结果可能是对象、数组或包含 @graph 的对象,应展开后根据 @type、name、url、sku 等字段筛选。

为什么复制出来的内容不能直接 JSON.parse?

常见原因是复制了 script 标签本身,或者文本包含变量赋值、注释、单引号、函数、尾随逗号和多余分号。先确认内容是否为严格 JSON,再根据真实格式处理;不要用全局替换引号或反斜杠的方式强行修复。

requests 获取不到浏览器中显示的数据怎么办?

requests 只下载服务器返回的初始响应,不会执行 JavaScript。应先检查目标数据是否来自 Fetch/XHR 接口;如果数据依赖浏览器执行环境,再使用合法的授权信息和 Playwright,并等待明确的数据节点或接口响应。

可以用正则表达式直接提取大括号中的 JSON 吗?

不建议把正则作为通用方案。JSON可能包含嵌套对象、数组、转义引号和字符串中的大括号,简单正则容易截断或跨脚本匹配。优先读取明确的 script 节点或接口响应,再使用标准 JSON 解析器。

提取网页 JSON 时需要注意哪些合规问题?

应遵守网站服务条款、robots 规则、版权和个人信息保护要求,控制访问频率,不绕过登录、验证码、鉴权或访问限制。长期或商业用途应优先使用官方 API 或获得授权的数据来源。

如何判断提取结果是否可靠?

至少检查 JSON 是否能完整解析、关键字段是否存在且类型正确、记录数量和唯一 ID 是否合理,并随机与页面可见内容交叉核对。批量任务还应记录失败数、空值比例、抓取时间和原始响应摘要。

总结

网页内嵌 JSON 数据是放在 HTML 脚本标签、结构化数据标签或前端框架状态容器中的结构化数据。实操时先用源代码、Elements 和 Network 面板确定数据来源,再选择 JavaScript、Python 或 Playwright提取;解析时使用标准 JSON 解析器,避免依赖固定脚本顺序、简单正则和无条件字符串替换;最后通过语法、字段类型、记录数量、唯一性及页面内容完成验证。