Utiliser Python Iterparse pour les gros fichiers XML

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

mon fichier est du format:

<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

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

33

5 réponses

Liza Daly fast_iter. Après traitement d'un élément,elem, il appelle elem.clear() pour supprimer les descendants et supprime également les frères et sœurs 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 êtes en train de traiter de gros fichiers XML.


Edit: The fast_iter posté ci-dessus est une version modifiée de Daly fast_iter. Après avoir traité un élément, il est plus agressif à enlever d'autres éléments qui ne sont plus nécessaire.

Le script ci-dessous montre la différence de comportement. Notez en particulier que orig_fast_iter ne supprime pas le A1 élément, tandis que l' mod_fast_iter supprimer, économisant ainsi plus 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()
48
répondu unutbu 2014-03-19 18:15:46

iterparse() permet de faire des choses en construisant l'arbre, cela signifie que si vous ne retirez pas ce dont vous n'avez plus besoin, vous finirez avec l'arbre entier à la fin.

Pour plus d'informations: lire par l'auteur de l'original ElementTree de mise en œuvre (mais il est également applicable à lxml)

4
répondu Steven 2011-08-24 08:38:16

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

1
répondu Elazar Leibovich 2011-08-24 06:19:58

notez qu'iterparse construit toujours un arbre, tout comme parse, mais vous pouvez sans risque réarranger ou enlever des parties de l'arbre pendant l'analyse. Par exemple, pour analyser des fichiers volumineux, vous pouvez vous débarrasser des éléments dès que vous avez traités:

for event, elem in iterparse(source): if elem.tag == "record": ... process record elements ... elem.clear() Le schéma ci-dessus a un inconvénient; elle n'efface pas l'élément racine, de sorte que vous finirez avec un élément unique avec beaucoup de vide éléments enfants. Si vos fichiers sont énormes, plutôt que de vastes, ce pourrait être un problème. Pour contourner cela, vous devez obtenir vos mains sur l'élément racine. La façon la plus simple de le faire est d'activer les événements start et de sauvegarder une référence au premier élément d'une variable:

obtenir un objet iterable

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

en faire un itérateur

context = iter(context)

obtenir l'élément racine

event, root = context.next()

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

donc c'est une question D'analyse incrémentielle , ce lien peut vous donner une réponse détaillée pour une réponse sommaire, vous pouvez vous référer le ci-dessus

0
répondu Ash Upadhyay 2017-10-30 09:53:33

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

0
répondu Jason Argo 2018-03-27 02:26:46