4

我正在使用 Pythonxml.etree.ElementTree模块的iterparse()方法读取一个巨大的(多千兆字节)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. 跳过带有无效字符的元素并继续下一个。

我如何在不亲自修改ElementTree代码的情况下实现这些解决方案?

4

1 回答 1

4

首先,关于 ElementTree 的所有东西在这里可能都无关紧要。尝试仅枚举由返回的文件f = open(filename, encoding='utf-8'),您可能会得到相同的错误。

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

errors 是一个可选字符串,它指定如何处理编码和解码错误——这不能在二进制模式下使用。如果存在编码错误,则传递 'strict' 以引发 ValueError 异常(默认值 None 具有相同的效果),或者传递 'ignore' 以忽略错误。(请注意,忽略编码错误会导致数据丢失。)“替换”会导致在存在格式错误的数据的位置插入替换标记(例如“?”)。编写时,可以使用 '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对象单独保留,而不是尝试将其转换为 Unicodestr对象,但是您只能使用处理bytes对象的 API。(我相信 的lxml实现ElementTree实际上可以做到这一点,但内置的不能,但不要引用我的话。)但即使你这样做了,它也不会让你走得太远,因为 XML代码本身会尝试解释无效的 UTF-8,然后需要知道你想对错误做什么,这通常会更难指定,因为它更远。

最后一点:

由于错误发生在 for 循环迭代之间,因此我可以包装 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)
于 2013-01-04T20:08:42.727 回答