【问题标题】:How should I deal with an XMLSyntaxError in Python's lxml while parsing a large XML file?在解析大型 XML 文件时,我应该如何处理 Python 的 lxml 中的 XMLSyntaxError?
【发布时间】:2012-02-11 21:40:11
【问题描述】:

我正在尝试使用 Python 的 lxml 库解析超过 2GB 的 XML 文件。不幸的是,XML 文件没有一行告诉字符编码,所以我必须手动设置它。但是,在遍历文件时,仍然偶尔会出现一些奇怪的字符。

我不确定如何确定该行的字符编码,但此外,lxml 将从 for 循环的范围内引发 XMLSyntaxError。如何正确捕获此错误并正确处理?这是一个简单的代码sn-p:

from lxml import etree
etparse = etree.iterparse(file("my_file.xml", 'r'), events=("start",), encoding="CP1252")
for event, elem in etparse:
    if elem.tag == "product":
        print "Found the product!"
        elem.clear()

这最终会产生错误:

XMLSyntaxError: PCDATA invalid Char value 31, line 1565367, column 50

文件的那一行如下所示:

% sed -n "1565367 p" my_file.xml
<romance_copy>Ravioli Florentine. Tender Ravioli Filled With Creamy Ricotta Cheese And

填充的“F”在我的终端中实际上是这样的:

【问题讨论】:

  • 您是否已经简单地尝试过“utf-8”进行编码?
  • @jsbueno:问题出在“Filled”中“F”之前的字符,其值为 31(十进制)或 0x1F。根据 XML 规范,这是一个无效字符,因此告诉它使用 UTF-8 编码不会有任何影响。问题是如何让 lxml 更优雅地处理坏字符(即不抛出异常)。我在 lxml 文档中没有找到执行此操作的选项。

标签: python xml encoding lxml


【解决方案1】:

正确的做法是确保 XML 文件的创建者确保: A.) 声明文件的编码 B.) XML 文件格式正确(没有无效字符控制字符,没有不属于编码方案的无效字符,所有元素都正确关闭等) C.) 如果您想确保某些属性/元素存在、具有某些值或对应于某种格式,请使用 DTD 或 XML 模式(注意:这会影响性能)

那么,现在回答你的问题。当你使用它来解析 XML 时,LXml 支持一大堆参数。 Check out the documentation。你会想看看这两个论点:

--> 恢复 --> 努力解析损坏的 XML
--> huge_tree --> 禁用安全限制并支持非常深的树和非常长的文本内容(仅影响 libxml2 2.7+)

它们会在一定程度上帮助您,但某些无效字符无法恢复,因此再次确保文件正确写入是清洁/运行良好代码的最佳选择。

啊,是的,还有一件事。 2GB是巨大的。我假设您在此文件中有一个类似元素的列表(书籍示例列表)。尝试在操作系统上使用正则表达式拆分文件,然后启动多个进程来分割文件。这样你就可以在你的盒子上使用更多的核心,并且处理时间会减少。当然,您必须处理将结果重新合并在一起的复杂性。我不能为你做这个权衡,但想把它作为“深思熟虑”送给你

帖子补充: 如果您无法控制输入文件并且其中包含错误字符,我会尝试通过在将其解析为文件之前迭代字符串来替换/删除这些错误字符。这是一个删除Unicode control characters that you wont need的代码示例:

#all unicode characters from 0x0000 - 0x0020 (33 total) are bad and will be replaced by "" (empty string)
for line in fileinput.input(xmlInputFileLocation, inplace=1):
    for pos in range(0,len(line)):
        if unichr(line[pos]) < 32:
            line[pos] = None
    print u''.join([c for c in line if c])

【讨论】:

  • +1,但iterparse 是一个基于事件的解析器,所以它可以很好地处理大文件。
  • 不幸的是,XML 文件来自第三方的夜间有效负载。我无法控制其中的内容。话虽如此,我无法控制文件编码的声明,而文件没有。 XML 文件格式不正确,其中包含一些奇怪的字符。而且,该文件不订阅任何 DTD 或 XML Schema,而且供应商似乎甚至不明白那是什么......不幸的是,我一个人在这里。
  • 在你的代码中,你使用unichr你的意思是ord
