Python正则表达式:re.search()在大文本文件上非常慢

时间:2013-11-19 21:56:15

标签: python regex performance

我的代码执行以下操作:

  1. 获取一个大型文本文件(即一份300页PDF格式的法律文件)。
  2. 找到某个关键字(例如“小”)。
  3. 向左返回n个字词,在关键字右侧返回n个字词。
  4. 注意:在此上下文中,“单词”是任何非空格字符串。 “$ cow123”就是一个词,但“医疗保健”将是两个字。

    这是我的问题: 代码需要花费很长时间才能在300页上运行,并且随着n的增加,该时间会迅速增加。

    这是我的代码:

    fileHandle = open('test_pdf.txt', mode='r')
    document = fileHandle.read()
    
    def search(searchText, doc, n):
    #Searches for text, and retrieves n words either side of the text, which are returned separately
    
        surround = r"\s*(\S*)\s*"
        groups = re.search(r'{}{}{}'.format(surround*n, searchText, surround*n), doc).groups()
        return groups[:n],groups[n:]
    

    这是令人讨厌的罪魁祸首:

    print search("\$27.5 million", document, 10)
    

    以下是测试此代码的方法: 从上面的代码块中复制函数定义并运行以下命令:

    t = "The world is a small place, we $.205% try to take care of it."
    print search("\$.205", t, 3)
    

    我怀疑我有一个令人讨厌的灾难性回溯案例,但我现在太新了,无法指出我的问题。

    如何加快代码速度?

4 个答案:

答案 0 :(得分:4)

如果只搜索固定字符串,使用re.search(甚至string.find)来查找字符串,没有任何周围的捕获组。然后在重新匹配对象上使用匹配的位置和长度(.start.end,或者find的返回值加上搜索字符串的长度)。在匹配之前获取子字符串并在其上执行/\s*(\S*)\s*\z/等,并在匹配后获取子字符串并在其上执行/\A\s*(\S*)\s*/等。

另外,有关回溯的帮助:您可以使用\s+\S+\s+之类的模式而不是\s*\S*\s*(两个空格块必须用非零分隔非空格的数量,否则它们不会是两个块),你不应该像你一样连续两个\s*。我认为r'\S+'.join([[r'\s+']*(n))会为捕获n之前的单词提供正确的模式(但我的Python生锈了,所以检查一下)。

答案 1 :(得分:1)

我在这里看到了几个问题。第一个也可能是最糟糕的是,“环绕”正则表达式中的所有内容都是可选的,而不仅仅是可选的,而且独立可选。鉴于此字符串:

"Lorem ipsum tritani impedit civibus ei pri"

...当searchText = "tritani"n = 1时,这是它在找到第一场比赛之前必须经历的事情:

regex:      \s*    \S*    \s*    tritani

offset 0:   ''   'Lorem'   ' '   FAIL
            ''   'Lorem'   ''    FAIL
            ''   'Lore'    ''    FAIL
            ''   'Lor'     ''    FAIL
            ''   'Lo'      ''    FAIL
            ''   'L'       ''    FAIL
            ''   ''        ''    FAIL

...然后它突然前进一个位置并重新开始:

offset 1:   ''   'orem'   ' '    FAIL
            ''   'orem'   ''     FAIL
            ''   'ore'    ''     FAIL
            ''   'or'     ''     FAIL
            ''   'o'      ''     FAIL
            ''   ''       ''     FAIL

......等等。根据RegexBuddy的调试器,它需要将近150步才能达到可以进行第一场比赛的偏移量:

position 5: ' '  'ipsum'  ' '    'tritani'

这只是一个单词可以跳过,而n=1。如果您设置n=2,最终会得到:

\s*(\S*)\s*\s*(\S*)\s*tritani\s*(\S*)\s*\s*(\S*)\s*

我相信你可以看到它的发展方向。请特别注意,当我将其更改为:

(?:\s+)(\S+)(?:\s+)(\S+)(?:\s+)tritani(?:\s+)(\S+)(?:\s+)(\S+)(?:\s+)

......它在20多步中找到了第一场比赛。这是最常见的正则表达式反模式之一:当您使用*时使用+。换句话说,如果它不是可选的,请不要视为可选项。

最后,您可能已经注意到\s*\s*自动生成的正则表达式

答案 2 :(得分:0)

你可以尝试使用mmap和适当的正则表达式标志,例如(未经测试):

import re
import mmap

with open('your file') as fin:
    mf = mmap.mmap(fin.fileno(), 0, access=mmap.ACCESS_READ)
    for match in re.finditer(your_re, mf, flags=re.DOTALL):
        print match.group() # do something with your match

这只会让内存使用率降低......

替代方案是有一个单词的滑动窗口(前后单个单词的简单示例)......:

import re
import mmap
from itertools import islice, tee, izip_longest

with open('testingdata.txt') as fin:
    mf = mmap.mmap(fin.fileno(), 0, access=mmap.ACCESS_READ)
    words = (m.group() for m in re.finditer('\w+', mf, flags=re.DOTALL))
    grouped = [islice(el, idx, None) for idx, el in enumerate(tee(words, 3))]
    for group in izip_longest(*grouped, fillvalue=''):
        if group[1] == 'something': # check criteria for group
            print group

答案 3 :(得分:-1)

我认为你完全倒退了(我对你刚才做的事情感到有点困惑!)

我建议查看我在cloud toolbox

的textools模块中开发的re_search函数

使用re_search你可以解决这个问题:

from cloudtb import textools
data_list = textools.re_search('my match', pdf_text_str)  # search for character objects
# you now have a list of strings and RegPart objects. Parse through them:
for i, regpart in enumerate(data_list):
    if isinstance(regpart, basestring):
        words = textools.re_search('\w+', regpart)
        # do stuff with words
    else:
        # I Think you are ignoring these? Not totally sure

以下是有关如何使用及其工作原理的链接: http://cloudformdesign.com/?p=183

除此之外,您的正则表达式也将以更易读的格式打印出来。

您可能还想查看我的工具Search The Sky或类似工具Kiki,以帮助您构建和理解正则表达式。