Какой самый быстрый способ анализа больших документов XML в Python? - PullRequest
54 голосов
/ 27 ноября 2008

В настоящее время я использую следующий код на основе главы 12.5 Python Cookbook:

from xml.parsers import expat

class Element(object):
    def __init__(self, name, attributes):
        self.name = name
        self.attributes = attributes
        self.cdata = ''
        self.children = []
    def addChild(self, element):
        self.children.append(element)
    def getAttribute(self,key):
        return self.attributes.get(key)
    def getData(self):
        return self.cdata
    def getElements(self, name=''):
        if name:
            return [c for c in self.children if c.name == name]
        else:
            return list(self.children)

class Xml2Obj(object):
    def __init__(self):
        self.root = None
        self.nodeStack = []
    def StartElement(self, name, attributes):
        element = Element(name.encode(), attributes)
        if self.nodeStack:
            parent = self.nodeStack[-1]
            parent.addChild(element)
        else:
            self.root = element
        self.nodeStack.append(element)
    def EndElement(self, name):
        self.nodeStack.pop()
    def CharacterData(self,data):
        if data.strip():
            data = data.encode()
            element = self.nodeStack[-1]
            element.cdata += data
    def Parse(self, filename):
        Parser = expat.ParserCreate()
        Parser.StartElementHandler = self.StartElement
        Parser.EndElementHandler = self.EndElement
        Parser.CharacterDataHandler = self.CharacterData
        ParserStatus = Parser.Parse(open(filename).read(),1)
        return self.root

Я работаю с XML-документами размером около 1 ГБ. Кто-нибудь знает более быстрый способ разобрать их?

Ответы [ 8 ]

60 голосов
/ 28 ноября 2008

Мне кажется, что вам не нужны какие-либо возможности DOM из вашей программы. Я бы поддержал использование библиотеки (c) ElementTree. Если вы используете функцию iterparse модуля cElementTree, вы можете пробираться через xml и обрабатывать события по мере их возникновения.

Обратите внимание, однако, совет Fredriks по использованию cElementTree iterparse function :

для анализа больших файлов вы можете избавиться от элементов, как только вы их обработали:

for event, elem in iterparse(source):
    if elem.tag == "record":
        ... process record elements ...
        elem.clear()

Приведенный выше шаблон имеет один недостаток; он не очищает корневой элемент, поэтому вы получите один элемент с множеством пустых дочерних элементов. Если ваши файлы огромные, а не просто большие, это может быть проблемой. Чтобы обойти это, вам нужно заполучить корневой элемент. Самый простой способ сделать это - включить стартовые события и сохранить ссылку на первый элемент в переменной:

# get an iterable
context = iterparse(source, events=("start", "end"))

# turn it into an iterator
context = iter(context)

# get the root element
event, root = context.next()

for event, elem in context:
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()

lxml.iterparse () не позволяет этого.

Предыдущий не работает на Python 3.7, рассмотрите следующий способ получить первый элемент.

# get an iterable
context = iterparse(source, events=("start", "end"))

is_first = True

for event, elem in context:
    # get the root element
    if is_first:
        root = elm
        is_first = False
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()
15 голосов
/ 27 ноября 2008

Вы пробовали модуль cElementTree?

cElementTree включен в Python 2.5 и более поздние версии как xml.etree.cElementTree. См. тесты .

удалена мертвая ссылка ImageShack

8 голосов
/ 27 ноября 2008

Я рекомендую вам использовать lxml , это Python-привязка для библиотеки libxml2, которая очень быстрая.

По моему опыту, libxml2 и expat имеют очень похожую производительность. Но я предпочитаю libxml2 (и lxml для python), потому что он, кажется, более активно разрабатывается и тестируется. Также libxml2 имеет больше возможностей.

lxml в основном API-совместим с xml.etree.ElementTree . И на его веб-сайте есть хорошая документация.

5 голосов
/ 27 ноября 2008

Регистрация обратных вызовов значительно замедляет анализ. [РЕДАКТИРОВАТЬ] Это потому, что (быстрый) код C должен вызывать интерпретатор Python, который не так быстр, как C. В основном, вы используете код C для чтения файла (быстро) и затем строите DOM в Python (медленно). [/ EDIT]

Попробуйте использовать xml.etree.ElementTree, который реализован на 100% в C и который может анализировать XML без каких-либо обратных вызовов к коду Python.

После того, как документ был проанализирован, вы можете отфильтровать его, чтобы получить то, что вы хотите.

Если это все еще слишком медленно и вам не нужен DOM, другой вариант - прочитать файл в строку и использовать простые строковые операции для его обработки.

4 голосов
/ 28 ноября 2008

Если ваше приложение чувствительно к производительности и может встретиться с большими файлами (как вы сказали,> 1 ГБ), тогда я бы настоятельно рекомендовал не использовать код, который вы показываете в своем вопросе, для простого Причина, по которой загружает весь документ в ОЗУ . Я бы посоветовал вам переосмыслить свой дизайн (если это вообще возможно), чтобы избежать одновременного хранения всего дерева документов в оперативной памяти. Не зная, каковы требования вашего приложения, я не могу правильно предложить какой-либо конкретный подход, кроме общего совета, чтобы попытаться использовать дизайн, основанный на событиях.

1 голос

expat ParseFile работает хорошо, если вам не нужно хранить все дерево в памяти, что рано или поздно приведет к потере оперативной памяти для больших файлов:

import xml.parsers.expat
parser = xml.parsers.expat.ParserCreate()
parser.ParseFile(open('path.xml', 'r'))

Считывает файлы на куски и передает их в анализатор без разрыва ОЗУ.

Документ: https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

0 голосов
/ 10 мая 2019

Я потратил довольно много времени, чтобы попробовать это, и кажется, что самый быстрый и наименее ресурсоемкий подход - использование lxml и iterparse, но при этом необходимо освободить ненужную память. В моем примере парсинг дампа arXiv:

from lxml import etree

context = etree.iterparse('path/to/file', events=('end',), tag='Record')

for event, element in context:
    record_id = element.findtext('.//{http://arxiv.org/OAI/arXiv/}id')
    created = element.findtext('.//{http://arxiv.org/OAI/arXiv/}created')

    print(record_id, created)

    # Free memory.
    element.clear()
    while element.getprevious() is not None:
        del element.getparent()[0]

Так что element.clear недостаточно, но и удаление любых ссылок на предыдущие элементы.

0 голосов
/ 29 ноября 2008

Видимо PyRXP действительно быстро.

Они утверждают, что это самый быстрый парсер, но cElementTree нет в их списке статистики.

...