我正在使用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元件。
解决方案的优先顺序如下:
如何实现这些解决方案,而无需自行修改ElementTree
代码?
答案 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)