Python - механизм для определения типа сжатого файла и распаковки

Сжатый файл можно разделить на следующие логические группы
a. Операционная система, в которой вы работаете (* ix, Win) и т. Д.
b. Различные типы алгоритмов сжатия (например, .zip, .Z, .bz2, .rar, .gzip). По крайней мере, из стандартного списка наиболее часто используемых сжатых файлов.
c. Затем у нас есть гудроновый механизм - там, где, я полагаю, нет сжатия. Но это больше похоже на конкатенацию.

Теперь, если мы начнем обращаться к вышеуказанному набору сжатых файлов,
a. Вариант (а) позаботится о питоне, поскольку это независимый от платформы язык.
b. Варианты (b) и (c) кажутся проблемными.

Что мне нужно
Как определить тип файла (тип сжатия), а затем UN-сжать их?


Нравится:

fileType = getFileType(fileName)  
switch(fileType):  
case .rar:  unrar....
case .zip:  unzip....

etc  

Итак, фундаментальный вопрос: как определить алгоритм сжатия на основе файла (при условии, что расширение не предоставлено или неверно)? Есть ли какой-то конкретный способ сделать это на Python?


person kumar_m_kiran    schedule 24.10.2012    source источник


Ответы (7)


На этой странице есть список «волшебных» подписей файлов. Возьмите те, которые вам нужны, и поместите их в диктатор, как показано ниже. Затем нам нужна функция, которая сопоставляет ключи dict с началом файла. Я написал предложение, хотя его можно оптимизировать, предварительно обработав magic_dict, например, в одно гигантское скомпилированное регулярное выражение.

magic_dict = {
    "\x1f\x8b\x08": "gz",
    "\x42\x5a\x68": "bz2",
    "\x50\x4b\x03\x04": "zip"
    }

max_len = max(len(x) for x in magic_dict)

def file_type(filename):
    with open(filename) as f:
        file_start = f.read(max_len)
    for magic, filetype in magic_dict.items():
        if file_start.startswith(magic):
            return filetype
    return "no match"

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

person Lauritz V. Thaulow    schedule 24.10.2012
comment
Это точно определяет тип файла. Однако вы должны вернуться в объект, созданный путем открытия файла и разрешения доступа. В противном случае вам придется снова тестировать тип файла, чтобы убедиться, что с ним нужно работать. Этого можно избежать, создав общую абстракцию, которая может работать со всеми поддерживаемыми типами файлов. Выкройка называется заводской. - person Ber; 24.10.2012
comment
Вы также можете использовать этот сайт для поиска нужных подписей: filesignatures.net/index.php - person Melvic Ybanez; 14.08.2016
comment
Формат файла zip позволяет добавлять произвольные данные в начало файла, поэтому проверка магического числа для файлов zip не во всех случаях является правильной. - person Bradley Odell; 27.09.2017
comment
@BradleyOdell AFAIK, это RAR, а не zip. - person Ark-kun; 26.02.2019
comment
Отличная идея, однако в Windows 10 я получаю следующую ошибку: UnicodeDecodeError: кодек charmap не может декодировать байт 0x8f в позиции 20: символы отображаются на ‹undefined› - person Matt Dnv; 13.03.2020

Основываясь на ответе lazyr и моем комментарии, я имею в виду следующее:

class CompressedFile (object):
    magic = None
    file_type = None
    mime_type = None
    proper_extension = None

    def __init__(self, f):
        # f is an open file or file like object
        self.f = f
        self.accessor = self.open()

    @classmethod
    def is_magic(self, data):
        return data.startswith(self.magic)

    def open(self):
        return None

import zipfile

class ZIPFile (CompressedFile):
    magic = '\x50\x4b\x03\x04'
    file_type = 'zip'
    mime_type = 'compressed/zip'

    def open(self):
        return zipfile.ZipFile(self.f)

import bz2

class BZ2File (CompressedFile):
    magic = '\x42\x5a\x68'
    file_type = 'bz2'
    mime_type = 'compressed/bz2'

    def open(self):
        return bz2.BZ2File(self.f)

import gzip

class GZFile (CompressedFile):
    magic = '\x1f\x8b\x08'
    file_type = 'gz'
    mime_type = 'compressed/gz'

    def open(self):
        return gzip.GzipFile(self.f)


# factory function to create a suitable instance for accessing files
def get_compressed_file(filename):
    with file(filename, 'rb') as f:
        start_of_file = f.read(1024)
        f.seek(0)
        for cls in (ZIPFile, BZ2File, GZFile):
            if cls.is_magic(start_of_file):
                return cls(f)

        return None

