Uso De Python Iterparse Para Archivos XML Grandes


Necesito escribir un analizador sintáctico en Python que pueda procesar algunos archivos extremadamente grandes ( > 2 GB ) en un equipo sin mucha memoria (solo 2 GB). Quería usar iterparse en lxml para hacerlo.

Mi archivo es del formato:

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

Y hasta ahora mi solución es:

from lxml import etree

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

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

del context

Desafortunadamente, esta solución todavía está consumiendo mucha memoria. Creo que el problema es que después de tratar con cada "ELEMENTO" tengo que hacer algo para limpiar los niños vacíos. ¿Puede alguien ofrecer algo sugerencias sobre lo que podría hacer después de procesar mis datos para limpiar correctamente?

5 answers

Intenta El fast_iter de Liza Daly. Después de procesar un elemento, elem, llama a elem.clear() para eliminar descendientes y también elimina los hermanos anteriores.

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)

El artículo de Daly es una excelente lectura, especialmente si está procesando archivos XML grandes.


Editar: El fast_iter publicado arriba es una versión modificada del fast_iter de Daly. Después de procesar un elemento, es más agresivo en la eliminación de otros elementos que ya no son necesarios.

El script de abajo muestra la diferencia de comportamiento. Tenga en cuenta en particular que orig_fast_iter no elimina el elemento A1, mientras que el mod_fast_iter lo elimina, ahorrando así más memoria.

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()
 48
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-19 18:15:46

iterparse() le permite hacer cosas mientras construye el árbol, eso significa que a menos que elimine lo que ya no necesita, aún terminará con el árbol completo al final.

Para más información: lea esto por el autor de la implementación original de ElementTree (pero también es aplicable a lxml)

 4
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-24 08:38:16

¿Por qué no utilizas el enfoque "callback" de sax?

 1
Author: Elazar Leibovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-24 06:19:58

Tenga en cuenta que iterparse todavía construye un árbol, al igual que parse, pero puede reorganizar o eliminar partes del árbol de forma segura mientras analiza. Por ejemplo, para analizar archivos grandes, puede deshacerse de elementos tan pronto como los haya procesado:

for event, elem in iterparse(source): if elem.tag == "record": ... process record elements ... elem.clear() El patrón anterior tiene un inconveniente; no borra el elemento raíz, por lo que terminará con un solo elemento con muchos elementos secundarios vacíos. Si sus archivos son enormes, en lugar de solo grandes, esto podría ser un problema. Para solucionar esto, necesito poner tus manos en el elemento raíz. La forma más fácil de hacerlo es habilitar los eventos de inicio y guardar una referencia al primer elemento de una variable:

Obtener un iterable

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

Conviértelo en un iterador

context = iter(context)

Obtiene el elemento raíz

event, root = context.next()

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

Así que esta es una cuestión de Análisis incremental, Este enlace puede darle una respuesta detallada para una respuesta resumida puede consultar la anterior

 0
Author: Ash Upadhyay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-30 09:53:33

El único problema con la raíz.el método clear () es devuelve NoneTypes. Esto significa que no puede, por ejemplo, editar los datos que analiza con métodos de cadena como replace() o title(). Dicho esto, este es un método óptimo para usar si solo está analizando los datos tal como están.

 0
Author: Jason Argo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-27 02:26:46