1

我正在尝试在 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 帮助我进行一些优化吗?

问候。

帕特里克

4

4 回答 4

3

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

(\S+.*\S+)

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

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

([^<]*)

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

请参阅: 灾难性回溯提防贪婪

于 2012-07-04T07:19:48.917 回答
1

即使你有大约 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";

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

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

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

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

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

于 2012-07-04T08:28:08.690 回答
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程序并帮助了我很多。

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

于 2012-07-05T00:11:30.377 回答
0

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

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

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

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

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

于 2012-07-04T03:30:42.870 回答