2011-02-08 8 views
12

Estoy escribiendo un script que funcionará con datos provenientes de la instrumentación como flujos gzip. En aproximadamente el 90% de los casos, el módulo gzip funciona perfectamente, pero algunas de las secuencias hacen que produzca IOError: Not a gzipped file. Si el encabezado gzip se elimina y la secuencia desinflada se alimenta directamente a zlib, en su lugar obtengo Error -3 while decompressing data: incorrect header check. Después de medio día de golpear mi cabeza contra la pared, descubrí que las secuencias que están teniendo problemas contienen una cantidad aparentemente aleatoria de bytes adicionales (que no son parte de los datos de gzip) añadidos al final.¿Cómo puedo trabajar con archivos Gzip que contienen datos adicionales?

Me parece extraño que Python no puede trabajar con estos archivos por dos razones:

  1. Tanto gzip y 7zip son capaces de abrir estos archivos "acolchados" sin problema. (. Gzip produce el mensaje decompression OK, trailing garbage ignored, 7zip tiene éxito en silencio)
  2. Tanto los documentos gzip y Python parecen indicar que esto debería funcionar: (énfasis mío)

    Gzip's format.txt:

    debe ser posible a detectar el final de los datos comprimidos con cualquier método de compresión, independientemente del tamaño real de los datos comprimidos. En particular, el descompresor debe ser capaz de detectar y omitir datos adicionales adjuntas a un archivo comprimido válida en un sistema de archivos orientados a registros, o cuando los datos comprimidos sólo se pueden leer desde un dispositivo en múltiplos de un cierto tamaño de bloque

    Python's gzip.GzipFile`:

    Llamando al método de un objeto GzipFileclose() no se cierra fileobj, ya que es posible que desee añadir más material después de que los datos comprimidos. Esto también le permite pasar un objeto StringIO abierto para escribir como fileobj, y recuperar el búfer de memoria resultante utilizando el método StringIO del objeto getvalue().

    Python's zlib.Decompress.unused_data:

    una cadena que contiene cualquier bytes más allá del final de los datos comprimidos. Es decir, esto sigue siendo "" hasta que esté disponible el último byte que contiene datos de compresión. Si toda la secuencia resultó contener datos comprimidos, esta es "", la cadena vacía.

    La única manera de determinar dónde termina una cadena de datos comprimidos es descomprimiéndolo realmente. Esto significa que cuando los datos comprimidos contienen parte de un archivo más grande, solo puede encontrar el final al leyendo los datos y alimentándolos seguido por una cadena no vacía en el método decompress() de un objeto de descompresión hasta que el atributo unused_data ya no sea la cadena vacía

Éstos son los cuatro enfoques que he probado. (Estos son ejemplos de Python 3.1, pero he probado 2.5 y 2.7 y tenía el mismo 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:]) 

¿Estoy haciendo algo mal?

ACTUALIZACIÓN

bien, mientras que el problema con gzip parece ser un error en el módulo, mis zlib problemas son auto-infligidas. ;-)

Mientras investigaba en gzip.py me di cuenta de lo que estaba haciendo mal - de forma predeterminada, zlib.decompress y otros. espere secuencias envueltas en zlib, no corrientes de desinflado al descubierto. Al pasar un valor negativo para wbits, puede indicar zlib para omitir el encabezado zlib y descromover la secuencia sin formato. Ambos funcionan:

# 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:]) 

Respuesta

18

Esto es un error. La calidad del módulo gzip en Python queda muy por debajo de la calidad que debería requerirse en la biblioteca estándar de Python.

El problema aquí es que el módulo gzip asume que el archivo es una secuencia de archivos gzip-format. Al final de los datos comprimidos, comienza desde cero, esperando un nuevo encabezado gzip; si no encuentra uno, genera una excepción. Esto está mal.

Por supuesto, es válida para concatenar dos archivos gzip, por ejemplo:

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

de error del módulo gzip es que no debe lanzar una excepción si no hay gzip cabecera de la segunda vez; simplemente debería finalizar el archivo. Debería solo generar una excepción si no hay encabezado la primera vez.

No hay una solución limpia sin modificar directamente el módulo gzip; si quieres hacerlo, mira al final del método _read. Debería establecer otra bandera, ej. reading_second_block, para indicar _read_gzip_header para aumentar EOFError en lugar de IOError.

Hay otros errores en este módulo. Por ejemplo, busca innecesariamente, lo que hace que falle en las secuencias no buscables, como los sockets de red. Esto me da muy poca confianza en este módulo: un desarrollador que no sabe que gzip necesita funcionar sin buscar está muy descalificado para implementarlo en la biblioteca estándar de Python.

+0

realidad me mucked acerca un poco con 'internos de gzip' al depurar el problema, pero no se me había ocurrido para arreglar el problema allí y el paquete del módulo modificado con mi script.Es feo como el infierno, pero parece que todavía puede ser la mejor opción disponible. : -/ –

+0

@Ben: es lo suficientemente autónomo como para que no sea un costo importante, al menos; solo un archivo Es más molesto con los módulos nativos. –

+0

Como solución temporal, suponiendo que no interrumpe las restricciones de tamaño o tiempo en su código, puede leer en un byte a la vez después de recibir el error. Añada cada byte en una lista, cuando reciba otro IOError con un parámetro 'No un archivo comprimido', ha llegado al final de los datos, ''. Únase a él y devuelva –

4

Tuve un problema similar en el pasado. Escribí un new module que funciona mejor con transmisiones. Puedes probarlo y ver si funciona para ti.

+3

¿Alguna vez consideró fusionar sus correcciones en el módulo gzip estándar? – Karmastan

+0

Lo consideré, pero tal como está tiene una dependencia en la otra parte del marco en el que se encuentra. Sin embargo, uno debería ser fácil de arreglar. – Keith

-1

No pude hacerlo para trabajar con las técnicas mencionadas anteriormente. por lo que hizo un trabajo alrededor usando archivo zip paquete

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]) 

funciona perfecto

Cuestiones relacionadas