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

このページではPythonでZIPファイルを扱う方法について解説します。

zip形式で圧縮する

PythonでZIPファイルを扱うには外部ライブラリのインストールは不要で標準のzipfileモジュールで操作することが可能です。

https://docs.python.org/ja/3/library/zipfile.html#module-zipfile

ファイルを圧縮する

まずは簡単なサンプルから、一つのテキストファイルをzipファイルに圧縮してみましょう。

import zipfile
with zipfile.ZipFile('sample.zip', 'w')as zf:
    zf.write('data/sample1.txt')

上のサンプルではdataディレクトリ配下のsample1.txtというテキストファイルを、sample.zipというzipファイルに圧縮しています。

zipfile.ZipFileクラスのコンストラクタの第1引数で指定したファイルに対して入出力操作が可能です。また、第2引数でファイルオープンモードを指定することが可能です。上のサンプルでは書き込みなので'w'を指定していますが、既存のファイルを読み込む場合は'r'を、追記する場合は'a'を指定します。

コンテキスト内のwriteメソッドで圧縮する対象のファイルを指定することができます。writeメソッドのarcnameを指定するとzipファイル内部でのパスを指定することができます。

import zipfile
with zipfile.ZipFile('sample.zip', 'w')as zf:
    zf.write('data/sample1.txt', arcname='sample1.txt')

上のサンプルでは最初のサンプルと同様、data/sample1.txtを圧縮していますが、直下にファイルが格納されます。

複数のファイルを圧縮する

先程はファイルを1つだけ圧縮しましたが、コンテキスト内で圧縮するファイルを追加することができます。複数ファイルをzipに圧縮する場合は以下のサンプルのように記述します。

import zipfile
with zipfile.ZipFile('sample.zip', 'w')as zf:
    zf.write('data/sample1.txt')
    zf.write('data/sample2.txt')
    zf.write('data/sample3.txt')

既存のZIPファイルにファイルを追加する

また、zipfileで開いたファイルは新たにファイルを追加することが可能です。先述の通りファイルオープンモードに追記の'a'を指定することでコンテキスト内でファイルの追加が可能となります。

import zipfile
with zipfile.ZipFile('sample.zip', 'a')as zf:
    zf.write('data/sample4.txt')
    zf.write('data/sample5.txt')

ディレクトリごと圧縮する

Pythonの標準モジュールosを使用するとディレクトリ配下のパスツリーを構築することができますので、先程のサンプルに流し込めばディレクトリごと圧縮することが可能です。ただし、標準モジュールのshutil(ファイル操作の機能を提供するモジュール)が同様の処理を提供しているため、こちらを使用する方が楽でしょう。

https://docs.python.org/ja/3/library/shutil.html#shutil.make_archive

import shutil

shutil.make_archive('dir_sample', 'zip', root_dir='data/sample_dir')

make_archiveメソッドはパラメータとして以下を指定することができます。

shutil.make_archive(base_name, format, [root_dir, [base_dir]])

base_nameに作成するファイル名を指定しますが、拡張子は省略します。formatにはアーカイブフォーマットとして'zip'を指定しますが、zip以外にtarを指定することが可能です。また、任意でroot_dir、base_dirの指定が可能で、圧縮するディレクトリのルートとroot_dirからの相対パスを指定します。省略した場合はカレントディレクトリが指定されます。

zipファイルを解凍する

zipファイルを解凍したり、中身を参照する場合はファイルのオープンモードに'r'を指定します。

解凍する

オープンモードに'r'を指定して開き、extractallメソッドを実行します。

import zipfile
with zipfile.ZipFile('sample.zip', 'r')as zf:
    zf.extractall('./output/')

引数に解凍先のパスを指定します。上のサンプルの場合、./output/ディレクトリ配下に解凍されます。

また、extractメソッドを使用して個別に解凍することも可能です。

import zipfile
with zipfile.ZipFile('sample.zip', 'r')as zf:
    zf.extract('data/sample1.txt', './output/')

上のサンプルの場合、data/sample1.txtのみが./output/配下に解凍されます。また、ファイルの有無を確認したい場合は次に説明するフィアルリストの取得で可能です。

解凍しないで中身を確認する

オープンモードに'r'を指定して開き、namelistメソッドを使用するとファイルリストを参照することができます。

import zipfile
with zipfile.ZipFile('sample.zip', 'r')as zf:
    print(zf.namelist()) # ['data/sample1.txt', 'data/sample2.txt', 'data/sample3.txt']

パスワード付きのzipを解凍する

extractall、extractで引数pwdでパスワードを指定するとパスワード付きzipfileを解凍することが可能です。パスワードは以下サンプルのようにbyteで指定します。

import zipfile
with zipfile.ZipFile('sample.zip', 'r')as zf:
    zf.extractall('./output/', pwd=b'パスワード')

with zipfile.ZipFile('sample.zip', 'r')as zf:
    zf.extract('data/sample1.txt', './output/', pwd=b'パスワード')

一方でPython3.7の時点で圧縮時のパスワードは残念ながらサポートされていません。

It supports decryption of encrypted files in ZIP archives, but it currently cannot create an encrypted file.
https://docs.python.org/3/library/zipfile.html#module-zipfile

代案として以下のサードパーティ製ライブラリを使用する手があります。
https://pypi.org/project/pyminizip/

もう一つ注意点ですが、暗号化zipfileの解凍はCライブラリではなくPythonで記述されているため「非常に遅い」です。大量に処理する場合はパフォーマンスに注意してください。

Decryption is extremely slow as it is implemented in native Python rather than C.
https://docs.python.org/3/library/zipfile.html#module-zipfile

zipfileの破損とcrc32

一通り使い方について説明しました。最後に業務で使用する場合の注意点について解説します。

システムでファイルを扱っていて問題になるのがファイルの破損です。大量のファイルを扱っているとクライアント側のエラー、サーバーサイドのディスクの損傷、転送時のエラーなど様々な理由でファイルの破損が発生しますので特に受託開発の場合は注意が必要です。

ZIPファイルの破損のチェック法ですが、ファイルヘッダのオフセット14番目以降にファイル内容のCRC-32が格納されいるためこれを利用します。もちろん自力で実装する必要はなく、Pythonのzipfile.ZipFileクラスにはこのCRC-32をチェックするメソッドがあります。

import zipfile
import sys

# zip作成
with zipfile.ZipFile('sample.zip', 'w')as zf:
    zf.write('data/sample1.txt')

# zipの破壊
with open('sample.zip', 'rb+') as f:
    f.seek(40)
    f.write(0x80.to_bytes(1, byteorder=sys.byteorder))

# zipの検査
with zipfile.ZipFile('sample.zip', 'r')as zf:
    t = zf.testzip()
    print(t) # data/sample1.txt

上のサンプルでは圧縮ファイルに対してファイルのオフセット40番目を1バイト壊した状態で検査しています。testzipメソッドでは最初に破損を検知したファイル名が出力され、破損が検知されていることが確認できます。