工作是逐行读取一个非常大的XML文件,并存储已经在字符串中读取的内容。当字符串包含标记“player”和“/ player”之间的完整记录时,此记录中xml标记的所有值都应作为制表符分隔行写入文本文件,并从已读取的块中删除记录。 / p>
在过程结束时,应打印未删除的部分(剩余部分),以检查是否所有记录都已正确处理,并且没有任何内容未经处理。
我已经在Perl中使用了这个代码并且它运行得很快,但我想切换到Python。
我目前使用的Python脚本非常慢。
Python速度慢,还是使用正则表达式做错了?
import re
fh=open("players_list_xml.xml")
outf=open("players.txt","w")
x=""
cnt=0
while(cnt<10000):
line=fh.readline().rstrip()
x+=line
mo=re.search(r"<player>(.*)</player>",x)
while(mo):
cnt=cnt+1
if((cnt%1000)==0):
print("processing",cnt)
x=re.sub(re.escape(mo.group()),"",x)
print("\t".join(re.findall(r"<[a-z]+>([^<]+)<[^>]+>",mo.group(1))),file=outf)
mo=re.search(r"<player>(.*)</player>",x)
print("remainder",x)
outf.close()
fh.close()
答案 0 :(得分:1)
你的正则表达式很慢,因为你正在使用“贪婪”表达式“回溯”(this answer提供了一个简单的Python示例)。此外,如评论中所述,您应该使用XML解析器来解析XML。 Regex从未对XML(或HTML)非常有用。
试图解释为什么你的特定表达很慢......
假设您的XML中有三个<player>...</player>
个元素。您的正则表达式将首先匹配第一个开始<player>
标记(该部分没问题)。然后(因为你正在使用贪婪的匹配)它将跳到文档的末尾并开始向后工作(回溯),直到它与最后的结束</player>
标记匹配。如果写的正则表达式写得不好,它就会停在那里(所有三个元素都会与它们之间的所有非玩家元素匹配)。但是,这种匹配显然是错误的,所以你做了一些改变。然后,新的正则表达式将继续前进,继续回溯直到它找到第一个结束</player>
标记。然后它会继续回溯,直到确定开始标记和最近找到的结束标记之间没有其他</player>
标记。然后它将为第二组标签重复该过程,并再次为第三组标签重复该过程。所有这些回溯都需要花费很多时间。这是一个相对较小的文件。在评论中,您提到您的文件包含“超过五十万条记录”。哎哟!我无法想象需要多长时间。而你实际上匹配所有元素,而不仅仅是“玩家”元素。然后,您正在针对每个元素运行第二个正则表达式,以检查它们是否是播放器元素。我永远不会想到这会很快。
为了避免所有回溯,你可以使用“nongreedy”或“懒惰”正则表达式。例如(大大简化了代码):
r"<player>(.*?)</player>"
请注意,?
表示上一个模式(.*
)不一致。在这个例子中,在找到第一个打开的<player>
标记之后,它会继续向前移动文档(不跳到最后),直到它找到第一个结束</player>
标记然后它将是确信模式已匹配并继续查找第二次出现(但只能在第一次出现结束后在文档中搜索)。
当然,非同意的表达会更快。根据我的经验,在*
或+
匹配时,nongreedy几乎总是你想要的(除了极少数情况下你不这样做)。
也就是说,如前所述,XML解析器更适合解析XML。事实上,许多XML解析器提供某种流式API,允许您将文档分段输入,以避免一次将整个文档加载到内存中(正则表达式不提供此优势)。我从lxml开始,然后移动到一些内置解析器,如果C依赖项不适合你。
答案 1 :(得分:0)
使用XML解析器:
import xml.parsers.expat
cnt=0
state="idle"
current_key=""
current_value=""
fields=[]
def start_element(name, attrs):
global state
global current_key
global current_value
global fields
if name=="player":
state="player"
elif state=="player":
current_key=name
def end_element(name):
global state
global current_key
global current_value
global fields
global cnt
if state=="player":
if name=="player":
state="idle"
line="\t".join(fields)
print(line,file=outf)
fields=[]
cnt+=1
if((cnt%10000)==0):
print(cnt,"players processed")
else:
fields.append(current_value)
current_key=""
current_value=""
def char_data(data):
global state
global current_key
global current_value
if state=="player" and not current_key=="":
current_value=data
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = start_element
p.EndElementHandler = end_element
p.CharacterDataHandler = char_data
fh=open("players_list_xml.xml")
outf=open("players.txt","w")
line=True
while((cnt<1000000) and line):
line=fh.readline().rstrip()
p.Parse(line)
outf.close()
fh.close()
这是相当多的代码。
至少这会从原始XML生成一个29MB的文本文件,这个大小似乎正确。
速度是合理的,虽然这是一个简单的版本,但需要对记录进行更多处理。
在一天结束时,似乎只有正则表达式的Perl脚本正在以专用XML解析器的速度运行,这是非常了不起的。
答案 2 :(得分:0)
正如其他人所说,正确答案是使用 XML 解析器来解析 XML。
关于为什么它比你的 perl 版本慢这么多的问题的答案是,由于某种原因,python 的正则表达式很慢,比处理相同表达式的 perl 慢得多。我经常发现使用正则表达式的代码在 perl 中的速度要快两倍多。