ZIPの中に同じ名前が2つ?Python zipfileで重複エントリーを見抜く

プログラミング・Web開発カテゴリを表すパンダのイラスト プログラミング・Web開発

この記事について この記事は、生成AIを活用した自動生成フローで作成しています。Python標準ライブラリzipfileの公式仕様を基に、ZIP内部で同じ名前が重複する場合を架空データで再現する手順を整理しました。

検証ステータス:📘 Python公式仕様確認済み・掲載コードの実機実行は未確認

ZIPの中に同じ名前が2つ?Python zipfileで重複エントリーを見抜く

ZIPを開いてファイル一覧を見ると、report.csvが一つだけあるように見える。しかし処理を自動化すると、想定外の内容を読み込んだり、展開後の件数が合わなかったりすることがあります。原因の候補の一つが、ZIP内部に同名のエントリーが複数入っている状態です。ZIPの一覧に表示される名前と、ZIPが保持している個々の項目は必ずしも一対一ではありません。

Pythonのzipfile.ZipFile.infolist()は個々の項目をZipInfoとして返します。一方、名前だけをキーにして辞書へまとめると重複を失います。この記事ではディスクに展開せず、メモリ上に作った架空のZIPで重複を検出し、業務でどう扱うかを考えます。

対象環境と実験の安全範囲

項目条件
対象Python 3の標準ライブラリ zipfile、io、collections
入力メモリ内の架空CSV二つ。実ファイルや個人情報を使用しない
実験ZIP作成、一覧取得、同名検出、個別エントリーの読取り
外部通信なし。追加パッケージ・管理者権限も不要
検証公式仕様を参照。以下の掲載コードの実行は未確認

作業対象はBytesIOというメモリ上のバイト列です。パソコン内の実際のZIPを展開したり、上書きしたりしません。業務ファイルへ応用する前に、まずこの小さな例の予想を立ててください。

まずは同名ファイルを二つ入れてみる

次のコードでは、同じreport.csvという名前で内容の違う二つの項目をZIPに書き込みます。Pythonのバージョンによっては重複名の書込み時にUserWarningが出ます。これは観察対象であり、警告を消すために無条件で無視する必要はありません。

import io
import zipfile
from collections import Counter

memory = io.BytesIO()
with zipfile.ZipFile(memory, "w") as z:
    z.writestr("report.csv", "id,value\n1,old\n")
    z.writestr("report.csv", "id,value\n2,new\n")
    z.writestr("note.txt", "sample only")

memory.seek(0)
with zipfile.ZipFile(memory, "r") as z:
    entries = z.infolist()
    counts = Counter(item.filename for item in entries)
    print("entries:", len(entries))
    print("names:", [item.filename for item in entries])
    print("duplicates:", {name: n for name, n in counts.items() if n > 1})
    for item in entries:
        if item.filename == "report.csv":
            print("member:", z.read(item).decode("utf-8").strip())

仕様に基づく期待結果は、エントリー総数が3、名前一覧にreport.csvが2回、重複数が2と表示されることです。最後のループでは二つのZipInfoオブジェクトを個別に渡すため、古い内容と新しい内容の両方を確認できます。ここで表示される順序は、作成時に追加した順序です。ただし、一般のZIPの順序を新旧や正しさの根拠にしてはいけません。

重要なのは、z.read(item)に文字列ではなくZipInfoを渡している点です。名前が同じときに、単にz.read("report.csv")と書くと、どのエントリーを意図しているか曖昧になります。公式ドキュメントも、重複名を区別する場合にZipInfoを渡せると説明しています。

なぜ一覧には重複が残るのか

ZIPは「名前をキーとした一意な辞書」ではなく、個々のファイル項目の情報を持つアーカイブです。infolist()が返すのは、名前文字列だけではなく、サイズ、圧縮方式、CRCなどのメタデータを持つZipInfoの列です。同じ名前でも別の項目として存在できます。

この違いを一枚の図にすると、どこで情報が失われるかが見えてきます。

flowchart TD
    A["ZIP内の全エントリー"] --> B["infolistで個別のZipInfoを取得"]
    B --> C["filenameごとに件数を集計"]
    C --> D{"同じ名前が複数あるか"}
    D -->|はい| E["重複を報告し個別に確認"]
    D -->|いいえ| F["次の検査へ進む"]

図のポイントは、名前で一つにまとめる前に件数を数えることです。ファイル名をキーにした辞書へ上書き代入すると、最後に残った値だけが見える可能性があります。その辞書を使ってから重複を調べても、元のZIPに何件あったか復元できません。

