PythonでZIPファイルを圧縮・解凍するzipfile

Modified: | Tags: Python, ファイル処理

Python標準ライブラリのzipfileモジュールを使うと、ファイルをZIPに圧縮したり、ZIPファイルを解凍(展開、unzip)したりできる。また、shutilモジュールのmake_archive(), unpack_archive()で、ディレクトリ(フォルダ)の圧縮、ZIPファイル全体の解凍が簡単にできる。

どちらも標準ライブラリに含まれているので追加のインストールは不要。

Pythonの組み込み関数zip()については以下の記事を参照。

本記事のサンプルコードでは以下のようにzipfileモジュールおよびshutilモジュールをインポートしている。

import zipfile
import shutil

ディレクトリ(フォルダ)をZIPに圧縮: shutil.make_archive()

ディレクトリ(フォルダ)をZIPファイルに圧縮するにはshutil.make_archive()を使う。

第一引数base_nameに作成するZIPファイルのパス(拡張子なし)、第二引数formatにアーカイブフォーマット('zip', 'tar', 'gztar', 'bztar', 'xztar')、第三引数root_dirに圧縮するディレクトリのパスを指定する。

例えば、カレントディレクトリに以下の構造のディレクトリdir_zipがあるとする。

dir_zip
├── dir_sub
│   └── file_sub.txt
└── file.txt

このディレクトリをカレントディレクトリのZIPファイルarchive_shutil.zipに圧縮する。

import shutil

shutil.make_archive('archive_shutil', format='zip', root_dir='dir_zip')

この場合、指定したディレクトリdir_zip自体はZIPに含まれない。

ディレクトリ自体も含めたい場合は、第三引数root_dirに対象ディレクトリの上位ディレクトリのパス、第四引数base_dirに対象ディレクトリのroot_dirからの相対パスを指定する。

shutil.make_archive('archive_shutil_base', format='zip',
                    root_dir='.', base_dir='dir_zip')

解凍した結果は次項を参照。

ZIPの中身をすべて解凍(展開): shutil.unpack_archive()

ZIPファイルの中身をすべて解凍(展開)するにはshutil.unpack_archive()を使う。

第一引数filenameにZIPファイルのパス、第二引数extract_dirに解凍先ディレクトリのパスを指定する。第二引数extract_dirを省略するとカレントディレクトリに解凍される。

ここでは前項で圧縮したZIPファイルを解凍する。

shutil.unpack_archive('archive_shutil.zip', 'dir_out')

以下のように展開される。

dir_out
├── dir_sub
│   └── file_sub.txt
└── file.txt

ドキュメントには明記されていないが、第二引数extract_dirに存在しないディレクトリを指定しても新規作成してくれる模様(Python3.11.4で確認)。

shutil.make_archive()の引数base_dirを設定したZIPは以下のように展開される。

shutil.unpack_archive('archive_shutil_base.zip', 'dir_out_base')
dir_out_base
└── dir_zip
    ├── dir_sub
    │   └── file_sub.txt
    └── file.txt

zipfileモジュールの基本: ZipFileオブジェクト

zipfileモジュールでは、ZIPファイルの読み書きを行うZipFileクラスが提供されている。

ZipFileオブジェクトはコンストラクタzipfile.ZipFile()に第一引数file(ZIPファイルのパス)、第二引数mode(読み込み'r'、書き込み'w'、追記'a'など)を指定して生成する。

ZipFileオブジェクトはclose()メソッドでクローズする必要があるが、with文を使うとブロックが終了したときに自動的にクローズされる。

モードを指定したりwith文を使ったりするなど、使い方は組み込み関数open()によるファイルの読み書きと似ている。

具体的な例は以降で紹介する。

ファイルを指定してZIPに圧縮

個別にファイルを指定してZIPファイルに圧縮するには、ZipFileオブジェクトを新規作成し、write()メソッドで圧縮したいファイルを追加していく。

ZipFileオブジェクトを新規作成

書き込みモードでZipFileオブジェクトを生成する。zipfile.ZipFile()の第一引数fileに新規作成するZIPファイルのパス、第二引数mode'w'(書き込み)を指定する。

書き込みモードでは引数compression, compresslevelで圧縮方式と圧縮レベルを指定できる。

圧縮方式compressionは以下。BZIP2、LZMAのほうが圧縮率が高い(より小さいサイズに圧縮できる)が、圧縮にかかる時間が長くなる。

  • zipfile.ZIP_STORED: 圧縮せず複数ファイルをまとめるだけ(デフォルト)
  • zipfile.ZIP_DEFLATED: 通常のZIP圧縮
  • zipfile.ZIP_BZIP2: BZIP2圧縮
  • zipfile.ZIP_LZMA: LZMA圧縮

