概要
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, 記事投稿