Counterは項目名ごとの出現回数を保持します。重複を見つける処理は簡単ですが、業務上は「重複を検出すること」と「どちらを採用するかを決めること」を分ける必要があります。後者はアーカイブの作成者、受渡し仕様、データの版管理規則が決める話だからです。

一つだけ条件を変えるとどうなる?

二つ目のwritestr("report.csv", ...)をwritestr("report-new.csv", ...)へ変更してみてください。変更するのは名前だけです。期待される重複辞書は空になり、総エントリー数は3のままです。この比較で、検査しているのがファイルの中身の一致ではなく、名前の衝突だと分かります。

逆に、名前が異なっていても内容が同じケースは、この検査では重複と判定されません。内容の重複を調べたい場合は、別途ハッシュやレコードの識別子を比較する必要があります。また、Report.csvとreport.csvはPythonの文字列比較では異なる名前です。Windows上へ展開するときの名前衝突とは条件が違うため、展開先のファイルシステムに合わせた追加確認が必要です。

もう一つの境界条件は、data/report.csvとreport.csvです。ZIP内では異なるパスなので、今回のfilename比較では重複しません。しかし展開時にフォルダー構造を無視してファイル名だけで保存すると衝突します。何を「同じ名前」とみなすかは、実際の後続処理とセットで設計してください。

失敗時は何を切り分ける?

zipfile.BadZipFileが発生した場合は、重複の問題と決めつけないでください。ZIPとしての構造が壊れている可能性があります。is_zipfile()で形式を事前確認する方法はありますが、形式確認が成功してもすべてのデータが正常に読める保証ではありません。読み取り時の例外処理や検査が必要です。

ファイル名が読めても内容の文字コードがUTF-8とは限りません。実験では自分でUTF-8相当のASCII文字列を書いているためdecode("utf-8")を使っていますが、業務CSVでは文字コードを別に確認します。復号に失敗したらZIPの重複検査ではなく、対象ファイルの文字コード問題として切り分けます。

ZIPに暗号化された項目がある場合、パスワードなしでは読めないことがあります。また、巨大な展開サイズや多数のエントリーを持つZIPを無制限に読み込むのは危険です。今回のコードは小さな架空データの教材であり、未知のアーカイブを安全に処理するための容量制限・件数制限・タイムアウトまで実装した製品コードではありません。

さらに、ファイル名に../や絶対パスに見える文字列が含まれる可能性も考慮してください。ZIP内の名前をそのまま任意の出力先へ連結して書き込む実装は避けます。今回の方法は展開しない検査なので、パスの安全性を評価する前にディスクへ書き込む必要がありません。

業務で使うなら、重複を検出して止める

例えば取引先から毎朝受け取るZIPにsales.csvが一つあるという契約なら、最初にアーカイブのエントリー一覧を取り、sales.csvが正確に1件か確認します。0件なら欠落、2件以上なら重複として受入保留にします。どちらか一つを自動選択するより、取引先へ再送を依頼した方が、集計誤りの原因を追いやすくなります。

受入検査は、ファイル名だけで完了しません。期待するCSVの列、レコード件数、日付、文字コード、圧縮前後のサイズ、必要ならチェックサムも確認します。重複がないことは必要な条件の一つであって、内容が正しい証拠ではないからです。特に給与や請求のように同じファイルを二度取り込むと問題になる処理では、アーカイブ内部の重複と、過去に受け入れたデータの再送を別々に検査します。

実運用のログには、受領日時、アーカイブの識別子、エントリー件数、重複した名前、保留理由を記録すると調査に役立ちます。ただし、実際のファイル名に顧客名や個人名が含まれる場合は、ログの公開範囲と保存期間を検討します。ZIP内のファイルを丸ごとログに出す必要はありません。

この方法を採用する判断基準は明確です。ZIP内の名前が一意であることを業務契約が要求するなら、取り込み前に重複を拒否する。一方、同名の項目を意図的に持つ特殊なアーカイブを扱うなら、名前ではなくZipInfoごとに処理し、採用ルールを明文化します。重複の発見だけで「壊れたZIP」と断定しないことが大切です。

公式情報・確認範囲

公式資料の仕様を基に期待結果を説明しました。掲載コードの実行結果、特定OSでの展開表示、WordPressでのMermaid表示は実測していません。読者の環境で結果が異なる場合はPythonバージョン、ZIPの作成元、文字コード、例外メッセージを記録して比較してください。

文書情報

記事タイトル
ZIPの中に同じ名前が2つ?Python zipfileで重複エントリーを見抜く
作成日
更新日
Source URL
https://papanda925.com/?p=18598

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

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