我正在尝试用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()
这最终会产生错误:
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
填充的'F'实际上在我的终端中看起来像这样:
答案 0 :(得分:9)
这里要做的是确保XML文件的创建者确保: A.)声明文件的编码 B.)XML文件格式正确(没有无效字符控制字符,没有不属于编码方案的无效字符,所有元素都正确关闭等) C.)如果要确保某些属性/元素存在,具有特定值或对应于某种格式,请使用DTD或XML模式(注意:这会影响性能)
所以,现在回答你的问题。当您使用它来解析XML时,LXml支持一大堆参数。 Check out the documentation。您将需要查看这两个参数:
- &GT;恢复 - &gt;努力解析破碎的XML - &GT; huge_tree - &gt;禁用安全限制并支持非常深的树和非常长的文本内容(仅影响libxml2 2.7+)
他们会在某种程度上帮助你,但是某些无效字符无法恢复,因此再次确保正确编写文件是清理/运行良好代码的最佳选择。
是啊还有一件事。 2GB是巨大的。我假设你在这个文件中有一个类似元素的列表(示例书籍列表)。尝试在操作系统上使用Regex Expression拆分文件,然后启动多个进程以分割碎片。这样,您就可以在盒子上使用更多的核心,处理时间也会缩短。当然,您必须处理将结果合并在一起的复杂性。我不能为你做这个交易,但想把它作为“思考的食物”给你:除了帖子: 如果您无法控制输入文件并且其中包含错误字符,我会尝试通过迭代字符串来替换/删除这些错误字符,然后再将其解析为文件。这是一个删除Unicode control characters that you wont need的代码示例:
#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])
答案 1 :(得分:6)
我也碰到了这个问题,在数据中获得\x16
(unicode&#39;同步空闲&#39;或者&#39; SYN&#39;字符,在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
是2千兆字节,您可能需要以更有效的方式执行此操作,但我忽略了这一点,尽管问题提到了它。在我的情况下,我是创建xml文件的人,但我需要在原始数据中处理这些字符,所以在将数据放入xml之前我将使用此函数。
答案 2 :(得分:2)
从Google找到了这个线索,而@Michael的答案最终使我找到了解决方案(至少是针对我的问题),我想在此处提供一些复制/粘贴答案,以解决可以这么简单地解决的问题:< / p>
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
无法解决的无效字符的方法。对我来说幸运的是,这足以使事情继续发展。
答案 3 :(得分:0)
codecs
Python模块提供了一个EncodedFile
类,它作为文件的包装器 -
你应该将这个类的对象传递给lxml,设置为用XML char实体替换未知字符 -
尝试这样做:
from lxml import etree
import codecs
enc_file = codecs.EncodedFile(file("my_file.xml"), "ASCII", "ASCII", "xmlcharrefreplace")
etparse = etree.iterparse(enc_file, events=("start",), encoding="CP1252")
...
传递的“xmlcharrefreplace”常量是“errors”参数,并指定如何处理未知字符。它可能是“严格”(引发错误),“忽略”(保持原样),“替换”(用“?”代替char),“xmlrefreplace”(创建“&amp; #xxxx;”xml引用)或“backslahreplace”(创建一个Python有效的反斜杠引用)。有关更多信息,请检查: http://docs.python.org/library/codecs.html