2012-01-15 12 views
5

Ho cercato di analizzare alcuni enormi file XML che LXML non ghermire, quindi sono costretto a analizzarli con xml.sax.Come restituire i dati da un parser Python SAX?

class SpamExtractor(sax.ContentHandler): 
    def startElement(self, name, attrs): 
     if name == "spam": 
      print("We found a spam!") 
      # now what? 

Il problema è che non capisco come in realtà return, o meglio, yield, le cose che questo gestore ritrova al chiamante, senza attendere l'intero file da analizzare. Finora, ho lavorato con threading.Thread e Queue.Queue, ma questo porta a tutti i tipi di problemi con i thread che mi distolgono davvero dal problema reale che sto cercando di risolvere.

So che potrei eseguire il parser SAX in un processo separato, ma ritengo che ci sia un modo più semplice per ottenere i dati. È lì?

risposta

6

ho pensato di dare a questo come un'altra risposta a causa di esso che è un approccio completamente diverso.

Si potrebbe voler controllare xml.etree.ElementTree.iterparse come appare a fare di più ciò che si vuole:

analizza una sezione XML in un albero elemento in modo incrementale, e riporta quello che sta succedendo per l'utente. source è un nome file o un oggetto file contenente dati XML. eventi è un elenco di eventi da segnalare. Se omessi, vengono segnalati solo gli eventi "di fine". parser è un'istanza parser opzionale. Se non specificato, viene utilizzato il parser XMLParser standard. Restituisce un iteratore che fornisce coppie (evento, elem).

Si potrebbe quindi scrivere un generatore che esegue l'iteratore, fa ciò che si desidera e fornisce i valori necessari.

es:

def find_spam(xml): 
    for event, element in xml.etree.ElementTree.iterparse(xml): 
     if element.tag == "spam": 
      print("We found a spam!") 
      # Potentially do something 
      yield element 

La differenza è in gran parte di ciò che si desidera. L'approccio iteratore di ElementTree è più incentrato sulla raccolta dei dati, mentre l'approccio SAX riguarda piuttosto l'agire su di esso.

+2

+1 ma aggiungerei quanto segue: (1) uso 'cElementTree', non' ElementTree' (2) 'lxml' ha anche un' iterparse' che fornisce la stessa o migliore funzionalità (3) è necessario menzionare l'eliminazione nodi dopo aver estratto le informazioni richieste (4) AFAICT (mai provato) un generatore dovrebbe funzionare OK –

+0

Vite SAX, sto andando con 'iterparse'! Grazie mille! –

+0

@JohnMachin Non sapevo che esistesse cElementTree - ovviamente, dove è necessaria la velocità, sarebbe una buona scelta - ma non vedo alcun motivo per cui la coperta suggerisca che è un uso in cui la velocità non è una priorità elevata. Per quanto riguarda l'eliminazione dei nodi, non vedo dove sia necessario, potresti spiegare? - Spiegato pochi secondi dopo dai larsman. –

0

La mia comprensione è che il parser SAX è destinato a svolgere il lavoro, non solo a trasferire i dati sulla catena alimentare.

es:

class SpamExtractor(sax.ContentHandler): 
    def __init__(self, canning_machine): 
     self.canning_machine = canning_machine 

    def startElement(self, name, attrs): 
     if name == "spam": 
      print("We found a spam!") 
      self.canning_machine.can(name, attrs) 
+0

Questo è in realtà il setup che ho ora, con un 'Queue' che sostituisce' canning_machine'. Ma quello che sarebbe davvero un modo per riporre gli oggetti trovati in un generatore, evitando i thread. (Raccogliere gli oggetti in un elenco non è un'opzione, l'elenco non si adatta alla memoria.) –

+0

Il mio punto è, dove si ha il ciclo che passa attraverso il generatore, spostare le cose fatte nel ciclo nel parser SAX. Non è possibile creare un generatore in quanto ciò richiederebbe la modifica della funzione '' xml.sax.parse''. Sono d'accordo, un generatore sarebbe un approccio pitonico, ma credo che tu sia limitato da '' xml.sax''. Nota che non ho fatto molto con SAX in Python, quindi potrei mancare qualche trucco. –

0

Fondamentalmente ci sono tre modi di XML parsing:

  1. SAX -approach: si tratta di un'implementazione del modello Visitatore, l'idea è che gli eventi sono spinti al codice.
  2. StAX -approach: dove si tira elemento successivo fino a quando si è pronti (utile per l'analisi parziale, cioè solo lettura intestazione SOAP)
  3. DOM -approach, dove si carica tutto in un albero in memoria

Sembra che tu abbia bisogno del secondo, ma non sono sicuro che sia da qualche parte nella libreria standard.

+0

Per chiarire, per SAX quelli non sono 'eventi' nel senso di eventi/messaggi asincroni, questi sono solo metodi della classe genitore che è possibile implementare e che verranno chiamati da esso mentre analizza. È possibile utilizzare un parser SAX per inviare eventi a una coda. –

5

David Beazley demonstrates come a "resa" i risultati di un sax ContentHandler utilizzando un coroutine:

cosax.py:

import xml.sax 

class EventHandler(xml.sax.ContentHandler): 
    def __init__(self,target): 
     self.target = target 
    def startElement(self,name,attrs): 
     self.target.send(('start',(name,attrs._attrs))) 
    def characters(self,text): 
     self.target.send(('text',text)) 
    def endElement(self,name): 
     self.target.send(('end',name)) 

def coroutine(func): 
    def start(*args,**kwargs): 
     cr = func(*args,**kwargs) 
     cr.next() 
     return cr 
    return start 

# example use 
if __name__ == '__main__': 
    @coroutine 
    def printer(): 
     while True: 
      event = (yield) 
      print event 

    xml.sax.parse("allroutes.xml", 
        EventHandler(printer())) 

Sopra, ogni volta self.target.send è chiamato, il codice all'interno printer piste a partire da event = (yield). event viene assegnato agli argomenti di self.target.send e il codice in printer viene eseguito fino al successivo (yield), un po 'come funziona un generatore.

Mentre un generatore è in genere pilotato da un for-loop, la coroutine (ad esempio printer) viene gestita dalle chiamate send.

Problemi correlati