如何在解析大XML文件时处理Python lxml中的XMLSyntaxError?
我正在尝试用Python的lxml库解析一个超过2GB的XML文件。不过,这个XML文件里没有说明字符编码的那一行,所以我得手动设置编码。在遍历文件的时候,偶尔会出现一些奇怪的字符。
我不太确定怎么判断这一行的字符编码,而且在for循环的范围内,lxml会抛出一个XMLSyntaxError错误。我该如何正确捕捉这个错误,并妥善处理呢?下面是一个简单的代码片段:
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
在我的终端中,"filled"这个单词的'F'实际上是这样的:
4 个回答
我也遇到过这个问题,数据中出现了\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
是2GB,你可能需要用更高效的方法来处理,但这里我就不讨论这个了,尽管问题中提到了。在我的情况下,我是创建xml文件的人,但我需要处理原始数据中的这些字符,所以我会在把数据放入xml之前使用这个函数。
我在谷歌上找到了这个讨论,虽然@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
并不能解决这个问题。幸运的是,这个方法对我来说已经足够了,让事情继续向前推进。
在这里,正确的做法是确保创建XML文件的人注意以下几点:
A.) 确保文件的编码方式被声明
B.) 确保XML文件格式正确(没有无效字符、控制字符,所有元素都正确关闭等)
C.) 如果你想确保某些属性或元素存在,具有特定值或符合某种格式,可以使用DTD或XML模式(注意:这会影响性能)
现在来回答你的问题。LXml在解析XML时支持很多参数。可以查看文档。你需要关注这两个参数:
--> recover --> 尽量解析损坏的XML
--> huge_tree --> 关闭安全限制,支持非常深的树结构和非常长的文本内容(仅适用于libxml2 2.7+)
这些参数在一定程度上会帮助你,但某些无效字符是无法恢复的,所以确保文件正确书写是你获得干净、正常工作的代码的最佳方法。
哦,还有一件事。2GB是很大的。我猜你在这个文件里有一堆类似的元素(比如书籍列表)。试着在操作系统上用正则表达式把文件拆分开,然后启动多个进程来处理这些部分。这样你就能更好地利用你的处理器核心,处理时间也会减少。当然,你还得处理把结果合并回来的复杂性。我不能为你做这个权衡,但想给你提供一个“思考的材料”。
补充说明:
如果你无法控制输入文件,并且里面有坏字符,我建议在解析文件之前,先遍历字符串,替换或移除这些坏字符。这里有一个代码示例,可以移除你不需要的Unicode控制字符:
#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])