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

Разбор огромных, плохо закодированных XML файлов в Python

Я работал над кодом, который анализирует внешние XML файлы. Некоторые из этих файлов огромны, до гигабайт данных. Излишне говорить, что эти файлы нужно анализировать как поток, потому что загрузка их в память слишком неэффективна и часто приводит к проблемам OutOfMemory.

Я использовал библиотеки miniDOM, ElementTree, cElementTree, и в настоящее время я использую lxml. Сейчас у меня есть рабочий, довольно эффективный с точки зрения памяти script, используя lxml.etree.iterparse. Проблема в том, что некоторые из файлов XML, которые мне нужны для синтаксического анализа, содержат ошибки кодирования (они рекламируют как UTF-8, но содержат символы, кодированные по-разному). При использовании lxml.etree.parse это можно устранить, используя опцию recover=True настраиваемого анализатора, но iterparse не принимает пользовательский парсер. (см. также этот вопрос)

Мой текущий код выглядит следующим образом:

from lxml import etree
events = ("start", "end")
context = etree.iterparse(xmlfile, events=events)
event, root_element = context.next() # <items>
for action, element in context:
    if action == 'end' and element.tag == 'item':
    # <parse>
    root_element.clear() 

Ошибка, когда iterparse встречает плохой символ (в этом случае это a ^Y):

lxml.etree.XMLSyntaxError: Input is not proper UTF-8, indicate encoding !
Bytes: 0x19 0x73 0x20 0x65, line 949490, column 25

Я даже не хочу расшифровывать эти данные, я могу просто отказаться от него. Однако я не знаю, как пропустить элемент - я пробовал context.next и continue в операторах try/except.

Любая помощь будет оценена!

Обновление

Дополнительная информация: Это строка, в которой выполняется ошибка iterparse:

<description><![CDATA:[musea de la photographie fonds mercator. Met meer dan 80.000 foto^Ys en 3 miljoen negatieven is het Muse de la...]]></description>

В соответствии с etree ошибка возникает в байтах 0x19 0x73 0x20 0x65.
Согласно hexedit, 19 73 20 65 переводится в ASCII .s e
. в этом месте должен быть апострофом (foto's).

Я также нашел этот вопрос, который не дает решения.

4b9b3361

Ответ 1

Поскольку проблема вызвана нелегальными символами XML, в этом случае 0x19 байт, я решил снять их. Я нашел следующее регулярное выражение на этом сайте:

invalid_xml = re.compile(u'[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]')

И я написал этот фрагмент кода, который удаляет незаконные байты, сохраняя XML файл:

conn = urllib2.urlopen(xmlfeed)
xmlfile = open('output', 'w')

while True:
    data = conn.read(4096)
    if data:
        newdata, count = invalid_xml.subn('', data)
        if count > 0 :
            print 'Removed %s illegal characters from XML feed' % count
        xmlfile.write(newdata)

    else:
        break

xmlfile.close()

Ответ 2

Если проблемы являются актуальными проблемами кодирования символов, а не неправильным XML, самым простым и, вероятно, самым эффективным решением является обращение к нему в точке чтения файла. Вот так:

import codecs
from lxml import etree
events = ("start", "end")
reader = codecs.EncodedFile(xmlfile, 'utf8', 'utf8', 'replace')
context = etree.iterparse(reader, events=events)

Это приведет к замене нечитаемых UTF8 байтов на '?'. Есть еще несколько вариантов; более подробную информацию см. в документации для модуля кодеков.

Ответ 3

Я использовал аналогичную часть кода:

 illegalxml = re.compile(u'[\x00-\x08\x0b\x0c\x0e-\x1F\uD800-\uDFFF\uFFFE\uFFFF]')

...

illegalxml.sub("?",mystring)

...

Однако это не сработало для всех возможных строк (строка 400 + MB).

Для окончательного решения я использовал декодирование/кодирование следующим образом:

outxml = "C:/path_to/xml_output_file.xml"
with open(outxml, "w") as out:
    valid_xmlstring = mystring.encode('latin1','xmlcharrefreplace').decode('utf8','xmlcharrefreplace')
    out.write(valid_xmlstring) 

Ответ 4

I had a similar problem with char "" in my xml file, which is also invalid xmlchar. This is because in the xml version 1.0, the characters like &#x0, &#xE are not allowed. And the rule is that all character composition as regular expression '&#x[0-1]?[0-9A-E]' are not allowed. My purpose it to correct the invalid char in a huge xml file, based on Rik answer, я improved it as below:

import re

invalid_xml = re.compile(r'&#x[0-1]?[0-9a-eA-E];')

new_file = open('new_file.xml','w') 
with open('old_file.xml') as f:
    for line in f:
        nline, count = invalid_xml.subn('',line)
        new_file.write(nline) 
new_file.close()