web-dev-qa-db-ja.com

PythonでBOM付きUTF-8をBOMなしのUTF-8に変換します

ここで2つの質問。通常、BOM付きUTF-8のファイルセットがあります。それらを(理想的には適切に)BOMなしのUTF-8に変換したいと思います。 codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors)がこれを処理するようです。しかし、私は実際に使用法の良い例を見ていない。これはこれを処理する最良の方法でしょうか?

source files:
Tue Jan 17$ file brh-m-157.json 
brh-m-157.json: UTF-8 Unicode (with BOM) text

また、(ASCIIおよびUTF-16)を明示的に認識して、異なる入力エンコーディングを処理できれば理想的です。これはすべて実行可能であるように思われます。既知のPythonエンコーディングとBOMなしのUTF-8としての出力?

編集1下からソルンを提案(ありがとう!)

fp = open('brh-m-157.json','rw')
s = fp.read()
u = s.decode('utf-8-sig')
s = u.encode('utf-8')
print fp.encoding  
fp.write(s)

これにより、次のエラーが表示されます。

IOError: [Errno 9] Bad file descriptor

ニュース速報

コメントでは、「r +」/「r + b」ではなく「rw」モードでファイルを開くという間違いがあると言われているので、最終的に質問を再編集して解決した部分を削除する必要があります。

62
timpone

"utf-8-sig" codec を使用するだけです:

fp = open("file.txt")
s = fp.read()
u = s.decode("utf-8-sig")

これにより、BOMなしのunicode文字列が得られます。次に使用できます

s = u.encode("utf-8")

sに戻って通常のUTF-8エンコード文字列を取得します。ファイルが大きい場合は、それらをすべてメモリに読み込まないでください。 BOMはファイルの先頭にある3バイトだけなので、次のコードを使用してファイルからそれらを取り除くことができます。

import os, sys, codecs

BUFSIZE = 4096
BOMLEN = len(codecs.BOM_UTF8)

path = sys.argv[1]
with open(path, "r+b") as fp:
    chunk = fp.read(BUFSIZE)
    if chunk.startswith(codecs.BOM_UTF8):
        i = 0
        chunk = chunk[BOMLEN:]
        while chunk:
            fp.seek(i)
            fp.write(chunk)
            i += len(chunk)
            fp.seek(BOMLEN, os.SEEK_CUR)
            chunk = fp.read(BUFSIZE)
        fp.seek(-BOMLEN, os.SEEK_CUR)
        fp.truncate()

ファイルを開き、チャンクを読み取り、読み取った場所より3バイト早くファイルに書き込みます。ファイルはインプレースで書き換えられます。より簡単な解決策は、 newtover's answer のような短いファイルを新しいファイルに書き込むことです。それは簡単ですが、短時間で2倍のディスク容量を使用します。

エンコーディングの推測に関しては、エンコーディングを特定度の高いものから低いものへループすることができます。

def decode(s):
    for encoding in "utf-8-sig", "utf-16":
        try:
            return s.decode(encoding)
        except UnicodeDecodeError:
            continue
    return s.decode("latin-1") # will always work

UTF-16エンコードファイルはUTF-8としてデコードされないため、最初にUTF-8を試します。それが失敗した場合、UTF-16を使用してみます。最後に、Latin-1を使用します。256バイトはすべてLatin-1で有効な値であるため、これは常に機能します。この場合、代わりにNoneを返すことをお勧めします。これは実際にはフォールバックであり、コードはこれをより慎重に処理する必要があるためです(可能な場合)。

99
Martin Geisler

Python 3では非常に簡単です。ファイルを読み取り、utf-8エンコーディングで書き換えます:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
37
Geng Jiawen
import codecs
import shutil
import sys

s = sys.stdin.read(3)
if s != codecs.BOM_UTF8:
    sys.stdout.write(s)

shutil.copyfileobj(sys.stdin, sys.stdout)
6
newtover

これは、BOMを使用せずにすべての種類のエンコーディングをUTF-8に変換し、Windowsのエンラインをユニバーサル形式に置き換えるための私の実装です。

def utf8_converter(file_path, universal_endline=True):
    '''
    Convert any type of file to UTF-8 without BOM
    and using universal endline by default.

    Parameters
    ----------
    file_path : string, file path.
    universal_endline : boolean (True),
                        by default convert endlines to universal format.
    '''

    # Fix file path
    file_path = os.path.realpath(os.path.expanduser(file_path))

    # Read from file
    file_open = open(file_path)
    raw = file_open.read()
    file_open.close()

    # Decode
    raw = raw.decode(chardet.detect(raw)['encoding'])
    # Remove windows end line
    if universal_endline:
        raw = raw.replace('\r\n', '\n')
    # Encode to UTF-8
    raw = raw.encode('utf8')
    # Remove BOM
    if raw.startswith(codecs.BOM_UTF8):
        raw = raw.replace(codecs.BOM_UTF8, '', 1)

    # Write to file
    file_open = open(file_path, 'w')
    file_open.write(raw)
    file_open.close()
    return 0
4
estevo

この質問は、UTF8 BOMヘッダーを持つファイルを開くときにconfigparser.ConfigParser().read(fp)で問題が発生したために見つかりました。

ConfigPhaserが次のエラーを報告する代わりに構成ファイルを開くことができるように、ヘッダーを削除するソリューションを探している人向け:File contains no section headers、次のようにファイルを開いてください。

configparser.ConfigParser().read(config_file_path, encoding="utf-8-sig")

これにより、ファイルのBOMヘッダーを削除する必要がなくなるため、労力を大幅に節約できます。

(これは無関係に聞こえますが、うまくいけば、これは私のように苦労している人々を助けることができます。)

0
Alto.Clef

コーデックを使用できます。

import codecs
with open("test.txt",'r') as filehandle:
    content = filehandle.read()
if content[:3] == codecs.BOM_UTF8:
    content = content[3:]
print content.decode("utf-8")
0
wcc526