[解決!Python]テキストファイルのエンコーディングを調べて、その内容を読み込むには(chardetパッケージ)解決!Python

別環境で作られたテキストファイルの内容を読み込む際には、まずそのエンコーディングを調べる必要がある。chardetパッケージを使って、これを行う方法を紹介する。

» 2021年05月11日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「解決!Python」のインデックス

連載目次

# chardetによるエンコーディングの判定とテキストデータのデコード
# sjis.txtの内容:このファイルはシフトJISでエンコーディングされています
from chardet import detect  # 「pip install chardet」などでインストールしておく

with open('sjis.txt', 'rb') as f:  # バイナリファイルとしてファイルをオープン
    b = f.read()  # ファイルの内容を全て読み込む

print(b)  # b'\x82\xb1\x82\xcc\x83t\x83@\x83C……\xa2\x82\xdc\x82\xb7\r\n'
enc = detect(b)  # chardet.detect関数を使ってエンコーディングを判定
print(enc)
# 出力結果:
# {'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}

# 得られたエンコーディング情報を使ってファイルをオープンし直す
with open('sjis.txt', encoding=enc['encoding']) as f:
    s = f.read()

print(repr(s))  # 'このファイルはシフトJISでエンコーディングされています\n'

# もしくは得られたエンコーディング情報を使ってバイト列をデコード
s = b.decode(encoding=enc['encoding'])
print(repr(s))  # 'このファイルはシフトJISでエンコーディングされています\r\n'

# ファイルサイズが大きい場合
from chardet.universaldetector import UniversalDetector

with open('sjis.txt', 'rb') as f:  # ファイルをバイナリファイルとしてオープン
    detector = UniversalDetector()  # UniversalDetectorオブジェクトを生成
    for line in f:  # 行末(\n)またはEOFまでを読み込みながら、以下を繰り返す
        detector.feed(line)  # 読み込んだデータをfeedメソッドに渡す
        if detector.done:  # 判定できたらdone属性がTrueになるのでループを終了
            break
    detector.close()  # ループ終了時にUniversalDetectorオブジェクトをクローズ

print(detector.result)
# 出力結果:
# {'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}

# UniversalDetectorオブジェクトもまとめてwith文で取り扱う
from chardet.universaldetector import UniversalDetector
from contextlib import closing

with open('sjis.txt', 'rb') as f, closing(UniversalDetector()) as detector:
    for line in f:
        detector.feed(line)
        if detector.done:
            break

print(detector.result)  # 結果を出力


chardetパッケージを使ってエンコーディングを検出して、その内容を読み込む

 Pythonのopen関数でテキストファイルをオープンする場合、特に指定をしない限り、コードを実行しようとしているプラットフォームごとに定められているエンコーディングを使って、そのファイルがオープンされる。

 ローカルマシンのハードディスクなどを対象として、決まったエンコーディングでファイルを読み書きしている分には問題はないが、別のマシンで作られたテキストファイルを読み書きしたり、ネットワーク経由で手に入れたテキストデータを扱ったりする際には、エンコーディングが手元のマシンのそれとは異なる場合がある。

 例えば、以下はシフトJISでエンコードされたテキストファイル(sjis.txt)の内容をmacOSで読み込もうとしているところだ(ファイルの内容は「このファイルはシフトJISでエンコーディングされています\n」)。

with open('sjis.txt') as f:  # sjis.txtファイルはシフトJISでエンコードされている
    content = f.read()


 これを実行した結果を以下に示す。

シフトJIS形式のファイルの読み込みに失敗したところ シフトJIS形式のファイルの読み込みに失敗したところ

 この通り、macOS上で特に指定をせずにシフトJISエンコーディングのテキストファイルの内容を読み込もうとすると失敗する(ファイルはオープンできるが、readメソッドで例外が発生する)。Windows環境(デフォルトのエンコーディングは「cp932」≒シフトJIS)でUTF-8エンコーディングのテキストファイルの内容を読み込もうとしたときにも同様なエラーが発生する。

 テキストファイルで使われているエンコーディングが分かっていれば、open関数のencodingパラメーターに'shift_jis'(や'UTF-8')などを渡すことでファイルの内容を読み込めるようになるが(「[解決!Python]エンコーディングを指定して、シフトJISなどのファイルを読み書きするには」を参照)、エンコーディングが分からないときには、それを判定する必要がある。

 chardetパッケージはまさにこれを行ってくれる。ただし、Pythonに標準で添付されるパッケージではないので、「pip install chardet」などとしてあらかじめインストールしておく必要がある。

 chardetパッケージの最も簡単な使い方は、エンコーディングが不明のテキストファイルを「バイナリファイルとして」オープンして、それをこのパッケージが提供するdetect関数に渡すことだ。以下に例を示す。

# 「pip install chardet」などを実行して、chardetパッケージをインストールしておく
from chardet import detect  # chardetパッケージからdetect関数をインポート

with open('sjis.txt', 'rb') as f:  # バイナリファイルとして読み込みオープン
    b = f.read()  # ファイルから全データを読み込み
    enc = detect(b)  # 読み込んだデータdetect関数に渡して判定する

