我应该如何处理Python中的XMLSyntaxError';解析一个大的XML文件时使用lxml吗?

我应该如何处理Python中的XMLSyntaxError';解析一个大的XML文件时使用lxml吗?,python,xml,encoding,lxml,Python,Xml,Encoding,Lxml,我试图用Python的lxml库解析一个超过2GB的XML文件。不幸的是,XML文件没有一行告诉字符编码,所以我必须手动设置它。不过,在遍历文件时,仍然会偶尔出现一些奇怪的字符 我不确定如何确定该行的字符编码,但此外,lxml将从for循环的范围中引发一个XMLSyntaxError。我怎样才能正确地捕捉这个错误,并正确地处理它?下面是一段过于简单的代码片段: from lxml import etree etparse = etree.iterparse(file("my_file.xml",

我试图用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无效字符值31,第1565367行,第50列

文件的该行如下所示:

% sed -n "1565367 p" my_file.xml
<romance_copy>Ravioli Florentine. Tender Ravioli Filled With Creamy Ricotta Cheese And
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
%sed-n“1565367 p”my_file.xml
佛罗伦萨饺子。嫩馄饨,内有奶油乳清干酪和奶酪
填充的“F”在我的终端中实际上是这样的:


正确的做法是确保XML文件的创建者确保: A.)声明文件的编码 B.)XML文件格式正确(无无效字符控制字符,无不属于编码方案的无效字符,所有元素正确关闭等) C.)如果要确保某些属性/元素存在、具有特定值或对应于特定格式,请使用DTD或XML模式(注意:这会影响性能)

那么,现在谈谈你的问题。当您使用LXml解析XML时,它支持一大堆参数。您将要查看以下两个参数:

-->恢复-->尝试通过断开的XML解析
-->巨树-->禁用安全限制并支持非常深的树和非常长的文本内容(仅影响libxml2.7+)

它们会在一定程度上帮助您,但某些无效字符无法从中恢复,因此,再次强调,确保正确写入文件是清理/良好运行代码的最佳选择

啊,是的,还有一件事。2GB是巨大的。我假设您在这个文件中有一个类似元素的列表(书籍的示例列表)。尝试在操作系统上使用正则表达式拆分文件,然后启动多个进程来拆分这些部分。这样,您就可以在机箱上使用更多的内核,并且处理时间也会缩短。当然,接下来您必须处理将结果重新合并在一起的复杂性。我不能为你做这个交易,但我想把它作为“思想食粮”送给你

添加到post: 如果您对输入文件没有控制权,并且文件中有坏字符,我会在将其解析为文件之前,通过迭代字符串来替换/删除这些坏字符。下面是一个删除以下内容的代码示例:

#0x0000-0x0020(总共33个)中的所有unicode字符都是错误的,将被替换为“”(空字符串)
对于fileinput.input(xmlInputFileLocation,inplace=1)中的行:
对于范围(0,长度(线))内的位置:
如果unichr(第[pos]行)小于32:
行[pos]=无
打印u“”。连接([c代表c,如果是c])

Python模块提供了一个
EncodedFile
类,该类用作文件的包装器- 您应该将此类的对象传递给lxml,设置为用XML字符实体替换未知字符--

尝试这样做:

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”参数,指定如何处理未知字符。它可以是“strict”(引发错误)、“ignore”(保持原样)、“replace”(用“?”替换字符)、“xmlrefreplace”(创建“&#xxxx;”xml引用)或“backslahresplace”(创建Python有效的反斜杠引用)。有关详细信息,请检查:

我也遇到了这个问题,在数据中获得了
\x16
(unicode“synchronous idle”或“SYN”字符,在xml中显示为
^V
),这导致在解析xml时出错:
XMLSyntaxError:PCDATA无效字符值22。
22是因为
ord('\x16')
是22

迈克尔的回答让我走上了正确的道路。但是一些低于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)

如果
value
为2G字节,您可能需要以更有效的方式执行此操作,但我忽略了这一点,尽管问题提到了这一点。在我的例子中,我是创建xml文件的人,但我需要处理原始数据中的这些字符,因此在将数据放入xml之前,我将使用此函数。

从Google找到此线程,@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
无法处理这些字符。幸运的是,这足以让事情继续进行。

您是否已经简单地尝试了“utf-8”编码?@jsbueno:问题在于“Filled”中“F”之前的字符,其值为31(十进制)或0x1F。根据XML规范,这是一个无效字符,因此告诉它使用UTF-8编码不会有什么区别。问题是如何让lxml更优雅地处理坏字符(即不要抛出异常)。我在lxml文档+1中没有找到这样做的选项,但是
iterparse
是一个基于事件的解析器,因此它可以很好地处理大型文件。不幸的是,XML文件来自第三方的夜间负载。我无法控制其中的内容。也就是说,我对文件编码的声明没有任何控制权,而文件没有。
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
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)