dam*_*mon 20 python xml encoding lxml
我正在尝试用Python的lxml库解析一个超过2GB的XML文件.不幸的是,XML文件没有告诉字符编码的行,所以我必须手动设置它.虽然迭代文件,但仍有一些奇怪的字符偶尔会出现.
我不确定如何确定行的字符编码,但此外,lxml将从for循环的范围引发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()
Run Code Online (Sandbox Code Playgroud)
这最终会产生错误:
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
Run Code Online (Sandbox Code Playgroud)
填充的'F'实际上在我的终端中看起来像这样:

在这里做的正确的事情是确保XML文件的创建者确保:A.)文件的编码被声明为B.)XML文件格式正确(没有无效的字符控制字符,没有无效不属于编码方案的字符,所有元素都已正确关闭等.)C.)如果要确保某些属性/元素存在,具有某些值或对应于某种格式,请使用DTD或XML模式(注意:这会受到性能影响)
所以,现在问你的问题.当您使用它来解析XML时,LXml支持一大堆参数.查看文档.您将需要查看这两个参数:
- > recover - >尽量解析破碎的XML
- > huge_tree - >禁用安全限制并支持非常深的树和非常长的文本内容(仅影响libxml2 2.7+)
它们会在某种程度上帮助你,但是某些无效字符可能无法恢复,因此再次确保正确编写文件是清理/正常工作代码的最佳选择.
啊是的还有一件事.2GB是巨大的.我假设你在这个文件中有一个类似元素的列表(示例书籍列表).尝试在操作系统上使用Regex Expression拆分文件,然后启动多个进程以分割碎片.这样,您就可以在盒子上使用更多的核心,处理时间也会缩短.当然,您必须处理将结果合并在一起的复杂性.我不能为你做这个交易,但想把它作为"思考的食物"给你
除了帖子: 如果您无法控制输入文件并且其中包含错误字符,我会尝试通过迭代字符串来替换/删除这些错误字符,然后再将其解析为文件.这是一个删除您不需要的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])
Run Code Online (Sandbox Code Playgroud)
我也碰到了这个,获取\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)
Run Code Online (Sandbox Code Playgroud)
这导致一个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]
Run Code Online (Sandbox Code Playgroud)
然后像这样使用它:
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
Run Code Online (Sandbox Code Playgroud)
如果value是2千兆字节,你可能需要以更有效的方式做到这一点,但我在这里忽略了,尽管这个问题提到了它.就我而言,我是一个创建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)
Run Code Online (Sandbox Code Playgroud)
我遇到了一个问题,我无法控制 XML 预处理,并且得到了一个包含无效字符的文件。@Michael 的回答继续详细说明了一种处理recover=True无法解决的无效字符的方法。对我来说幸运的是,这足以让事情继续发展。