閉じる

Python標準のzipfileとgzipで圧縮・解凍する方法をわかりやすく解説

ファイルをまとめて圧縮したい時はZIP、ログやテキストを軽くしたい時はGzipが便利です。

本記事では、Python標準のzipfilegzipを使って、圧縮・解凍を実践的に解説します。

追加インストール不要で今すぐ使える方法に絞り、初心者の方でも迷わないようにサンプルコードと注意点を丁寧に紹介します。

Pythonの圧縮・解凍の基本と選び方(zipfile/gzip)

標準ライブラリで追加インストール不要

Pythonにはzipfilegzipが標準添付されており、どちらもインストールなしで使えます。

学習コストが低く、配布先にも余計な依存を増やさないのが大きな利点です。

zipfileとgzipの違いと用途

両者の違いを整理しておくと選び方が明確になります。

観点zipfilegzip
主な形式ZIPアーカイブGzip圧縮(単一ストリーム)
まとめ方複数ファイルやフォルダを1つのZIPに格納可能基本的に1ファイル→1.gz
代表的な拡張子.zip.gz
圧縮方式DEFLATE(標準)、一部BZIP2やLZMAも可DEFLATE
典型用途配布用アーカイブ、成果物の打包テキストやログの軽量化、ストリーム圧縮
パス(階層)ディレクトリ構造を保持構造は持たない

フォルダごと固めたい→zipfile、単一ファイルをスリム化したい→gzipと覚えると判断が早くなります。

zipfileを使う場面

  • プロジェクト一式や成果物をひとまとめに配布したい時に適しています。
  • 複数ファイルのディレクトリ構造を保ったまま、1つのファイルにまとめたい時に使います。

gzipを使う場面

  • ログやCSV、JSONなど1つのテキストファイルを軽くしたい時に有効です。
  • ストリーム処理(標準入出力やネットワーク転送)と相性が良く、パイプラインの途中で圧縮・展開したい時に便利です。

基本用語(圧縮・展開・アーカイブ)

  • 圧縮: データサイズを小さくすることです。
  • 展開(解凍): 圧縮したデータを元に戻すことです。
  • アーカイブ: 複数ファイルやフォルダを1つのまとまりにする形式です。ZIPは圧縮+アーカイブGzipは圧縮のみという違いがあります。

zipfileの使い方(複数ファイルやフォルダをZIP圧縮)

ファイルをZIPに圧縮する

最小の例です。

1つのファイルをZIPに格納します。

Python
# zipfile_basic_file.py
import zipfile

# 圧縮対象のファイルと、作成するZIPファイル名を指定します
src_file = "example.txt"
zip_name = "single_file.zip"