圧縮レベルcompresslevelは、ZIP_DEFLATEDの場合、zlib.compressobj()levelに対応する。1が高速・低圧縮、9が低速・高圧縮。デフォルトは-1で現在は6に対応している。

level は圧縮レベルです。0 から 9 、または -1 の整数を取り、1 (Z_BEST_SPEED) は最も高速で最小限の圧縮を行い、9 (Z_BEST_COMPRESSION) は最も低速で最大限の圧縮を行います。0 (Z_NO_COMPRESSION) は圧縮しません。デフォルトは -1 です (Z_DEFAULT_COMPRESSION)。Z_DEFAULT_COMPRESSION は、速度と圧縮の間のデフォルトの妥協点 (現在、レベル 6 に対応します) を表します。 zlib.compressobj() --- gzip 互換の圧縮 — Python 3.11.4 ドキュメント

write()メソッドでファイルを追加

ZipFileオブジェクトのwrite()メソッドでは、第一引数filenameのファイルを第二引数arcnameという名前でZIPファイルに書き込む。arcnameを省略するとfilenameがそのまま使われる。arcnameにディレクトリ構造を指定することも可能。

import zipfile

with zipfile.ZipFile('archive_zipfile.zip', 'w',
                     compression=zipfile.ZIP_DEFLATED,
                     compresslevel=9) as zf:
    zf.write('dir_zip/file.txt', arcname='file.txt')
    zf.write('dir_zip/dir_sub/file_sub.txt', arcname='dir_sub/file_sub.txt')

write()メソッドで引数compress_type, compresslevelを指定して、ファイルごとに圧縮方式と圧縮レベルを選択することもできる。

既存のZIPに新たなファイルを追加

既存のZIPファイルに新たなファイルを追加するには、追記モードでZipFileオブジェクトを生成する。zipfile.ZipFile()の第一引数fileにZIPファイルのパス、第二引数mode'a'(追記)を指定する。

既存のファイルを追加

追記モードのZipFileオブジェクトのwrite()メソッドで、既存のファイルを追加できる。

カレントディレクトリに存在するanother_file.txtを追加する例。引数arcnameは省略。

with zipfile.ZipFile('archive_zipfile.zip', 'a') as zf:
    zf.write('another_file.txt')

ファイルを新規作成して追加

ファイルを新規作成して追加することも可能。追記モードのZipFileオブジェクトのopen()メソッドを使う。

第一引数に新規作成するファイルのZIP内でのパスを指定し、第二引数mode'w'とする。

開いたファイルオブジェクトのwrite()メソッドでファイルの中身を書き込める。

with zipfile.ZipFile('archive_zipfile.zip', 'a') as zf:
    with zf.open('dir_sub/new_file.txt', 'w') as f:
        f.write(b'text in new file')

write()の引数は文字列strではなくバイト列bytesで指定しなければならない。テキストを書き込むにはb'...'とするか、文字列strencode()メソッドで変換する。

print(type(b'text'))
# <class 'bytes'>

print(type('text'.encode('utf-8')))
# <class 'bytes'>

ZipFileオブジェクトのopen()でZIP内のファイルを読み込む例は後述。

ZIPに含まれるファイルのリストを確認

ZIPファイルの中身を確認するには、読み込みモード(デフォルト)でZipFileオブジェクトを生成する。

ZipFileオブジェクトのnamelist()メソッドで、アーカイブされているアイテムのリストを取得できる。

with zipfile.ZipFile('archive_zipfile.zip') as zf:
    print(zf.namelist())
# ['file.txt', 'dir_sub/file_sub.txt', 'another_file.txt', 'dir_sub/new_file.txt']

with zipfile.ZipFile('archive_shutil.zip') as zf:
    print(zf.namelist())
# ['dir_sub/', 'file.txt', 'dir_sub/file_sub.txt']

上の結果から分かるように、shutil.make_archive()で作成したZIPの場合はディレクトリも個別にリストアップされる。条件は詳しく調べていないが、MacのFinderの標準機能で圧縮したZIPも同様だった。

ディレクトリを除外したい場合はリスト内包表記を用いればよい。

with zipfile.ZipFile('archive_shutil.zip') as zf:
    print([x for x in zf.namelist() if not x.endswith('/')])
# ['file.txt', 'dir_sub/file_sub.txt']

ZIPからファイルを選択して解凍(展開)

ZIPファイルを解凍するには、読み込みモード(デフォルト)でZipFileオブジェクトを生成する。

特定のファイルだけを解凍して取り出したい場合は、extract()メソッドを使う。

第一引数memberに解凍するファイル名(ZIPファイル内のディレクトリに格納されている場合はそれも含めたパス)、第二引数pathに解凍先のディレクトリのパスを指定する。

