Python | ファイル・OS 操作:ZIP 圧縮 zipfile

Python Python
スポンサーリンク
  1. 概要(zipfile は「作る・入れる・取り出す」をひとまとめにする標準機能)
  2. 基本の操作(ここが重要)
    1. ZIP を新規作成・追加・一覧・解凍
    2. モード選択の要点(w/a/r/x)
  3. 圧縮の詳細と設計(方式・arcname・レベル)
    1. 圧縮方式と圧縮レベル
    2. arcname(ZIP 内のパス)で「見やすく安全に」
    3. 文字列データを直接入れる(writestr)
  4. ディレクトリ丸ごと圧縮(再帰・除外・相対化)
    1. os.walk / pathlib で再帰して入れる
    2. 除外ルールを挟む(例:ビルド生成物除外)
  5. 解凍の安全策(ZIP スリップ対策・部分抽出)
    1. ZIP スリップ(パストラバーサル)を防ぐ
    2. 部分的に取り出す(特定ファイルだけ)
    3. パスワード付き ZIP の注意
  6. 実務パターン(バックアップ・配布パッケージ・インメモリ)
    1. 日付フォルダのバックアップを ZIP 化
    2. 配布用に「必要ファイルだけ」束ねる
    3. インメモリで生成(HTTP 応答など)
  7. よくある落とし穴の回避(上書き・巨大サイズ・メタデータ)
    1. 上書きと追記の混同を避ける
    2. 巨大ファイル・大量ファイルの扱い
    3. パーミッション・タイムスタンプなどのメタデータ
  8. 例題で身につける(定番から一歩先まで)
    1. 例題1:フォルダ丸ごとを相対パスで圧縮
    2. 例題2:追記で構成を整える(サブフォルダに入れる)
    3. 例題3:安全抽出(ZIP スリップ対策)
    4. 例題4:インメモリ ZIP を作ってすぐ使う
  9. まとめ

概要(zipfile は「作る・入れる・取り出す」をひとまとめにする標準機能)

zipfile は ZIP アーカイブの作成・追加・一覧取得・解凍を行う標準ライブラリです。外部インストール不要で、ファイル単体からフォルダ丸ごとまで扱えます。重要なのは「モード(w/a/r/x)の選択」「圧縮方式と arcname の設計」「安全な解凍(パスの検証)」です。with 文で ZipFile を開いて閉じ忘れをなくし、pathlib と組み合わせると結合・作成・読み書きが安全にまとまります。

基本の操作(ここが重要)

ZIP を新規作成・追加・一覧・解凍

from zipfile import ZipFile, ZIP_DEFLATED
from pathlib import Path

# 新規作成(書き込みモード 'w'。既存があれば上書き)
with ZipFile("archive.zip", mode="w", compression=ZIP_DEFLATED) as z:
    z.write("data/report.csv", arcname="report.csv")  # 中の名前を制御(後述)

# 既存 ZIP に追加('a')
with ZipFile("archive.zip", mode="a", compression=ZIP_DEFLATED) as z:
    z.write("images/logo.png", arcname="img/logo.png")

# 中身の一覧(ファイル名の列)
with ZipFile("archive.zip", mode="r") as z:
    print(z.namelist())

# 全解凍(先に出力先フォルダを作る)
out = Path("extracted")
out.mkdir(parents=True, exist_ok=True)
with ZipFile("archive.zip") as z:
    z.extractall(out)
Python

モード選択の要点(w/a/r/x)

  • w: 新規作成。既存があれば上書き。
  • a: 追記。既存の内容は保持、追加分を入れる。
  • r: 読み込みのみ。書き込み不可。
  • x: 新規専用。既存なら失敗(安全に新規作成したいとき)。

圧縮の詳細と設計(方式・arcname・レベル)

