
本文介绍如何从 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'Apps*=s*{')) if not script_tag: raise ValueError("未找到包含'App = {'的 script 标签") script_text = script_tag.string # 定位 'App =' 后的第一个 '{' start_idx = re.search(r'Apps*=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 对象的推荐实践。






























