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