filename='test.zip'
cf = get_compressed_file(filename)
if cf is not None:
    print filename, 'is a', cf.mime_type, 'file'
    print cf.accessor

Теперь можно получить доступ к сжатым данным с помощью cf.accessor. Все модули предоставляют для этого похожие методы, такие как read (), write () и т. Д.

person Ber    schedule 24.10.2012
comment
в функции get_compressed_file вы выполняете cls (f), f - это обработчик файлов, в то время как ваши открытые функции ожидают имена файлов ... Я изменил его, чтобы закрыть f и вместо этого передать имя файла. Есть ли способ лучше? - person fransua; 17.03.2015
comment
мой предыдущий комментарий может быть связан с версией python ... в python2 bz2.BZ2File принимает только строку - person fransua; 17.03.2015

Это сложный вопрос, который зависит от ряда факторов: самый важный из них - насколько портативным должно быть ваше решение.

Основы поиска типа файла для данного файла - это найти в файле идентифицирующий заголовок, обычно называемый " магическая последовательность "или заголовок подписи, который определяет, что файл относится к определенному типу. Его имя или расширение обычно не используется, если его можно избежать. Для некоторых файлов он встроен в Python. Например, для работы с .tar файлами вы можете использовать модуль tarfile, который имеет удобный метод is_tarfile. Есть аналогичный модуль под названием zipfile. Эти модули также позволят вам извлекать файлы на чистом Python.

Например:

f = file('myfile','r')
if zipfile.is_zipfile(f):
    zip = zipfile.ZipFile(f)
    zip.extractall('/dest/dir')
elif tarfile.is_tarfile(f):
    ...

Если ваше решение - только Linux или OSX, есть также команда file, которая сделает за вас большую часть работы. Вы также можете использовать встроенные инструменты для распаковки файлов. Если вы просто выполняете простой сценарий, этот метод проще и даст вам лучшую производительность.

person Krumelur    schedule 24.10.2012

Принятое решение выглядит великолепно, но оно не работает с python-3, вот модификации, которые заставили его работать - с использованием двоичного ввода-вывода вместо строк:

magic_dict = {
    b"\x1f\x8b\x08": "gz",
    b"\x42\x5a\x68": "bz2",
    b"\x50\x4b\x03\x04": "zip"
    }
''' SKIP '''
    with open(filename, "rb") as f:
''' The rest is the same '''
person Дмитрий Чернявский    schedule 13.08.2020

«а» полностью ложно.

"b" можно легко интерпретировать неправильно, поскольку ".zip" не означает, что файл на самом деле является zip-файлом. Это может быть JPEG с расширением zip (если хотите, для запутывания).

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

person alexandernst    schedule 24.10.2012
comment
С опцией (a) я имел в виду только код, написанный на python для разжатия, скажем, Unix, должен работать для того же файла без сжатия в WIN. Есть какая-то конкретная причина, по которой я ошибаюсь? - person kumar_m_kiran; 24.10.2012
comment
Алгоритм сжатия не зависит от ОС. Вы можете сжать файл в Unix, затем распаковать его в WIndows, затем отправить его на Mac и снова сжать, сравнить сжатый файл из Unix и файл из Mac, и они будут немного равны. - person alexandernst; 24.10.2012
comment
@kumar_m_kiran Как правило (скорее всего) вы можете использовать один и тот же код Python для распаковки файла в разных ОС с помощью Python. Вы хотели классифицировать на основе кода Python, необходимого для распаковки в разных ОС (что приносит независимость от платформы), с (неправильным) пониманием того, что для разных ОС потребуется другой код Python (что, вообще говоря, неверно). Но вы сказали это с помощью набора слов, которые означают что-то другое, и Александр вас поправил. - person Ankur Agarwal; 25.07.2017

Если упражнение состоит в том, чтобы идентифицировать его, просто чтобы пометить файлы, у вас есть множество ответов. Если вы хотите распаковать архив, почему бы вам просто не попытаться поймать выполнение / ошибки? Например:

>>> tarfile.is_tarfile('lala.txt')
False
>>> zipfile.is_zipfile('lala.txt')
False
>>> with bz2.BZ2File('startup.bat','r') as f:
...    f.read()
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
IOError: invalid data stream
person Burhan Khalid    schedule 24.10.2012

Обновление 2019:
Я искал решение, чтобы определить, был ли файл .csv заархивирован или нет. Ответ, который дал @Lauritz, вызывал у меня ошибки, я полагаю, это просто потому, что способ чтения файлов изменился за последние 7 лет.

Эта библиотека у меня отлично сработала! https://pypi.org/project/filetype/

person Red    schedule 28.11.2019