zip の中のファイル名
zip ファイル内にアーカイブされている各ファイルのファイル名エンコーディングは、(現バージョンだと) UTF-8 フラグの有無を指定することができるようですが、 UTF-8 以外のエンコーディングは指定することができません。
日本語ロケールの Windows で圧縮すると(ツールにもよりますが)、ファイル名は Shift_JIS (CP932) で書き込まれます。最近の Linux や Mac は UTF-8 がほとんどです。
異なる OS で圧縮された zip ファイルを展開するときに、 UTF-8 フラグが付いていれば問題なく展開することが可能ですが、 Shift_JIS (CP932) が使われている場合、ファイル名の文字化けが起きることがあります。
具体的に言うと Windows → Linux / Mac など。この場合は unar
などのアーカイバを使ったり、 convmv
などで化けたファイル名を修正する方法があります。
これとは別に Python の ZipFile ライブラリでも正しくファイル名を認識することができません。
Python3 の ZipFile ライブラリ
ZipFile ライブラリでは、 UTF-8 フラグがあれば UTF-8 として、そうでなければ CP437 として、バイト列を文字列に変換しています。
このため ZipFile.extractall()
などを使って展開しようとすると日本語ファイル名が文字化けして展開されます。
対処するためには ZipInfo.filename
を CP437 としてバイト列に戻したのち、正しいエンコーディングで文字列に戻し、それを ZipFile.extract(ZipInfo)
とします。
import zipfile
f = r'/file/to/path'
with zipfile.ZipFile(f) as z:
for info in z.infolist():
info.filename = info.filename.encode('cp437').decode('cp932')
z.extract(info)
上記は、本来のエンコーディングを CP932 と決め打ちで処理していますが、実際はそうとは限らないので、エンコーディング判定とか例外処理とかしたほうがよいです。
が…… 駄目っ……!
ZipFile を使って展開する処理を Mac とかで実行する場合は問題ないのですが、 Windows で展開しようとする場合は、ファイル名によってはエラーが発生します。
ZipInfo.filename
は os.sep
を /
に置換されています。つまり Windows の場合 \
(\x5c
) が /
(\x2f
) に置換されます。
CP437 は1バイト文字のエンコーディングで、 ASCII 印字文字( \x20
- \x7f
)においては ASCII 互換ですので、(本来マルチバイト文字の一部であっても)この置換処理が行われてしまいます。その結果、一旦バイト列に戻すと b'\x90\x2f'
といったパターンが出現してしまいます。
Shift_JIS (CP932) では2バイト目の \x2f
は絶対に使用しないようになっているため、このようなバイト列を CP932 として再び文字列に変換しようとしたときにデコードエラーが発生してしまいます。
この問題が起きるのは、2バイト目が \x5c
の文字。
そう。いわゆる「ダメ文字」です。(ダメな理由はこれとは別でしたが)
いやー。 Shift_JIS (CP932) のダメ文字とかここ10年くらい存在をすっかり忘れてました。しかも \x2f
に置換したことで Shift_JIS (CP932) として不正なバイト列となるという変化球で来るとは。
解決法
置換処理が行われる前の( CP437 としてデコードされた)ファイル名情報は ZipInfo.orig_filename
に格納されていますので、こっちを使うことで解決することができます。
import os
import zipfile
f = r'/file/to/path'
with zipfile.ZipFile(f) as z:
for info in z.infolist():
info.filename = info.orig_filename.encode('cp437').decode('cp932')
if os.sep != "/" and os.sep in info.filename:
info.filename = info.filename.replace(os.sep, "/")
z.extract(info)