
本文介绍如何从 html 页面的 `
在现代动态网站中,许多新闻列表、产品数据或配置信息并非通过传统 DOM 元素渲染,而是以 JavaScript 对象字面量(如 App = { ... }; 或 window.DATA = {...};)的形式内联在
以下是一个健壮、通用的解析流程,适用于 App = {...}; 类型的嵌入式数据:
✅ 步骤详解与推荐代码
import requests
from bs4 import BeautifulSoup
import json
import re
def extract_json_from_script(html_content: str, var_name: str = "App") -> dict:
"""
从 HTML 文本中提取指定变量名的 JS 对象,并解析为 Python 字典。
Args:
html_content: 完整 HTML 字符串
var_name: 目标 JS 变量名(默认 'App')
Returns:
解析后的字典对象
Raises:
ValueError: 未找到变量或 JSON 格式无效
"""
soup = BeautifulSoup(html_content, "html.parser")
# 方法1:精确匹配 script 标签中包含 "var_name = {" 的文本节点(推荐)
script_tag = soup.find("script", string=re.compile(rf"{var_name}\s*=\s*\{{"))
if not script_tag:
raise ValueError(f"未找到包含 '{var_name} = {{' 的 script 标签")
script_text = script_tag.string
# 使用正则提取最外层 { } 包裹的 JSON 内容(支持嵌套)
# 注意:此正则可处理多层嵌套花括号,避免被内部 `{` 误截断
match = re.search(rf"{var_name}\s*=\s*(\{{(?:[^{{}}]|(?R))*\}});", script_text, re.DOTALL | re.VERBOSE)
if not match:
# 回退方案:尝试简单匹配到第一个完整闭合的 }
match = re.search(rf"{var_name}\s*=\s*(\{{.*?\}});", script_text, re.DOTALL)
if not match:
raise ValueError(f"无法从 script 中提取有效的 {var_name} JSON 对象")
json_str = match.group(1)
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
raise ValueError(f"JSON 解析失败:{e.msg} (位置: {e.pos})\n上下文: {json_str[max(0, e.pos-30):e.pos+30]}")
# 使用示例
if __name__ == "__main__":
url = "https://polymetalinternational.com/en/investors-and-media/news/press-releases/"
response = requests.get(url, timeout=10)
response.raise_for_status()
try:
app_data = extract_json_from_script(response.text, "App")
# 提取新闻标题列表(路径根据实际 JSON 结构调整)
press_items = app_data.get("components", {}).get("press-release", {}).get("items", [])
print(f"共获取 {len(press_items)} 条新闻:\n")
for i, item in enumerate(press_items[:5], 1): # 仅打印前5条示例
name = item.get("name", "N/A")
date_ts = item.get("date", 0)
link = item.get("link", "")
themes = ", ".join(item.get("theme", []))
print(f"{i}. [{name}] ({themes}) — {date_ts} → {link}")
except Exception as e:
print(f"解析失败:{e}")⚠️ 关键注意事项
- 不要使用 str.replace() 替换 null/false:JS 的 null 和 Python 的 None 语义不同,且 json.loads() 本身不接受 null;但更重要的是,盲目全局替换(如 'null'.replace('null', 'None'))会污染真实字段值(例如 "filename": "null_report.pdf" 会被错误修改)。
- 避免硬编码切片(如 [:-1] 或 split("};")[0]):原始答案中 app_content.split("};")[0] + "}" 在存在多层嵌套或注释时极易失效(如 {"a": {"b": {}}}; 中 }; 出现在内部)。正则递归匹配((?R))或使用 ast.literal_eval() 是更可靠的选择(见下文备选方案)。
- 处理编码与特殊字符:网页返回内容需确保正确解码(response.encoding 或 response.content.decode("utf-8")),尤其当页面含 Unicode(如俄文 \u041f\u0440\u0435\u0441\u0441-\u0440\u0435\u043b\u0438\u0437\u044b)时,json.loads() 原生支持 UTF-8,无需额外解码。
- 异常防御:始终检查 HTTP 状态码、
? 备选方案:使用 ast.literal_eval()(适用于纯 JS 对象字面量)
若确认目标内容是无函数、无 undefined、无注释的纯对象字面量(如 App = {"a": 1, "b": null};),可借助 ast.literal_eval() 安全执行(比 eval() 安全,仅允许基本字面量):
import ast
# 将 JS null/true/false 映射为 Python None/True/False
js_to_py = {"null": "None", "true": "True", "false": "False"}
cleaned = script_text.split("App = ", 1)[1].split(";", 1)[0]
for js, py in js_to_py.items():
cleaned = cleaned.replace(js, py)
try:
data = ast.literal_eval(cleaned) # 安全等价于 json.loads(),但支持 JS 布尔/空值
except (ValueError, SyntaxError) as e:
raise ValueError(f"ast.literal_eval 解析失败: {e}")✅ 推荐主流程使用 re + json.loads()(更标准、可控性强);ast.literal_eval() 作为补充方案,适合 JS 语法极简的场景。
✅ 总结
从网页
遵循此方法,即可稳定解析 Polymetal 等网站的 App 数据,亦可轻松适配其他类似结构(如 window.__INITIAL_STATE__、dataLayer 等)。










