春江暮客

春江暮客的个人学习分享网站

用 Python 校验 ZIP 备份:检查 CRC、重复成员和漏打包文件

2026-10-17 技术
用 Python 校验 ZIP 备份:检查 CRC、重复成员和漏打包文件

备份脚本返回成功,backup.zip 也存在,却不一定能恢复:复制中断可能留下残缺归档,打包参数写错还可能漏掉配置文件。本文给初级到中级读者一个只读检查工具,先核对 ZIP 成员,再完整读取并检查 CRC,最后用退出码决定是否继续。

1. 方法:成员清单和完整读取都要检查

已有的 zipfile 基础教程介绍了读写和解压。本篇专门为已经写完、校验期间不会变化的本地备份添加验收步骤:拒绝重复成员名,核对必需文件,再调用 testzip()。

Python 官方 testzip 文档说明,该方法读取归档内文件并检查 CRC 和文件头,发现问题时返回首个坏文件名,否则返回 None。只列出目录并不能完成这项检查。

这里仅接受未加密的 ZIP_STORED 和 ZIP_DEFLATED 归档,并拒绝没有文件的归档。CRC 可帮助发现意外损坏;若要与可信原件核对内容,可配合 SHA-256 文件清单。本工具不验证 JSON、CSV 的业务内容,也不保证以后解压时的路径安全。

2. 准备环境和演示备份

使用 Python 3.9 或更新版本及可用的 zlib 模块,无需安装第三方包。示例在 Linux、Python 3.11.4 下测试,命令适用于 bash 和 zsh。

  1. 创建新演示目录;后续命令在同一个终端和目录运行。重复实验时请换一个目录名:
python3 --version
mkdir zip-backup-demo
cd zip-backup-demo
python3 - <<'PY_DATA'
from zipfile import ZipFile, ZIP_DEFLATED
with ZipFile("backup.zip", "x", compression=ZIP_DEFLATED) as archive:
    archive.writestr("config/task.json", '{"enabled":true}\n')
    archive.writestr("reports/daily.csv", "id,value\n1,42\n")
PY_DATA
  1. 将下一节保存为 check_zip.py,或下载 完整检查脚本。示例使用 x 模式创建 ZIP,已有同名归档时会报错,避免覆盖。

3. 完整实现

"""Check a completed local ZIP backup without extraction. Python 3.9+."""
import argparse
import json
from pathlib import Path
import sys
import zipfile
import zlib


