【Python】テキストファイルの文字コードを判別してファイルを開く

Pythonのopenで使用するencodingはプラットフォームに依存します。
Windowsであればcp932です。

Windows上のUTF-8で保存されたテキストファイルをencoding引数に何もつけずにopenするとエラーが発生します。

1with open('UTF-8.txt') as f:
2    s = f.read()
3    print(s) # UnicodeDecodeError: 'cp932' codec can't decodeが発生する

テキストファイルを開く前に文字コードを判別してから適切なencoding引数で開くようにしてみます。

chardetを使用して文字コードを判別する

chardetを使用して、文字コードを判別します。
https://pypi.org/project/chardet/

pipを使用してchardetをインストールします。

1pip install chardet

UTF-8とShift-JISの文字コードのテキストファイルを用意して、それぞれ下記の内容でファイルを保存しました。

1あいうえお

このテキストファイルをchardetのdetectメソッドを使用してテキストファイルの文字コードを判別してみます。

detectメソッドの引数にはbytesまたはbytearrayを渡す必要があります。
openでファイルを開くときにmodeの引数にrbを指定し、バイナリモードで読み込んだファイルを
detectの引数として使用します。

 1import chardet
 2
 3with open('UTF-8.txt', 'rb') as f:
 4    print('UTF-8.txt')
 5    print(chardet.detect(f.read()))
 6
 7print()
 8with open('Shift-JIS.txt', 'rb') as f:
 9    print('Shift-JIS.txt')
10    print(chardet.detect(f.read()))

ファイルの文字コード、文字コードの確度、ファイルに書かれている言語を表示してくれます。

1UTF-8.txt
2{'encoding': 'utf-8', 'confidence': 0.9690625, 'language': ''}
3
4Shift-JIS.txt
5{'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}

UTF-8のファイルは判別できていますが、Shift-JISのファイルは間違った文字コードとして判別しています。
bytesの内容から判別を行っているようですがbytesの元となるテキストファイルの内容によっては
うまく判別できないようです。

青空文庫(夏目漱石の「こころ」)のファイルを利用してテストしてみたところ正常にUTF-8とShift-JISと判別できました。 https://www.aozora.gr.jp/cards/000148/card773.html

 1import chardet
 2
 3# 文字コードUTF-8の「こころ」テキストファイル
 4with open('kokoro_UTF-8.txt', 'rb') as f:
 5    print('kokoro_UTF-8.txt')
 6    print(chardet.detect(f.read()))
 7
 8print()
 9
10# 文字コードShift-JISの「こころ」テキストファイル
11with open('kokoro_Shift-JIS.txt', 'rb') as f:
12    print('kokoro_Shift-JIS.txt')
13    print(chardet.detect(f.read()))
1kokoro_UTF-8.txt
2{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}
3
4kokoro_Shift-JIS.txt
5{'encoding': 'SHIFT_JIS', 'confidence': 0.99, 'language': 'Japanese'}</pre>

文字コードを判別してからテキストファイルを開く

chardetのマニュアルに複数のファイルの文字コードを判別する方法が掲載されています。
こちらのコードを応用して、事前に文字コードを判別してからテキストファイルを開いてみます。
https://chardet.readthedocs.io/en/latest/usage.html#example-detecting-encodings-of-multiple-files

 1import glob
 2from chardet.universaldetector import UniversalDetector
 3
 4
 5def detect_character_code(pathname):
 6    """
 7    pathnameから該当するファイルの文字コードを判別して
 8    ファイル名と文字コードのdictを返す
 9
10    :param pathname: 文字コードを判別したいフォルダ
11    :return: ファイル名がキー、文字コードが値のdict
12    """
13    files_code_dic = {}
14    detector = UniversalDetector()
15    for file in glob.glob(pathname):
16        with open(file, 'rb') as f:
17            detector.reset()
18            for line in f.readlines():
19                detector.feed(line)
20                if detector.done:
21                    break
22            detector.close()
23            files_code_dic[file] = detector.result['encoding']
24    return files_code_dic
25
26
27if __name__ == '__main__':
28    # カレントフォルダ内のテキストファイルの文字コードを判別
29    path = './'
30    filename = '*'
31    extension = 'txt'
32    pathname = path + filename + '.' + extension
33    files_code = detect_character_code(pathname)
34    for filename in glob.glob(pathname):
35        with open(filename, encoding=files_code[filename]) as f:
36            for line in f.readlines():
37                print(filename)
38                print(line)
39                break

コードを動かすとカレントフォルダ内のテキストファイルの文字コードを判別してからテキストファイルを開きます。

開いたファイルの名前と、ファイルの内容の1行目を表示します。

1.\kokoro_Shift-JIS.txt
2こころ
3
4.\kokoro_UTF-8.txt
5こころ

chardetは非常に便利ですが、ファイルの内容によっては文字コードを誤認識する可能性があります。
文字コードの確度が低い場合は、手動で文字コードを指定してからファイルを読み込む必要がでてきます。

関連ページ