web-dev-qa-db-fra.com

Utilisation de Python Iterparse pour les gros fichiers XML

J'ai besoin d'écrire un analyseur en Python capable de traiter des fichiers extrêmement volumineux (> 2 Go) sur un ordinateur sans beaucoup de mémoire (seulement 2 Go). Je voulais utiliser iterparse en lxml pour le faire.

Mon fichier est au format:

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

et jusqu'ici ma solution est:

from lxml import etree

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

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

del context

Malheureusement, cette solution consomme encore beaucoup de mémoire. Je pense que le problème est qu'après avoir traité chaque "ITEM", je dois faire quelque chose pour nettoyer les enfants vides. Quelqu'un peut-il offrir des suggestions sur ce que je pourrais faire après le traitement correct de mes données pour le nettoyer correctement?

36
Dave Johnshon

Essayez fast_iter de Liza Daly . Après le traitement d'un élément, elem, il appelle elem.clear() pour supprimer les descendants et supprime également les frères et soeurs précédents. 

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)

L'article de Daly est une excellente lecture, surtout si vous traitez des fichiers XML volumineux.


Edit: Le fast_iter publié ci-dessus est une version modifiée du fast_iter de Daly. Après le traitement d'un élément, il est plus agressif de supprimer les autres éléments devenus inutiles.

Le script ci-dessous montre la différence de comportement. Notez en particulier que orig_fast_iter ne supprime pas l'élément A1, alors que le mod_fast_iter le supprime, économisant ainsi davantage de mémoire.

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

iterparse() vous permet de faire des choses tout en construisant l’arbre, ce qui signifie que si vous ne supprimez plus ce dont vous n’avez plus besoin, vous finirez toujours par avoir l’arbre entier.

Pour plus d'informations, lisez this par l'auteur de la mise en oeuvre d'ElementTree d'origine (mais cela s'applique également à lxml)

4
Steven

Pourquoi n'utilisez-vous pas l'approche de "rappel" de sax ?

1
Elazar Leibovich

Le seul problème avec la méthode root.clear () est qu’elle ne renvoie NoneTypes. Cela signifie que vous ne pouvez pas, par exemple, éditer les données que vous analysez avec des méthodes de type chaîne comme replace () ou title (). Cela dit, il s'agit d'une méthode optimale à utiliser si vous analysez simplement les données telles quelles.

0
Jason Argo