python3のzipfileで日本語ファイルの文字化け

python3/Windows7の環境で、zipfile標準ライブラリを使って日本語ファイルを圧縮したらファイル名が文字化けした。

調べたら理由が2つ。

  1. zipfileの仕様で強制的にファイル名がUTF-8エンコードされる
  2. Windows7の初期状態ではファイル名がShift-JIS(CP932)以外でエンコードされていると文字化けする

なのでUTF-8エンコードされたファイル名があると文字化けしちゃう。

簡単なのは、2.の問題を解決し、UTF-8のファイル名を扱える様にするのが早い。

Microsoftのパッチ。

参照:

1.を解決するには標準ライブラリを書き換えエンコードを変える、という荒業でも対処できるが...。

逆にShift-JISでエンコードされたZIPを解凍するとき

結論的には扱わない方が良さそう。

zipfile標準ライブラリを書き換える方法で対処している。が、予めファイル名のエンコードを知っている必要があるわけで...。

その辺も考慮して作り込む覚悟がないので扱わないこととする。

またはtar.gzへ切り替えるか。

余談

python2系なら解決できる

つまり原因は...。

誤った正規化

ZipFile.__init__でファイル名のバイト列中にある文字、os.sepをスラッシュに正規化している。この処理でsjisの2バイト文字のバックスラッシュがWindowsのパス区切りと誤認されて全てスラッシュに置き換えられてしまう。結果、解凍されるフォルダ名が文字化けする。

os.makedirsの誤った分割

zipfile.ZipFile.extractは内部でos.makedirs(unixでいうmake-pのような関数)を呼び出す。この関数は、パス文字列をos.sepで分割して、未だ存在しないフォルダを作成していく。このとき、1.で正規化されたパス文字列が/で分割され、誤ったパスでディレクトリが作成されていく。

ということらしい。

実はWindowsカーネルではUnicode

わざわざSJISへ変換してる(恐らく下位互換のため)

インターネット上で「Windows のファイル名の文字コードSJIS が使用される」という情報が散見されますが、正確には、Windows2000, WindowsXP などの NT 系 Windows カーネルでは、Windows カーネル内部では、ファイル名は全て Unicode で扱われており、状況によって SJIS への変換が行われます。

中略。

ファイル圧縮ツールを使用する場合  
(1) NTFS ファイルシステム上のファイルを Windows のファイル圧縮ツール(zip, lha など)で圧縮ファイルにまとめると、Windows カーネルは、ファイル名を Unicode -> SJIS 変換してファイル圧縮ツールに渡し、ファイル圧縮ツールは、SJIS のファイル名で圧縮ファイル内に保存します。  
 
(2) (1) の圧縮ファイルを NTFS ファイルシステム上に解凍すると、ファイル圧縮ツールSJIS のファイル名でファイルを展開し、Windows カーネルは、ファイル名を SJIS -> Unicode 変換して、NTFS ファイルシステムに保存します。  
 
⇒ この時、ファイル名に SJIS に存在しない文字があると、圧縮・解凍時の文字コード変換がうまくできないため、エラーが発生したり、解凍するとファイル名に文字化けが発生したりすることがあります。  
 
(3) (1) の圧縮ファイルを Linux に持っていき、Linux 上のツールで解凍すると、(Linux 上のツール文字コード変換機能を持たない場合)Linux 上のツールは、SJIS のファイル名のままでファイルを解凍して、ファイルシステムに保存します。  
 
⇒ この時、Linux の端末の文字コード設定が SJIS 以外の場合、端末上でファイル名に文字化けが発生します。