it-swarm.dev

Converter UTF-8 com BOM para UTF-8 sem BOM em Python

Duas perguntas aqui. Eu tenho um conjunto de arquivos que normalmente são UTF-8 com BOM. Eu gostaria de convertê-los (idealmente no local) para UTF-8 sem BOM. Parece que codecs.StreamRecoder(stream, encode, decode, Reader, Writer, errors) lidaria com isso. Mas eu realmente não vejo bons exemplos de uso. Esta seria a melhor maneira de lidar com isso? 

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

Além disso, seria ideal se pudéssemos lidar com diferentes codificações de entrada sem saber explicitamente (visto ASCII e UTF-16). Parece que tudo isso deve ser viável. Existe uma solução que possa tomar qualquer codificação e saída Python conhecidas como UTF-8 sem BOM?

edit 1 sol'n proposto de baixo (obrigado!)

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)

Isso me dá o seguinte erro: 

IOError: [Errno 9] Bad file descriptor

Newsflash

Estou sendo dito nos comentários que o erro é que eu abro o arquivo com o modo 'rw' em vez de 'r +'/'r + b', então eu eventualmente reeditaria minha pergunta e removeria a parte resolvida.

54
timpone

Basta usar o codec "utf-8-sig" :

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

Isso dá a você uma string unicode sem a BOM. Você pode então usar

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

para obter uma string codificada UTF-8 normal de volta em s. Se seus arquivos são grandes, você deve evitar ler todos eles na memória. A BOM tem apenas três bytes no início do arquivo, portanto, você pode usar esse código para removê-los do arquivo:

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()

Ele abre o arquivo, lê um trecho e o escreve no arquivo 3 bytes antes de onde o leu. O arquivo é reescrito no local. A solução mais fácil é escrever o arquivo mais curto em um novo arquivo como newtover's answer . Isso seria mais simples, mas use o dobro do espaço em disco por um curto período.

Quanto a adivinhar a codificação, você pode simplesmente percorrer a codificação de mais ou menos específico:

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

Um arquivo codificado em UTF-16 não decodificará como UTF-8, então tentamos primeiro com UTF-8. Se isso falhar, tentamos com o UTF-16. Finalmente, usamos o Latin-1 - isso sempre funcionará, já que todos os 256 bytes são valores legais no Latin-1. Você pode querer retornar None neste caso, já que é realmente um substituto e seu código pode querer lidar com isso com mais cuidado (se puder).

87
Martin Geisler

No Python 3 é bem fácil: leia o arquivo e reescreva-o com a codificação utf-8:

s = open(bom_file, mode='r', encoding='utf-8-sig').read()
open(bom_file, mode='w', encoding='utf-8').write(s)
29
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

Esta é a minha implementação para converter qualquer tipo de codificação para UTF-8 sem BOM e substituir as janelas de linha por formato universal:

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

Eu encontrei esta pergunta porque ter problemas com configparser.ConfigParser().read(fp) ao abrir arquivos com o cabeçalho da lista de materiais UTF8. 

Para aqueles que estão procurando uma solução para remover o cabeçalho para que o ConfigPhaser possa abrir o arquivo de configuração em vez de relatar um erro de: File contains no section headers, abra o arquivo da seguinte forma:

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

Isso pode poupar muito esforço, tornando desnecessária a remoção do cabeçalho da lista de materiais do arquivo.

(Eu sei que isso não parece relacionado, mas espero que isso ajude as pessoas lutando como eu.)

0
Alto.Clef

Você pode usar codecs.

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