[python3]文字コードの判定

python3になってから文字列型はUTF-8になったため、文字コード不明のファイルを開いたり、ダウンロードしたりするとread()でUnicodeDecodeErrorになってしまう。(例えば、EUCの変数データを無変換でUTF-8を期待する変数に代入することはできない)

>>> fhinput = open("eucsample.txt","r")
>>> fhinput.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python3/lib/python3.4/codecs.py", line 313, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa4 in position 0: invalid start byte
>>> fhinput.close()

ファイルを開く際にバイナリモードで開けばread()メソッドの型はバイナリ(bytes)型になるので、python2の頃と同様にファイルの内容を変数に格納できるので総当りでdecode(文字コード変換)に挑戦できる。以下のサンプルコードは文字コードEUCのファイルをバイナリで開いて総当り文字コード変換を試みた例。

>>> fhinput = open("eucsample.txt","rb")
>>> htmlbytes=fhinput.read()
>>> htmlbytes
b'\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\n\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\n\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa8\n\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\n\xc8\xf8\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa'
>>> htmlbytes.decode('shift_jisx0213')
'、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「、「\n、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、\n、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ヲ、ィ\n、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ、ィ\nネ?ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ、ェ'
>>> htmlbytes.decode('iso2022jp')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'iso2022_jp' codec can't decode byte 0xa4 in position 0: illegal multibyte sequence
>>> htmlbytes.decode('euc_jisx0213')
'ああああああああああああああああああああああああ\nいいいいいいいいいいいいいい いいいいいいいいいいいい\nうううううううううううううううううううううえ\nええええええええええええええ\n尾おおおおおおおおおおおおおおお'

EUCのはずなのにshift_jisx0213で成功してしまってますが・・・
総当りで文字コード変換に挑戦するコードの例は以下。

def conv_charset_file(inputfile,outputfile):
    try:
        fhinput = open(inputfile,"rb")
        htmlbytes = fhinput.read()
        fhinput.close()
    except:
        return None,"文字コード変換できません"
        
    codelst = ('utf_8','euc_jisx0213','shift_jisx0213','iso2022jp','iso2022_jp_ext','iso2022_kr','big5','big5hkscs','johab','euc_kr','utf_16','iso8859_15','latin_1','ascii')
    
    code = ""
    for encoding in codelst:
        try:
            htmlstr = htmlbytes.decode(encoding) # bytes文字列から指定文字コードの文字列に変換
            htmlstr = htmlstr.encode('utf-8') # uft-8文字列に変換
            code=encoding
            break
        except:
            pass
            
    if code == "" :
        return None,"文字コード変換できません"
    
    try:
        fhwrite = open(outputfile,"w")
        fhwrite.write(htmlstr)
        fhwrite.close()
    except:
        return None,"文字コード変換できません"
    
    return code,None

ちなみにHTMLであればファイル内に文字コードが定義されているので、そこから文字コードを拾ってこればよいのでは思ってしまうが、そうはうまくいかない。内容を検索するには変数に格納しないといけないが、バイナリ型ではないと格納できない。またバイナリ型は文字列比較できないので、文字列型に変換しないといけないが、decodeせずに文字列に変換すると16進数表記をさらに「\」をエスケープされているので、全く別物の文字列と化してしまっている

 >>> fhinput = open("eucsample.txt","rb")
>>> htmlbytes=fhinput.read()
>>> htmlbytes
b'\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\xa4\xa2\n\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\xa4\n\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa6\xa4\xa8\n\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\xa4\xa8\n\xc8\xf8\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa\xa4\xaa'
>>> import re
>>> regcheck=re.compile('content="text/html; *?charset="*(.+?)"',re.I|re.S|re.M)
>>> regcheck.search(htmlbytes)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't use a string pattern on a bytes-like object
>>> regcheck.search(str(htmlbytes))
>>>
>>> str(htmlbytes)
"b'\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\xa4\\xa2\\n\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\xa4\\n\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa6\\xa4\\xa8\\n\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\xa4\\xa8\\n\\xc8\\xf8\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa\\xa4\\xaa'"