Escrever texto Unicode em um arquivo de texto?

225

Estou retirando dados de um documento do Google, processando-os e gravando-os em um arquivo (que eventualmente colarei em uma página do Wordpress).

Possui alguns símbolos não ASCII. Como posso convertê-los com segurança em símbolos que podem ser usados ​​na fonte HTML?

Atualmente, estou convertendo tudo para Unicode no caminho, juntando tudo em uma string Python e fazendo:

import codecs
f = codecs.open('out.txt', mode="w", encoding="iso-8859-1")
f.write(all_html.encode("iso-8859-1", "replace"))

Há um erro de codificação na última linha:

UnicodeDecodeError: o codec 'ascii' não pode decodificar o byte 0xa0 na posição 12286: ordinal fora do intervalo (128)

Solução parcial:

Este Python é executado sem erro:

row = [unicode(x.strip()) if x is not None else u'' for x in row]
all_html = row[0] + "<br/>" + row[1]
f = open('out.txt', 'w')
f.write(all_html.encode("utf-8"))

Mas se eu abrir o arquivo de texto real, vejo muitos símbolos como:

Qur’an 

Talvez eu precise escrever em algo que não seja um arquivo de texto?

simon
fonte
1
O programa que você está usando para abri-lo não está interpretando o texto UTF-8 corretamente. Deve haver uma opção para abrir o arquivo como UTF-8.
Thomas K

Respostas:

322

Lide exclusivamente com objetos unicode, tanto quanto possível, decodificando coisas em objetos unicode quando você os obtiver pela primeira vez e codificando-os conforme necessário na saída.

Se a sua string for realmente um objeto unicode, será necessário convertê-la em um objeto string codificado em unicode antes de gravá-la em um arquivo:

foo = u'Δ, Й, ק, ‎ م, ๗, あ, 叶, 葉, and 말.'
f = open('test', 'w')
f.write(foo.encode('utf8'))
f.close()

Ao ler esse arquivo novamente, você obterá uma sequência codificada em unicode que poderá decodificar em um objeto unicode:

f = file('test', 'r')
print f.read().decode('utf8')
quasistoico
fonte
Obrigado. Isso funciona sem erro, mas se eu abrir o arquivo de texto, vejo vários símbolos estranhos :) Preciso copiar e colar o texto em uma página do Wordpress (não pergunte). Existe alguma maneira de realmente imprimir os símbolos existentes? Eu acho que não para um arquivo txt, certo, mas talvez para outra coisa?
simon
1
O que você está usando para abrir o arquivo de texto? Suponho que você esteja no Windows e o abra no Bloco de Notas, que não é muito inteligente com codificações. O que acontece quando você o abre no Wordpad?
quasistoic
@quasistoic De onde vem o método do arquivo ?
Omar Cusma Fait 03/10/19
Eu precisava ativar o modo binário, ou seja, f = open ('test', 'wb'), conforme descrito em stackoverflow.com/a/5513856/6580199 - caso contrário, eu obteria o argumento "TypeError: write () deve ser str, não bytes "
Benji
72

No Python 2.6+, você pode usario.open() o padrão ( embutidoopen() ) no Python 3:

import io

with io.open(filename, 'w', encoding=character_encoding) as file:
    file.write(unicode_text)

Pode ser mais conveniente se você precisar escrever o texto de forma incremental (não é necessário ligar unicode_text.encode(character_encoding)várias vezes). Ao contrário do codecsmódulo, o iomódulo possui um suporte adequado para novas linhas universais.

jfs
fonte
1
Cara, eu gastei muito tempo para encontrar isso! Obrigado!
Georgy Gobozov 22/02
2
Isso funciona para o Python 3 também (óbvio, mas ainda vale a pena destacar).
Hipopótamo
37

O manuseio de strings Unicode já é padronizado no Python 3.

  1. caracteres já estão armazenados em Unicode (32 bits) na memória
  2. Você só precisa abrir o arquivo em utf-8
    (a conversão Unicode de 32 bits em utf-8 de comprimento de bytes variável é automaticamente executada da memória para o arquivo).

    out1 = "(嘉南大圳 ㄐㄧㄚ ㄋㄢˊ ㄉㄚˋ ㄗㄨㄣˋ )"
    fobj = open("t1.txt", "w", encoding="utf-8")
    fobj.write(out1)
    fobj.close()
david m lee
fonte
Mas isso não funciona no Python 2, certo? (Eu deveria disse, neste 3 de código Python, parece tão concisa e razoável)
Liwen Zhao
não deve funcionar no Python 2. Ficamos no Python 3. 3 é muito melhor.
David m lee
18

O arquivo aberto por codecs.opené um arquivo que pega unicodedados, codifica iso-8859-1e grava no arquivo. No entanto, o que você tenta escrever não é unicode; você pega unicodee codifica em iso-8859-1 si mesmo . É isso que o unicode.encodemétodo faz, e o resultado da codificação de uma string unicode é uma bytestring (um strtipo).

Você deve usar normal open()e codificar o unicode por conta própria ou (geralmente uma idéia melhor) usar codecs.open()e não codificar os dados por conta própria.

Thomas Wouters
fonte
17

Prefácio: seu visualizador funcionará?

Verifique se o visualizador / editor / terminal (no entanto, você está interagindo com o arquivo codificado utf-8) pode lê-lo. Isso costuma ser um problema no Windows , por exemplo, o Bloco de Notas.

Escrever texto Unicode em um arquivo de texto?

No Python 2, use a openpartir do iomódulo (é o mesmo que o embutido openno Python 3):

import io

As práticas recomendadas, em geral, são usadas UTF-8para gravar arquivos (nem precisamos nos preocupar com a ordem de bytes com utf-8).

