chardetを使ってみた
えーと、テキストのencodingをいい感じに判定してくれるPythonモジュール、chardetを使ってみました。で、色々地雷が埋まったファイル群を処理させていただいたのですが、改行のないギガバイト級ファイルで見事固まってくれました。わざわざ嵌めたので、解決に苦労はしませんでしたが、不意打ちで嵌まった人がいたらちょっと悩むかもしれないので、解決法を書いておきます。特にWEBで言及している人が見つからなかったので!
よくあるコードは、こんなんです。
import chardet
def readfile(path):
detector = chardet.UniversalDetector()
with open(path, 'rb') as f:
detector.feed(line) # ここから帰ってこない
if detector.done:
break
detector.close()
# 得られたエンコーディング情報を使ってファイルをオープンし直すが、
# それでもエラーになる場合は、エラー文字を読み飛ばして無理やり読む
with open(path, encoding=detector.result['encoding'], errors='ignore') as f:
s = f.read()
return s
chardetを紹介しているウェブ上のサンプルソースはどれもこれも大体同じだから、詳しい説明はそちらに譲ります。
で、前述のファイルを食わせると、「ここから帰ってこない」というコメントをつけた行で固まる。これは痛い。
例えばこんな風に直す
きれいじゃないけど、要は小出しにUniversalDetectorにfeedしてあげるとよろし、と言う話。ただ、2バイト文字が境界に来たりして、本当はちょっと危険なのだが、その辺は、64kbyteもあれば判定つくよね、ってことで細かいことは気にしていない。境界に来るようなケースを試したけど、特にヤバい現象は起きない模様なので、それはそれで良しとします。あと、後半で読み込み直したときにファイルがでかすぎる問題は、今回の本題じゃないので皆さんそれぞれご自分のタスクに合った解決をしてくれれば幸甚。
import chardet
def readfile(path):
detector = chardet.UniversalDetector()
with open(path, 'rb') as f:
for line in f:
# 馬鹿みたいに改行のないテキストを読んでしまったとき、UniversalDetectorが固まるのを防ぐ
if len(line) > 65536:
for i in range(0, len(line), 65536):
detector.feed(line[i: i + 65536])
if detector.done:
break
else: # いつ見てもキモい多重ループ脱出イディオム
continue
break
else:
detector.feed(line)
if detector.done:
break
detector.close()
# 得られたエンコーディング情報を使ってファイルをオープンし直すが、
# それでもエラーになる場合は、エラー文字を読み飛ばして無理やり読む
with open(path, encoding=detector.result['encoding'], errors='ignore') as f:
s = f.read()
return s
あと、大量のテキストを処理する場合は、途中で落ちると大変残念な気持ちになります。地味にerrors='ignore'も大事かも。