Как я могу работать с файлами Gzip, которые содержат дополнительные данные? - PullRequest
12 голосов
/ 08 февраля 2011

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

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

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

    Gzip's format.txt :

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

    gzip.GzipFile` Python :

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

    Python's 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:])

Я что-то не так делаю?

ОБНОВЛЕНИЕ

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

При копании в gzip.py я понял, что делаю неправильно - по умолчанию zlib.decompress и др.ожидайте zlib-обернутые потоки, а не голые потоки с дефляцией.Передав отрицательное значение для 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:])

Ответы [ 4 ]

18 голосов
/ 08 февраля 2011

Это ошибка.Качество модуля 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.

5 голосов
/ 08 февраля 2011

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

1 голос
/ 09 февраля 2019

У меня была именно эта проблема, но ни один из этих ответов не решил мою проблему. Итак, вот что я сделал, чтобы решить проблему:

#for gzip files
unzipped = zlib.decompress(gzip_data, zlib.MAX_WBITS|16)

#for zlib files
unzipped = zlib.decompress(gzip_data, zlib.MAX_WBITS)


#automatic header detection (zlib or gzip):
unzipped = zlib.decompress(gzip_data, zlib.MAX_WBITS|32)

В зависимости от вашего случая может потребоваться декодировать ваши данные, например:

unzipped = unzipped.decode()

https://docs.python.org/3/library/zlib.html

0 голосов
/ 09 июня 2015

Я не мог заставить его работать с вышеупомянутыми техниками.так обошлось, используя пакет 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])

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...