严重的正则表达式挂起蟒蛇

时间:2014-01-28 04:08:48

标签: python regex

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

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

这是表达式:

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()

1 个答案:

答案 0 :(得分:1)

假设您要保留域名扩展名,可以像这样更改代码的正则表达式部分:

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分钟 - > 2S

由于您逐行阅读文件,因此一行中只有一个可能匹配,最好使用返回第一个匹配而不是re.search的{​​{1}}。

每行使用一次模式,这就是我选择在循环之前编译模式的原因。

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

由于两个原因(至少),所有[都很慢:

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

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

要做到这一点,你必须用负字符类和贪心量词替换所有.*?

所有未被捕获的捕获组已被非捕获组.*?替换。

其他一些微不足道的变化,如(?:...)(http://|https://) => https?://。所有(/|\s|:) => [?/ :]都已替换为空格。

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