
本文介绍如何从 html 页面的 `
在现代前端渲染的网站中(尤其是使用 Vue、React 或服务端预渲染的站点),关键数据常以 JavaScript 对象形式直接注入 HTML 的
直接对原始字符串做 str.replace("null", "None") 或 str.replace("false", "False") 极其危险:一旦新闻标题中包含 "null"(如 "Version null release")或 "false"(如 "is false positive"),替换将污染真实业务数据,导致解析失败或逻辑错误。因此,必须采用语义化、上下文感知的提取策略,而非文本层面的模糊替换。
✅ 推荐方案:定位 + 截取 + 安全 JSON 化
核心思路分三步:
- 定位目标 :用 BeautifulSoup 精确查找含 App = 的脚本;
- 提取纯对象字面量内容:跳过 App = 前缀,截取至第一个完整闭合的 }(需处理嵌套);
- 转为标准 JSON 并加载:将 JS 风格字面量(true/false/null)安全映射为 JSON 兼容格式。
以下为健壮、可复用的实现:
立即学习“Java免费学习笔记(深入)”;
import requests
from bs4 import BeautifulSoup
import json
import re
def extract_app_json_from_html(html_content: str) -> dict:
"""
从 HTML 字符串中提取 App 对象并解析为 Python 字典
自动处理嵌套花括号,避免误截断
"""
soup = BeautifulSoup(html_content, 'html.parser')
# 查找包含 'App = ' 的 script 标签(支持多空格/换行)
script_tag = soup.find('script', string=re.compile(r'App\s*=\s*{'))
if not script_tag:
raise ValueError("未找到包含 'App = {' 的 script 标签")
script_text = script_tag.string
# 定位 'App = ' 后的第一个 '{'
start_idx = re.search(r'App\s*=\s*{', script_text).end()
# 手动匹配最外层 {}(处理嵌套)
brace_count = 0
end_idx = -1
for i, char in enumerate(script_text[start_idx:], start=start_idx):
if char == '{':
brace_count += 1
elif char == '}':
brace_count -= 1
if brace_count == 0:
end_idx = i + 1
break
if end_idx == -1:
raise ValueError("未能匹配到闭合的 '}'")
# 提取纯对象字符串(JS 格式)
js_obj_str = script_text[start_idx:end_idx].strip()
# 安全转换 JS 字面量为 JSON:仅替换顶层关键字(非字符串内)
# 使用正则确保只替换独立单词(前后非字母/数字/下划线)
json_compatible = (
js_obj_str
.replace('true', 'true') # JSON 中 true 已兼容,无需改
.replace('false', 'false') # 同理
.replace('null', 'null') # JSON 中 null 已兼容
)
# ⚠️ 注意:标准 JSON 与 JS 在布尔/空值上完全一致(true/false/null),无需替换!
# 上述 replace 是冗余的;真正需处理的是单引号、末尾逗号等 —— 但 json.loads 不支持。
# 因此更稳妥的做法是:用 ast.literal_eval(仅当内容可信时)或调用 JS 引擎(如 PyExecJS)。
# 但实践中,该网站输出的 App 对象实际已是严格 JSON 格式(双引号、无尾逗号),
# 故直接 json.loads 即可 —— 错误通常源于截取不完整(如多截了 `;` 或少截了 `}`)。
try:
return json.loads(json_compatible)
except json.JSONDecodeError as e:
# 提供调试线索
print(f"JSON 解析失败,上下文(前100字符):{json_compatible[:100]!r}")
raise e
# 使用示例
url = "https://polymetalinternational.com/en/investors-and-media/news/press-releases/"
response = requests.get(url, timeout=10)
response.raise_for_status()
app_data = extract_app_json_from_html(response.text)
press_releases = app_data["components"]["press-release"]["items"]
print(f"共获取 {len(press_releases)} 条新闻:")
for item in press_releases[:3]: # 仅打印前3条示意
print(f"• {item['name']} ({item['date']}) → {item['link']}")? 关键注意事项
- 不要盲目替换 null/false:JSON 规范与 JavaScript 在这些字面量上完全一致,json.loads() 原生支持。报错主因是截取范围错误(如未正确匹配嵌套 })或存在非法字符(如单引号、注释)。
- 优先验证数据源格式:打开网页源码,搜索 App =,确认其内容是否已为合法 JSON(双引号包裹字符串、无尾逗号)。多数企业站会直接输出 JSON 字符串。
- 处理编码与异常:务必设置 requests.get(..., timeout=10) 和 response.raise_for_status(),防止网络超时或 HTTP 错误静默失败。
-
反爬提示:目标网站可能校验 User-Agent,建议添加请求头:
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"} response = requests.get(url, headers=headers)
此方法兼顾鲁棒性与简洁性,适用于绝大多数将结构化数据内联于 HTML 的场景,是 Web 数据采集中解析嵌入式 JS 对象的推荐实践。










