2015-04-01 15 views
7

Sto cercando di analizzare il pianeta.osm di OpenStreetMap, compresso in formato bz2. Poiché è già 41G, non voglio decomprimere completamente il file.Analisi di un file .bz2 di grandi dimensioni (40 GB) con lxml iterparse in python. Errore non visualizzato con file non compresso

Così ho capito come analizzare parti del file planet.osm utilizzando bz2 e lxml, utilizzando il seguente codice

from lxml import etree as et 
from bz2 import BZ2File 

path = "where/my/fileis.osm.bz2" 
with BZ2File(path) as xml_file: 
    parser = et.iterparse(xml_file, events=('end',)) 
    for events, elem in parser: 

     if elem.tag == "tag": 
      continue 
     if elem.tag == "node": 
      (do something) 


    ## Do some cleaning 
    # Get rid of that element 
    elem.clear() 

    # Also eliminate now-empty references from the root node to node   
    while elem.getprevious() is not None: 
     del elem.getparent()[0] 

che funziona perfettamente con il Geofabrick extracts. Tuttavia, quando si tenta di analizzare il pianeta-latest.osm.bz2 con lo stesso script ottengo l'errore:

xml.etree.XMLSyntaxError: Specification mandate value for attribute num_change, line 3684, column 60

Qui ci sono le cose che ho provato:

  • Controllare il pianeta-latest. osm.bz2 md5sum
  • Controlla il pianeta-latest.osm in cui lo script con bz2 si interrompe. Non c'è nessun errore apparente e l'attributo è chiamato "num_changes", non "num_change" come indicato nell'errore
  • Inoltre ho fatto qualcosa di stupido, ma l'errore mi ha lasciato perplesso: ho aperto il pianeta-latest.osm.bz2 in mode 'rb' [c = BZ2File ('file.osm.bz2', 'rb')] e poi ha passato c.read() a iterparse(), che mi ha restituito un errore che dice che (stringa molto lunga) non può essere aperta. cosa strana, (molto lunga stringa) termina proprio dove l'errore "Specification mandato valore" si riferisce a ...

Poi provato per decomprimere il primo planet.osm.gz2 usin una semplice

bzcat planet.osm.gz2 > planet.osm 

E ha eseguito il parser direttamente su planet.osm. E ... ha funzionato! Sono molto perplesso e non sono riuscito a trovare alcun puntatore sul perché questo possa accadere e su come risolverlo. La mia ipotesi sarebbe che ci sia qualcosa tra la decompressione e l'analisi, ma non ne sono sicuro. Per favore aiutami a capire!

+0

non posso dire per certo, naturalmente, ma il BZ2File (file.osm.bz2 ' , 'rb') 'sembra errato perché il primo argomento è supposto essere un _filename_ (cioè una stringa) secondo i documenti. – martineau

+0

Grazie per aver segnalato questo! Ma era corretto nel codice originale, ho appena modificato la mia domanda per evitare confusione. – scities

+1

Ci potrebbe essere un bug nel modulo bz2 (dubito che venga testato dai manutentori sugli ingressi da 40GB molto spesso). Prova a scrivere uno script Python che usa il modulo bz2 per decomprimere i dati e scriverlo in un nuovo file, e verificare che l'output corrisponda all'output 'bzcat'. –

risposta

5

Si scopre che il problema è con il file planet.osm compresso.

come indicato sul OSM Wiki, il file pianeta è compresso come un file multistream, e il modulo bz2 Python non può leggere i file multistream. Tuttavia, la documentazione di bz2 indica un modulo alternativo in grado di leggere tali file, bz2file. L'ho usato e funziona perfettamente!

Quindi il codice dovrebbe leggere:

from lxml import etree as et 
from bz2file import BZ2File 

path = "where/my/fileis.osm.bz2" 
with BZ2File(path) as xml_file: 
    parser = et.iterparse(xml_file, events=('end',)) 
    for events, elem in parser: 

     if elem.tag == "tag": 
      continue 
     if elem.tag == "node": 
      (do something) 


    ## Do some cleaning 
    # Get rid of that element 
    elem.clear() 

    # Also eliminate now-empty references from the root node to node   
    while elem.getprevious() is not None: 
     del elem.getparent()[0] 

Inoltre, fatto qualche ricerca su utilizzando il formato PBF (come consigliato nei commenti), sono incappato in imposm.parser, un modulo Python che implementa un parser generico per OSM dati (in formato pbf o xml). Potresti dare un'occhiata a questo!

2

In alternativa è possibile utilizzare l'uscita di bzcat comando (in grado di gestire i file multistream troppo):

p = subprocess.Popen(["bzcat", "data.bz2"], stdout=subprocess.PIPE) 
parser = et.iterparse(p.stdout, ...) 
# at the end just check that p.returncode == 0 so there were no errors