问题 用Python解析巨大的,编码错误的XML文件


我一直在研究解析外部XML文件的代码。其中一些文件非常庞大,高达千兆字节的数据。毋庸置疑,这些文件需要被解析为流,因为将它们加载到内存中的效率太低,并且经常导致OutOfMemory问题。

我使用了库miniDOM,ElementTree,cElementTree,我目前正在使用lxml。 现在我有一个工作的,非常有效的内存脚本,使用 lxml.etree.iterparse。问题是我需要解析的一些XML文件包含编码错误(它们通告为UTF-8,但包含不同编码的字符)。使用时 lxml.etree.parse 这可以通过使用 recover=True 自定义解析器的选项,但是 iterparse 不接受自定义解析器。 (也可以看看: 这个问题

我当前的代码如下所示:

from lxml import etree
events = ("start", "end")
context = etree.iterparse(xmlfile, events=events)
event, root_element = context.next() # <items>
for action, element in context:
    if action == 'end' and element.tag == 'item':
    # <parse>
    root_element.clear() 

错误时 iterparse 遇到一个坏人(在这种情况下,它是一个 ^Y):

lxml.etree.XMLSyntaxError: Input is not proper UTF-8, indicate encoding !
Bytes: 0x19 0x73 0x20 0x65, line 949490, column 25

我甚至不想解码这些数据,我可以放弃它。但是我不知道有什么方法可以跳过这个元素 - 我试过了 context.next 和 continue 在try / except语句中。

任何帮助,将不胜感激!

更新

一些额外的信息: 这是iterparse失败的行:

<description><![CDATA:[musea de la photographie fonds mercator. Met meer dan 80.000 foto^Ys en 3 miljoen negatieven is het Muse de la...]]></description>

根据etree,错误发生在字节 0x19 0x73 0x20 0x65
根据hexedit, 19 73 20 65 转换为ASCII .s e
. 在这个地方应该是撇号(foto的)。

我也找到了 这个问题,这不提供解决方案。


12160
2017-07-09 17:46


起源

你尝试过美味的汤吗? - Dmitry Zagorulkin
是否可以执行预处理步骤来纠正编码?您甚至可以在使用StringIO对象并将输出提供给etree的管道中执行此操作。 - Dana the Sane
@DanatheSane当然,关于如何解决这个问题的任何提示? - Rik
@Rik如果你把一些代码放在一起来解析标签,attr和内容解析,你可以将有问题的输入提供给chardet(参见 stackoverflow.com/questions/436220/...)并在你去的时候重新写文件。我不确定文档中的编码问题在哪里,但如果它们有些孤立,这不应该产生太多的开销。 - Dana the Sane
请发布包含您的顶级标记和DTD(如果有)以及片段的完整XML文档,以便其他人可以测试您正在测试的相同内容。此外,如果您可以在错误之前显示几个字节可能有帮助(因此我们可以看到我们是否有一半的UTF-8字符或其他东西)。 - abarnert


答案:


如果问题是实际的字符编码问题,而不是格式错误的XML,最简单,也可能是最有效的解决方案是在文件读取点处理它。喜欢这个:

import codecs
from lxml import etree
events = ("start", "end")
reader = codecs.EncodedFile(xmlfile, 'utf8', 'utf8', 'replace')
context = etree.iterparse(reader, events=events)

这将导致非UTF8可读字节被'?'替换。还有其他一些选择;有关更多信息,请参阅编解码器模块的文档。


9
2017-07-09 18:01



嗯,这看起来是一个很好的解决方案,但我刚刚尝试过 - 在同一点上出现同样的错误,即使我将'replace'更改为'ignore'也是如此。 (要回答上面的问题,这是Python 2.7,不需要compat。) - Rik
你可以在某处发布XML文件(或者更好的是,显示问题的小文档),以便人们可以帮助调试它吗? - abarnert
我已经更新了原帖。 - Rik


由于问题是由于 非法 XML字符,在这种情况下是0x19字节,我决定剥离它们。我找到了以下正则表达式 在这个网站上

invalid_xml = re.compile(u'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]')

我编写了这段代码,在保存xml提要时删除非法字节:

conn = urllib2.urlopen(xmlfeed)
xmlfile = open('output', 'w')

while True:
    data = conn.read(4096)
    if data:
        newdata, count = invalid_xml.subn('', data)
        if count > 0 :
            print 'Removed %s illegal characters from XML feed' % count
        xmlfile.write(newdata)

    else:
        break

xmlfile.close()

2
2017-07-10 21:43





我使用了类似的代码:

 illegalxml = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')

...

illegalxml.sub("?",mystring)

...

但是,这不适用于所有可能的字符串(400 + MB字符串)。

对于最终解决方案,我使用解码/编码如下:

outxml = "C:/path_to/xml_output_file.xml"
with open(outxml, "w") as out:
    valid_xmlstring = mystring.encode('latin1','xmlcharrefreplace').decode('utf8','xmlcharrefreplace')
    out.write(valid_xmlstring) 

1
2017-09-01 00:37





我对char有一个类似的问题“?”在我的xml文件中,它也是无效的xmlchar。这是因为在xml版本1.0中,不允许使用&#x0,&#xE等字符。规则是不允许所有字符组成为正则表达式'&#x [0-1]?[0-9A-E]'。我的目的是根据Rik的答案纠正一个巨大的xml文件中的无效字符,我改进如下:

import re

invalid_xml = re.compile(r'&#x[0-1]?[0-9a-eA-E];')

new_file = open('new_file.xml','w') 
with open('old_file.xml') as f:
    for line in f:
        nline, count = invalid_xml.subn('',line)
        new_file.write(nline) 
new_file.close()

0
2018-01-08 10:30