1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

UniversalDetectorが固まる問題に直面した方へ

Last updated at Posted at 2024-02-18

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'も大事かも。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?