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

Каков самый быстрый способ анализа больших XML-документов в Python?

В настоящее время я использую следующий код на основе главы 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 ГБ. Кто-нибудь знает более быстрый способ разобрать их?

4b9b3361

Ответ 1

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

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

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

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()

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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

Doc: https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

Ответ 7

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

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

Ответ 8

Я потратил довольно много времени, чтобы попробовать это, и кажется, что самый быстрый и наименее ресурсоемкий подход - это использование 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 не достаточно, но также удаление любых ссылок на предыдущие элементы.