print(enc)
# 出力結果:
#{'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}


 detect関数の戻り値は辞書であり、この関数によって推測されたエンコーディングは'encoding'キーの値となっている。上の例を見ると分かる通り、確度('confidence'キー)と言語('language'キー)も戻される。この場合は、エンコーディングはシフトJISで、その確度は99%ということだ。

 エンコーディングが分かったら、それをopen関数に指定して、テキストファイルを再度オープンすると、そのエンコーディングを使ってファイルの読み書きが行われるようになる。

with open('sjis.txt', encoding=enc['encoding']) as f:
    s = f.read()

print(repr(s))  # 'このファイルはシフトJISでエンコーディングされています\n'


 あるいは、全てのデータを既に読み込み済みなので、バイト列のdecodeメソッドにエンコーディングを指定する方法もある。

s = b.decode(enc['encoding'])
print(repr(s))  # 'このファイルはシフトJISでエンコーディングされています\r\n'


 2つの方法の出力結果を見ると分かるが、バイト列として読み込んだテキストデータをdecodeメソッドでデコードしたときには、改行文字がプラットフォーム固有の値のままである点には注意が必要だ(Windowsでは「\r\n」、macOSやLinuxでは「\n」)。そのため、後者の方法でデコードしたときには、文字列のreplaceメソッドなどで改行文字を変換する必要があるかもしれない(エンコーディングを指定してオープンし直した場合は、デフォルトで改行コードは自動的に変換される)。

ファイルサイズが大きな場合

 ファイルサイズが大きな場合に、エンコーディングを調べるためだけに全ての内容を読み込むのは無駄が大きいと考えるかもしれない。そのようなときには、chardetパッケージが提供するUniversalDetectorクラスを使用する。

 UniversalDetectorクラスには、feedというインスタンスメソッドとdoneという属性がある。feedメソッドはバイト列を引数に受け取り、上で見たdetect関数と同様にエンコーディングの推定を行う。そして、ある程度の確度で推定ができると、done属性をTrueにする。これらを次のような手順で使用して、エンコーディングを判定できる。

  1. テキストファイルをバイナリファイルとしてオープンする
  2. UniversalDetectorオブジェクトを生成する
  3. ファイルから行末またはファイル末尾(EOF)までをバイト列として読み込む
  4. 読み込んだバイト列をUniversalDetectorオブジェクトのfeedメソッドに渡す
  5. UniversalDetectorオブジェクトのdone属性をチェックする
  6. done属性がTrueになる(判定できたと判断される)まで3〜5を繰り返す
  7. UniversalDetectorオブジェクトのcloseメソッドを呼び出す
  8. ファイルをクローズする
  9. 判定結果はUniversalDetectorオブジェクトのresult属性に格納される

 これをコードにすると次のようになる。

from chardet.universaldetector import UniversalDetector

with open('sjis.txt', 'rb') as f:  # ファイルをバイナリファイルとしてオープン
    detector = UniversalDetector()  # UniversalDetectorオブジェクトを生成
    for line in f:  # 行末またはEOFまでを読み込みながら、以下を繰り返す
        detector.feed(line)  # 読み込んだデータをfeedメソッドに渡す
        if detector.done:  # 判定できたらdone属性がTrueになるのでループを終了
            break
    detector.close()  # ループ終了時にUniversalDetectorオブジェクトをクローズ

print(detector.result)  # 結果を出力


 この例ではテキストファイルをバイナリファイルとしてオープンし、それをfor文に反復可能オブジェクトとして渡している。これにより行末またはファイル末尾(EOF)までを読み込みながら、for文のブロックが実行される(バイナリファイルでは、「\n」が改行文字として扱われる)。そのブロックの中で読み込んだバイト列をfeedメソッドに渡して推測を行い、done属性の値をチェックする。これがTrueであれば、エンコーディングの推測が終わったものとしてループを終了する。推測結果はUniversalDetectorオブジェクトのresult属性に保存されている。そうではなくファイル末尾まで読み込んでも推測が終わらなかった場合も、ループ終了時点での推測結果がresult属性に保存されている。

 なお、このコードはcontextlib.closingクラスを使って、次のようにも記述できる。

from chardet.universaldetector import UniversalDetector
from contextlib import closing

with open('sjis.txt', 'rb') as f, closing(UniversalDetector()) as detector:
    for line in f:
        detector.feed(line)
        if detector.done:
            break

print(detector.result)


 こうすることで、with文のブロックが実行完了した際に、あるいはブロック内で例外が発生したときに必ずclosingメソッドが呼び出されるようになる。

 1行分のデータではなく、決まったサイズのデータをfeedメソッドに渡すのであれば、次のように書くことも可能だ。

from chardet.universaldetector import UniversalDetector
from contextlib import closing

with open('sjis.txt', 'rb') as f, closing(UniversalDetector()) as detector:
    size = 100  # 一度に読み込むデータのサイズ(バイト数)
    b = f.read(size)  # 指定したサイズだけファイルから読み込み
    while b:  # データがある間、以下を実行
        detector.feed(b)
        if detector.done:
            break
        b = f.read(size)

print(detector.result)


「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。