圧縮方式と圧縮レベル

  • ZIP_STORED: 無圧縮。高速だがサイズは減らない。
  • ZIP_DEFLATED: 一般的な deflate。ほとんどの場面でこれ。
  • ZIP_BZIP2 / ZIP_LZMA: より高圧縮(重い)。テキスト中心・サイズ重視なら検討。
from zipfile import ZipFile, ZIP_BZIP2
with ZipFile("docs.zip", "w", compression=ZIP_BZIP2, compresslevel=9) as z:
    z.write("docs/manual.txt")
Python

compresslevel は 0〜9(方式に依存)。高いほど小さく・遅くなります。

arcname(ZIP 内のパス)で「見やすく安全に」

from zipfile import ZipFile
with ZipFile("bundle.zip", "w") as z:
    # 絶対パスは入れない。必要な相対構造だけを格納
    z.write("project/README.md", arcname="README.md")
    z.write("project/config/app.yaml", arcname="config/app.yaml")
Python

保存名(arcname)を制御すると、展開後の構成が分かりやすくなり、不要なローカルパス混入も防げます。

文字列データを直接入れる(writestr)

from zipfile import ZipFile
with ZipFile("conf.zip", "w") as z:
    z.writestr("settings.ini", "name=alice\nretry=3\n")
Python

生成物や小さなテキストをファイル化せずに格納できます。

ディレクトリ丸ごと圧縮(再帰・除外・相対化)

os.walk / pathlib で再帰して入れる

from zipfile import ZipFile, ZIP_DEFLATED
from pathlib import Path

root = Path("project")
with ZipFile("project.zip", "w", compression=ZIP_DEFLATED) as z:
    for p in root.rglob("*"):
        if p.is_file():
            z.write(p, arcname=p.relative_to(root))  # ルートからの相対パス
Python

除外ルールを挟む(例:ビルド生成物除外)

exclude = {".git", "__pycache__"}
with ZipFile("src.zip", "w") as z:
    for p in Path("src").rglob("*"):
        if any(part in exclude for part in p.parts):  # 途中に該当フォルダがあれば除外
            continue
        if p.is_file():
            z.write(p, arcname=p.relative_to("src"))
Python

相対化は relative_to で確実に(絶対やカレント依存を避ける)。

解凍の安全策(ZIP スリップ対策・部分抽出)

ZIP スリップ(パストラバーサル)を防ぐ

悪意ある ZIP は「../」を含む格納名で、意図外の場所へ書き出す恐れがあります。抽出先を基準として検証します。

from zipfile import ZipFile
from pathlib import Path

def safe_extract(zpath, outdir):
    outdir = Path(outdir).resolve()
    with ZipFile(zpath) as z:
        for name in z.namelist():
            dest = (outdir / name).resolve()
            if outdir not in dest.parents and dest != outdir:
                raise ValueError(f"危険なパス: {name}")
            z.extract(name, outdir)

safe_extract("archive.zip", "extracted")
Python

部分的に取り出す(特定ファイルだけ)

from zipfile import ZipFile
with ZipFile("archive.zip") as z:
    z.extract("config/app.yaml", "extracted")
    data = z.read("README.md")  # bytes を直接取得
Python

パスワード付き ZIP の注意

zipfile は「復号(読み取り)」のパスワード指定は可能ですが、暗号化して「書き込み」は標準では対応が限定的です(外部ライブラリの検討)。読み取りは setpassword、または extract/read で pwd 引数を使います。

from zipfile import ZipFile
with ZipFile("secure.zip") as z:
    z.setpassword(b"secret")
    z.extractall("out")
Python

実務パターン(バックアップ・配布パッケージ・インメモリ)

日付フォルダのバックアップを ZIP 化

from zipfile import ZipFile, ZIP_DEFLATED
from pathlib import Path
from datetime import date

src = Path("logs") / date.today().strftime("%Y/%m/%d")
out = Path("backup"); out.mkdir(parents=True, exist_ok=True)
zip_path = out / f"logs_{date.today().strftime('%Y%m%d')}.zip"

