Подтвердить что ты не робот

Как я могу работать с файлами Gzip, которые содержат дополнительные данные?

Я пишу script, который будет работать с данными, поступающими из инструментария в виде потоков gzip. Примерно в 90% случаев модуль gzip работает отлично, но некоторые потоки заставляют его создавать IOError: Not a gzipped file. Если заголовок gzip удаляется, а поток дефляции подается непосредственно на zlib, вместо этого я получаю Error -3 while decompressing data: incorrect header check. Примерно через пол дня, ударив головой о стену, я обнаружил, что потоки, которые имеют проблемы, содержат, по-видимому, случайное количество дополнительных байтов (которые не являются частью данных gzip), прилагаемых к концу.

Мне кажется странным, что Python не может работать с этими файлами по двум причинам:

  • Оба Gzip и 7zip могут открывать эти "проложенные" файлы без проблем. (Gzip выдает сообщение decompression OK, trailing garbage ignored, 7zip успешно завершает работу.)
  • Оба документа Gzip и Python, по-видимому, указывают, что это должно работать: (акцент мой)

    Gzip format.txt:

    Должно быть возможно обнаруживать конец сжатых данных любым способом сжатия, независимо от фактического размера сжатых данных. В частности, декомпрессор должен иметь возможность обнаруживать и пропускать дополнительные данные в действительный сжатый файл в файловой системе, ориентированной на запись, или когда сжатые данные могут считываться только с устройства в кратном виде определенный размер блока.

    Python gzip.GzipFile`:

    Вызов метода GzipFile objects close() не закрывает fileobj, , поскольку вы можете добавить больше материала после сжатых данных. Это также позволяет передать объект StringIO, открытый для записи как fileobj, и получить полученный буфер памяти с помощью метода StringIO objects getvalue().

    Python zlib.Decompress.unused_data:

    Строка, содержащая любые байты, прошедшие после конца сжатых данных. То есть, это остается "", пока не будет доступен последний байт, содержащий данные сжатия. Если вся строка оказалась сжатой, это "", пустая строка.

    Единственный способ определить, где заканчивается строка сжатых данных, - это фактически декомпрессировать ее. Это означает, что когда сжатые данные содержат часть более крупного файла, вы можете найти только его конец с помощью чтения данных и подачи его, за которым следует некоторая непустая строка, в объекты декомпрессии decompress() пока атрибут unused_data больше не является пустой строкой.

Вот четыре подхода, которые я пробовал. (Эти примеры - Python 3.1, но я тестировал 2.5 и 2.7 и имел ту же проблему.)

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

Я что-то делаю неправильно?

UPDATE

Хорошо, в то время как проблема с gzip кажется ошибкой в ​​модуле, мои проблемы zlib самонастраиваются.; -)

Во время копания в gzip.py я понял, что я делаю неправильно - по умолчанию, zlib.decompress et al. ожидают потоки zlib-wrapped, а не голые потоки спуска. Передавая отрицательное значение для wbits, вы можете сказать zlib пропустить заголовок zlib и дефрагментировать исходный поток. Обе эти работы:

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

Ответ 1

Это ошибка. Качество модуля gzip в Python далеко не соответствует качеству, которое требуется в стандартной библиотеке Python.

Проблема здесь в том, что модуль gzip предполагает, что файл является потоком файлов формата gzip. В конце сжатых данных он начинается с нуля, ожидая новый заголовок gzip; если он не находит его, он вызывает исключение. Это неправильно.

Конечно, для объединения двух файлов gzip, например:

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

Ошибка модуля gzip заключается в том, что он не должен создавать исключение, если во второй раз нет заголовка gzip; он должен просто закончить файл. Он должен только вызывать исключение, если в первый раз нет заголовка.

Нет никакого обходного пути без изменения модуля gzip; если вы хотите это сделать, посмотрите на нижнюю часть метода _read. Он должен установить другой флаг, например. reading_second_block, чтобы сообщить _read_gzip_header поднять EOFError вместо IOError.

В этом модуле есть и другие ошибки. Например, он ищет ненужное, заставляя его терпеть неудачу на несекабельных потоках, таких как сетевые сокеты. Это дает мне очень мало уверенности в этом модуле: разработчик, который не знает, что gzip должен функционировать без иска, плохо не соответствует требованиям для его реализации для стандартной библиотеки Python.

Ответ 2

У меня была аналогичная проблема в прошлом. Я написал новый новый модуль, который лучше работает с потоками. Вы можете попробовать это и посмотреть, работает ли оно для вас.

Ответ 3

Я не мог заставить его работать с вышеупомянутыми методами. поэтому сделал работу с использованием пакета zipfile

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

Отлично работает