【问题标题】:Parsing huge, badly encoded XML files in Python在 Python 中解析巨大的、编码错误的 XML 文件
【发布时间】:2012-07-09 03:52:59
【问题描述】:

我一直致力于解析外部 XML 文件的代码。其中一些文件很大,高达千兆字节的数据。不用说,这些文件需要被解析为流,因为将它们加载到内存中效率太低,并且经常导致 OutOfMemory 问题。

我使用了 miniDOM、ElementTree、cElementTree 库,目前正在使用 lxml。 现在我有一个使用lxml.etree.iterparse 的有效的、非常节省内存的脚本。问题是我需要解析的一些 XML 文件包含编码错误(它们宣传为 UTF-8,但包含不同编码的字符)。使用lxml.etree.parse 时,可以使用自定义解析器的recover=True 选项来解决此问题,但iterparse 不接受自定义解析器。 (另见:this question

我当前的代码如下所示:

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

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

任何帮助将不胜感激!

更新

一些附加信息: 这是 iterparse 失败的那一行:

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

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

我还找到了this question,它没有提供解决方案。

【问题讨论】:

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

标签: python xml encoding iterparse


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    我用了一段类似的代码:

     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) 
    

    【讨论】:

      【解决方案3】:

      由于问题是由非法 XML 字符引起的,在本例中是 0x19 字节,我决定将它们去掉。我找到了以下正则表达式on this site

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

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

        • 嗯,这看起来是一个很好的解决方案,但我刚刚尝试过 - 在同一点出现同样的错误,即使我将“替换”更改为“忽略”。 (要回答你上面的问题,这是 Python 2.7,不需要兼容。)
        • 您能否将 XML 文件(或者,最好是显示问题的小文档)发布到某个地方,以便人们帮助调试它?
        猜你喜欢
        • 2010-10-29
        • 2014-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-31
        相关资源
        最近更新 更多