with ZipFile(zip_path, "w", ZIP_DEFLATED) as z:
    for p in src.rglob("*.log"):
        z.write(p, arcname=p.relative_to(src))
Python

配布用に「必要ファイルだけ」束ねる

from zipfile import ZipFile
files = ["README.md", "LICENSE", "dist/app.exe"]
with ZipFile("release.zip", "w") as z:
    for f in files:
        z.write(f, arcname=Path(f).name)  # ルート直下へ平坦化
Python

インメモリで生成(HTTP 応答など)

from zipfile import ZipFile, ZIP_DEFLATED
from io import BytesIO

buf = BytesIO()
with ZipFile(buf, "w", ZIP_DEFLATED) as z:
    z.writestr("hello.txt", "world")
# buf.getvalue() をレスポンスへ
Python

ファイルシステムを使わず、その場で ZIP バイト列を作れます。

よくある落とし穴の回避(上書き・巨大サイズ・メタデータ)

上書きと追記の混同を避ける

  • w: 既存 ZIP を消して作り直す。
  • a: 既存へ追加する。重複ファイル名が混在し得るため、設計か検証で回避。

巨大ファイル・大量ファイルの扱い

  • allowZip64(デフォルト有効)で 2GB 超にも対応。
  • 圧縮方式・レベルはサイズと速度のトレードオフ。テキスト中心なら高圧縮が効きやすい。バイナリ(PNG/JPEG など)はすでに圧縮済みで効果薄。

パーミッション・タイムスタンプなどのメタデータ

ZipInfo(infolist)から属性や時刻を参照可能。完全な復元が必要なら、格納時の方針を決める(例:arcname、展開先で chmod など)。

from zipfile import ZipFile
with ZipFile("archive.zip") as z:
    for info in z.infolist():
        print(info.filename, info.file_size, info.date_time)
Python

例題で身につける(定番から一歩先まで)

例題1:フォルダ丸ごとを相対パスで圧縮

from zipfile import ZipFile, ZIP_DEFLATED
from pathlib import Path

root = Path("project")
with ZipFile("project.zip", "w", ZIP_DEFLATED) as z:
    for p in root.rglob("*"):
        if p.is_file():
            z.write(p, arcname=p.relative_to(root))
Python

例題2:追記で構成を整える(サブフォルダに入れる)

from zipfile import ZipFile, ZIP_DEFLATED

with ZipFile("bundle.zip", "a", ZIP_DEFLATED) as z:
    z.write("docs/manual.pdf", arcname="docs/manual.pdf")
    z.writestr("VERSION", "1.2.3")
Python

例題3:安全抽出(ZIP スリップ対策)

from zipfile import ZipFile
from pathlib import Path

def safe_extract(zpath, outdir):
    outdir = Path(outdir).resolve()
    with ZipFile(zpath) as z:
        for name in z.namelist():
            dest = (outdir / name).resolve()
            if outdir not in dest.parents and dest != outdir:
                raise ValueError(f"危険なパス: {name}")
            z.extract(name, outdir)

safe_extract("bundle.zip", "out")
Python

例題4:インメモリ ZIP を作ってすぐ使う

from zipfile import ZipFile, ZIP_DEFLATED
from io import BytesIO

buf = BytesIO()
with ZipFile(buf, "w", ZIP_DEFLATED) as z:
    z.writestr("hello.txt", "こんにちは")
data = buf.getvalue()  # この bytes を送信・保存などに使う
Python

まとめ

zipfile は「モード選択(w/a/r/x)」「圧縮方式とレベル」「arcname の設計」を押さえれば、配布・バックアップ・一括保存が短く安全に書けます。フォルダ丸ごと圧縮は relative_to で相対化、不要物は除外。解凍は ZIP スリップ対策で出力先配下に限定。with 文で確実にクローズし、pathlib と併用して結合・作成・検証を段階化すれば、現場で使える「壊れない ZIP 処理」になります。

タイトルとURLをコピーしました