今回は SJIS 環境で作られた zip ファイルを Python で文字化けを起こさずに展開する方法についてです。
Windows 等の SJIS 環境で作られた zip ファイルを Python の標準ライブラリの zipfile
でそのまま展開すると、日本語を含むファイル名が文字化けすることがあります。
import zipfile
with zipfile.ZipFile('格納するファイル名に日本語を含む.zip') as zfile:
zfile.extractall('out')
# => zip ファイルに含まれるファイルの名前が文字化けしている
文字化けが起こる原因はおおよそ次のとおりです。
- zip ファイルの仕様としてもともとサポートされているのは
utf-8
とcp437
のみ(cp437
は IBM PC で使われていたもの) - しかし SJIS 環境( Windows )では zip ファイル作成時にファイル名が SJIS で登録される
- 一方 Python は zip の仕様に従ってファイル名を
utf-8
またはcp437
でデコードする
結果として、 SJIS でエンコードされたファイル名が utf-8
または cp437
でデコードされてしまい文字化けが起こることになります。
解決策ですが、 zip ファイルが明らかに SJIS のものだとわかっている場合は、 SJIS でデコードし直せば OK です。
次の extract_sjis_zip()
は SJIS の zip ファイルを展開、 list_sjis_zip()
は zip ファイルに格納されているファイル情報の list
を返します。
import zipfile
from pathlib import Path
from typing import List
def extract_sjis_zip(src_zip: str, dest: str) -> None:
"""SJIS の zip ファイルをファイル名の文字化けを避けて展開する"""
with zipfile.ZipFile(src_zip) as zfile:
for info in zfile.infolist():
_rename(info)
zfile.extract(info, DEST_DIR)
def list_sjis_zip(src_zip: str) -> List[zipfile.ZipInfo]:
"""SJIS の zip ファイルの中身をファイル名の文字化けを避けてリストアップする"""
info_all = []
with zipfile.ZipFile(src_zip) as zfile:
for info in zfile.infolist():
_rename(info)
info_all.append(info)
return info_all
def _rename(info: zipfile.ZipInfo) -> None:
"""ヘルパー: `ZipInfo` のファイル名を SJIS でデコードし直す"""
LANG_ENC_FLAG = 0x800
encoding = 'utf-8' if info.flag_bits & LANG_ENC_FLAG else 'cp437'
info.filename = info.filename.encode(encoding).decode('cp932')
zip ファイルが SJIS で作られたものかどうか不明な場合は分岐が必要になると思います(私は試していません)。
以上です。
尚、この処理はファイル名だけが対象であり、テキストファイルの中身をエンコードし直すものではないので、参考にされる際はご注意ください。