# with構文でクローズを自動化します
# compressionにZIP_DEFLATEDを指定すると一般的な圧縮が有効になります
with zipfile.ZipFile(zip_name, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
    # arcnameを指定すると、ZIP内の名前を任意に変えられます
    zf.write(src_file, arcname="docs/example.txt")

print(f"Created: {zip_name}")
実行結果
Created: single_file.zip

フォルダをまるごとZIPにする

フォルダ以下を再帰的に辿って、相対パスを保ちつつZIP化します。

Python
# zipfile_zip_directory.py
import os
import zipfile

def zip_directory(src_dir: str, zip_name: str, compresslevel: int = 6) -> None:
    # src_dirの末尾スラッシュの有無に左右されないようにabs化
    src_dir_abs = os.path.abspath(src_dir)

    with zipfile.ZipFile(zip_name, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=compresslevel) as zf:
        for root, _, files in os.walk(src_dir_abs):
            for file in files:
                full_path = os.path.join(root, file)
                # ZIP内に入れるときはsrc_dirからの相対パスを使うのが安全です
                arcname = os.path.relpath(full_path, start=src_dir_abs)
                zf.write(full_path, arcname=arcname)
        # 内容確認(学習用): 実運用では省略可
        print("Archive members:")
        for name in zf.namelist():
            print(" -", name)

if __name__ == "__main__":
    zip_directory("project_folder", "project_archive.zip", compresslevel=6)
実行結果
Archive members:
 - README.md
 - src/main.py
 - data/input.csv

ZIPを解凍する

ZIPを安全に解凍します。

信頼できないZIPを安易に展開するのは危険です。

パストラバーサル(../を含む悪意あるパス)対策を入れた安全な関数例を示します。

Python
# zipfile_extract_safe.py
import os
import zipfile

def safe_extractall(zip_path: str, dest_dir: str) -> None:
    # 出力先の絶対パス
    dest_abs = os.path.abspath(dest_dir)

    with zipfile.ZipFile(zip_path, "r") as zf:
        # 各メンバーの展開先パスを検証
        for info in zf.infolist():
            extracted_abs = os.path.abspath(os.path.join(dest_abs, info.filename))
            # commonpathで出力先ディレクトリ配下かを確認
            if os.path.commonpath([dest_abs, extracted_abs]) != dest_abs:
                raise ValueError(f"Unsafe path detected: {info.filename}")
        # 安全が確認できたらまとめて展開
        zf.extractall(dest_abs)

if __name__ == "__main__":
    os.makedirs("unpacked", exist_ok=True)
    safe_extractall("project_archive.zip", "unpacked")
    print("Extracted to 'unpacked'")
実行結果
Extracted to 'unpacked'

圧縮レベルとパスの扱い

圧縮レベルは0〜9を指定できます。

0は無圧縮(保存のみ)、9は最も小さくなるが遅い傾向です。

実務では6前後のバランスが使いやすいことが多いです。

指定例意味
compresslevel=0無圧縮(ZIP_STORED相当に近い使い方)
compresslevel=6バランス重視の圧縮
compresslevel=9最高圧縮(時間とCPUを余計に使う)

パスについては、arcnameに相対パスを使うことが重要です。

絶対パスやドライブレターをZIP内に入れると、展開時に意図せず上書き事故を招きます。

Python
# zipfile_paths_and_levels.py
import zipfile

with zipfile.ZipFile("levels.zip", "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
    # ZIP内は相対パスで揃える
    zf.write("large.txt", arcname="docs/large.txt")

日本語ファイル名の注意

近年の環境ではUTF-8のファイル名を正しく扱えるツールが増えましたが、非常に古い解凍ソフトでは文字化けすることがあります。

対策として次の方法が現実的です。

  • 配布先の解凍ツールを指定し、UTF-8対応を前提にする。
  • ZIP内のファイル名はASCIIに近い安全な文字にするため、arcnameで置き換える。
Python
# 日本語ファイル名を安全な英字名に差し替えて格納する例
import zipfile
with zipfile.ZipFile("safe_name.zip", "w", compression=zipfile.ZIP_DEFLATED) as zf:
    zf.write("レポート2025年3月版.txt", arcname="report_2025_03.txt")

with構文で安全に扱う

ファイルハンドルの閉じ忘れは不具合の温床です。

withを使うとクローズが自動化され、途中で例外が発生しても安全です。

Python
# with構文の基本形
import zipfile

with zipfile.ZipFile("data.zip", "w", zipfile.ZIP_DEFLATED) as zf:
    zf.write("data.csv", arcname="data/data.csv")
# ここに来た時点で自動的にクローズされています

gzipの使い方(単一ファイルを.gzで圧縮・解凍)

テキストをgzip圧縮する

テキストはgzip.open(..., mode="wt")で直接書けます。

encodingを明示すると後で扱いやすくなります。

Python
# gzip_text_write.py
import gzip

text = "こんにちは、Gzip圧縮の世界!\n圧縮するとサイズを節約できます。"

with gzip.open("hello.txt.gz", mode="wt", encoding="utf-8", compresslevel=9) as f:
    f.write(text)

print("Wrote hello.txt.gz")
実行結果
Wrote hello.txt.gz

バイナリをgzip圧縮する

バイナリファイル(例: 画像)はrbで読み、wbで書きます。

メモリ上のbytesを一発で圧縮するgzip.compressも便利です。

Python
# gzip_binary_file.py
import gzip

# ファイルから読み込んで.gzに書く(ストリーム)
with open("image.png", "rb") as src, gzip.open("image.png.gz", "wb", compresslevel=6) as dst:
    # 大きいファイルでもそのままコピー可能
    dst.write(src.read())

# メモリ上のbytesを圧縮する方法
data = b"A" * 1024  # 1KBのダミーデータ
compressed = gzip.compress(data, compresslevel=6)
decompressed = gzip.decompress(compressed)
assert decompressed == data

print("Binary gzip done")
実行結果
Binary gzip done

gzipを解凍して読む

rtでテキストとして読み、rbでバイナリとして読みます。

Python
# gzip_read.py
import gzip

# テキストとして読む
with gzip.open("hello.txt.gz", "rt", encoding="utf-8") as f:
    content = f.read()
print(content.splitlines()[0])  # 先頭行を確認

# バイナリとして読む
with gzip.open("image.png.gz", "rb") as f:
    data = f.read()
print(f"Read {len(data)} bytes")
実行結果
こんにちは、Gzip圧縮の世界!
Read 12345 bytes

圧縮レベルとモード指定

gzip.openの主なモードは"rb""wb""rt""wt"です。

圧縮レベルは0〜9で、9が最もサイズが小さくなりやすいですが遅くなります。

再現性が重要ならmtime=0を指定して、タイムスタンプ差によるバイト列の揺らぎを抑えます。

Python
# gzip_modes_and_levels.py
import gzip

# テキストを書き出す。再現性のためmtime=0を指定
with gzip.open("log.txt.gz", "wt", encoding="utf-8", compresslevel=6, mtime=0) as f:
    f.write("line1\nline2\n")

拡張子(.gz)の付け方

一般的には元の拡張子の後ろに.gzを付けます(例: log.txt → log.txt.gz)。

展開後の元ファイル名を保持したい場合はGzipヘッダのfilenameが使われることもありますが、通常はファイル名で管理すれば十分です。

Python
# gzip_extension.py
import os
import gzip

src = "app.log"
dst = src + ".gz"  # "app.log.gz"

with open(src, "rb") as fin, gzip.open(dst, "wb") as fout:
    fout.write(fin.read())

print(f"Compressed: {dst}")
実行結果
Compressed: app.log.gz

実用レシピとよくあるつまずき(Python初心者向け)

ディレクトリをZIPにして配布する

配布でありがちな要件を満たす、隠しファイル除外・空ディレクトリ除外の例です。

Python
# recipe_zip_for_distribution.py
import os
import zipfile

def zip_for_distribution(src_dir: str, zip_name: str) -> None:
    src_abs = os.path.abspath(src_dir)
    with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
        for root, dirs, files in os.walk(src_abs):
            # .gitなどの隠しディレクトリを除外(必要に応じて拡張)
            dirs[:] = [d for d in dirs if not d.startswith(".")]
            for file in files:
                if file.startswith("."):
                    continue
                full_path = os.path.join(root, file)
                arcname = os.path.relpath(full_path, src_abs)
                zf.write(full_path, arcname=arcname)
    print(f"Packaged: {zip_name}")

if __name__ == "__main__":
    zip_for_distribution("my_project", "my_project.zip")
実行結果
Packaged: my_project.zip

ログを日次でgzip圧縮する

ローテーション済みのログ(例: app-2025-09-16.log)をGzip化する簡単なスクリプトです。

Python
# recipe_gzip_daily_logs.py
import os
import gzip
from datetime import datetime

def gzip_today_log(prefix: str = "app"):
    today = datetime.now().strftime("%Y-%m-%d")
    src = f"{prefix}-{today}.log"
    dst = src + ".gz"
    if not os.path.exists(src):
        print(f"No log for today: {src}")
        return

    with open(src, "rb") as fin, gzip.open(dst, "wb", compresslevel=6, mtime=0) as fout:
        # mtime=0でビルドの再現性を高める
        fout.write(fin.read())
    print(f"Compressed: {dst}")

if __name__ == "__main__":
    gzip_today_log()
実行結果
Compressed: app-2025-09-16.log.gz

特定の拡張子だけZIPに含める

ソース配布では.pyや.mdだけ含めたいなど、拡張子で絞ることがよくあります。

Python
# recipe_zip_selected_ext.py
import os
import zipfile

def zip_selected_extensions(src_dir: str, zip_name: str, exts: tuple[str, ...]) -> None:
    src_abs = os.path.abspath(src_dir)
    with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
        for root, _, files in os.walk(src_abs):
            for file in files:
                if file.lower().endswith(exts):
                    full_path = os.path.join(root, file)
                    arcname = os.path.relpath(full_path, src_abs)
                    zf.write(full_path, arcname=arcname)
    print(f"Created: {zip_name}")

if __name__ == "__main__":
    # .py と .md を含める
    zip_selected_extensions("my_project", "src_docs.zip", (".py", ".md"))
実行結果
Created: src_docs.zip

エラー対処(ファイル未存在・権限)

初心者の方がつまずきやすいのがFileNotFoundErrorPermissionErrorです。

最小限のガードを入れましょう。

Python
# recipe_error_handling.py
import os
import zipfile

def safe_zip_write(zip_name: str, file_to_add: str) -> None:
    if not os.path.exists(file_to_add):
        print(f"Not found: {file_to_add}")
        return

    try:
        with zipfile.ZipFile(zip_name, "a", zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
            zf.write(file_to_add, arcname=os.path.basename(file_to_add))
        print(f"Added to {zip_name}: {file_to_add}")
    except PermissionError:
        print("Permission denied. Try running with proper permissions or change destination.")
    except OSError as e:
        print(f"OS error: {e}")

if __name__ == "__main__":
    safe_zip_write("assets.zip", "logo.png")
実行結果
Added to assets.zip: logo.png

展開先フォルダの指定とパス区切り

ZIP内のパスは一般にスラッシュ(/)で表現され、zipfileはOSに合わせて展開時に良きに計らってくれます。

出力先の指定はextractall(path=...)で行います。

パス結合はos.path.joinを使うとOS差異(Windowsの円記号など)を気にせずに済みます。

Python
# recipe_extract_to_dir.py
import os
import zipfile

zip_path = "my_project.zip"
out_root = "dist"
subdir = "v1.0.0"

dest = os.path.join(out_root, subdir)  # dist/v1.0.0 (OSにより区切りが変化)
os.makedirs(dest, exist_ok=True)

with zipfile.ZipFile(zip_path, "r") as zf:
    zf.extractall(path=dest)

print(f"Extracted to: {dest}")
実行結果
Extracted to: dist/v1.0.0
注意

不審なZIPを展開しないでください。

特にパストラバーサル巨大な展開サイズ(Zip Bomb)には注意が必要です。

上の安全な展開関数のように検査を入れるのが安心です。

まとめ

複数ファイルやフォルダをまとめるならzipfile、単一ファイルを軽くするならgzipが基本の選び方です。

zipfileではarcnameで相対パスを使う・with構文で自動クローズ、gzipではモード(rt/wt/rb/wb)とencoding、compresslevelの指定がコツです。

日本語ファイル名は可能なら英数字に置き換えるか、UTF-8対応の解凍ツールを前提にしましょう。

さらに不審なZIPの展開は危険である点を忘れず、必要に応じてパス検証や権限チェックを取り入れてください。

これらの基本を押さえることで、日々の配布やログ圧縮などの実務を安全かつ効率的に進めることができます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!