Python Tips: SJIS の zip ファイルを展開したい

今回は SJIS 環境で作られた zip ファイルを Python で文字化けを起こさずに展開する方法についてです。

Windows 等の SJIS 環境で作られた zip ファイルを Python の標準ライブラリの zipfile でそのまま展開すると、日本語を含むファイル名が文字化けすることがあります。

import zipfile


with zipfile.ZipFile('格納するファイル名に日本語を含む.zip') as zfile:
    zfile.extractall('out')
# => zip ファイルに含まれるファイルの名前が文字化けしている

文字化けが起こる原因はおおよそ次のとおりです。

  • zip ファイルの仕様としてもともとサポートされているのは utf-8cp437 のみ( 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 で作られたものかどうか不明な場合は分岐が必要になると思います(私は試していません)。

以上です。

尚、この処理はファイル名だけが対象であり、テキストファイルの中身をエンコードし直すものではないので、参考にされる際はご注意ください。

参考