Windowsのエクスプローラを使っていると、コンテキストメニューからzipアーカイブファイルを作成することができますが、同じことをPythonでやってみたいと思います。
ディレクトリを圧縮する場合、shutil
モジュールの.make_archive()
メソッドも使えます。具体的にはこちらの記述で"xztar"
(format
引数)を"zip"
に変更するだけです。
ただしshutil
ではファイルの種類や更新時刻など特定の条件を指定するなど細かい制御が難しいので、ここではzipfile
モジュールを使うことにします。
ここではcreate_zipfile()
関数が、指定されたディレクトリに対してzipアーカイブファイルを作成します。使い方はmainの部分に例示しています。
zipfile
は圧縮を行うためのモジュールで、対象のファイルパスをアーカイブファイル内の(相対)パスと合わせて指定することで、元の階層を維持することができます。
先にスクリプト全体を記載しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | import os import zipfile import shutil import traceback # get file paths under specified directory def _get_child_item(base_path): for root, dirs, files in os.walk(base_path): for file in files: yield os.path.join(root, file) def create_zipfile(dir_path, remove=False): """ create zip archive file from specified directory param: dir_path<string>: path to directory. error out if not exists. param: remove<bool>: specify if remove directory after compressing. set to False as default. return value <bool>: result if the task was completed successfully. """ # check if it is directory if not os.path.isdir(dir_path): raise Exception("path not available: {}".format(dir_path)); try: # find all files in the path childs = [x for x in _get_child_item(dir_path)] # check if target file has any child file if childs == []: print("no file found in the specified path: {}".format(dir_path)) return False else: # check if the specified path ends with separator "/" if os.path.basename(dir_path) == "": zipfile_path = os.path.dirname(dir_path) + ".zip" else: zipfile_path = dir_path + ".zip" if os.path.isfile(zipfile_path): print("zip archive file already exists, skip compressing") return False with zipfile.ZipFile(zipfile_path, 'w', zipfile.ZIP_DEFLATED) as f: for item in childs: relative = os.path.relpath(item, dir_path) # do archiving print("compressing: {}".format(relative)); f.write(item, relative) if remove: print("removing directory...") shutil.rmtree(dir_path) return True except: print(traceback.format_exc()) return False if __name__ == "__main__": target_path = "D:/data" if create_zipfile(target_path, remove=False): print("succeed") else: print("failed") |
以下ポイントを簡単に解説します。
ファイルの検索
特定ディレクトリパス以下に含まれるすべてのファイルパスを再帰的に取得するために、os.walk()
を利用しています。
ファイルシステム(木構造)の階層を辿って、見つかったファイルのパスを順次yield
で返してきます。
1 2 3 4 | def _get_child_item(base_path): for root, dirs, files in os.walk(base_path): for file in files: yield os.path.join(root, file) |
ちなみに、同じ方法でサブディレクトリのパス(この例ではdirs
)を取得することもできます。
ファイルの圧縮(アーカイブファイルの作成)
zipfile.ZipFile
オブジェクトをファイルストリームとして開き、write()
メソッドで書庫(アーカイブファイル)にファイルを追加していきます。
サポートされている圧縮方法はいくつかありますが、ここでは一般的なもの(zipfile.ZIP_DEFLATED
、gzip互換)を指定しています。デフォルト値(compression
を省略した場合)は圧縮されない(単一の書庫ファイルにまとめられるだけ)なので注意して下さい。
with
構文で呼んでいるのでストリームのクローズ(f.close()
)は不要です。
1 2 3 4 | with zipfile.ZipFile(zipfile_path, 'w', compression=zipfile.ZIP_DEFLATED) as f: for item in childs: relative = os.path.relpath(item, dir_path) f.write(item, relative) |
ディレクトリの削除
ファイルシステムの操作で地味に面倒なのが、削除操作ですね。
普通にos.rmdir()
を呼んでしまうと、ディレクトリが空でない場合(中にファイルが存在する場合)がエラーになります。
shutil.rmtree()
を使うとファイルシステムを辿って削除を行うので、
幾分か楽に削除操作ができるようになります。ただし、この場合はいわゆるリサイクル(ゴミ箱に移動)ではなく、即削除になってしまう点には注意が必要です。
また、権限不足はエラーになる(当たり前ですが)など、万能というわけではありません。
1 | shutil.rmtree(dir_path) |