Парсинг поврежденного XML с lxml.etree.iterparse

Я пытаюсь проанализировать огромный XML-файл с lxml в памяти эффективный способ (т.е. передаю потоком лениво от диска вместо того, чтобы загрузить целый файл в памяти). К сожалению, файл содержит некоторые плохие символы ASCII, которые повреждают синтаксический анализатор по умолчанию. Синтаксический анализатор работает, если я установил recover=True, но iterparse метод не берет восстановить параметр или пользовательский объект синтаксического анализатора. Кто-либо знает, как использовать iterparse для парсинга поврежденного xml?

#this works, but loads the whole file into memory
parser = lxml.etree.XMLParser(recover=True) #recovers from bad characters.
tree = lxml.etree.parse(filename, parser)

#how do I do the equivalent with iterparse?  (using iterparse so the file can be streamed lazily from disk)
context = lxml.etree.iterparse(filename, tag='RECORD')
#record contains 6 elements that I need to extract the text from

Спасибо за помощь!

РЕДАКТИРОВАНИЕ - Вот является примером типов кодирования ошибок, с которыми я сталкиваюсь:

In [17]: data
Out[17]: '\t<articletext>&lt;p&gt;The cafeteria rang with excited voices.  Our barbershop quartet, The Bell \r Tones was asked to perform at the local Home for the Blind in the next town.  We, of course, were glad to entertain such a worthy group and immediately agreed .  One wag joked, "Which uniform should we wear?"  followed with, "Oh, that\'s right, they\'ll never notice."  The others didn\'t respond to this, in fact, one said that we should wear the nicest outfit we had.&lt;/p&gt;&lt;p&gt;A small stage was set up for us and a pretty decent P.A. system was donated for the occasion.  The audience was made up of blind persons of every age, from the thirties to the nineties.  Some sported sighted companions or nurses who stood or sat by their side, sharing the moment equally.  I observed several German shepherds lying at their feet, adoration showing in their eyes as they wondered what was going on.  After a short introduction in which we identified ourselves, stating our voice part and a little about our livelihood, we began our program.  Some songs were completely familiar and others, called "Oh, yeah" songs, only the chorus came to mind.  We didn\'t mind at all that some sang along \x1e they enjoyed it so much.&lt;/p&gt;&lt;p&gt;In fact, a popular part of our program is when the audience gets to sing some of the old favorites.  The harmony parts were quite evident as they tried their voices to the different parts.  I think there was more group singing in the old days than there is now, but to blind people, sound and music is more important.   We received a big hand at the finale and were made to promise to return the following year.  Everyone was treated to coffee and cake, our quartet going around to the different circles of friends to sing a favorite song up close and personal.  As we approached a new group, one blind lady amazed me by turning to me saying, "You\'re the baritone, aren\'t you?"  Previously no one had ever been able to tell which singer sang which part but this lady was listening with her whole heart.&lt;/p&gt;&lt;p&gt;Retired portrait photographer.  Main hobby - quartet singing.&lt;/p&gt;</articletext>\n'

In [18]: lxml.etree.from
lxml.etree.fromstring      lxml.etree.fromstringlist  

In [18]: lxml.etree.fromstring(data)
---------------------------------------------------------------------------
XMLSyntaxError                            Traceback (most recent call last)

/mnt/articles/<ipython console> in <module>()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree.fromstring (src/lxml/lxml.etree.c:48270)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._parseMemoryDocument (src/lxml/lxml.etree.c:71812)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._parseDoc (src/lxml/lxml.etree.c:70673)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._BaseParser._parseDoc (src/lxml/lxml.etree.c:67442)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._ParserContext._handleParseResultDoc (src/lxml/lxml.etree.c:63824)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._handleParseResult (src/lxml/lxml.etree.c:64745)()

/usr/lib/python2.5/site-packages/lxml-2.2.4-py2.5-linux-i686.egg/lxml/etree.so in lxml.etree._raiseParseError (src/lxml/lxml.etree.c:64088)()

XMLSyntaxError: PCDATA invalid Char value 30, line 1, column 1190

In [19]: chardet.detect(data)
Out[19]: {'confidence': 1.0, 'encoding': 'ascii'}

Как Вы видите, chardet думает, что это - файл ASCII, но существует "\x1e" прямо в середине этого примера, который заставляет lxml повысить исключение.

17
задан erikcw 1 March 2010 в 00:11
поделиться

2 ответа

Я решил проблему, создав класс с файловым интерфейсом объекта. Метод read () класса считывает строку из файла и заменяет все «плохие символы» перед возвратом строки в iterparse.

#psudo code

class myFile(object):
    def __init__(self, filename):
        self.f = open(filename)

    def read(self, size=None):
        return self.f.next().replace('\x1e', '').replace('some other bad character...' ,'')


