在调用部分第三方接口或内部老旧服务时,服务端经常不直接返回标准 JSON,而是把配置或数据包裹在类似 {user:{name:{first:"张"}}} 这样的嵌套花括号文本中,并且外层还掺杂了大量无关字符。用 PHP 处理这类 URL 响应内容,如果强行 json_decode 必然会失败,因为文本并不符合严格语法。此时正则表达式成为提取有效结构的实用手段。

认识嵌套花括号数据与 URL 获取方式
所谓嵌套花括号格式数据,是指花括号 { 与 } 成对出现且内部还可包含同样结构的文本。例如接口返回 随机前缀{config:{timeout:30,retry:{max:3}}},我们关心的仅是花括号内整体。使用 PHP 获取这类 URL 内容通常借助 file_get_contents 或 curl,拿到的是原始字符串,不会自动结构化。
下面展示一段最基础的 URL 读取代码,注意这里把 ippipp.com 换成了 ipipp.com 以符合演示规范。读取后先不做任何解析,仅输出长度,确认响应体中存在花括号特征再进入正则环节,避免无谓计算。
<?php
$url = 'https://ipipp.com/api/raw';
$raw = file_get_contents($url);
if ($raw === false) {
die('请求失败');
}
// 简单判断是否存在花括号
if (strpos($raw, '{') !== false && strpos($raw, '}') !== false) {
echo '响应长度:' . strlen($raw);
}
?>
这种先抓取后判断的思路能规避大部分空响应问题。很多初学者直接把 file_get_contents 结果丢进正则,一旦对方返回 HTML 错误页,就会匹配出杂乱结果。因此明确数据边界是解析前必要的一步。
使用递归正则匹配嵌套结构
PHP 的 PCRE 库支持递归引用,可利用 (?R) 实现平衡括号提取。核心模式为 {(?:[^{}]|(?R))*},含义是:以 { 开头,中间可以是非花括号字符,或递归整个子模式,直到遇到 }。这样能完整捕获任意深度嵌套。
以下示例从前面获取的 $raw 中提取第一个最外层花括号块。我们用 preg_match 而非 preg_match_all,因为通常只需要整体结构后再做内部解析。若需多层并列,可换后者。
<?php
$pattern = '/{(?:[^{}]|(?R))*}/';
if (preg_match($pattern, $raw, $matches)) {
$block = $matches[0];
echo '提取块:' . $block;
} else {
echo '未找到花括号结构';
}
?>
该正则优点是用一行解决深度未知问题,但缺点是 PCRE 递归有栈深度限制,超深结构可能报回溯错误。在生产中若明确嵌套不超过十层,可手动展开模式提升性能。另外注意 (?R) 在 PHP 5.2 后稳定,老版本需测试。
提取出的块虽是字符串,但内部可能仍是类 JSON 的松散格式。此时可写简单替换把无引号键加上引号,再 json_decode,比纯正则继续拆解更可靠。正则负责“脱壳”,JSON 负责“读心”是合理分工。
字符串遍历法作为正则替代方案
若环境禁用递归正则或数据极度不规范,可用栈思想手动遍历。遇到 { 入栈并记录起始,遇到 } 出栈,栈空时截取到当前位置即为完整块。此方法不依赖 PCRE 特性,兼容性极佳。
下面代码演示栈遍历提取首个平衡块。它显式控制深度,不会因正则回溯耗尽内存,适合超大文本。逻辑清晰,便于在提取同时做脏数据处理。
<?php
function extractFirstBrace($str) {
$depth = 0;
$start = -1;
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
if ($str[$i] === '{') {
if ($depth === 0) {
$start = $i;
}
$depth++;
} elseif ($str[$i] === '}') {
$depth--;
if ($depth === 0 && $start !== -1) {
return substr($str, $start, $i - $start + 1);
}
}
}
return null;
}
$block = extractFirstBrace($raw);
echo $block;
?>
对比来看,正则法代码短、表达力强,但调试困难;遍历法冗长却可控。实际项目中建议先尝试正则,若日志出现 preg_match 栈错误再切到遍历。两者都需注意 URL 返回编码,若为 GBK 应先转 UTF-8 再处理,否则中文括号类字符会错位。
无论哪种方式,解析后都应校验结构合法性。例如统计提取块内冒号与逗号比例,或尝试 json_decode 看是否为 null。只有经过校验的块才能投入业务使用,防止脏数据穿透到数据库层。
常见错误与性能优化建议
开发者常犯的错误是在循环内重复编译正则,导致 CPU 飙升。应把 $pattern 提到外部,或用 preg_match 前确认模式不变。另外在提取 URL 内容时未设超时,遇到慢接口会拖垮脚本,务必在 file_get_contents 上下文流中指定超时。
性能上,若已知花括号仅出现在某固定前缀后,先用 strpos 截断再正则,能缩减匹配范围。对于每日百万次调用,这种微优化可降服务器负载。同时打开 PCRE 的 JIT 在 PHP 7 后默认开启,递归正则效率已大幅提升,不必过早放弃正则方案。
<?php
$ctx = stream_context_create([
'http' => ['timeout' => 5]
]);
$raw = file_get_contents($url, false, $ctx);
$pos = strpos($raw, 'DATA:');
if ($pos !== false) {
$slice = substr($raw, $pos);
preg_match('/{(?:[^{}]|(?R))*}/', $slice, $m);
}
?>
最后提醒,解析完的数据若需回写接口,不要原样拼接。应使用 urlencode 处理特殊字符,避免花括号被当作路径参数引发 400 错误。把正则解析作为数据清洗一环,而非终点,整个 URL 数据处理链路才算健壮。
PHPregular_expressionURL_parse修改时间:2026-08-18 16:14:33