Разбор сломанного XML с помощью lxml.etree.iterparse - PullRequest
14 голосов
/ 01 марта 2010

Я пытаюсь проанализировать огромный xml-файл с помощью lxml эффективным способом, связанным с памятью (т. Е. Лениво воспроизводить поток с диска вместо загрузки всего файла в память) К сожалению, файл содержит некоторые плохие символы ascii, которые нарушают синтаксический анализатор по умолчанию. Синтаксический анализатор работает, если я установил recovery = 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 вызвать исключение.

Ответы [ 3 ]

38 голосов
/ 29 января 2012

Edit:

Это более старый ответ, и я бы сделал это по-другому сегодня. И я не просто имею в виду тупую змею ... с тех пор BeutifulSoup4 доступна, и это действительно довольно приятно. Я рекомендую это всем, кто спотыкается здесь.


В настоящее время принятый ответ, ну, не то, что нужно делать. Сам вопрос также имеет неверное предположение:

parser = lxml.etree.XMLParser (recover = True) # восстанавливается после плохих символов.

На самом деле recover=True предназначен для восстановления из искаженного XML . Однако есть опция "кодирования" , которая бы исправила вашу проблему.

parser = lxml.etree.XMLParser(encoding='utf-8' #Your encoding issue.
                              recover=True, #I assume you probably still want to recover from bad xml, it's quite nice. If not, remove.
                              )

Вот и все, вот решение.


BTW - Для тех, кто борется с синтаксическим анализом XML в python, особенно из сторонних источников. Я знаю, я знаю, документация плохая, и там много ТАКИХ красных селедок; много плохих советов.

  • lxml.etree.fromstring ()? - Это для отлично сформированного XML, глупо
  • BeautifulStoneSoup? - Медленно, и имеет тупую политику для себя закрывающие теги
  • lxml.etree.HTMLParser ()? - (потому что xml не работает) Вот секрет - HTMLParser () это ... Парсер с recovery = True
  • lxml.html.soupparser? - Обнаружение кодирования должно быть лучше, но оно имеет те же недостатки BeautifulSoup для самозакрывающихся тегов. Возможно, вы можете комбинировать XMLParser с UnicodeDammit от BeautifulSoup
  • UnicodeDammit и другие мелочи для исправления кодировок? - Ну, UnicodeDammit довольно симпатичен, мне нравится название, и оно полезно для вещей, помимо xml, но обычно все исправляется, если вы делаете правильные вещи с XMLParser ()

Возможно, вы пробуете все что угодно из того, что доступно онлайн. Документация lxml может быть лучше. Приведенный выше код - это то, что вам нужно для 90% случаев анализа XML. Здесь я переформулирую это:

magical_parser = XMLParser(encoding='utf-8', recover=True)
tree = etree.parse(StringIO(your_xml_string), magical_parser) #or pass in an open file object

Пожалуйста. Мои головные боли == ваше здравомыслие. Кроме того, в нем есть и другие функции, которые могут вам понадобиться для XML.

4 голосов
/ 05 марта 2010

Я решил проблему, создав класс с интерфейсом объекта типа File. Метод 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 подавиться. Я думаю, что синтаксический анализ lxml SAX также работал бы (кажется, поддерживает опцию восстановления), но это решение работало как чудо!

0 голосов
/ 01 марта 2010

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

Получите chardet и загрузите в него свой дамп MySQL. Скажите нам, что там написано.

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

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

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

chardet правильно. С чего вы взяли, что "\ x1e" не ASCII? Это символ ASCII, управляющий символ C0 с именем «РАЗДЕЛИТЕЛЬ ЗАПИСИ».

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

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

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

Обновление 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 (исключая &lt; и &gt;), которые являются действительными символами 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' решит его проблему?" Это было вызвано утверждением «Однако существует опция« кодирования », которая исправила бы вашу проблему». Но кодировка = ascii выдает тот же результат. Так же опуская кодировку arg. Это НЕ проблема кодирования. Дело закрыто.

...