2011-02-08 13 views
12

Sto scrivendo uno script che funzionerà con i dati provenienti dalla strumentazione come flussi gzip. In circa il 90% dei casi, il modulo gzip funziona perfettamente, ma alcuni flussi causano la produzione di IOError: Not a gzipped file. Se l'intestazione gzip viene rimossa e il flusso di deflate alimentato direttamente a zlib, ho invece ottenuto Error -3 while decompressing data: incorrect header check. Dopo circa mezza giornata di sbattere la testa contro il muro, ho scoperto che i flussi che hanno problemi contengono un numero apparentemente casuale di byte extra (che non fanno parte dei dati gzip) aggiunti alla fine.Come posso lavorare con file Gzip che contengono dati aggiuntivi?

Mi sembra strano che Python non può funzionare con questi file per due ragioni:

  1. Sia Gzip e 7zip sono in grado di aprire questi file "imbottiti" senza alcun problema. (. Gzip genera il messaggio decompression OK, trailing garbage ignored, 7zip riesce in silenzio)
  2. Entrambi i documenti Gzip e Python sembrano indicare che questo dovrebbe funzionare: (sottolineatura mia)

    Gzip's format.txt:

    deve essere possibile a rilevare la fine dei dati compressi con qualsiasi metodo di compressione, indipendentemente dalla dimensione effettiva dei dati compressi. In particolare, decompressore deve essere in grado di rilevare e saltare dati aggiuntivi annesse ad un file compresso valido su un file system record-oriented, o quando i dati compressi possono essere letti solo da un dispositivo in multipli di un alcune dimensioni del blocco.

    Python's gzip.GzipFile`:

    Chiamare il metodo di un oggetto GzipFileclose() non chiude fileobj, dal momento che si potrebbe desiderare di aggiungere più materiale dopo i dati compressi. Ciò consente inoltre di passare un oggetto StringIO aperto per la scrittura come fileobj e recuperare il buffer di memoria risultante utilizzando il metodo getvalue() dell'oggetto StringIO.

    Python's zlib.Decompress.unused_data:

    Una stringa che contiene ogni byte successivo alla fine dei dati compressi. Cioè, questo rimane "" finché non è disponibile l'ultimo byte che contiene dati di compressione. Se l'intera stringa risulta contenere dati compressi, questa è "", la stringa vuota.

    L'unico modo per determinare dove finisce una stringa di dati compressi è in realtà decomprimerlo. Ciò significa che quando i dati compressi sono contenuti in un file più grande, è possibile trovarne solo la fine tramite lettura dei dati e alimentazione seguita da una stringa non vuota nel metodo dell'oggetto decompressione fino a quando l'attributo unused_data non è più la stringa vuota.

Qui ci sono quattro approcci che ho provato. (Questi esempi sono Python 3.1, ma ho provato 2.5 e 2.7 e ha avuto lo stesso problema.)

# approach 1 - gzip.open 
with gzip.open(filename) as datafile: 
    data = datafile.read() 

# approach 2 - gzip.GzipFile 
with open(filename, "rb") as gzipfile: 
    with gzip.GzipFile(fileobj=gzipfile) as datafile: 
     data = datafile.read() 

# approach 3 - zlib.decompress 
with open(filename, "rb") as gzipfile: 
    data = zlib.decompress(gzipfile.read()[10:]) 

# approach 4 - zlib.decompressobj 
with open(filename, "rb") as gzipfile: 
    decompressor = zlib.decompressobj() 
    data = decompressor.decompress(gzipfile.read()[10:]) 

Sto facendo qualcosa di sbagliato?

UPDATE

Va bene, mentre il problema con gzip sembra essere un bug nel modulo, i miei zlib problemi sono auto-inflitta. ;-)

