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?
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()
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)
Pourquoi n'utilisez-vous pas l'approche de "rappel" de sax ?
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.