先日、python3でファイルを読み込んで各行を処理するプログラムを書いてる時に UnicodeDecodeError
と出会いました。ある行にutf-8で(decodeでき)ない行が紛れ込んでいることが原因でした。
処理としては UnicdeDecodeError
が出る行をスキップして処理することにしたので、どのようにしたのか書いておきます。
コードの中にencodeとかdecodeとか出てきますがpython3です。
UnicodeDecodeErrorが途中の行で発生するファイルを作成する
def main():
with open("hoge.txt", 'wb') as f:
f.write("あ\n".encode('utf-8'))
f.write("い\n".encode('utf-8'))
f.write("う\n".encode('utf-8'))
f.write("シフトジス\n".encode('shift-jis')) # この行だけ別の文字コードでencodeする
f.write("え\n".encode('utf-8'))
f.write("お\n".encode('utf-8'))
f.write("か\n".encode('utf-8'))
if __name__ == "__main__":
main()
$ python create_file.py
上で作成したファイルの中身を普通に吐き出そうとしてみる
def main():
with open("hoge.txt", 'r') as f:
line = f.readline()
while line:
print(line.rstrip())
line = f.readline()
if __name__ == "__main__":
main()
$ python output_line.py
Traceback (most recent call last):
File "output_line.py", line 9, in <module>
main()
File "output_line.py", line 3, in main
line = f.readline()
File "/path/to/.pyenv/versions/3.5.1/lib/python3.5/codecs.py", line 321, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x83 in position 12: invalid start byte
途中にshit-jisのバイト列が入ってるので怒られます。このエラー文を見ると、 f.readline()
の中でutf-8型でdecodeしようとしていることがわかります。その為、ファイルの途中で変なバイト列があると、f.readline()できなくなってしまうようです。try・exceptでエラーが出ないようにしても、結局f.readline()できないので次の行に進めなくなってしまいます。
f.readline()でUnicodeDecodeErrorを出さないようにする方法
簡単で、バイト列として行を受け取るようにします。
先ほどのコードでopenする時に r
ではなく rb
にすると出力でエラーが出なくなります。
with open("hoge.txt", 'rb') as f:
$ python output_line.py
b'\xe3\x81\x82'
b'\xe3\x81\x84'
b'\xe3\x81\x86'
b'\x83V\x83t\x83g\x83W\x83X'
b'\xe3\x81\x88'
b'\xe3\x81\x8a'
b'\xe3\x81\x8b'
ただ、バイト列だと扱い辛いのでstr型に戻したいです。
最終形:UnicodeDecodeErrorが出る行を飛ばして処理する
出力をstrにしてUnicodeDecodeErrorが出る行をスキップする為には、decodeとtry・exceptを使います。最終形は下のコードです。
def main():
with open("hoge.txt", 'rb') as f: # 'r'でなく'rb'
line = f.readline()
while line:
try:
line = line.decode("utf-8")
except UnicodeDecodeError:
# utf-8でないバイト列が含まれる行はスキップする
line = f.readline()
continue
# ...
# 何か行いたい処理
print(line.rstrip())
# ...
line = f.readline()
if __name__ == "__main__":
main()
$ python output_line.py
あ
い
う
え
お
か
標準入力から受け取る場合
import sys
def main():
for line in sys.stdin.buffer: # bufferをつけるとバイト列で受け取れる
try:
line = line.decode("utf-8")
except UnicodeDecodeError:
# utf-8でないバイト列が含まれる行はスキップする
continue
...
# 何か行いたい処理
print(line.rstrip())
# ...
if __name__ == "__main__":
main()
$ cat hoge.txt | python output_line.py
あ
い
う
え
お
か