Mentre scavo in gzip.py mi sono reso conto di cosa stavo facendo male: per impostazione predefinita, zlib.decompress e altri. si aspettano flussi con avvolgimento di zlib, non flussi di deflazione nudi. Inserendo un valore negativo per wbits, è possibile indicare a zlib di ignorare l'intestazione di zlib e decrittografare il flusso non elaborato. Entrambi funzionano:

# approach 5 - zlib.decompress with negative wbits 
with open(filename, "rb") as gzipfile: 
    data = zlib.decompress(gzipfile.read()[10:], -zlib.MAX_WBITS) 

# approach 6 - zlib.decompressobj with negative wbits 
with open(filename, "rb") as gzipfile: 
    decompressor = zlib.decompressobj(-zlib.MAX_WBITS) 
    data = decompressor.decompress(gzipfile.read()[10:]) 

risposta

18

Questo è un bug. La qualità del modulo gzip in Python è di gran lunga inferiore alla qualità che dovrebbe essere richiesta nella libreria standard Python.

Il problema è che il modulo gzip presuppone che il file sia un flusso di file in formato gzip. Alla fine dei dati compressi, inizia da zero, aspettandosi una nuova intestazione gzip; se non ne trova uno, solleva un'eccezione. Questo è sbagliato.

Naturalmente, è valida per concatenare due file gzip, ad esempio:

echo testing > test.txt 
gzip test.txt 
cat test.txt.gz test.txt.gz > test2.txt.gz 
zcat test2.txt.gz 
# testing 
# testing 

errori del modulo gzip è che non dovrebbe sollevare un'eccezione se non c'è gzip intestazione la seconda volta; dovrebbe semplicemente finire il file. Dovrebbe essere solo sollevare un'eccezione se non c'è alcuna intestazione la prima volta.

Non c'è soluzione alternativa senza modificare direttamente il modulo gzip; se vuoi farlo, guarda il fondo del metodo _read. Dovrebbe impostare un'altra bandiera, ad es. reading_second_block, per comunicare allo _read_gzip_header di aumentare EOFError anziché IOError.

Ci sono altri bug in questo modulo. Ad esempio, cerca inutilmente, causando il fallimento su flussi non cercabili, come i socket di rete. Questo mi dà poca fiducia in questo modulo: uno sviluppatore che non sa che gzip ha bisogno di funzionare senza ricerca è malamente non qualificato per implementarlo per la libreria standard Python.

+0

mi piacerebbe davvero muck circa un po 'con 'interni di gzip' durante il debug del problema, ma non era venuto in mente di risolvere il problema c'è e pacchetto il modulo modificato con il mio script.È brutto da morire, ma sembra che potrebbe essere ancora la migliore opzione disponibile. : -/ –

+0

@ Ben: È abbastanza autonomo che non è un costo importante, almeno; solo un file. È più fastidioso con i moduli nativi. –

+0

Come soluzione, supponendo che non interrompa le dimensioni o i limiti di tempo del codice, è possibile leggere un byte alla volta dopo aver ricevuto l'errore. Aggiungi ogni byte in un elenco, quando ricevi un altro errore IOError con un parametro "Non un file gzip", hai raggiunto la fine dei dati, "" .invia e restituisci –

4

Ho avuto un problema simile in passato. Ho scritto un new module che funziona meglio con i flussi. Puoi provarlo e vedere se funziona per te.

+3

Hai mai pensato di unire le tue correzioni nel modulo gzip standard? – Karmastan

+0

L'ho preso in considerazione, ma dato che ha una dipendenza dall'altra parte del framework in cui si trova. Quello dovrebbe essere facile da risolvere, comunque. – Keith

-1

Non riuscivo a farlo funzionare con le tecniche sopra menzionate. così fatto un lavoro in giro usando zipfile pacchetto

import zipfile 
from io import BytesIO 
mock_file = BytesIO(data) #data is the compressed string 
z = zipfile.ZipFile(file = mock_file) 
neat_data = z.read(z.namelist()[0]) 

funziona perfettamente

Problemi correlati