web-dev-qa-db-fra.com

Quel est le moyen le plus rapide pour analyser de gros documents XML en Python?

J'exécute actuellement le code suivant basé sur le chapitre 12.5 du livre de recettes Python:

from xml.parsers import expat

class Element(object):
    def __init__(self, name, attributes):
        self.name = name
        self.attributes = attributes
        self.cdata = ''
        self.children = []
    def addChild(self, element):
        self.children.append(element)
    def getAttribute(self,key):
        return self.attributes.get(key)
    def getData(self):
        return self.cdata
    def getElements(self, name=''):
        if name:
            return [c for c in self.children if c.name == name]
        else:
            return list(self.children)

class Xml2Obj(object):
    def __init__(self):
        self.root = None
        self.nodeStack = []
    def StartElement(self, name, attributes):
        element = Element(name.encode(), attributes)
        if self.nodeStack:
            parent = self.nodeStack[-1]
            parent.addChild(element)
        else:
            self.root = element
        self.nodeStack.append(element)
    def EndElement(self, name):
        self.nodeStack.pop()
    def CharacterData(self,data):
        if data.strip():
            data = data.encode()
            element = self.nodeStack[-1]
            element.cdata += data
    def Parse(self, filename):
        Parser = expat.ParserCreate()
        Parser.StartElementHandler = self.StartElement
        Parser.EndElementHandler = self.EndElement
        Parser.CharacterDataHandler = self.CharacterData
        ParserStatus = Parser.Parse(open(filename).read(),1)
        return self.root

Je travaille avec des documents XML d'environ 1 Go. Quelqu'un connaît-il un moyen plus rapide de les analyser?

56
Jeroen Dirks

J'ai l'impression que vous n'avez pas besoin de capacités DOM de votre programme. J'appuierais l'utilisation de la bibliothèque (c) ElementTree. Si vous utilisez la fonction iterparse du module cElementTree, vous pouvez parcourir le xml et gérer les événements au fur et à mesure qu'ils se produisent.

Notez cependant les conseils de Fredriks sur l'utilisation de cElementTree fonction iterparse :

pour analyser des fichiers volumineux, vous pouvez vous débarrasser des éléments dès que vous les avez traités:

for event, elem in iterparse(source):
    if elem.tag == "record":
        ... process record elements ...
        elem.clear()

Le modèle ci-dessus a un inconvénient; il n'efface pas l'élément racine, vous vous retrouverez donc avec un seul élément avec beaucoup d'éléments enfants vides. Si vos fichiers sont volumineux, plutôt que simplement volumineux, cela peut être un problème. Pour contourner ce problème, vous devez mettre la main sur l'élément racine. La façon la plus simple de procéder consiste à activer les événements de démarrage et à enregistrer une référence au premier élément d'une variable:

# get an iterable
context = iterparse(source, events=("start", "end"))

# turn it into an iterator
context = iter(context)

# get the root element
event, root = context.next()

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

lxml.iterparse () ne le permet pas.

Le précédent ne fonctionne pas sur Python 3.7, considérez la façon suivante pour obtenir le premier élément.

# get an iterable
context = iterparse(source, events=("start", "end"))

is_first = True

for event, elem in context:
    # get the root element
    if is_first:
        root = Elm
        is_first = False
    if event == "end" and elem.tag == "record":
        ... process record elements ...
        root.clear()
63
Steen

Avez-vous essayé le module cElementTree?

cElementTree est inclus avec Python 2.5 et versions ultérieures, sous la forme xml.etree.cElementTree. Reportez-vous à benchmarks .

Suppression du lien ImageShack mort

15
bhadra

Je vous recommande d'utiliser lxml , c'est une liaison python pour la bibliothèque libxml2 qui est vraiment rapide.

D'après mon expérience, libxml2 et expat ont des performances très similaires. Mais je préfère libxml2 (et lxml pour python) car il semble être développé et testé plus activement. Libxml2 a également plus de fonctionnalités.

lxml est principalement compatible API avec xml.etree.ElementTree . Et il y a une bonne documentation sur son site Web.

8
Manuel Ceron

L'enregistrement des rappels ralentit considérablement l'analyse. [EDIT] C'est parce que le code C (rapide) doit invoquer l'interpréteur python qui n'est tout simplement pas aussi rapide que C. En gros, vous utilisez le code C pour lire le fichier ( rapide), puis créez le DOM dans Python (lent). [/ EDIT]

Essayez d'utiliser xml.etree.ElementTree qui est implémenté à 100% en C et qui peut analyser XML sans aucun rappel vers python code.

Une fois le document analysé, vous pouvez le filtrer pour obtenir ce que vous voulez.

Si c'est encore trop lent et que vous n'avez pas besoin d'un DOM, une autre option consiste à lire le fichier dans une chaîne et à utiliser des opérations de chaîne simples pour le traiter.

5
Aaron Digulla

Si votre application est sensible aux performances et susceptible de rencontrer des fichiers volumineux (comme vous l'avez dit,> 1 Go), je vous déconseille d'utiliser fortement contre le code affiché dans votre question pour la simplicité raison il charge tout le document dans la RAM . Je vous encourage à repenser votre conception (si possible) pour éviter de tenir l'arborescence complète du document dans RAM à la fois. Ne sachant pas quelles sont les exigences de votre application, je ne peux pas suggérer correctement approche spécifique, autre que le conseil générique pour essayer d'utiliser une conception "basée sur les événements".

4
Matt Campbell

expat ParseFile fonctionne bien si vous n'avez pas besoin de stocker la totalité de l'arborescence en mémoire, ce qui fera tôt ou tard exploser votre RAM pour les fichiers volumineux:

import xml.parsers.expat
parser = xml.parsers.expat.ParserCreate()
parser.ParseFile(open('path.xml', 'r'))

Il lit les fichiers en morceaux et les transmet à l'analyseur sans exploser la RAM.

Doc: https://docs.python.org/2/library/pyexpat.html#xml.parsers.expat.xmlparser.ParseFile

Apparemment PyRXP est vraiment rapide.

Ils prétendent que c'est l'analyseur le plus rapide - mais cElementTree n'est pas dans leur liste de statistiques.

0
Matthew Schinckel

J'ai passé un certain temps à essayer cela et il semble que l'approche la plus rapide et la moins gourmande en mémoire utilise lxml et iterparse, mais en veillant à libérer de la mémoire inutile. Dans mon exemple, analyser le vidage arXiv:

from lxml import etree

context = etree.iterparse('path/to/file', events=('end',), tag='Record')

for event, element in context:
    record_id = element.findtext('.//{http://arxiv.org/OAI/arXiv/}id')
    created = element.findtext('.//{http://arxiv.org/OAI/arXiv/}created')

    print(record_id, created)

    # Free memory.
    element.clear()
    while element.getprevious() is not None:
        del element.getparent()[0]

Donc element.clear ne suffit pas, mais aussi la suppression des liens vers les éléments précédents.

0
Mitar