encoding = 'utf-8'

O utf-8 é a codificação mais moderna e universalmente utilizável - funciona em todos os navegadores da web, na maioria dos editores de texto (veja suas configurações se houver problemas) e na maioria dos terminais / shells.

No Windows, você pode tentar utf-16lese estiver limitado à exibição da saída no Bloco de notas (ou em outro visualizador limitado).

encoding = 'utf-16le' # sorry, Windows users... :(

E basta abri-lo com o gerenciador de contexto e escrever seus caracteres unicode:

with io.open(filename, 'w', encoding=encoding) as f:
    f.write(unicode_object)

Exemplo usando muitos caracteres Unicode

Aqui está um exemplo que tenta mapear todos os caracteres possíveis com até três bits de largura (4 é o máximo, mas isso seria um pouco distante) da representação digital (em números inteiros) para uma saída imprimível codificada, juntamente com seu nome, se possível (coloque isso em um arquivo chamado uni.py):

from __future__ import print_function
import io
from unicodedata import name, category
from curses.ascii import controlnames
from collections import Counter

try: # use these if Python 2
    unicode_chr, range = unichr, xrange
except NameError: # Python 3
    unicode_chr = chr

exclude_categories = set(('Co', 'Cn'))
counts = Counter()
control_names = dict(enumerate(controlnames))
with io.open('unidata', 'w', encoding='utf-8') as f:
    for x in range((2**8)**3): 
        try:
            char = unicode_chr(x)
        except ValueError:
            continue # can't map to unicode, try next x
        cat = category(char)
        counts.update((cat,))
        if cat in exclude_categories:
            continue # get rid of noise & greatly shorten result file
        try:
            uname = name(char)
        except ValueError: # probably control character, don't use actual
            uname = control_names.get(x, '')
            f.write(u'{0:>6x} {1}    {2}\n'.format(x, cat, uname))
        else:
            f.write(u'{0:>6x} {1}  {2}  {3}\n'.format(x, cat, char, uname))
# may as well describe the types we logged.
for cat, count in counts.items():
    print('{0} chars of category, {1}'.format(count, cat))

Isso deve ser executado na ordem de cerca de um minuto, e você pode visualizar o arquivo de dados e, se o visualizador de arquivos puder exibir unicode, você o verá. Informações sobre as categorias podem ser encontradas aqui . Com base nas contagens, provavelmente podemos melhorar nossos resultados excluindo as categorias Cn e Co, que não possuem símbolos associados a elas.

$ python uni.py

Ele exibirá o mapeamento hexadecimal, categoria , símbolo (a menos que não consiga obter o nome, provavelmente um caractere de controle) e o nome do símbolo. por exemplo

Eu recomendo lessno Unix ou Cygwin (não imprima / copie o arquivo inteiro para sua saída):

$ less unidata

por exemplo, exibirá semelhante às seguintes linhas que eu amostramos usando Python 2 (unicode 5.2):

     0 Cc NUL
    20 Zs     SPACE
    21 Po  !  EXCLAMATION MARK
    b6 So    PILCROW SIGN
    d0 Lu  Ð  LATIN CAPITAL LETTER ETH
   e59 Nd    THAI DIGIT NINE
  2887 So    BRAILLE PATTERN DOTS-1238
  bc13 Lo    HANGUL SYLLABLE MIH
  ffeb Sm    HALFWIDTH RIGHTWARDS ARROW

Meu Python 3.5 do Anaconda tem unicode 8.0, eu presumo que a maioria dos 3 teria.

Aaron Hall
fonte
3

Como imprimir caracteres unicode em um arquivo:

Salve isso no arquivo: foo.py:

#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys 
UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print(u'e with obfuscation: é')

Execute-o e envie a saída para o arquivo:

python foo.py > tmp.txt

Abra o arquivo tmp.txt e olhe dentro, você vê o seguinte:

el@apollo:~$ cat tmp.txt 
e with obfuscation: é

Assim, você salvou o unicode e com um sinal de ofuscação em um arquivo.

Eric Leschinski
fonte
2
Fiquei bastante animado com esta resposta, mas ela apresenta um erro na minha máquina. Quando copio / colo seu código, recebo um erro: "TypeError: deve ser str, não bytes"
Richard Rast 6/14
1

Esse erro surge quando você tenta codificar uma sequência não unicode: tenta decodificá-la, assumindo que ela esteja em ASCII simples. Existem duas possibilidades:

  1. Você está codificando para uma bytestring, mas como você usou codecs.open, o método write espera um objeto unicode. Então você o codifica e tenta decodificá-lo novamente. Tente: em f.write(all_html)vez disso.
  2. all_html não é, de fato, um objeto unicode. Quando você faz .encode(...), ele primeiro tenta decodificá-lo.
Thomas K
fonte
0

No caso de escrever em python3

>>> a = u'bats\u00E0'
>>> print a
batsà
>>> f = open("/tmp/test", "w")
>>> f.write(a)
>>> f.close()
>>> data = open("/tmp/test").read()
>>> data
'batsà'

No caso de escrever em python2:

>>> a = u'bats\u00E0'
>>> f = open("/tmp/test", "w")
>>> f.write(a)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

Para evitar esse erro, você teria que codificá-lo em bytes usando os codecs "utf-8" como este:

>>> f.write(a.encode("utf-8"))
>>> f.close()

e decodifique os dados durante a leitura usando os codecs "utf-8":

>>> data = open("/tmp/test").read()
>>> data.decode("utf-8")
u'bats\xe0'

E também, se você tentar executar a impressão nessa string, ela decodificará automaticamente usando os codecs "utf-8" como este

>>> print a
batsà
ashish14
fonte