【Python】ファイルやフォルダを圧縮・解凍する
ファイルの圧縮や解凍といった作業は割と手作業でやったりもするが、システム化されていない部分での圧縮・解凍でも、簡単なルールに則ったものであれば、プログラムを書いた方が楽にできることがある。
合計で数GBになるようなデータを手元で扱ったりする場合、PCのストレージ容量が気になったりする。
そういう時などは、最初にプログラムで個別や必要な単位で圧縮し、必要に応じて解凍して使うなども考えられる。
ファイル個別の圧縮
指定したファイル1個を圧縮
ファイルの圧縮、解凍はzipfileモジュールを使う。
パスの扱いは、os.pathでもいいが、個人的にはpathlibが好きなのでpathlibを使う。
1from pathlib import Path
2import zipfile
3
4# pathlib.Path オブジェクトを作成
5# zipで圧縮する対象のファイル
6target_path = Path("./zip_test_dir/my_file1.txt")
7
8# ZIPファイルを作成
9# str(target_path.with_suffix(".zip"))は
10# 'zip_test_dir/my_file1.zip' となる(元のファイルの拡張子をzipにしたもの)
11with zipfile.ZipFile(str(target_path.with_suffix(".zip")), 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
12 # Path オブジェクトを str に変換して渡す
13 # str(target_path)は
14 # 'zip_test_dir/my_file1.txt' となる
15 zipf.write(str(target_path), arcname=target_path.name)
16
17# 出力結果
18# ├── my_file1.txt
19# ├── my_file1.zip
ファイルオープン時のwith open(filepath, "r") as f
と同じような感じで、with句を使う。
圧縮方式の引数compressionを指定しないと、圧縮されないことがあるので注意。
arcnameは圧縮ファイルのルートパスからの相対パスを指定することになる。
ファイル名のみを指定することで、圧縮ファイルのルートパス直下にファイルが格納される。
arcnameについてはフォルダとして圧縮する場合に指定する必要が出てくるが、ファイルを個別で圧縮する場合は、とりあえずファイル名を指定しておけばOK。
複数ファイルを個別に圧縮
対象ディレクトリ直下の全ファイルを個別に圧縮
複数のファイルを個別に圧縮したい場合、対象ファイルをforで回して、個別にwith zipfile.ZipFile()で圧縮していく。
1from pathlib import Path
2import zipfile
3
4# pathlib.Path オブジェクトを作成
5# zipで圧縮する対象のファイル
6target_path = Path("./zip_test_dir/my_dir1/")
7
8# target_pathの直下にあるファイルやディレクトリを全て取得
9for file in target_path.glob("*"):
10 if file.is_file():
11 # ファイル名の拡張子を.zipに設定する。compressionで圧縮方法を設定する。
12 with zipfile.ZipFile(file.with_suffix(".zip"), "w", compression=zipfile.ZIP_DEFLATED) as zipf:
13 # 圧縮対象のファイルを設定。arcnameはファイル名のみを指定する。これで解凍時に解凍先フォルダ直下にファイルが解凍される。
14 zipf.write(file, arcname=file.name)
15
16# 出力結果
17# my_dir1ディレクトリ直下ファイルはzipファイルが作成される。
18# my_dir1にあるmy_child_dir1内のファイルは圧縮対象外
19# ├── my_dir1
20# │ ├── my_child_dir1
21# │ │ ├── my_child_file1.txt -> 圧縮対象外(my_dir1直下ではない)
22# │ │ └── my_child_file2.csv -> 圧縮対象外(my_dir1直下ではない)
23# │ ├── my_child_file1.txt -> 圧縮対象
24# │ ├── my_child_file1.zip -> 圧縮されたファイル
25# │ ├── my_child_file2.csv -> 圧縮対象
26# │ └── my_child_file2.zip -> 圧縮されたファイル
ファイルを1個ずつ圧縮する処理をforで繰り返しているだけなので、ネストは深くなるが、特に複雑なことをしているわけではない。
対象ディレクトリ配下の全ファイルを再帰的に個別に圧縮
path.glob(“**/*”)でファイル一覧取得時にpath直下のものだけでなく、配下のもの全てを再帰的に取得するようにする。
path.rglob(“*”)でも同じ。
1from pathlib import Path
2import zipfile
3
4target_path = Path("./zip_test_dir/my_dir3/")
5
6# target_pathの配下にあるファイルやディレクトリを全て再帰的に取得する
7for file in target_path.glob("**/*"):
8 if file.is_file():
9 # ファイル名の拡張子を.zipに設定する。compressionで圧縮方法を設定する。
10 with zipfile.ZipFile(file.with_suffix(".zip"), "w", compression=zipfile.ZIP_DEFLATED) as zipf:
11 # 圧縮対象のファイルを設定。arcnameはファイル名のみを指定する。これで解凍時に解凍先フォルダ直下にファイルが解凍される。
12 zipf.write(file, arcname=file.name)
13# my_dir3配下の全てのファイルを個別に圧縮
14# ├── my_dir3
15# │ ├── my_child_dir1
16# │ │ ├── my_child_file1.txt
17# │ │ ├── my_child_file1.zip
18# │ │ ├── my_child_file2.csv
19# │ │ └── my_child_file2.zip
20# │ ├── my_child_file1.txt
21# │ ├── my_child_file1.zip
22# │ ├── my_child_file2.csv
23# │ └── my_child_file2.zip
ファイルサイズが一定以上のファイルのみを個別に圧縮
圧縮時にはpath.is_file()でファイルかどうかの確認をしているが、それに加えて、ファイルサイズの確認も行う。
以下の例ではファイルサイズが5KB以上の場合に圧縮処理を行うようにしている。
path.stat().st_sizeでファイルサイズ(byte)を取得できる。
1from pathlib import Path
2import zipfile
3
4target_path = Path("./zip_test_dir/my_dir4/")
5
6for file in target_path.glob("**/*"):
7 # file.stat().st_sizeでファイルサイズ(byte)を取得し、それが5KB以上かをチェック。
8 if file.is_file() and file.stat().st_size >= 5 * 1024:
9 with zipfile.ZipFile(file.with_suffix(".zip"), "w", compression=zipfile.ZIP_DEFLATED) as zipf:
10 zipf.write(file, arcname=file.name)
11
12# ファイルサイズが5KB以上のファイルのみ圧縮する。
13# ├── my_dir4
14# │ ├── my_child_dir1
15# │ │ ├── my_child_file1.txt
16# │ │ └── my_child_file2.csv
17# │ ├── my_child_file1.txt -> 圧縮対象(ファイルサイズ5KB以上)
18# │ ├── my_child_file1.zip
19# │ └── my_child_file2.csv
圧縮したファイルを圧縮後に削除する
ファイルを圧縮したい時は、ストレージの容量を節約したい時なので、圧縮後は元ファイルを削除することが多い(はず)。
path.unlink()でファイルを削除できる。
1from pathlib import Path
2import zipfile
3
4target_path = Path("./zip_test_dir/my_dir5/")
5
6for file in target_path.glob("**/*"):
7 # file.stat().st_sizeでファイルサイズ(byte)を取得し、それが2KB以上かをチェック。
8 if file.is_file() and file.stat().st_size >= 5 * 1024:
9 with zipfile.ZipFile(file.with_suffix(".zip"), "w", compression=zipfile.ZIP_DEFLATED) as zipf:
10 zipf.write(file, arcname=file.name)
11 # ファイルを削除する
12 file.unlink()
13
14# 圧縮したファイルは削除
15# ├── my_dir5
16# │ ├── my_child_dir1
17# │ │ ├── my_child_file1.txt
18# │ │ └── my_child_file2.csv
19# │ ├── my_child_file1.zip -> my_child_file1.txtは圧縮後に削除された
20# │ └── my_child_file2.csv
なお、上記の処理の場合、unlink対象はファイルなので、確実に削除できるが、もしunlink対象がディレクトリで、そのディレクトリが空でない場合はエラーとなる。
ディレクトリ単位でまとめて再帰的に削除するには、shutil.rmtree(path)を使うことで指定したディレクトリを配下のファイルも含めてまとめて削除できるが、間違って削除してしまうととんでもないことになるので、あまりお勧めしない。
フォルダの圧縮
フォルダの圧縮といいつつ、圧縮自体はファイル個別に行うので、圧縮対象のファイルは選べる。
対象ディレクトリ配下のファイル全てを圧縮して1つのzipにまとめる
with句とforループのネストが入れ替わる感じ。
with句ではディレクトリ名.zipを指定する。
そして、そのwith句の中で、圧縮したいファイルを取得して圧縮処理をしていく。
ポイントはarcnameに相対パスを指定するところ。
1from pathlib import Path
2import zipfile
3
4# 圧縮対象のフォルダ指定
5target_path = Path("./zip_test_dir/my_dir6/")
6# 圧縮後のzipファイル名作成
7zip_path = target_path.with_suffix(".zip") # zip_test_dir/my_dir6.zip
8
9with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
10 # ディレクトリやファイルを再起的に取得
11 for file in folder.rglob('*'):
12 if file.is_file(): # ファイルのみを対象にする
13 # ルートパスからの相対パスを取得。解凍時にディレクトリ構成を保つため。
14 # my_dir6からfileまでの相対パスを取得する。
15 rel_path = file.relative_to(target_path)
16 zipf.write(file, arcname=rel_path)
17
18# my_dir6のzipが作成される
19# ├── my_dir6
20# │ ├── my_child_dir1
21# │ │ ├── my_child_file1.txt
22# │ │ └── my_child_file2.csv
23# │ ├── my_child_file1.txt
24# │ └── my_child_file2.csv
25# ├── my_dir6.zip
file.relative_to(target_path)
の部分では、例えば、
my_dir6/my_child_file1.txt
の場合、rel_path
はmy_child_file1.txt
(my_dir6の直下なのでファイル名のみ)となる。
my_dir6/my_child_dir/my_child_file1.txt
の場合、rel_path
はmy_child_dir1/my_child_file1.txt
となる。
arcnameに相対パスを指定することで、zipファイルを解凍した際に、元のディレクトリ構造を維持して解凍できる。
解凍結果
解凍先であるextracted_dir6の配下に、圧縮前の構造が維持された状態でファイルが解凍されていることがわかる。
1# 解凍結果
2# 解凍するzipファイルのパス
3zip_path = Path("./zip_test_dir/my_dir6.zip")
4# 解凍先ディレクトリの指定
5extracted_path = Path("./zip_test_dir/extracted_dir6")
6
7# ZIPファイルを解凍
8with zipfile.ZipFile(zip_path, 'r') as zipf:
9 # 全てのファイルを解凍。解凍先のディレクトリが存在しない場合、自動で作成される
10 # 解凍先のディレクトリ直下にzip時の構造を保ったままファイルが解凍される
11 zipf.extractall(extracted_path)
12
13# extrace_dir6の直下にmy_dir6配下にあったファイルが解凍される。ディレクトリ構造を保ったまま
14# ├── extracted_dir6
15# │ ├── my_child_dir1
16# │ │ ├── my_child_file1.txt
17# │ │ └── my_child_file2.csv
18# │ ├── my_child_file1.txt
19# │ └── my_child_file2.csv
圧縮時に相対パスを指定しない場合の例
aracnameに相対パスを指定しなくても、zipファイルの作成は可能。
1from pathlib import Path
2import zipfile
3
4# 圧縮対象のフォルダ指定
5target_path = Path("./zip_test_dir/my_dir7/")
6# 圧縮後のzipファイル名作成
7zip_path = target_path.with_suffix(".zip") # zip_test_dir/my_dir7.zip
8
9with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
10 # ディレクトリやファイルを再起的に取得
11 for file in folder.rglob('*'):
12 if file.is_file(): # ファイルのみを対象にする
13 # 相対パスを指定しない場合、
14 # 今回の場合、./zip_test_dirから始まっているので、解凍時にはzip_test_dirから作成される
15 zipf.write(file, arcname=file)
16
17# my_dir7を圧縮
18# ├── my_dir7
19# │ ├── my_child_dir1
20# │ │ ├── my_child1_file1.txt
21# │ │ └── my_child1_file2.csv
22# │ ├── my_child_file1.txt
23# │ └── my_child_file2.csv
24# ├── my_dir7.zip
解凍結果
1# 解凍結果
2# 解凍するzipファイルのパス
3zip_path = Path("./zip_test_dir/my_dir7.zip")
4# 解凍先ディレクトリの指定
5extracted_path = Path("./zip_test_dir/extracted_dir7")
6
7# ZIPファイルを解凍
8with zipfile.ZipFile(zip_path, 'r') as zipf:
9 # 全てのファイルを解凍。解凍先のディレクトリが存在しない場合、自動で作成される
10 # 解凍先のディレクトリ直下にzip時の構造を保ったままファイルが解凍される
11 zipf.extractall(extracted_path)
12
13# ├── extracted_dir7
14# │ └── zip_test_dir
15# │ └── my_dir6
16# │ ├── my_child_dir1
17# │ │ ├── my_child_file1.txt
18# │ │ └── my_child_file2.csv
19# │ ├── my_child_file1.txt
20# │ └── my_child_file2.csv
extracted_dir7の下にzip_test_dirとmy_dir6が作成されている。
これは圧縮時にwith zipfile.ZipFile(zip_path, "w") as zipf
の部分で指定したzip_pathが./zip_test/my_dir7
であり、
圧縮処理のzipf.write(file, arcname=file)のfileはの./zip_test/my_dir7/my_child_file1.txt
ようになっているためである。
解凍時のディレクトリ構成が上記で問題なければ、これで圧縮しても良い。
ファイルやフォルダの解凍
解凍先ディレクトリを用意して解凍する
解凍処理もwith句とzipfile.ZipFileを使う。
extractall(path)でpathにzipファイルを解凍する。
1# 解凍するzipファイルのパス
2zip_path = Path("./zip_test_dir/my_dir1.zip")
3# 解凍先ディレクトリの指定
4extracted_path = Path("./zip_test_dir/extracted_dir1")
5
6# ZIPファイルを解凍
7with zipfile.ZipFile(zip_path, 'r') as zipf:
8 # 全てのファイルを解凍。解凍先のディレクトリが存在しない場合、自動で作成される
9 # 解凍先のディレクトリ直下にzip時の構造を保ったままファイルが解凍される
10 zipf.extractall(extracted_path)
11
12# ├── extracted_dir1
13# │ ├── my_child_dir1
14# │ │ ├── my_child_file1.txt
15# │ │ └── my_child_file2.csv
16# │ ├── my_child_file1.txt
17# │ └── my_child_file2.csv
zipファイルのある場所に解凍する
解凍先パスにzipファイルのあるディレクトリ(zip_file.parent
)を指定することで、zipファイルのある場所に解凍できる。
1zip_path = Path("./zip_test_dir/my_dir5/")
2
3for zip_file in zip_path.glob("**/*.zip"):
4 with zipfile.ZipFile(zip_file, "r") as zipf:
5 zipf.extractall(zip_file.parent)
6
7# 解凍前
8# ├── my_dir5
9# │ ├── my_child_dir1
10# │ │ ├── my_child_file1.txt
11# │ │ └── my_child_file2.csv
12# │ ├── my_child_file1.zip
13# │ └── my_child_file2.csv
14
15# 解凍後
16# ├── my_dir5
17# │ ├── my_child_dir1
18# │ │ ├── my_child_file1.txt
19# │ │ └── my_child_file2.csv
20# │ ├── my_child_file1.txt
21# │ ├── my_child_file1.zip
22# │ └── my_child_file2.csv
まとめ
1from pathlib import Path
2import zipfile
3
4# 指定したディレクトリ配下のファイルを個別に圧縮。
5# 圧縮対象は5KB以上のファイル。圧縮後はファイルを削除。
6target_path = Path("./zip_test_dir/my_dir5/")
7
8for file in target_path.glob("**/*"):
9 # file.stat().st_sizeでファイルサイズ(byte)を取得し、それが2KB以上かをチェック。
10 if file.is_file() and file.stat().st_size >= 5 * 1024:
11 with zipfile.ZipFile(file.with_suffix(".zip"), "w", compression=zipfile.ZIP_DEFLATED) as zipf:
12 zipf.write(file, arcname=file.name)
13 # ファイルを削除する
14 file.unlink()
15
16# 指定したディレクトリを圧縮
17# 圧縮対象のフォルダ指定
18target_path = Path("./zip_test_dir/my_dir6/")
19# 圧縮後のzipファイル名作成
20zip_path = target_path.with_suffix(".zip") # zip_test_dir/my_dir6.zip
21
22with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED) as zipf:
23 # ディレクトリやファイルを再起的に取得
24 for file in folder.rglob('*'):
25 if file.is_file(): # ファイルのみを対象にする
26 # ルートパスからの相対パスを取得。解凍時にディレクトリ構成を保つため。
27 # my_dir6からfileまでの相対パスを取得する。
28 rel_path = file.relative_to(target_path)
29 zipf.write(file, arcname=rel_path)
30
31# 指定したディレクトリに解凍
32# 解凍するzipファイルのパス
33zip_path = Path("./zip_test_dir/my_dir1.zip")
34# 解凍先ディレクトリの指定
35extracted_path = Path("./zip_test_dir/extracted_dir1")
36
37# ZIPファイルを解凍
38with zipfile.ZipFile(zip_path, 'r') as zipf:
39 # 全てのファイルを解凍。解凍先のディレクトリが存在しない場合、自動で作成される
40 # 解凍先のディレクトリ直下にzip時の構造を保ったままファイルが解凍される
41 zipf.extractall(extracted_path)
42
43# zipファイルのある場所に解凍
44zip_path = Path("./zip_test_dir/my_dir5/")
45
46for zip_file in zip_path.glob("**/*.zip"):
47 with zipfile.ZipFile(zip_file, "r") as zipf:
48 zipf.extractall(zip_file.parent)
49