因为正则不支持嵌套计数,.*? 遇到第一个 } 就终止,无法区分内外层大括号,导致截断、混入字符串内花括号或注释,提取结果非法。

为什么直接用 re.search(r'{.*?}', text) 会失败
网页里常见的 JSON 块往往嵌在 JavaScript 变量赋值或函数调用中,比如 var data = { "user": { "id": 1, "tags": ["a", "b"] } };。正则的 .*? 是非贪婪匹配,但无法处理大括号嵌套——它会在第一个 } 就停止,导致截断成 { "user": { "id": 1,根本不是合法 JSON。
根本原因:正则表达式本身不支持嵌套计数,无法区分“外层闭合的大括号”和“内层嵌套的大括号”。
常见错误现象:
- 提取结果缺失后半部分,json.loads() 报 JSONDecodeError: Expecting property name enclosed in double quotes
- 匹配到多个碎片(如只拿到某个对象字面量的一小段)
- 混入了注释、字符串里的花括号(如 "text": "{not json}")
用 re.finditer + 手动括号计数提取完整 JSON 对象
这是最可控、无需额外依赖的方案,适用于单个顶层 JSON 对象(即以 { 开头、对应闭合 } 结尾)。
- 先用
re.finditer(r'[{}"]', text)扫描所有花括号和双引号位置,跳过字符串内的括号(靠引号配对状态判断) - 维护一个
brace_count和一个in_string标志位 - 遇到
"切换in_string;仅当not in_string时才更新brace_count - 从第一个
{开始计数,当brace_count回到 0 时,截取起始到当前位置的子串
示例关键逻辑:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
import re
<p>def extract_json_object(text):
stack = []
start = -1
in_string = False
escape = False</p><pre class="brush:python;toolbar:false;">for i, c in enumerate(text):
if escape:
escape = False
continue
if c == '\':
escape = True
continue
if c == '"' and not in_string:
in_string = True
continue
if c == '"' and in_string and not escape:
in_string = False
continue
if not in_string:
if c == '{':
if not stack:
start = i
stack.append('{')
elif c == '}':
if stack:
stack.pop()
if not stack and start != -1:
yield text[start:i+1]
start = -1
用 js2py 或 py_mini_racer 解析 JS 上下文更稳妥
当 JSON 藏在 JSON.parse(...)、eval(...)、或经过变量拼接(如 a + b + '{...}' + c)时,纯正则基本失效。这时需要真正执行/解析 JS 上下文。
-
js2py纯 Python 实现,轻量,能运行简单 JS 表达式并提取变量值:import js2pyctx = js2py.EvalJs()ctx.execute('var data = { "x": 1 };')print(ctx.data.to_dict()) # → {'x': 1} -
py_mini_racer绑定 V8,性能好、兼容性强,但需编译依赖,适合服务端长期运行场景 - 注意:JS 中若含浏览器 API(
document,fetch),js2py会报错,需提前 patch 或过滤掉无关代码
预处理 HTML + 定位 script 标签再提取更高效
多数网页把初始化数据放在 <script></script> 标签里,尤其是 type="application/json" 或紧邻 id="data" 的 script。与其全文扫描,不如先用 BeautifulSoup 锁定范围。
- 优先检查:
soup.find('script', type='application/json')—— 这类内容可直接json.loads(script.string) - 其次搜索:
soup.find('script', string=re.compile(r'datas*=s*{')),再对.string内容做括号计数提取 - 避免对整个
response.text运行正则:HTML 体积大时,re.findall(r'{.*?}', ...)可能 O(n²) 回溯爆炸,卡死或内存溢出
真正麻烦的从来不是“怎么写正则”,而是“怎么确认你拿到的是那个正确的、未被转义、未被截断、且能被 json.loads 消化的 JSON 字符串”。嵌套、引号、反斜杠、Unicode、JS 注释——每个都可能让看似工作的正则在某个页面突然崩掉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










