Python саксофон в lxml для 80 + ГБ XML - PullRequest
10 голосов
/ 21 марта 2012

Как бы вы прочитали XML-файл, используя sax, и преобразовали его в элемент lxml etree.iterparse?

Чтобы дать общее представление о проблеме, я создал инструмент приема XML, использующий lxml для подачи XML, который будет иметь размер 25–500 МБ, который требуется для приема дважды в день, но должен выполнять один время загрузки файла размером 60 - 100 ГБ.

Я решил использовать lxml, основываясь на спецификациях, согласно которым размер узла не должен превышать 4-8 ГБ, что, как я думал, позволит узлу считываться в память и очищаться после завершения.

Обзор, если код ниже

elements = etree.iterparse(
    self._source, events = ('end',)
)
for event, element in elements:
    finished = True
    if element.tag == 'Artist-Types':
        self.artist_types(element)

def artist_types(self, element):
    """
    Imports artist types

    :param list element: etree.Element
    :returns boolean:
    """
    self._log.info("Importing Artist types")
    count = 0
    for child in element:
        failed = False
        fields = self._getElementFields(child, (
            ('id', 'Id'),
            ('type_code', 'Type-Code'),
            ('created_date', 'Created-Date')
        ))
        if self._type is IMPORT_INC and has_artist_type(fields['id']):
            if update_artist_type(fields['id'], fields['type_code']):
                count = count + 1
            else:
                failed = True
        else:
            if create_artist_type(fields['type_code'],
                fields['created_date'], fields['id']):
                count = count + 1
            else:
                failed = True
        if failed:
            self._log.error("Failed to import artist type %s %s" %
                (fields['id'], fields['type_code'])
            )
    self._log.info("Imported %d Artist Types Records" % count)
    self._artist_type_count = count
    self._cleanup(element)
    del element

Дайте мне знать, могу ли я добавить какие-либо разъяснения.

Ответы [ 3 ]

21 голосов
/ 22 марта 2012

iterparse - это итеративный парсер. Он будет генерировать Element объекты и события и постепенно создавать все дерево Element при его анализе, поэтому в конечном итоге в памяти будет все дерево.

Однако поведение ограниченной памяти легко: удаляйте ненужные элементы, когда вы их анализируете.

Типичная рабочая нагрузка типа «гигантский xml» - это один корневой элемент с большим количеством дочерних элементов, которые представляют записи. Я предполагаю, что это та структура XML, с которой вы работаете?

Обычно достаточно использовать clear() для очистки элемента, который вы обрабатываете. Ваше использование памяти будет немного расти, но это не очень много. Если у вас действительно огромный файл, то даже пустые Element объекты будут потреблять слишком много, и в этом случае вы также должны удалить ранее видимые Element объекты. Обратите внимание, что вы не можете безопасно удалить текущий элемент. Документация lxml.etree.iterparse описывает эту технику .

В этом случае вы будете обрабатывать запись каждый раз при обнаружении </record>, затем удаляете все предыдущие элементы записи.

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

from lxml import etree
import resource

class InfiniteXML (object):
    def __init__(self):
        self._root = True
    def read(self, len=None):
        if self._root:
            self._root=False
            return "<?xml version='1.0' encoding='US-ASCII'?><records>\n"
        else:
            return """<record>\n\t<ancestor attribute="value">text value</ancestor>\n</record>\n"""

def parse(fp):
    context = etree.iterparse(fp, events=('end',))
    for action, elem in context:
        if elem.tag=='record':
            # processing goes here
            pass

        #memory usage
        print resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

        # cleanup
        # first empty children from current element
            # This is not absolutely necessary if you are also deleting siblings,
            # but it will allow you to free memory earlier.
        elem.clear()
        # second, delete previous siblings (records)
        while elem.getprevious() is not None:
            del elem.getparent()[0]
        # make sure you have no references to Element objects outside the loop

parse(InfiniteXML())
3 голосов
/ 21 марта 2012

Я нашел этот полезный пример на http://effbot.org/zone/element-iterparse.htm. Жирный акцент - мой.

Инкрементальный синтаксический анализ #

Обратите внимание, что iterparse по-прежнему создает дерево, так же, как parse,но вы можете безопасно переставить или удалить части дерева при разборе.Например, для разбора больших файлов вы можете избавиться от элементов, как только вы их обработали:

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

(будущие выпуски упростят доступ к корневому элементу из цикла)

0 голосов
/ 03 октября 2017

Этому пару лет, и у меня недостаточно репутации, чтобы напрямую комментировать принятый ответ, но я попытался использовать это для анализа OSM, где я нахожу все пересечения в стране. Моя первоначальная проблема заключалась в том, что у меня не хватало оперативной памяти, поэтому я подумал, что мне придется использовать синтаксический анализатор SAX, но вместо этого нашел ответ. Странно, что это не было правильно, и использование предложенной очистки каким-то образом очищало элемент elem перед чтением его (все еще не уверенный, как это происходило). Удалил elem.clear() из кода и теперь он работает отлично!

...