Как я должен разбираться с 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]()
Ответы
Ответ 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
. К счастью для меня, этого было достаточно, чтобы все продолжалось.