#iterparse
context = lxml.etree.iterparse(myFile('bigfile.xml', tag='RECORD')

Мне пришлось несколько раз редактировать класс myFile, добавляя еще несколько вызовов replace () для нескольких других символов, которые мешали lxml. Я думаю, что синтаксический анализ SAX lxml тоже сработал бы (похоже, поддерживает параметр восстановления), но это решение сработало как шарм!

4
ответ дан 30 November 2019 в 11:08
поделиться

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

Возьмите chardet и скормите ему свой дамп MySQL. Расскажите нам, что там написано.

Покажите нам первые 200-300 байт вашего дампа, используя, например, print repr(dump[:300])

Update Вы написали """Как вы можете видеть, chardet думает, что это ascii файл, но есть "\x1e" прямо в середине этого примера, который заставляет lxml поднимать исключение."""

Я не вижу здесь "плохого юникода".

chardet прав. Почему вы думаете, что "\x1e" не ASCII? Это символ ASCII, управляющий символ C0 под названием "RECORD SEPARATOR".

В сообщении об ошибке говорится, что у вас недопустимый символ. Это также верно. Единственными управляющими символами, которые допустимы в XML, являются "\t", "\r" и "\n". MySQL должен ворчать по этому поводу и/или предлагать вам способ экранирования, например, _x001e_ (тьфу!)

Учитывая контекст, похоже, что этот символ можно удалить без потерь. Вы можете исправить свою базу данных или удалить подобные символы из своего дампа (после проверки, что все они исчезают), или выбрать менее придирчивый и менее вонючий формат вывода, чем XML.

Обновление 2 Предположительно, вы хотите использовать iterparse() не потому, что это ваша конечная цель, а потому, что вы хотите сэкономить память. Если бы вы использовали формат типа CSV, у вас не было бы проблем с памятью.

Update 3 В ответ на комментарий от @Purrell:

попробуй сам, чувак. pastie.org/3280965

Вот содержимое этой пасты; она заслуживает сохранения:

from lxml.etree import etree

data = '\t<articletext>&lt;p&gt;The cafeteria rang with excited voices.  Our barbershop quartet, The Bell \r Tones was asked to perform at the local Home for the Blind in the next town.  We, of course, were glad to entertain such a worthy group and immediately agreed .  One wag joked, "Which uniform should we wear?"  followed with, "Oh, that\'s right, they\'ll never notice."  The others didn\'t respond to this, in fact, one said that we should wear the nicest outfit we had.&lt;/p&gt;&lt;p&gt;A small stage was set up for us and a pretty decent P.A. system was donated for the occasion.  The audience was made up of blind persons of every age, from the thirties to the nineties.  Some sported sighted companions or nurses who stood or sat by their side, sharing the moment equally.  I observed several German shepherds lying at their feet, adoration showing in their eyes as they wondered what was going on.  After a short introduction in which we identified ourselves, stating our voice part and a little about our livelihood, we began our program.  Some songs were completely familiar and others, called "Oh, yeah" songs, only the chorus came to mind.  We didn\'t mind at all that some sang along \x1e they enjoyed it so much.&lt;/p&gt;&lt;p&gt;In fact, a popular part of our program is when the audience gets to sing some of the old favorites.  The harmony parts were quite evident as they tried their voices to the different parts.  I think there was more group singing in the old days than there is now, but to blind people, sound and music is more important.   We received a big hand at the finale and were made to promise to return the following year.  Everyone was treated to coffee and cake, our quartet going around to the different circles of friends to sing a favorite song up close and personal.  As we approached a new group, one blind lady amazed me by turning to me saying, "You\'re the baritone, aren\'t you?"  Previously no one had ever been able to tell which singer sang which part but this lady was listening with her whole heart.&lt;/p&gt;&lt;p&gt;Retired portrait photographer.  Main hobby - quartet singing.&lt;/p&gt;</articletext>\n'

magical_parser = etree.XMLParser(encoding='utf-8', recover=True)
tree = etree.parse(StringIO(data), magical_parser)

Чтобы заставить его работать, нужно исправить один импорт и поставить другой. Данные чудовищны. Нет никакого вывода, чтобы показать результат. Вот замена с урезанными до минимума данными. 5 частей текста ASCII (за исключением < и >), которые все являются допустимыми символами XML, заменены на t1, ..., t5. Преступный \x1e находится на флангах t2 и t3.

[output wraps at column 80]
Python 2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> from lxml import etree
>>> from cStringIO import StringIO
>>> data = '<article>&lt;p&gt;t1&lt;/p&gt;&lt;p&gt;t2\x1et3&lt;/p&gt;&lt;p&gt;t4
&lt;/p&gt;&lt;p&gt;t5&lt;/p&gt;</article>'
>>> magical_parser = etree.XMLParser(encoding='utf-8', recover=True)
>>> tree = etree.parse(StringIO(data), magical_parser)
>>> print(repr(tree.getroot().text))
'<p>t1</p><p>t2t3/ppt4/ppt5/p'

Не то, что я бы назвал "восстановлением"; после плохого персонажа исчезают < и >.

Пастиш был ответом на мой вопрос "Что заставляет вас думать, что кодировка='utf-8' решит его проблему?". Это было вызвано заявлением "Однако существует опция "encoding", которая могла бы решить вашу проблему. Но кодировка=ascii выдает тот же результат. Также как и отсутствие аргумента кодировки. Это НЕ проблема кодировки. Дело закрыто.

0
ответ дан 30 November 2019 в 11:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: