Использование Python Iterparse для больших файлов XML - PullRequest
36 голосов
/ 24 августа 2011

Мне нужно написать синтаксический анализатор на Python, который может обрабатывать некоторые очень большие файлы (> 2 ГБ) на компьютере без большого объема памяти (всего 2 ГБ).Я хотел использовать iterparse в lxml для этого.

Мой файл имеет формат:

<item>
  <title>Item 1</title>
  <desc>Description 1</desc>
</item>
<item>
  <title>Item 2</title>
  <desc>Description 2</desc>
</item>

, и до сих пор мое решение:

from lxml import etree

context = etree.iterparse( MYFILE, tag='item' )

for event, elem in context :
      print elem.xpath( 'description/text( )' )

del context

К сожалениюхотя, это решение все еще поглощает много памяти.Я думаю, что проблема заключается в том, что после работы с каждым «ПУНКТОМ» мне нужно что-то сделать, чтобы убрать пустых детей.Кто-нибудь может предложить несколько советов о том, что я мог бы сделать после обработки моих данных для правильной очистки?

Ответы [ 5 ]

53 голосов
/ 24 августа 2011

Попробуйте fast_iter Лизы Дейли .После обработки элемента elem он вызывает elem.clear(), чтобы удалить потомков, а также удаляет предшествующих братьев и сестер.

def fast_iter(context, func, *args, **kwargs):
    """
    http://lxml.de/parsing.html#modifying-the-tree
    Based on Liza Daly's fast_iter
    http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
    See also http://effbot.org/zone/element-iterparse.htm
    """
    for event, elem in context:
        func(elem, *args, **kwargs)
        # It's safe to call clear() here because no descendants will be
        # accessed
        elem.clear()
        # Also eliminate now-empty references from the root node to elem
        for ancestor in elem.xpath('ancestor-or-self::*'):
            while ancestor.getprevious() is not None:
                del ancestor.getparent()[0]
    del context


def process_element(elem):
    print elem.xpath( 'description/text( )' )

context = etree.iterparse( MYFILE, tag='item' )
fast_iter(context,process_element)

Статья Дали отлично читается, особенно если вы обрабатываете большие XML-файлы.


Редактировать: fast_iter, размещенный выше, является модифицированной версией Дали fast_iter.После обработки элемента он более агрессивно удаляет другие элементы, которые больше не нужны.

Сценарий ниже показывает разницу в поведении.В частности, обратите внимание, что orig_fast_iter не удаляет элемент A1, а mod_fast_iter удаляет его, тем самым экономя больше памяти.

import lxml.etree as ET
import textwrap
import io

def setup_ABC():
    content = textwrap.dedent('''\
      <root>
        <A1>
          <B1></B1>
          <C>1<D1></D1></C>
          <E1></E1>
        </A1>
        <A2>
          <B2></B2>
          <C>2<D></D></C>
          <E2></E2>
        </A2>
      </root>
        ''')
    return content


def study_fast_iter():
    def orig_fast_iter(context, func, *args, **kwargs):
        for event, elem in context:
            print('Processing {e}'.format(e=ET.tostring(elem)))
            func(elem, *args, **kwargs)
            print('Clearing {e}'.format(e=ET.tostring(elem)))
            elem.clear()
            while elem.getprevious() is not None:
                print('Deleting {p}'.format(
                    p=(elem.getparent()[0]).tag))
                del elem.getparent()[0]
        del context

    def mod_fast_iter(context, func, *args, **kwargs):
        """
        http://www.ibm.com/developerworks/xml/library/x-hiperfparse/
        Author: Liza Daly
        See also http://effbot.org/zone/element-iterparse.htm
        """
        for event, elem in context:
            print('Processing {e}'.format(e=ET.tostring(elem)))
            func(elem, *args, **kwargs)
            # It's safe to call clear() here because no descendants will be
            # accessed
            print('Clearing {e}'.format(e=ET.tostring(elem)))
            elem.clear()
            # Also eliminate now-empty references from the root node to elem
            for ancestor in elem.xpath('ancestor-or-self::*'):
                print('Checking ancestor: {a}'.format(a=ancestor.tag))
                while ancestor.getprevious() is not None:
                    print(
                        'Deleting {p}'.format(p=(ancestor.getparent()[0]).tag))
                    del ancestor.getparent()[0]
        del context

    content = setup_ABC()
    context = ET.iterparse(io.BytesIO(content), events=('end', ), tag='C')
    orig_fast_iter(context, lambda elem: None)
    # Processing <C>1<D1/></C>
    # Clearing <C>1<D1/></C>
    # Deleting B1
    # Processing <C>2<D/></C>
    # Clearing <C>2<D/></C>
    # Deleting B2

    print('-' * 80)
    """
    The improved fast_iter deletes A1. The original fast_iter does not.
    """
    content = setup_ABC()
    context = ET.iterparse(io.BytesIO(content), events=('end', ), tag='C')
    mod_fast_iter(context, lambda elem: None)
    # Processing <C>1<D1/></C>
    # Clearing <C>1<D1/></C>
    # Checking ancestor: root
    # Checking ancestor: A1
    # Checking ancestor: C
    # Deleting B1
    # Processing <C>2<D/></C>
    # Clearing <C>2<D/></C>
    # Checking ancestor: root
    # Checking ancestor: A2
    # Deleting A1
    # Checking ancestor: C
    # Deleting B2

study_fast_iter()
4 голосов
/ 24 августа 2011

iterparse() позволяет вам делать вещи при построении дерева , это означает, что если вы не удалите то, что вам больше не нужно, у вас все равно останется целое дерево в конце.

Для получения дополнительной информации: прочитайте это от автора оригинальной реализации ElementTree (но это также применимо к lxml)

1 голос
/ 24 августа 2011

Почему вы не используете подход "обратного вызова" sax ?

0 голосов
/ 27 марта 2018

Единственная проблема с методом root.clear () - он возвращает NoneTypes. Это означает, что вы не можете, например, редактировать данные, которые вы анализируете, с помощью строковых методов, таких как replace () или title (). Тем не менее, это оптимальный метод для использования, если вы просто анализируете данные как есть.

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

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

for event, elem in iterparse(source): if elem.tag == "record": ... process record elements ... elem.clear() У приведенного выше шаблона есть один недостаток;он не очищает корневой элемент, поэтому вы получите один элемент с множеством пустых дочерних элементов.Если ваши файлы огромные, а не просто большие, это может быть проблемой.Чтобы обойти это, вам нужно заполучить корневой элемент.Самый простой способ сделать это - включить стартовые события и сохранить ссылку на первый элемент в переменной:

получить итеративный

context = iterparse(source, events=("start", "end"))

повернуть егов итератор

context = iter(context)

получить корневой элемент

event, root = context.next()

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

Так что это вопрос инкрементального анализа, Эта ссылка может дать вам подробный ответ для краткого ответа вы можете сослаться на выше

...