攝影或3C

Python zipfile 全攻略:實作讀寫、解壓、重建,以及 write/writestr 的正確用法; with zipfile.ZipFile(zip_path, mode=”r”) as z: print(z.namelist()) ; z.infolist()

sample.zip
將副檔名由docx改為zip:

準備

  • Python 3.8 以上
  • 標準庫 zipfile、pathlib 已內建,無需安裝
  1. 開啟與檢視 ZIP
  • 列出 sample.zip 的所有項目與基本資訊
from pathlib import Path
import zipfile

zip_path = Path(r"D:\Temp\sample.zip")

with zipfile.ZipFile(zip_path, mode="r") as z:
    # 所有檔名
    print(z.namelist())

    # 更詳細每個 ZipInfo
    for info in z.infolist():
        print(f"{info.filename:45} size={info.file_size} "
              f"compressed={info.compress_size} method={info.compress_type}")

 輸出:

  1. 讀取檔案內容
  • 以 word/document.xml 為例,讀出 bytes,再自行解碼為文字
with zipfile.ZipFile(zip_path, "r") as z:
    raw = z.read("word/document.xml")      # bytes
    text = raw.decode("utf-8", errors="replace")
    print(text[:500])  # 只看前 500 個字元

輸出:

  1. 解壓縮(含安全版本)
  • 解到 D:\Temp\out,並避免 Zip Slip
from pathlib import Path

def safe_extractall(z: zipfile.ZipFile, dest: Path):
    dest = dest.resolve()
    for member in z.infolist():
        target = (dest / member.filename).resolve()
        if not str(target).startswith(str(dest)):
            raise RuntimeError(f"Blocked unsafe path: {member.filename}")
        z.extract(member, dest)

out_dir = Path(r"D:\Temp\out")
with zipfile.ZipFile(zip_path, "r") as z:
    safe_extractall(z, out_dir)
  1. write 與 writestr 的差異與示例
    核心結論
  • write 用於把磁碟上的既有檔案加入 ZIP;它會讀取來源檔案的內容與檔案中繼資料(時間、大小、可能的權限)。
  • writestr 用於把記憶體中的資料(str 或 bytes)加入 ZIP;若要自訂檔名、時間、權限、壓縮法,可先建立 ZipInfo 再用 writestr 寫入。

對照表(重點)

  • 資料來源
    • write(path[, arcname]): 來源必須是磁碟檔案 path。
    • writestr(name_or_info, data): 來源是你手中的 data(bytes 或 str)。
  • 中繼資料
    • write 會帶入來源檔案的 mtime、可能的 Unix 權限到 ZipInfo。
    • writestr 預設用現在時間;要精準控制,請傳入 ZipInfo。
  • 文字編碼
    • write 不處理編碼;它直接讀 bytes。
    • writestr 若 data 是 str,會用 UTF-8 編碼後再寫入;若是 bytes,原樣寫入。
  • 使用場合
    • write:打包現有檔案或資料夾。
    • writestr:動態產生內容(報表、清單、快照)、從網路/資料庫取得的 bytes、不想落地的暫存內容。

4.1 用 write 把磁碟檔案加入 sample.zip 的副本

  • 我們建立 D:\Temp\sample_new.zip,拷貝 sample.zip 全內容,再加一個磁碟上的檔案。
from pathlib import Path
import zipfile

src = Path(r"D:\Temp\sample.zip")
dst = Path(r"D:\Temp\sample_new.zip")

# 先準備一個要加入的磁碟檔案
readme_path = Path(r"D:\Temp\README.txt")
readme_path.write_text("This README is from filesystem.\n", encoding="utf-8")

with zipfile.ZipFile(src, "r") as zin, \
     zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zout:

    # 複製原有內容保持壓縮法與時間
    for info in zin.infolist():
        zout.writestr(info, zin.read(info.filename))

    #  write來源是磁碟檔arcname 決定在 ZIP 的路徑
    zout.write(readme_path, arcname="notes/README_from_disk.txt")

4.2 用 writestr 動態加入檔案(文字與位元組)

  • 加入一個 Markdown 文字檔與一張假的二進位資料(例如小圖的占位 bytes)
import zipfile
from pathlib import Path

dst = Path(r"D:\Temp\sample_mem.zip")

with zipfile.ZipFile(Path(r"D:\Temp\sample.zip"), "r") as zin, \
     zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zout:

    # 複製原始內容
    for info in zin.infolist():
        zout.writestr(info, zin.read(info.filename))

    # 文字str)— 會自動以 UTF-8 編碼
    zout.writestr("notes/guide.md",
                  "# 使用說明\n\n這是一段由 writestr 動態產生的內容。\n")

    # 位元組bytes)— 原樣寫入
    fake_png = b"\x89PNG\r\n\x1a\n" + b"\x00" * 64  # 只是示例不是真正的 PNG
    zout.writestr("media/placeholder.png", fake_png)

