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

Анализ разбитого 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

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

EDIT. Ниже приведен пример типов ошибок кодирования, в которые я запускаю:

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\ 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 причиной исключения.

4b9b3361

Ответ 1

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

Ответ 2

Изменить:

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


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

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

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

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

Что это, что решение.


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

  • lxml.etree.fromstring()? - для идеально сформированного XML, глупых
  • BeautifulStoneSoup?. Медленно, и у вас есть путь-глупая политика для себя закрывающие теги
  • lxml.etree.HTMLParser()? - (потому что xml не работает) Здесь секрет - HTMLParser() - это... Parser with recover = True
  • lxml.html.soupparser?. Обнаружение кодировки должно быть лучше, но оно имеет те же недостатки BeautifulSoup, что и для самозакрывающихся тегов. Возможно, вы можете объединить XMLParser с BeautifulSoup UnicodeDammit
  • 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.

Ответ 3

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

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

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

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

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

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

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

Учитывая контекст, похоже, что этот символ можно удалить без потери. Возможно, вы захотите исправить свою базу данных, или вы можете удалить такие символы из своего дампа (после проверки того, что все они пропущены), или вы можете выбрать менее привлекательный и менее впечатляющий формат вывода, чем 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\ 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. Это НЕ проблема кодирования. Закрыто.