【问题标题】:PHP REG EXP backtrack issuePHP REGEX 回溯问题
【发布时间】:2012-07-04 12:32:09
【问题描述】:

我正在尝试在 preg_match_all 中的 PHP 中使用这个 reg exp

/\d+ (?:<[^>]+>)(?:<[^>]+>)(\S+.*\S+)(?:<[^>]+>)\s*(\S+) (?:L|R)\s*\w* \w*\s*(?:\w+\s*){14}(\d+)\s*(\d)\s*(\d*\xA0*\d{3}\xA0*\d{3})/is

有一些数据样本:

38 <A NAME="Philip McRae"><A HREF="xtrastats.html#Philip McRae">Philip McRae</A>            C L  OK    58 71 69 49 33 89 71 45 48 69 50 35 32 61   21   3    787 000
43 <A NAME="Alexander Nikulin"><A HREF="xtrastats.html#Alexander Nikulin">Alexander Nikulin</A>       C L  OK    41 68 71 40 28 90 67 29 31 60 31 37 34 50   26   0      0 000 <a href="http://www.hockeydb.com/ihdb/stats/pdisplay.php?pid=78680" target="_blank">HDB</a>
20 <A NAME="Christian Hanson"><A HREF="xtrastats.html#Christian Hanson">Christian Hanson</A>        C R  OK    57 72 71 54 33 79 70 42 45 71 46 40 36 60   25   1    875 000 <a href="http://www.hockeydb.com/ihdb/stats/pdisplay.php?pid=73824" target="_blank">HDB</a>

我得到了大约 1500 行。

我需要匹配这个:

Philip McRae, C, 21, 3, 787 000 (Name, Position, Age, Contract Lenght, Salary)

每次我运行我的代码时,我都会收到一个致命错误:Maximum execution time of 30 seconds exceeded 错误。

经过一番搜索,我在脚本顶部添加了这一行,但这并不能解决我的问题

ini_set("pcre.backtrack_limit",10000000);

任何人都可以帮助我使用这个 reg exp 进行一些优化吗?

问候。

帕特里克

【问题讨论】:

  • 我们需要知道你想让表达式做什么来帮助你。
  • 在我的问题中添加了数据样本。
  • 我在数据样本中没有得到与该正则表达式的任何匹配。您是否使用较少的数据进行了测试?
  • @jared 你在 Windows 或 Linux 上测试它吗?在 Windows 上,将\xA0 替换为 .(dot)
  • 使用正则表达式来匹配这种复杂的东西对我来说总体来说很糟糕。为什么不把它分成几个更小的正则表达式并使用状态机呢?没有构建正则表达式来解析 HTML 的标签结构。还有,用 DOMDocument 来解析呢?

标签: php regex optimization pcre backtracking


【解决方案1】:

@hakre 和 @bodhizero

根据您的意见和帮助,我已将正则表达式修改为:

\d{1,2}+ (?:<[^>]++>)(?:<[^>]++>)([^<]*+)(?:<[^>]++>)\s*+(\S{1,2}+) (?:L|R)\s*+\w*+ \w*+\s*+(?:\w++\s*+){14}(\d{1,2}+)\s*+(\d)\s*(\d*+.*?\d{0,3}+.*?\d{3}+)(?: <[^>]++>[^<]*+<[^>]++>)*?

结果:2秒左右解析整个文件!!!

我使用Regexbuddy 程序并帮助了我很多。

我希望我能给出两个答案,但我不能

【讨论】:

    【解决方案2】:

    我不会尝试重写您的正则表达式,因为我们没有要求,但这里的主要问题是您的姓名组:

    (\S+.*\S+)
    

    .* 很贪心。这意味着它将消耗尽可能多的内容,包括您期望表达式的其余部分匹配的内容,并且不会止步于此。由于您有 /s 模式修饰符,因此点也将匹配换行符,从而允许 .* 在尝试匹配 \S 并开始其漫长的回溯之旅之前使用整个文件。

    一种解决方案是使用? 使.* 变得懒惰,即.*?,但由于您知道名称包含在元素中,您可以简单地为整个组使用否定字符类:

    ([^<]*)
    

    这应该可以解决您的问题,但您可能不想在这种情况下使用/s 模式修饰符,或者您至少应该在模​​式中添加行首和行尾锚点。您还应该尽量限制使用*

    请看: Catastrophic backtrackingWatch out for greediness

    【讨论】:

    • 解决我的问题,看我的回答。非常感谢。
    • 我自己说得再好不过了!我最喜欢的三个经验法则:当你的意思是+时,不要使用*;不要使用点 (.) 作为您的第一选择;如果您确实使用点,请不要使用单行/DOTALL 模式。
    【解决方案3】:

    即使你有大约 1 500 行,你想解决的问题是每一行。

    如果您能够逐行处理输入,那么您已经大大减少了问题。

    $file = new SplFileObject($path);
    foreach ($file as $i => $line) {
        printf("#%'0-4d: %s\n", $i, $line);
    }
    

    这只是一个例子,当然,正则表达式引擎本身可以用它的多行修饰符(m)做类似的事情。但是,如果您执行上述 foreach,您可以直接 break 使用第一行进行测试:

    foreach ($file as $i => $line) {
        printf("#%'0-4d: %s\n", $i, $line);
        $pattern = '(^\d++ <A NAME="([^"]++)"><A HREF="xtrastats.html#Philip McRae">Philip McRae</A>            C L  OK    58 71 69 49 33 89 71 45 48 69 50 35 32 61   21   3    787 000)$';
        $r = preg_match($pattern, $line, $matches);
        if (FALSE === $r) {
            throw new Exception(sprintf("Regex failed (%d)", preg_last_error());
        }
        if (!$r) {
            throw new Exception(sprintf("Pattern does not match."));
        }
        var_dump($matches);
        if ($i > 0) break; # exit foreach after X lines.
    }
    echo "Done.\n";
    

    正如您在此示例中所见,该模式尚不完整,但您可以逐步完成整行替换。

    它还使用锚点作为字符串的开头 (^) 和字符串的结尾 ($)。

    它还使用所有格量词 (+),因此如果它们不匹配,则不会发生回溯(类似于原子分组,但更容易编写)。

    继续逐步改进您的正则表达式模式。如果正则表达式未编译,则会引发异常。以及当一行不匹配时。

    你应该在一段时间后完成你的工作,改进错误处理并从长远来看创建一些稳定高效的代码。

    【讨论】:

    • 解决我的问题,看我的回答。非常感谢。
    【解决方案4】:

    您需要限制用于正则表达式匹配的数据量,或更改set_time_limitmemory_limit 限制。

    preg_match_all() 占用大量 CPU 资源,根据服务器 CPU 的强大程度,它可能会导致执行时间和内存问题。

    一种解决方案是将其添加到代码的顶部:

    set_time_limit(0);
    ini_set('memory_limit', '128M');
    

    您的另一个选项是将脚本限制为每次页面加载时更少的 preg_match_all() 匹配。

    【讨论】:

    • preg_* 方法只是 PCRE 正则表达式库的一个外观,只要正则表达式写得相当好,它就可以正常工作。如果您的代码在输入 preg_* 方法之一时陷入困境,那么正则表达式很可能是罪魁祸首。如果你在正则表达式中找不到它的原因,请找人帮你看看,因为它在那里。但不要只是投入更多资源。
    猜你喜欢
    • 2017-02-24
    • 2020-12-20
    • 2011-10-15
    • 2019-07-06
    • 1970-01-01
    • 2021-02-25
    • 2015-06-21
    • 2012-03-03
    相关资源
    最近更新 更多