with zipfile.ZipFile('archive_zipfile.zip') as zf:
    zf.extract('file.txt', 'dir_out_extract')
    zf.extract('dir_sub/file_sub.txt', 'dir_out_extract')

すべて解凍したい場合はextractall()メソッドを使う。第一引数pathに解凍先のディレクトリのパスを指定する。

with zipfile.ZipFile('archive_zipfile.zip') as zf:
    zf.extractall('dir_out_extractall')

いずれの場合も、引数pathを省略するとカレントディレクトリに解凍される。また、ドキュメントには明記されていないが、存在しないディレクトリを指定しても新規作成してくれる模様(Python3.11.4で確認)。

ZIP内のファイルを読み込み

ZIPファイル内のファイルを直接開いて読み込むこともできる。

読み込みモード(デフォルト)でZipFileオブジェクトを作成し、open()メソッドで中のファイルを開く。

open()の第一引数にZIP内のファイルの名前(ディレクトリ構造を含んでもよい)を指定する。第二引数modeはデフォルトが'r'(読み込み)なので省略可能。

開いたファイルオブジェクトのread()メソッドで中身を読み込める。バイト列bytesが返されるがdecode()メソッドで文字列strに変換できる。

with zipfile.ZipFile('archive_zipfile.zip') as zf:
    with zf.open('dir_sub/new_file.txt') as f:
        b = f.read()

print(b)
# b'text in new file'

print(type(b))
# <class 'bytes'>

s = b.decode('utf-8')
print(s)
# text in new file

print(type(s))
# <class 'str'>

read()のほか、組み込み関数open()で開いたファイルオブジェクトと同様、readline(), readlines()も使える。

パスワード付きのZIPの処理(暗号化・復号)

zipfileモジュールはパスワード付きZIP(暗号化ZIP)の解凍(復号)はできるが、パスワード付きZIPの作成(暗号化)はできない(Python 3.11.4時点)。

このモジュールは暗号化されたアーカイブの復号をサポートしますが、現在暗号化ファイルを作成することはできません。 zipfile --- ZIP アーカイブの処理 — Python 3.11.4 ドキュメント

また、復号もAESには対応していない。

The zipfile module from the Python standard library supports only CRC32 encrypted zip files (see here: http://hg.python.org/cpython/file/71adf21421d9/Lib/zipfile.py#l420 ). zip - Python unzip AES-128 encrypted file - Stack Overflow

shutil.make_archive(), shutil.unpack_archive()は暗号化にも復号にも対応していない。

pyzipper

上記Stack Overflowの回答にあるpyzipperがAESでの暗号化・復号に対応していてzipfileとほぼ同じように使える。

他にもっと適当なライブラリがあるかもしれないが、ここでは一例として取り上げる。

パスワードを付けてZIPを作成(暗号化)するには、pyzipper.AESZipFile()encryption=pyzipper.WZ_AESを指定し、setpassword()メソッドでパスワードを設定する。バイト列bytesで指定する必要があるので注意。

import pyzipper

with pyzipper.AESZipFile('archive_with_pass.zip', 'w',
                         encryption=pyzipper.WZ_AES) as zf:
    zf.setpassword(b'password')
    zf.write('dir_zip/file.txt', arcname='file.txt')
    zf.write('dir_zip/dir_sub/file_sub.txt', arcname='dir_sub/file_sub.txt')

パスワード付きZIPの解凍(復号)。こちらもsetpassword()でパスワードを指定する。

with pyzipper.AESZipFile('archive_with_pass.zip') as zf:
    zf.setpassword(b'password')
    zf.extractall('dir_out_pyzipper')

当然ながら、パスワードが間違っていると解凍できない。

# with pyzipper.AESZipFile('archive_with_pass.zip') as zf:
#     zf.setpassword(b'wrong_password')
#     zf.extractall('dir_out_pass')
# RuntimeError: Bad password for file 'file.txt'

zipfileではパスワードを指定できるが、上述のようにAESには対応してない。CRC32であればこちらでも解凍可能。

# with zipfile.ZipFile('archive_with_pass.zip') as zf:
#     zf.setpassword(b'password')
#     zf.extractall('dir_out_pass')
# NotImplementedError: That compression method is not supported

subprocess.run()でコマンド実行

zipfileやpyzipperではうまくいかないがコマンドでは処理できている、という場合は、Pythonのプログラム内でコマンドを実行するsubprocess.run()を使う方法もある。

例として7-zip(要インストール)の7zコマンドを使う。

import subprocess

subprocess.run(['7z', 'x', 'archive_with_pass.zip', '-ppassword', '-odir_out_7z'])

以下のコマンドと同等。xが展開、-p<パスワード>, -o<展開先ディレクトリ>(スペース不要なので注意)。

$ 7z x archive_with_pass.zip -ppassword -odir_out_pass_7z'

関連カテゴリー

関連記事