正则表达需要很长时间

时间:2011-09-18 22:33:35

标签: regex perl

我有以下脚本,它抓取一个网页,然后做一个正则表达式来查找我正在寻找的项目:

use warnings;
use strict;
use LWP::Simple;

my $content=get('http://mytempscripts.com/2011/09/temporary-post.html') or die $!;
$content=~s/\n//g;
$content=~s/ / /g;
$content=~/<b>this is a temp post<\/b><br \/><br \/>(.*?)<div style='clear: both;'><\/div>/;
my $temp=$1;


while($temp=~/((.*?)([0-9]{1,})(.*?)\s+(.*?)([0-9]{1,})(.*?)\s+(.*?)([0-9]    {1,})(.*?)\s+)/g){
print "found a match\n";
}

这样可行,但需要很长时间。当我将正则表达式缩短到以下时,我得到的结果不到一秒钟。为什么我的原始正则表达式需要这么长时间?我该如何纠正?

while($temp=~/((.*?)([0-9]{1,})(.*?)\s+(.*?)([0-9]{1,})(.*?)\s+(.*?)([0-9]    {1,})(.*?)\s+)/g){
print "found a match\n";
}

2 个答案:

答案 0 :(得分:1)

正则表达式类似于Perl中的sort函数。你认为这很简单,因为它只是一个命令,但最后,它使用了大量的处理能力来完成这项工作。

您可以采取一些措施来帮助解决问题:

  1. 尽可能简化语法。
  2. 如果您在循环中使用该正则表达式,则使用qr //预编译正则表达式模式。这将阻止Perl每次循环编译你的正则表达式。
  3. 尽量避免必须执行backtracking的正则表达式语法。这通常最终成为最常见的匹配模式(例如.*)。
  4. 这个可怜的事实是,在Perl写了几十年后,我从来没有掌握正则表达式解析的深层秘密。我已多次尝试去理解它,但这通常意味着在网上进行研究,而且......好吧......我被网络上的所有其他东西分散了注意力。

    并且,并不是那么困难,任何半智商的智商为240,并且对虐待狂的偏爱应该很容易就能找到它。


      @David W。:我想我对回溯感到困惑。我不得不多次阅读你的链接,但仍然不太了解如何在我的情况下实现它(或者,不实现它)。 - user522962

    我们举一个简单的例子:

    my $string = 'foobarfubar';
    $string =~ /foo.*bar.*(.+)/;
    my $result = $1;
    

    $result会是什么?它将是r。你看这是怎么回事?让我们看看会发生什么。

    最初,正则表达式被分解为标记,并使用第一个标记foo.*。这实际上匹配整个字符串:

    "foobarfubar" =~ /foo.*/
    

    但是,如果第一个正则表达式标记捕获整个字符串,则正则表达式的其余部分将失败。因此,正则表达式匹配算法必须回溯:

    "foobarfubar" =~ /foo.*/    #/bar.*/ doesn't match
    "foobarfuba" =~ /foo.*/     #/bar.*/ doesn't match.
    "foobarfub" =~ /foo.*/      #/bar.*/ doesn't match.
    "foobarfu" =~ /foo.*/       #/bar.*/ doesn't match.
    "foobarf" =~ /foo.*/        #/bar.*/ doesn't match.
    "foobar" =~ /foo.*/         #/bar.*/ doesn't match.
     ...
    "foo" =~ /foo.*/            #Now /bar.*/ can match!
    

    现在,字符串的其余部分也是如此:

    "foobarfubar" =~ /foo.*bar.*/  #But the final /.+/ doesn't match
    "foobarfuba"  =~ /foo.*bar.*/  #And the final /.+/ can match the "r"!
    

    .*.+表达式往往会发生回溯,因为它们太松散了。我看到你正在使用非贪婪的比赛,这可能有所帮助,但如果你不小心,它仍然是一个问题 - 特别是如果你有非常长而复杂的正则表达式。

    我希望这有助于解释回溯。

    您遇到的问题不是您的程序不起作用,而是需要很长很长时间。

    我希望我的答案的一般要点是正则表达式解析不像Perl那样简单。我可以在程序中看到命令sort @foo;,但忘记如果@foo包含大约一百万个条目,则可能需要一段时间。理论上,Perl可以使用冒泡排序,因此算法是O 2 。我希望Perl实际上使用更有效的算法,我的实际时间将更接近O * log(O)。但是,所有这一切都被我简单的一行声明所隐藏。

    我不知道回溯是否是您的问题,但是您将整个网页输出视为单个字符串以匹配正则表达式,这可能导致非常长的字符串。您尝试将其与您反复执行的另一个正则表达式进行匹配。显然,这是一个过程密集的步骤,它被一个Perl语句隐藏(很像sort @foo隐藏其复杂性)。

    在周末开始思考这个问题,你真的不应该尝试用正则表达式解析HTML或XML,因为它太邋。了。你最终会得到一些相当低效和脆弱的东西。

    在这样的情况下,最好使用我更熟悉的HTML::ParserXML::Simple之类的内容,但不一定适用于格式不正确的HTML。

    Perl正则表达式很好,但它们很容易摆脱我们的控制。

答案 1 :(得分:0)

您可能尝试的一件事是将所有捕获组(...)更改为非捕获组(?:...)

如果您需要打印出“找到匹配”,这将为匹配器节省一些精力,但如果您的真实代码更多,我不确定您是否可以实现这一点。

另外,一般来说,有很多通配符如(。*?)只会增加我认为的重量,所以也许知道你想要匹配的东西你能消除其中的一些吗?我不能肯定地说;在这里看不到任何纯正式的优化。