4.3 用 writestr + ZipInfo 精準控制時間、權限、壓縮法

  • 適合需要可執行權限、特定時間戳、或每個檔案不同壓縮方法的情境
import time, stat, zipfile
from pathlib import Path

dst = Path(r"D:\Temp\sample_meta.zip")

with zipfile.ZipFile(Path(r"D:\Temp\sample.zip"), "r") as zin, \
     zipfile.ZipFile(dst, "w") as zout:

    # 複製
    for info in zin.infolist():
        zout.writestr(info, zin.read(info.filename))

    # 建立 ZipInfo 並自訂屬性
    info = zipfile.ZipInfo("bin/run.sh")
    info.date_time = time.localtime(1730000000)[:6]  # 自訂時間
    info.compress_type = zipfile.ZIP_DEFLATED
    info.external_attr = (stat.S_IFREG | 0o755) << 16  # Unix 可執行

    content = b"#!/usr/bin/env bash\necho Hello\n"
    zout.writestr(info, content)
  1. 覆蓋與刪除
  • ZIP 不支援原地刪除;要「刪掉」檔案,典型作法是重建新 ZIP,過濾你不要的項目。
  • 覆蓋同名檔時,’a’ 模式會新增一個同名 entry;通常最新的會被讀到,但舊的仍在檔內。要乾淨覆蓋,還是重建。

重建並排除特定路徑的範例:刪除所有 word/media/ 下的檔案

from pathlib import Path
import zipfile

def rebuild_without_media(src: Path, dst: Path):
    with zipfile.ZipFile(src, "r") as zin, \
         zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED) as zout:
        for info in zin.infolist():
            if info.filename.startswith("word/media/"):
                # 跳過等於刪除
                continue
            data = zin.read(info.filename)
            # 以原 ZipInfo 寫回保留時間與屬性
            zout.writestr(info, data)

rebuild_without_media(Path(r"D:\Temp\sample.zip"), Path(r"D:\Temp\sample_nopic.zip"))
  1. 修改既有檔案內容(以 word/document.xml 為例)
  • 讀出、修改內容,再重建 ZIP;這裡示範把 XML 文字最前面插入註解
import zipfile
from pathlib import Path

src = Path(r"D:\Temp\sample.zip")
dst = Path(r"D:\Temp\sample_edit.zip")

with zipfile.ZipFile(src, "r") as zin, \
     zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED) as zout:

    for info in zin.infolist():
        data = zin.read(info.filename)
        if info.filename == "word/document.xml":
            text = data.decode("utf-8", errors="replace")
            text = "<!-- edited by zipfile -->\n" + text
            data = text.encode("utf-8")
        zout.writestr(info, data)
  1. 常見實務建議與陷阱
  • 檔名編碼:ZIP 檔名預設 CP437;Python 會依旗標處理 UTF-8。跨語系檔名若有亂碼,檢查 ZipInfo.flag_bits 的 UTF-8 旗標。
  • 壓縮方法:對大量已壓縮檔(jpg/png/mp4/zip),用 ZIP_STORED 省時間;對文字/CSV/JSON 用 ZIP_DEFLATED 並設 compresslevel=6 是常見平衡。
  • 路徑分隔:ZIP 內一律使用正斜線 /,Windows 也一樣。zipfile 會自動把 Windows 路徑轉為 /。
  • 大檔:allowZip64 預設已開;處理超過 2GB 沒問題。
  • 測試:z.testzip() 可快速檢查是否有損壞的 entry(回傳第一個壞檔名或 None)。
  • 安全:對未知來源 ZIP,解壓前先做路徑檢查(避免 Zip Slip)。
  1. write vs writestr 速查清單
  • 何時用 write?
    • 你已有磁碟檔案,要保留它的修改時間、可能的權限。
    • 批次打包資料夾。
  • 何時用 writestr?
    • 內容來自記憶體(動態生成、網路抓取、資料庫)。
    • 你要自訂 ZipInfo(檔名、時間、權限、壓縮方式)。
  • 兩者都能控制壓縮方法與等級:
    • 在 ZipFile(…) 指定 compression/compresslevel,或對 writestr 個別覆寫 compress_type/compresslevel。
  • 範例一句話辨別
    • write(Path(“C:/logs/today.log”), arcname=”logs/today.log”)
    • writestr(“logs/today.log”, b”…log bytes…”)

結語

  • 以上範例皆可直接套用在 D:\Temp\sample.zip。若你的 ZIP 結構如截圖所示(word/… 目錄),讀寫與重建流程都相同;只需確保路徑字串與檔名大小寫一致即可。若後續要結合 lxml 對 XML 內容做更精細的變更,也可以把「讀出 → 修改 → writestr 寫回」這個步驟替換成 XML 解析與序列化。

推薦hahow線上學習python: https://igrape.net/30afN

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