ZIPファイルフォーマットは、アーカイブ(書庫)とデータ圧縮を行うための標準フォーマットです。
Pythonではzipfileモジュールを使ってこれらの操作を行うことが出来ます。
本記事では、主に下記2点について基本的なやり方についてまとめます。
- ZIPファイルを展開(解凍)する
- ファイルやディレクトリのZIPファイルへアーカイブ・圧縮する
具体例と共にサンプルコードではコメントも随時入れていますので理解の助けになると思います。
ZIPファイルフォーマットとは?
ZIPはアーカイブ(書庫化)とデータ圧縮を行う標準フォーマットです。
- アーカイブ(書庫化):
- データ圧縮:
複数のファイルを一つのファイルにまとめること。
データの情報量を保ったままデータ量を減らして別のデータに変換すること。
zipfileモジュールは圧縮アルゴリズム(zlib、bz2、Lzma等)を選択することができます。
ZIPファイルの展開(解凍)
冒頭にも述べたように、PythonでZIPファイルを操作するには、zipfile モジュールを使います。
まずはZipFileオブジェクトを生成します。
ファイルの展開(解凍)の場合は、引数modeは’r’を指定します。
(アーカイブ(圧縮)する場合は’w’または’a’を指定。詳細はこちら参照)
# zipfileモジュールの読み込み
import zipfile
# ZipFileオブジェクトを生成
>>> zf = zipfile.ZipFile("./sample.zip", "r")
★ ZIPファイルを展開(解凍)する処理 ★
#最後にZipFileオブジェクトをクローズ
>>> zf.close()
ZIPファイルを展開(解凍)する処理は上記の★部の箇所に記述します。
具体的な方法について以降の章にて述べます。
ZIPファイルをカレントディレクトリに展開
extractall() を使います。
ZIPファイルを単純にカレントディレクトリに展開(解凍)するだけならこれが便利。
使い方は以下です。
ZipFile.extractall(path=None, members=None, pwd=None)
この場合はデフォルト設定のみで出来るので、引数は何も設定する必要ありません。
具体例を以下に示します。
>>> import zipfile
# 'r'モードでzipfileオブジェクトを生成
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# 引数は特に設定せず(全てデフォルト)
>>> zp.extractall()
# ZipFileオブジェクトをクローズ
>>> zp.close()
出力結果をtreeコマンドで確認してみました。
下記に示す様にsample.zipの中身がカレントディレクトリに展開(解凍)されています(★のファイル)。
$ tree
.
├── file01.txt ★展開されたファイル
├── file02.txt ★展開されたファイル
├── file03.txt ★展開されたファイル
└── sample.zip
展開(解凍)先を指定する
extractall() の引数pathに展開先のディレクトリを指定します。
指定したディレクトリが存在しない場合は、カレントディレクトリに新規に作成されます。
具定例を以下に示します。
>>> import zipfile
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# 引数pathに展開先ディレクトリを指定
>>> zp.extractall(path='./test/')
#ZipFileオブジェクトをクローズ
>>> zp.close()
出力結果をtreeコマンドで確認してみます。指定した通りにtestディレクトリが作成されてその中にファイルが展開されています。
$ tree
.
├── sample.zip
└── test ★指定したディレクトリ
├── file01.txt
├── file02.txt
└── file03.txt
ファイルを選んで展開(解凍)する
展開(解凍)するファイルを指定することもできます。
extractall() の引数membersに、展開するファイル名をリストで指定します。
尚、このリストは、namelist()で取得されるリストの一部である必要があります。
例えば、下記の様にして取得します。
# zpは生成したZipFileオブジェクト
>>> zp.namelist()
['file01.txt', 'file02.txt', 'file03.txt']
file02.txtとfile03.txtの2つのファイルだけを展開する場合は、これらのファイル名をリストで指定します。
>>> import zipfile
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# namelist()で取得したリストの一部を指定
>>> zp.extractall(members=['file02.txt', 'file03.txt'])
#ZipFileオブジェクトをクローズ
>>> zp.close()
結果をtreeコマンドで確認します。指定した通りfile02.txtとfile03.txtのみが展開されています。
$ tree
.
├── file02.txt ★展開されたファイル
├── file03.txt ★展開されたファイル
└── sample.zip
ファイルを一つだけ展開(解凍)する
ファイルを一つだけ展開する場合は、extract() も使えます。
extractall()と異なるのは、
- ファイル名の指定はリストではなく、文字列
- 展開されたファイルのパスが返る
という点です。
具体的な例で示します。
>>> import zipfile
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# 展開するファイル名を文字列で指定
>>> zp.extract(member='file02.txt')
'/home/hibikisan/rensyu/sample-folder01/file02.txt'
# ZipFileオブジェクトをクローズ
>>> zp.close()
結果をtreeコマンドで確認します。指定したファイルのみが展開されています。
$ tree
.
├── file02.txt ★指定したファイルのみ展開(解凍)
└── sample.zip
アーカイブ(圧縮)されているファイルのリストを取得する
上記でも出てきましたが、namelist() を使います。ファイル名がリスト形式で返ってきます。
具体例を示します。引数は特にありません。
>>> import zipfile
# ZipFileオブジェクトの生成
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# アーカイブされているファイルのリストを取得
>>> zp.namelist()
['file01.txt', 'file02.txt', 'file03.txt']
# ZipFileオブジェクトをクローズ
>>> zp.close()
アーカイブ(圧縮)されているファイルの中身だけ読み込む
ここまではZIPファイルの中身を展開(解凍)してファイルに保存する方法について述べてきました。
しかし、展開したファイルを保存しなくても中身のデータを読み込むことが可能です。
例えば以下2つ方法が考えられます。
(その1)zipfile.open()を使う方法
zipfile.open() を使ってアーカイブ(圧縮)されたファイルにアクセスし、データを読み込むことが出来ます。
使い方は以下です。
ZipFile.open(name, mode='r', pwd=None, *, force_zip64=False)
引数nameに、読み出したいファイル名を設定します。
具体例は以下です。
>>> import zipfile
# Readモードでzipfileオブジェクトを生成
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# 読み出したいファイルを引数に指定
>>> f = zp.open('file01.txt')
# あとは通常のファイル読み出しと同様
>>> data = f.read()
# ファイルの中身を取り出すことが出来ました
>>> data
b'This is file01.\n'
# close()するのを忘れずに
>>> zp.close()
(その2)zipfile.read()を使う方法
zipfile.read() を使うと、アーカイブ(圧縮)されたファイルから直接データを読み出すことが可能です。
使い方は以下です。
ZipFile.read(name, pwd=None)
引数nameに、読み出したいファイル名を設定します。
具体例は以下です。
>>> import zipfile
# Readモードでzipfileオブジェクトを生成
>>> zp = zipfile.ZipFile("./sample.zip", "r")
# 指定したファイルの中身のデータがByte列で返ります。
>>> zp.read('file01.txt')
b'This is file01.\n'
>>> zp.close()
ファイルをZIPフォーマットにアーカイブ(圧縮)する
ここでは、以下3つのファイルをsample.zipという名前でアーカイブする例を考えます。
$ tree
.
├── file01.txt
├── file02.txt
└── file03.txt
まず、zipfile.ZipFile()を使ってZipFileオブジェクトを生成します。
引数の設定は下記の通りです。
- 第一引数path:作りたいZIPファイル名
- 第二引数mode: “w”(書き込みモード)
- 第三引数compression: 圧縮モードを選択(詳細はこちら参照。ここではデフォルトの無圧縮とします)
>>> import zipfile
>>> zp = zipfile.ZipFile("./sample.zip", "w")
次に、ZipFile.write() を使ってアーカイブ(圧縮)したいファイルをZipFileオブジェクトに追加していきます。
このメソッドの使い方は以下です。
ZipFile.write(filename, arcname=None, compress_type=None, compresslevel=None)
それぞれの引数は、
- 第一引数filename: アーカイブするファイル名
- 第二引数arcname: ZipFileオブジェクトに追加する際のファイル名(デフォルトはfilenameと同じ)
- 第三引数compress_type: 圧縮方式(デフォルトは設定無し)
また、ZipFile.close()の前であればファイルを複数追加できます。
(close()した後にファイルを追記するには、’a’モード(上書きモード)でファイルを開きます。)
コード例を以下に示します。
# ファイルをZipFileオブジェクトへ追加
>>> zp.write('file01.txt') # 無圧縮
>>> zp.write('file02.txt') # 無圧縮
>>> zp.write('file03.txt', compress_type = zipfile.ZIP_DEFLATED) # 通常のZIP圧縮
# ZipFileオブジェクトをクローズ
>>> zp.close()
結果をtreeコマンドで確認します。指定したファイル名でZIPファイルが生成されました。
$ tree
.
├── file01.txt
├── file02.txt
├── file03.txt
└── sample.zip ★ここにZIPファイルが生成
>>> zp.write('ziptest.py', './dir3/ziptest.py')
ディレクトリをZIP形式にアーカイブする
zipfileモジュールにはディレクトリの中身を丸ごとZIPアーカイブする関数が無いので、少し工夫が必要です。
ここでは、以下のディレクトリ構成を例にして、testディレクトリを丸ごとアーカイブする方法を考えます。
tree
.
└── test
├── sample01.txt
└── sub01
├── sample02.txt
└── sample03.txt
大まかな手順は以下です。
- 書き込みモードでZipFileオブジェクトを開く
- (例えばos.walk()で)アーカイブしたいディレクトリ内のファイル一覧を取得
- 取得したファイルパスを順次ZipFileオブジェクトに書き込む
コード例を以下に示します。
>>> import os
>>> import zipfile
# ZIPファイル名
>>> zip_name = 'ziptest.zip'
# アーカイブするディレクトリ名
# (testディレクトリはカレントディレクトリの直下にあることを前提)
>>> dir = 'test'
# ZipFileオブジェクトを'w'モードで生成
>>> zp = zipfile.ZipFile(zip_name, 'w')
# testディレクトリ内のファイル名一覧を取得
>>> for dirname, subdirs, filenames in os.walk(dir):
... for fname in filenames:
... zp.write(os.path.join(dirname, fname))
...
# ZipFileオブジェクトをクローズ
>>> zp.close()
結果を確認します。設定した通りZIPファイルが生成されています。
$ tree
.
├── test ★このディレクトリを丸ごとアーカイブする
│ ├── sample01.txt
│ └── sub01
│ ├── sample02.txt
│ └── sample03.txt
└── ziptest.zip ★ここにZIPファイルが生成
またZIPファイルの中身を確認すると、設定通りにディレクトリが丸ごと圧縮されていることがわかります。
#圧縮ファイルziptest.zipの中身の確認
>>> import zipfile
>>> zp = zipfile.ZipFile('ziptest.zip', 'r')
>>> zp.namelist()
['test/sample01.txt', 'test/sub01/sample03.txt', 'test/sub01/sample02.txt']
ZipFileオブジェクトの生成について(参考)
zipfileモジュールを使ってZIPファイルの操作を行うには、まずZipFile()を使ってZipFileオブジェクトを生成します。
使い方は以下です。
class zipfile.ZipFile(file, mode='r', compression=ZIP_STORED, allowZip64=True, compresslevel=None)
主な引数を下表に示します。
パラメータ | 説明 | |
---|---|---|
第1引数 | file | ファイルパス(文字列) |
第2引数 | mode='r' | モード設定 'r' :読み込み 'w':書き込み(新規上書き) 'a' :書き込み(追記) ※ファイルオブジェクト読み込み時のmode設定と同じ |
第3引数 | compression=ZIP_STORED | 圧縮アルゴリズム ・ZIP_STORED: 圧縮無し ・ZIP_DEFLATED: 通常の ZIP 圧縮 ・ZIP_BZIP2: BZIP2 圧縮 ・ZIP_LZMA: LZMA 圧縮 が設定可能(※1)) |
以下に具体的な例を示します。最後にclose() でZipFileオブジェクトを閉じることを忘れずに。
# zipfileモジュールの読み込み
import zipfile
import os
# ZipFileオブジェクトを生成
>>> zf = zipfile.ZipFile('./testdir2.zip', mode='r', compression=zipfile.ZIP_STORED)
# ZIPファイルを操作する処理
*** 展開、新規作成、ファイル追加等(詳細は後述) ***
#最後にZipFileオブジェクトをクローズ
>>> zf.close()
ちなみに、この一連の流れはwithステートメントを使って下記のようにもっと簡単に書くことも出来ます。
>>> with zipfile.ZipFile('./testdir2.zip', mode='r', compression=zipfile.ZIP_STORED) as zf:
... filelist = zf.namelist()
... print(filelist)
...
['testdir2/', 'testdir2/sample2.txt', 'testdir2/sample1.txt', 'testdir2/sample0/', 'testdir2/sample0/sample01.txt']
(※)圧縮アルゴリズム設定に関するTips
- 圧縮アルゴリズムの設定方法
上記のZipFile()の設定ではcompression=zipfile.ZIP_DEFLATEDと設定していますが、compression=ZIP_DEFLATEDとしたい場合は、下記のようにzipfileモジュールをインポートします。
from zipfile import ZIP_DEFLATED
または
from zipfile import *
圧縮アルゴリズムの数値定数はソースコード: Lib/zipfile.py にて下記のように定義されています。zipfile.getinfo()で得られるZipInfo.compress_typeはこの整数値で返ってくるので注意が必要です。
# constants for Zip file compression methods
ZIP_STORED = 0
ZIP_DEFLATED = 8
ZIP_BZIP2 = 12
ZIP_LZMA = 14
圧縮アルゴリズムを実行できるモジュール(zlib、bz2、Lzma)が組み込まれていない場合はRuntimeErrorとなります。また、Pythonが認識できないアルゴリズムを設定した場合はNotImplementedErrorが返ります。
環境
- OS: Ubuntu18.04LTS
- Python3.8.4
まとめ
ZIPファイルを展開(解凍)したりZIP形式へアーカイブ・圧縮するにはzipfileモジュールを使います。
- 単純にZIPファイルを展開(解凍)するだけならextractall()が簡単で便利。
- 展開するファイルを選択したり、展開先の場所を指定することも可能。
- ZIP形式へアーカイブ・圧縮するにはファイルを一つ一つ選択する必要がある。
- ディレクトリをまとめてアーカイブするには、os.walk()等を使ってファイルパスの一覧を取得して、順次ZipFileオブジェクトへ書き込む。