严重正则表达式挂起时间python

严重正则表达式挂起时间python,python,regex,Python,Regex,我有一个小的(40mb)服务器日志,链接 我有一个正则表达式,我用它来解析代码,这需要花费难以置信的时间(5分钟以上)。我对regex比较陌生,所以我不确定为什么这么小的文件需要这么长时间 下面是一个表达式: valid=re.findall(r'(\d+/[a-zA-Z]+/\d+).*?(GET|POST)\s+(http://|https//)([a-zA-Z]+.+?)\.[^/].*?\.([a-zA-Z]+)(/|\s|:).*?\s200\s', line) 当我在这行末尾加上“

我有一个小的(40mb)服务器日志,链接

我有一个正则表达式,我用它来解析代码,这需要花费难以置信的时间(5分钟以上)。我对regex比较陌生,所以我不确定为什么这么小的文件需要这么长时间

下面是一个表达式:

valid=re.findall(r'(\d+/[a-zA-Z]+/\d+).*?(GET|POST)\s+(http://|https//)([a-zA-Z]+.+?)\.[^/].*?\.([a-zA-Z]+)(/|\s|:).*?\s200\s', line)
当我在这行末尾加上“200”时,事情真的开始变得一团糟了

下面是完整的代码:

    import re
#todo
#specify toplevel domain lookback
######

fhandle=open("access_log.txt", "rU")
access_log=fhandle.readlines()

validfile=open("valid3.txt", "w")
invalidfile=open("invalid3.txt", "w")


valid_dict=dict()
invalid_list=list()
valid_list=list()


#part 1
#read file. apply regex and append into internal data structure (a 2d dictionary)
for line in access_log:
    valid=re.findall(r'(\d+/[a-zA-Z]+/\d+).*?(GET|POST)\s+(http://|https//)([a-zA-Z]+.+?)\.[^/].*?\.([a-zA-Z]+)(/|\s|:).*?\s200\s', line)
    #valid=re.findall(r'(\d+/[a-zA-Z]+/\d+).*?(GET|POST)\s+(http://|https://)([a-zA-Z]+.+?)\.[^/].*?\.([a-zA-Z]+)(/|\s|:).*?\s+200\s', line)
    if valid:
        date=valid[0][0]
        domain=valid[0][4].lower()
        valid_list.append(line)


        #writes results into 2d dcitonary (dictionary of dictonaries)
        if date not in valid_dict:
            valid_dict[date]={}
        else:
            if domain in valid_dict[date]:
                valid_dict[date][domain]+=1
            else:
                valid_dict[date][domain]=1
    #writes remainder files into invalid file log
    else:
        invalid_list.append(line)



#step 2
#format output file for tsv
#ordered chronologically, with Key:Value pairs orgainzed alphabeticallv by key (Domain Name)
date_workspace=''
domain_workspace=''

for date in sorted(valid_dict.iterkeys()):
    date_workspace+=date + "\t"

    for domain_name in sorted(valid_dict[date].iterkeys()):
        domain_workspace+="%s:%s\t" % (domain_name, valid_dict[date][domain_name])

    date_workspace+=domain_workspace
    date_workspace+="\n"    
    domain_workspace=''

# Step 3
# write output
validfile.write(date_workspace)
for line in invalid_list:
    invalidfile.write(line) 



fhandle.close()
validfile.close()
invalidfile.close()

假设您希望保留域名扩展名,您可以如下更改代码的regex部分:

pattern = re.compile(r'^[^[]+\[(\d+/[a-zA-Z]+/\d+)[^]]+] "(?:GET|POST) https?://[a-zA-Z]+[^?/\s]*\.([a-zA-Z]+)[?/ :][^"]*" 200 ')

for line in access_log:
    valid=pattern.search(line)

    if valid:
        date=valid.group(1)
        domain=valid.group(2).lower()
        valid_list.append(line)
改进:5分钟->2秒

由于逐行读取文件,一行中只有一个可能的匹配项,因此最好使用返回第一个匹配项的
re.search
,而不是
re.findall

模式每行使用一次,这就是为什么我选择在循环之前编译模式

模式现在以字符串锚定的开头锚定
^
,行的开头现在用
[^[]+\[
描述(所有不是
[
的都有一次或多次后跟
[
)。此改进非常重要,因为它避免了正则表达式引擎尝试在行的每个字符处启动模式

所有
*?
都很慢,原因有二(至少):

  • 惰性量词必须测试以下子模式是否与每个字符匹配

  • 如果模式稍后失败,因为
    *?
    可以匹配所有字符,正则表达式引擎没有任何理由停止回溯。换句话说,最好的方法是尽可能明确

为此,必须将所有
*?
替换为负字符类和贪婪量词

所有未指定的捕获组都已替换为非捕获组
(?:…)

其他一些琐碎的更改也被做了,比如
(http://https://)=>https?://
(//\s:)=>[?/:]
。所有
\s+
都被一个空格替换


作为旁白,我确信有很多python日志解析器/分析器可以帮助您。还要注意,您的日志文件使用csv格式。

这通常是由于嵌套的
+
/
*
量词引起的。@user2357112:他的模式中没有嵌套的量词。之后没有找到任何嵌套的量词检查正则表达式。但是,
*?
的许多实例可能会产生类似的效果。它仅限于多项式时间问题,而不是指数时间问题,但是仍然有很多方法可以匹配各种
*?
模式,如果没有任何一种模式可以使整体匹配工作正常,正则表达式引擎将需要单独执行这些模式。Ac实际上,我可能是在错误地看待经济放缓。这可能只是因为202146行包含大量数据,或者可能是Python的字符串连接优化没有触发,而构建
date\u workspace
的代码正在运行。谢谢你,我也很欣赏示例regex。正如我所说的那样使用正则表达式时,我发现我对字符串的大部分内容关心得很少,我只是希望引擎检查下一项。我已经开始在心理上等同于告诉引擎“继续前进,直到找到我的下一个特定组”。有没有更周全的方法来解决这个问题?@Mike:是的,就像我在帖子中解释的那样,方法是在下一个组之前描述内容,例如使用与内容匹配但不包含组的第一个字符的字符类。例如,路径中有一个目录:
/.+?/
=>
/[^/]+/
我明白了,这是一种不同的思维方式。感谢您的澄清!