def check_zip(path, required):
    with zipfile.ZipFile(path, "r") as archive:
        seen = set()
        files = set()
        total = 0
        for info in archive.infolist():
            name = info.filename
            if name in seen:
                raise ValueError(f"duplicate member: {name!r}")
            seen.add(name)
            if info.flag_bits & 1:
                raise ValueError(f"encrypted member is unsupported: {name!r}")
            if info.compress_type not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
                raise ValueError(f"unsupported compression: {name!r}")
            if not info.is_dir():
                files.add(name)
                total += info.file_size
        if not files:
            raise ValueError("archive contains no files")
        missing = sorted(set(required) - files)
        if missing:
            raise ValueError(f"missing required members: {missing!r}")
        bad = archive.testzip()
        if bad is not None:
            raise ValueError(f"CRC/header check failed: {bad!r}")
    return {
        "file": str(path), "files": len(files),
        "uncompressed_bytes": total, "valid": True,
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument("file", type=Path, help="completed, unchanging ZIP backup")
    parser.add_argument(
        "--require", action="append", default=[], metavar="MEMBER",
        help="exact non-directory member name; repeat for multiple files",
    )
    args = parser.parse_args()
    if any(not name or name.endswith("/") for name in args.require):
        parser.error("--require needs a nonempty file member name, without trailing /")
    try:
        result = check_zip(args.file, args.require)
    except (OSError, ValueError, EOFError, RuntimeError,
            NotImplementedError, zipfile.BadZipFile, zlib.error) as exc:
        print(f"ERROR: {args.file}: {exc}", file=sys.stderr)
        return 1
    print(json.dumps(result, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

infolist()返回成员信息;ZIP 可以包含同名成员,因此在完整读取前先拒绝重复名,避免“读取的是哪一份”产生歧义。--require 区分大小写,按归档内的完整名称精确匹配,使用 / 分隔目录;目录条目不能满足文件要求。

程序不调用解压方法,但会解压数据流进行校验,耗时随内容增长;目录信息也会占内存。它面向可信来源的自有备份,没有资源限额或恶意归档隔离机制,不应用作开放上传接口的安全检查器。

4. 运行并核对报告

  1. 查看帮助,检查两个必需文件:
python3 check_zip.py --help
python3 check_zip.py backup.zip \
  --require config/task.json --require reports/daily.csv > report.json
cat report.json

预期输出:

{"file": "backup.zip", "files": 2, "uncompressed_bytes": 31, "valid": true}

uncompressed_bytes 是非目录成员声明的未压缩大小总和,不是 ZIP 文件占用的磁盘空间。报告仅在所有检查通过后输出。

  1. 检查报告内容,并确认没有解压到当前目录:
python3 - <<'PY_CHECK'
import json
from pathlib import Path
report = json.loads(Path("report.json").read_text(encoding="utf-8"))
assert report == {
    "file": "backup.zip", "files": 2,
    "uncompressed_bytes": 31, "valid": True,
}
assert not Path("config").exists()
assert not Path("reports").exists()
print("PASS: two members checked; no files extracted")
PY_CHECK

普通失败返回 1,并写 stderr;参数错误返回 2,这是 argparse 的默认行为。成功返回 0。重定向会提前创建或截断 report.json,请使用新的报告路径。

5. 验证截断、CRC 损坏和漏文件

  1. 只在演示目录构造故障副本;保留原始 backup.zip:
python3 - <<'PY_BAD'
from pathlib import Path
from zipfile import ZipFile, ZIP_STORED
import warnings

Path("truncated.zip").write_bytes(Path("backup.zip").read_bytes()[:20])
with warnings.catch_warnings():
    warnings.simplefilter("ignore", UserWarning)
    with ZipFile("duplicate.zip", "x") as archive:
        archive.writestr("same.txt", "first")
        archive.writestr("same.txt", "second")
with ZipFile("crc-bad.zip", "x", compression=ZIP_STORED) as archive:
    archive.writestr("note.txt", b"unique-demo-payload")
raw = Path("crc-bad.zip").read_bytes()
assert raw.count(b"unique-demo-payload") == 1
Path("crc-bad.zip").write_bytes(raw.replace(
    b"unique-demo-payload", b"Unique-demo-payload", 1
))
PY_BAD

crc-bad.zip 使用未压缩存储,替换一个等长内容字节,让成员清单仍可读,但 CRC 与实际内容不一致。

  1. 验证失败退出、空 stdout 和输入未改动:
python3 - <<'PY_TEST'
from pathlib import Path
import subprocess
import sys

cases = [
    (["truncated.zip"], "File is not a zip file"),
    (["duplicate.zip"], "duplicate member"),
    (["crc-bad.zip"], "CRC/header check failed"),
    (["backup.zip", "--require", "missing.csv"], "missing required members"),
]
for args, expected in cases:
    path = Path(args[0])
    before = path.read_bytes()
    result = subprocess.run(
        [sys.executable, "check_zip.py", *args],
        capture_output=True, text=True, check=False,
    )
    assert result.returncode == 1, result
    assert result.stdout == "", result.stdout
    assert expected in result.stderr, result.stderr
    assert path.read_bytes() == before
print("PASS: four failures rejected; archives unchanged")
PY_TEST

预期输出 PASS: four failures rejected; archives unchanged。这四种失败都不会产生成功 JSON;重复成员和漏文件在读取内容前就会被拒绝。

6. 接入自动化与排错

只在检查成功时继续下一步,下面用一句输出模拟后续任务:

if python3 check_zip.py backup.zip --require config/task.json; then
  python3 -c 'print("NEXT: backup accepted")'
else
  printf 'STOP: backup validation failed\n' >&2
  exit 1
fi

将输入改为 crc-bad.zip 后,检查失败并阻止后续输出。真实任务应使用同一份未变化的备份;发布或复制完成后再检查,归档仍在写入时不要启动验收。

现象 具体处理
File is not a zip file 确认下载的不是 HTML 错误页;重新复制或生成完整备份。
CRC/header check failed 保留坏文件作排查,从可信源重新生成或传输,再跑检查。
duplicate member 检查是否反复使用追加模式;在新路径重新打包,每个成员只写一次。
missing required members 核对归档内路径和大小写,例如 config/task.json,不要填服务器绝对路径。
encrypted member / unsupported compression 按本工具约定生成未加密的 STORED/DEFLATED 备份,或使用适配原格式的校验工具。
archive contains no files 检查输入目录和打包过滤规则,目录条目本身不算文件。

如果备份只是单个 .gz 日志,应使用 gzip 截断与 CRC 检查教程,不要把 gzip 文件交给 ZIP 工具。

总结

先核对必需成员和重复名称,再完整读取 ZIP 检查 CRC,通过后才输出成功报告。用故障副本验证失败退出,让自动化流程确实停在检查步骤。重要备份还应结合可信哈希、内容校验和恢复演练。

标签

1024 12306 ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn chatgpt checkpoint cli cloudflare codex cofoldarena copy cpu监控 cron csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization datetime debugging deployment desktop-app devtools disk-space disown docker dovecot download electron encoding esm esm2 esm3 esmc esmfold2 faceswap fasta fastmcp faulthandler ffmpeg file-descriptors file-io flashppi flask folium frontend game generator git github-actions google google-research grep gzip harness hls html http hugo indexnow javascript jev json jsonl just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp mirror mmcif model-evaluation monitoring mp3 mp4 mpnn multiomics mutation mysql nanobert nanobodies networkx nginx normalize numpy ollama omegatherm pandas password pdb pep-723 phaser pillow pip postfix preprocessing print protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security selenium seo sequence-identity sha256 signals sklearn socket solana somaticsignatures spl sqlite ssh standardize static-site subprocess symlink sysadmin system-one tcp tensorflow tkinter toml tron tronpy troubleshooting turtle typesafe-ai ubuntu usdt uv vhhbert vite webp wordcloud wordpress workflow yaml zipfile zoneinfo 后台 寓言 概率 经济 贸易 迅雷解析 钱包

友情链接

其它