【解决方案2】:

我也遇到了这个问题,在数据中获取了\x16(unicode '同步空闲'或'SYN'字符,在 xml 中显示为^V),这导致解析 xml 时出错:XMLSyntaxError: PCDATA invalid Char value 22. 22 是因为 ord('\x16') 是 22。

@michael 的回答让我走上了正轨。但是一些低于 32 的控制字符很好,比如回车或制表符,一些更高的字符仍然不好。所以:

# Get list of bad characters that would lead to XMLSyntaxError.
# Calculated manually like this:
from lxml import etree
from StringIO import StringIO
BAD = []
for i in range(0, 10000):
    try:
        x = etree.parse(StringIO('<p>%s</p>' % unichr(i)))
    except etree.XMLSyntaxError:
        BAD.append(i)

这导致了一个可以硬编码的 31 个字符的列表,而不是在代码中进行上述计算:

BAD = [
    0, 1, 2, 3, 4, 5, 6, 7, 8,
    11, 12,
    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
    # Two are perfectly valid characters but go wrong for different reasons.
    # 38 is '&' which gives: xmlParseEntityRef: no name.
    # 60 is '<' which gives: StartTag: invalid element namea different error.
]
BAD_BASESTRING_CHARS = [chr(b) for b in BAD]
BAD_UNICODE_CHARS = [unichr(b) for b in BAD]

然后像这样使用它:

def remove_bad_chars(value):
    # Remove bad control characters.
    if isinstance(value, unicode):
        for char in BAD_UNICODE_CHARS:
            value = value.replace(char, u'')
    elif isinstance(value, basestring):
        for char in BAD_BASESTRING_CHARS:
            value = value.replace(char, '')
    return value

如果value 是 2 GB,您可能需要以更有效的方式执行此操作,但我在这里忽略了这一点,尽管问题提到了它。就我而言,我是创建 xml 文件的人,但我需要处理原始数据中的这些字符,所以我会在将数据放入 xml 之前使用此功能。

【讨论】:

    【解决方案3】:

    从 Google 找到了这个帖子,虽然 @Michael 的回答最终引导我找到了一个解决方案(至少解决了我的问题),但我想在这里为可以简单解决的问题提供更多的复制/粘贴答案:

    from lxml import etree
    
    # Create a parser
    parser = etree.XMLParser(recover=True)
    
    parsed_file = etree.parse('/path/to/your/janky/xml/file.xml', parser=parser)
    

    我遇到了一个问题,我无法控制 XML 预处理,并且收到了一个包含无效字符的文件。 @Michael 的回答继续详细说明了一种处理 recover=True 无法解决的无效字符的方法。对我来说幸运的是,这足以让事情继续发展。

    【讨论】:

      【解决方案4】:

      codecs Python 模块提供了一个 EncodedFile 类,该类用作文件的包装器 - 您应该将此类的一个对象传递给 lxml,设置为用 XML char 实体替换未知字符--

      尝试这样做:

      from lxml import etree
      import codecs
      
      enc_file = codecs.EncodedFile(file("my_file.xml"), "ASCII", "ASCII", "xmlcharrefreplace")
      
      etparse = etree.iterparse(enc_file, events=("start",), encoding="CP1252")
      ...
      

      传递的“xmlcharrefreplace”常量是“errors”参数,它指定如何处理未知字符。它可以是“strict”(引发错误)、“ignore”(保持原样)、“replace”(将 char 替换为“?”)、“xmlrefreplace”(创建一个“xxx;”xml 引用)或“ backslahreplace”(创建一个 Python 有效的反斜杠引用)。如需更多信息,请查看: http://docs.python.org/library/codecs.html

      【讨论】:

      • 不幸的是,这似乎给出了同样的错误,即使我使用“忽略”或“替换”。 XMLSyntaxError: PCDATA invalid Char value 31, line 1565367, column 50
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-28
      • 2012-08-12
      • 2020-10-16
      • 2014-08-22
      • 1970-01-01
      • 2013-05-10
      相关资源
      最近更新 更多