iMind Developers Blog

iMind開発者ブログ

Pythonのchardetで文字コード判定

概要

Pythonで文字コード判定をしたくなることはよくあるので、やり方をメモしておく。

バージョン情報

  • Python 3.4.3
  • chardet==3.0.4

サンプルデータの用意

utfとsjisのファイルを生成しておく。

$ echo "
イエスは身を起こして言われた。「あなたがたのうちで罪のない者が、最初に彼女に石を投げなさい。」 彼らはそれを聞くと、年長者たちから始めて、ひとりひとり出て行き、イエスがひとり残された。女はそのままそこにいた。
" > utf.txt
$ nkf -s utf.txt > sjis.txt

bytesから文字コード判定

chardet.detectで文字コード判定をする。

import chardet

with open('utf.txt', 'rb') as f:
    b = f.read()
chardet.detect(b)
    #=> {'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}

with open('sjis.txt', 'rb') as f:
    b = f.read()
chardet.detect(b)
    #=> {'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}

第一候補以外も確認する

判定結果は内部的には文字コードAが90%、文字コードBが10%のように複数の確率を持っている。最上位以外の文字コードについても表示させてみる。

with open('sjis.txt', 'rb') as f:
    b = f.read()

# auto closeでUniversalDetectorを生成してbytesをfeed
with contextlib.closing(chardet.UniversalDetector()) as detector:
    detector.feed(b)

# _charset_probersの中身を確認
for prober in detector._charset_probers:
    print(prober.charset_name, prober.get_confidence())

    #=> SHIFT_JIS 0.99
    #=> windows-1251 0.01
    #=> ISO-8859-1 0.01

shift_jis(99%), windows-1251(1%), ISO-8859-1(1%)という結果になった。

上記の処理はchardetの__init__.pyで行われている処理を参考にした。正確にやるには下記の detect_all のような処理が必要そう(v3.0.4にはこの関数は入っていない)。

https://github.com/chardet/chardet/blob/master/chardet/__init__.py#L45

データ量が多い場合

巨大なファイルの文字コードを判定したい場合は、文字列全体を渡してしまうと処理に時間がかかるので、少しずつfeedして概ね判定結果が出たら終了するような動きをさせる。

下記は100byteごとにfeedして、推測が完了(detect.done)したらループを終了する処理。

chunk_size = 100

with open('sjis.txt', 'rb') as f, \
        contextlib.closing(chardet.UniversalDetector()) as detector:
    while True:
        chunk = f.read(chunk_size)
        if not chunk:
            break
        # chunk_sizeずつfeed
        detector.feed(chunk)
        # 推定結果が定まるとdetector.doneがTrueになる
        if detector.done:
            break

enc = detector.result['encoding']
    #=> 'SHIFT_JIS'

上記はファイルからchunk_sizeずつ読み込んでいる。下記はbytesから少しずつ読み込んで出力する例。

import io

chunk_size = 100
with open('sjis.txt', 'rb') as f:
    b = f.read()
    buf = io.BytesIO(b)

with contextlib.closing(chardet.UniversalDetector()) as detector:
    while True:
        chunk = buf.read(chunk_size)
        if not chunk:
            break
        detector.feed(chunk)
        if detector.done:
            break

enc = detector.result['encoding']

推定した文字コードでdecode

推定した文字コードでbytesをデコードする。

with open('sjis.txt', 'rb') as f:
    b = f.read()

enc = chardet.detect(b)['encoding']

b.decode(enc)

たまにdecodeでこのbyte扱えんぞとエラーになることがあるので、そうしたケースも許容したい場合は errors="ignore" を設定しておくと良いかもしれない。

b.decode(enc, errors="ignore")

改定履歴

Author: Masato Watanabe, Date: 2019-08-24, 記事投稿