如何解决xml.etree.ElementTree.iterparse()中的Unicode错误?

时间:2013-01-04 19:42:00

标签: python xml unicode python-3.x elementtree

我正在使用Python的xml.etree.ElementTree模块的iterparse()方法读取一个巨大的(数GB)XML文件。问题是在某些XML文件的文本中偶尔会出现Unicode错误(或者至少Python 3认为是Unicode错误)。我的循环设置如下:

import xml.etree.ElementTree as etree

def foo():
    # ...
    f = open(filename, encoding='utf-8')
    xmlit = iter(etree.iterparse(f, events=('start', 'end')))
    (event, root) = next(xmlit)
    for (event, elem) in xmlit: # line 26
        if event != 'end':
            continue
        if elem.tag == 'foo':
            do_something()
            root.clear()
        elif elem.tag == 'bar':
            do_something_else()
            root.clear()
    # ...

遇到遇到Unicode错误的元素时,出现以下回溯的错误:

Traceback (most recent call last):
  File "<path to above file>", line 26, in foo
    for (event, elem) in xmlit:
  File "C:\Python32\lib\xml\etree\ElementTree.py", line 1314, in __next__
    self._parser.feed(data)
  File "C:\Python32\lib\xml\etree\ElementTree.py", line 1668, in feed
    self._parser.Parse(data, 0)
UnicodeEncodeError: 'utf-8' codec can't encode character '\ud800' in position 16383: surrogates not allowed

由于在for循环迭代之间发生错误,我可以包装try块的唯一位置是for循环之外,这意味着我无法继续下一个XML元件。

解决方案的优先顺序如下:

  1. 接收一个不一定有效的Unicode字符串作为元素的文本,而不是引发异常。
  2. 接收有效的Unicode字符串,替换或删除无效字符。
  3. 使用无效字符跳过元素,然后转到下一个元素。
  4. 如何实现这些解决方案,而无需自行修改ElementTree代码?

1 个答案:

答案 0 :(得分:4)

首先,关于ElementTree的所有内容可能与此无关。尝试枚举f = open(filename, encoding='utf-8')返回的文件,您可能会收到相同的错误。

如果是这样,解决方案是覆盖默认编码错误处理程序,如the docs中所述:

  

errors是一个可选字符串,用于指定如何处理编码和解码错误 - 这不能在二进制模式下使用。如果存在编码错误(默认值为None具有相同的效果),则传递'strict'以引发ValueError异常,或者通过'ignore'忽略错误。 (请注意,忽略编码错误可能会导致数据丢失。)'replace'会导致替换标记(例如“?”)插入有错误数据的位置。在编写时,可以使用'xmlcharrefreplace'(用适当的XML字符引用替换)或'backslashreplace'(用反斜杠转义序列替换)。使用codecs.register_error()注册的任何其他错误处理名称也是有效的。

所以,你可以这样做:

f = open(filename, encoding='utf-8', errors='replace')

这符合您的第二优先级 - 无效字符将被'?'替换。

无法满足您的第一优先级,因为无法表示“不一定有效的Unicode字符串”。根据定义,Unicode字符串是一系列Unicode代码点,这就是Python处理str类型的方式。如果你有无效的UTF-8并想把它变成一个字符串,你需要指定 它应该变成一个字符串 - 这就是errors的用途。

您也可以以二进制模式打开文件,并将UTF-8单独保留为bytes对象,而不是尝试将其转换为Unicode str对象,但是您可以仅使用与bytes个对象一起使用的API。 (我相信lxml实现ElementTree实际上可以做到这一点,但内置的不能,但不要引用我。)但即使你这样做,它也不会不会让你走得太远,因为XML代码本身将试图解释无效的UTF-8,然后需要知道你想要做什么错误,这通常会更难指定,因为它更远了。

最后一点:

  

由于在循环迭代之间发生错误,我可以包装try块的唯一地方是for循环,这意味着我无法继续使用下一个XML元素。

好吧,你实际上不必使用for循环;您可以将其转换为带有显式while调用的next循环。任何时候你需要这样做,这通常表明你做错了 - 但有时它表明你正在处理一个破损的库,这是唯一可用的解决方法。

此:

for (event, elem) in xmlit: # line 26
    doStuffWith(event, elem)

实际上相当于:

while True:
    try:
        event, elem = next(xmlit)
    except StopIteration:
        break
    doStuffWith(event, elem)

现在,有一个明显的地方可以添加try - 尽管你甚至不需要;您可以将另一个except附加到现有的try

问题是,你打算在这做什么?无法保证迭代器在抛出异常后能够继续。实际上,创建迭代器的所有最简单方法都不会 能够这样做。你可以自己测试一下这种情况是否正确。

在极少数情况下,当你需要这个,并且它实际上有帮助时,你可能想要把它包起来。像这样:

def skip_exceptions(it):
    while True:
      try:
          yield next(it)
      except StopIteration:
          raise
      except Exception as e:
          logging.info('Skipping iteration because of exception {}'.format(e))        

然后你就做了:

for (event, elem) in skip_exceptions(xmlit):
    doStuffWith(event, elem)