Как мне иметь дело с XMLSyntaxError в Python lxml при анализе большого файла XML? - PullRequest
18 голосов
/ 17 января 2012

Я пытаюсь проанализировать файл XML размером более 2 ГБ с библиотекой Python lxml. К сожалению, в XML-файле нет строки, указывающей кодировку символов, поэтому я должен установить ее вручную. Хотя во время итерации файла все же появляются странные символы, которые время от времени появляются.

Я не уверен, как определить кодировку символов строки, но, кроме того, lxml вызовет ошибку XMLSyntaxError из области действия цикла for. Как я могу правильно уловить эту ошибку и правильно с ней справиться? Вот упрощенный фрагмент кода:

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» выглядит так:

xml line causing the error

Ответы [ 4 ]

9 голосов
/ 17 января 2012

Что нужно сделать, это убедиться, что создатель XML-файла гарантирует, что: А) что кодировка файла объявлена B.) что файл XML правильно сформирован (нет недопустимых символов, управляющих символов, недопустимых символов, которые не попадают в схему кодирования, все элементы правильно закрыты и т. Д.) C.) использовать DTD или схему XML, если вы хотите убедиться, что определенные атрибуты / элементы существуют, имеют определенные значения или соответствуют определенному формату (примечание: это приведет к снижению производительности)

Итак, теперь к вашему вопросу. LXml поддерживает целую кучу аргументов, когда вы используете его для анализа XML. Ознакомьтесь с документацией . Вы захотите взглянуть на эти два аргумента:

-> восстанавливать -> стараться разобрать сломанный XML
-> огромное_дерево -> отключить ограничения безопасности и поддерживать очень глубокие деревья и очень длинный текстовый контент (влияет только на libxml2 2.7+)

Они в некоторой степени помогут вам, но некоторые недопустимые символы просто не могут быть восстановлены, поэтому еще раз, чтобы убедиться, что файл написан правильно, это ваш лучший выбор для очистки / хорошо работающего кода.

Ах да, и еще одна вещь. 2ГБ огромно. Я предполагаю, что у вас есть список похожих элементов в этом файле (пример списка книг). Попробуйте разделить файл с помощью выражения Regex в ОС, затем запустите несколько процессов, чтобы разделить части. Таким образом, вы сможете использовать больше своих ядер на вашем устройстве, и время обработки сократится. Конечно, тогда вам придется иметь дело со сложностью объединения результатов вместе. Я не могу сделать этот обмен для вас, но хотел дать вам это как «пищу для размышлений»

Дополнение к сообщению: Если у вас нет контроля над входным файлом и в нем содержатся плохие символы, я бы попытался заменить / удалить эти плохие символы, перебирая строку, прежде чем анализировать ее как файл. Вот пример кода, который удаляет управляющие символы 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])
6 голосов
/ 23 июня 2016

Я тоже столкнулся с этим, получив данные \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 равен 2 гигабайту, вам, возможно, потребуется сделать это более эффективным способом, но я игнорирую это здесь, хотя вопрос упоминает об этом. В моем случае я создаю xml-файл, но мне нужно разобраться с этими символами в исходных данных, поэтому я буду использовать эту функцию перед помещением данных в xml.

2 голосов
/ 17 января 2019

Нашел эту ветку в 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, и мне выдавали файл с недопустимыми символами. @ В ответе Майкла уточняется способ обращения к недопустимым символам, с которыми recover=True не может обратиться. К счастью для меня, этого было достаточно, чтобы все продолжалось.

0 голосов
/ 17 января 2012

Модуль codecs 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" и указывает, что делать с неизвестными символами. Это может быть «строгий» (вызывает ошибку), «игнорировать» (оставить как есть), «заменить» (заменяет char на «?»), «Xmlrefreplace» (создает «& # xxxx;« ссылку xml)) или « backslahreplace "(создает действительную ссылку на обратную косую черту в Python). Для получения дополнительной информации, проверьте: http://docs.python.org/library/codecs.html

...