Подтвердить что ты не робот

Как я должен разбираться с XMLSyntaxError в Python lxml при анализе большого XML файла?

Я пытаюсь разобрать XML файл, содержащий более 2 ГБ с помощью библиотеки LXML Python. К сожалению, в 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

4b9b3361

Ответ 1

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

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

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

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

Ах да и еще одно. 2 ГБ огромен. Я предполагаю, что у вас есть список похожих элементов в этом файле (пример списка книг). Попробуйте разделить файл с выражением 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])

Ответ 2

Я тоже столкнулся с этим, получив \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.

Ответ 3

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

Ответ 4

Нашел эту ветку в 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. К счастью для меня, этого было достаточно, чтобы все продолжалось.