毎月届くZIPからCSVだけを読むなら、すべての中身をフォルダーへ展開しなくても処理できます。PythonのZipFile.openで対象ファイルを開き、テキストとしてCSVの読み取りへ渡せます。
ZIPの中のパス名、対象ファイルの件数、文字コードを先に決めます。最初に見つかったCSVを無条件に採用しないようにすると、入力の構成が変わったときにも気づけます。
対象名と列を確認して読む
次はCSVとメモを持つ架空のZIPを、メモリー上に作る例です。data/sales.csvだけを選び、同じ名前の項目が複数ないかも確認します。ディスクへの展開は行いません。
import csv
from io import BytesIO, TextIOWrapper
from zipfile import ZipFile
buffer = BytesIO()
with ZipFile(buffer, "w") as archive:
archive.writestr(
"data/sales.csv", "ID,金額\nA,100\nB,200\n".encode("utf-8-sig")
)
archive.writestr("notes.txt", "確認用")
with ZipFile(BytesIO(buffer.getvalue())) as archive:
matches = [item for item in archive.infolist() if item.filename == "data/sales.csv"]
if len(matches) != 1:
raise ValueError("対象CSVが一つではありません")
info = matches[0]
if info.is_dir() or info.file_size > 1_000_000:
raise ValueError("対象の形式かサイズを確認してください")
with archive.open(info) as raw:
with TextIOWrapper(raw, encoding="utf-8-sig", newline="") as text:
reader = csv.DictReader(text)
if reader.fieldnames != ["ID", "金額"]:
raise ValueError("CSVの列が違います")
count = 0
total = 0
for row in reader:
count += 1
total += int(row["金額"])
print("CSVの件数:", count)
print("金額合計:", total)
assert count == 2 and total == 300
対象CSVは二件、合計300です。Windows・Python 3.12.10で、指定したCSVだけを読めることを確認しました。実際の受領ファイルや大容量アーカイブを処理した結果ではありません。
バイト列をCSVへ直接渡さない
ZipFile.openで返るrawは、バイナリの読み取り口です。csv.DictReaderが読む文字列へ変えるため、TextIOWrapperへ文字コードを指定しています。
ZIP内の名前に日本語があることと、CSV本文の文字コードがUTF-8であることは別です。入力元の仕様に合わせ、本文のencodingを決めます。文字化けした値を無視して合計へ進む処理にはしません。
対象が増えた場合は止めて確認
同じ名前のメンバーが二つあるZIPや、月ごとに別のCSVが入る構成もあり得ます。対象名の条件と、許容する件数を決めてから読むようにします。
例のサイズ上限は練習用の条件です。file_sizeは宣言値であり、この一項目を見るだけで未知のアーカイブを安全に処理できるという意味ではありません。信頼できないファイルを扱う場合は、圧縮率や処理量、時間などの上限も含めて別に検討します。
展開しない形でも、読んだ内容を無制限にメモリーへためれば負荷は増えます。今回のように件数と合計を積み上げるなら、結果に必要な値だけを残せます。
複数入力の検算は複数CSVをまとめる方法、ファイルの扱いはPythonのファイル読み書きで確認できます。
