PythonでZIPファイルを圧縮・解凍するzipfile
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'...'
とするか、文字列str
のencode()
メソッドで変換する。
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も同様だった。
ディレクトリを除外したい場合はリスト内包表記を用いればよい。
- 関連記事: Pythonリスト内包表記の使い方
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'