« ^ »

文字コードの判定に使用されるchardetはどうやって文字コードを判定しているか

所要時間: 約 3分

文字コードが不明なデータを受け取り処理をする必要があり、そのデータから使用されている文字コードを判定する方法を探していた。Pythonにはchardetという文字コードの判定用のライブラリがある。これを使うと、そのバイト列が何の文字コードでエンコードされているか判定できる。とても便利ではあるけれど、どうやってそれを実現しているのかを知りたくなった。そのため、chardetについて調べる事にする。

https://github.com/chardet/chardet

chardet.universaldetector.UniversalDetector()によって判定は行われる。このクラスのインスタンス属性である result には {'encoding': None, 'confidence': 0.0, 'language': None} のような形式のデータが保持される。encodingには文字コードが、confidenceにはその文字コードである可能性が設定される。確実にその文字コードと言い切れる時と、言い切れない時があり、言い切れる時は1.0が設定される。

UTF系の文字コードの場合、先頭のデータを見る事で、判定が▽できるようだ。例えばBOM付きUTF-8の判定は以下のように実施している。

            # If the data starts with BOM, we know it is UTF
            if byte_str.startswith(codecs.BOM_UTF8):
                # EF BB BF  UTF-8 with BOM
                self.result = {'encoding': "UTF-8-SIG",
                               'confidence': 1.0,
                               'language': ''}

CP932のような日本語向けの文字コードは、UniversalDetector()で直接行っている訳ではなく、UniversalDetectorから移譲される形で判定処理が実行される。具体的には次のような呼び出しの構図がある。

UniversalDetector -> MBCSGroupProber -> SJISProber -> SJISContextAnalysis

最終的にはSJISContextAnalysis.feed()によって判定される。

    def get_order(self, byte_str):
        if not byte_str:
            return -1, 1
        # find out current char's byte length
        first_char = byte_str[0]
        if (0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC):
            char_len = 2
            if (first_char == 0x87) or (0xFA <= first_char <= 0xFC):
                self._charset_name = "CP932"
        else:
            char_len = 1

        # return its order if it is hiragana
        if len(byte_str) > 1:
            second_char = byte_str[1]
            if (first_char == 202) and (0x9F <= second_char <= 0xF1):
                return second_char - 0x9F, char_len

        return -1, char_len

実際に判定をしてみると次のようになる。

>>> import chardet
>>> chardet.detect("日本語".encode("cp932"))
{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}
>>> chardet.detect("日本語".encode("sjis"))
{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}
>>> chardet.detect("日本語".encode("utf8"))
{'encoding': 'utf-8', 'confidence': 0.87625, 'language': ''}
>>> chardet.detect("テスト。".encode("cp932"))
{'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}
>>> chardet.detect("難しい。".encode("cp932"))
{'encoding': None, 'confidence': 0.0, 'language': None}
>>>

cp932を指定してエンコードした値は、=Windows-1252= であったり SHIFT_JIS であったり、または判定不能でNoneとなったりするらしい。これは扱いが難しい