sample.zip
將副檔名由docx改為zip:
準備
- Python 3.8 以上
- 標準庫 zipfile、pathlib 已內建,無需安裝
- 開啟與檢視 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}")輸出:
- 讀取檔案內容
- 以 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 個字元輸出:
- 解壓縮(含安全版本)
- 解到 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)- 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)- 覆蓋與刪除
- 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"))- 修改既有檔案內容(以 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)- 常見實務建議與陷阱
- 檔名編碼: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)。
- 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