Converter Unicode em ASCII sem erros em Python


178

Meu código apenas raspa uma página da Web e depois a converte em Unicode.

html = urllib.urlopen(link).read()
html.encode("utf8","ignore")
self.response.out.write(html)

Mas eu recebo um UnicodeDecodeError:


Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/webapp/__init__.py", line 507, in __call__
    handler.get(*groups)
  File "/Users/greg/clounce/main.py", line 55, in get
    html.encode("utf8","ignore")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 2818: ordinal not in range(128)

Suponho que isso signifique que o HTML contenha alguma tentativa incorreta de Unicode em algum lugar. Posso simplesmente eliminar os bytes de código que estão causando o problema em vez de receber um erro?


2
Considero um erro se caracteres importantes são descartados! (Além disso, onde é a pergunta?)
— Arafangion

Parece que você pode ter encontrado um "espaço sem interrupção" na página da web? precisaria ser precedido por um c2byte ou você provavelmente obteria um erro de decodificação: hexutf8.com/?q=C2A0
— jar

Respostas:


105

Atualização de 2018:

A partir de fevereiro de 2018, o uso de compressões como gzipse tornou bastante popular (cerca de 73% de todos os sites usam, incluindo sites grandes como Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow e Stack Exchange Network).
Se você fizer uma decodificação simples, como na resposta original, com uma resposta compactada com gzip, receberá um erro semelhante ou semelhante a este:

UnicodeDecodeError: o codec 'utf8' não pode decodificar o byte 0x8b na posição 1: byte de código inesperado

Para decodificar uma resposta gzpipped, você precisa adicionar os seguintes módulos (no Python 3):

import gzip
import io

Nota: No Python 2 você usaria em StringIOvez deio

Em seguida, você pode analisar o conteúdo da seguinte maneira:

response = urlopen("https://example.com/gzipped-ressource")
buffer = io.BytesIO(response.read()) # Use StringIO.StringIO(response.read()) in Python 2
gzipped_file = gzip.GzipFile(fileobj=buffer)
decoded = gzipped_file.read()
content = decoded.decode("utf-8") # Replace utf-8 with the source encoding of your requested resource

Este código lê a resposta e coloca os bytes em um buffer. O gzipmódulo lê o buffer usando a GZipFilefunção Depois disso, o arquivo compactado com gzip pode ser lido em bytes novamente e decodificado para texto normalmente legível no final.

Resposta original de 2010:

Podemos obter o valor real usado link?

Além disso, geralmente encontramos esse problema aqui quando estamos tentando .encode()uma sequência de bytes já codificada. Portanto, você pode tentar decodificá-lo primeiro, como em

html = urllib.urlopen(link).read()
unicode_str = html.decode(<source encoding>)
encoded_str = unicode_str.encode("utf8")

Como um exemplo:

html = '\xa0'
encoded_str = html.encode("utf8")

Falha com

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 0: ordinal not in range(128)

Enquanto:

html = '\xa0'
decoded_str = html.decode("windows-1252")
encoded_str = decoded_str.encode("utf8")

Sucesso sem erro. Observe que "windows-1252" é algo que usei como exemplo . Eu peguei isso do chardet e tinha 0,5 confiança de que está certo! (bem, como fornecido com uma cadeia de caracteres de 1 caractere, o que você espera) Você deve alterar isso para a codificação da cadeia de bytes retornada .urlopen().read()para a que se aplica ao conteúdo recuperado.

Outro problema que vejo é que o .encode()método string retorna a string modificada e não modifica a fonte no local. Portanto, é meio inútil ter, self.response.out.write(html)pois html não é a sequência codificada de html.encode (se é isso que você estava originalmente buscando).

Como Ignacio sugeriu, verifique na página de origem a codificação real da string retornada read(). Está em uma das metatags ou no cabeçalho ContentType na resposta. Use isso então como o parâmetro para .decode().

Observe, no entanto, que não se deve presumir que outros desenvolvedores sejam responsáveis ​​o suficiente para garantir que o cabeçalho e / ou as declarações do conjunto de meta caracteres correspondam ao conteúdo real. (Que é uma PITA, sim, eu deveria saber, eu era uma dessas antes).


1
No seu exemplo eu acho que você significou para a última linha a ser encoded_str = decoded_str.encode("utf8")
— Ajith Antony

1
Eu tentei no Python 2.7.15 e recebi esta mensagem raise IOError, 'Not a gzipped file'. Qual foi a falha que fiz?
— Hyun-geun Kim

222
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Decodifique a sequência que você voltar, usando o conjunto de caracteres na metatag apropriada na resposta ou no Content-Typecabeçalho e, em seguida, codifique.

O método encode(encoding, errors)aceita manipuladores personalizados para erros. Os valores padrão, além disso ignore, são:

>>> u'aあä'.encode('ascii', 'replace')
b'a??'
>>> u'aあä'.encode('ascii', 'xmlcharrefreplace')
b'a&#12354;&#228;'
>>> u'aあä'.encode('ascii', 'backslashreplace')
b'a\\u3042\\xe4'

Consulte https://docs.python.org/3/library/stdtypes.html#str.encode


119

Como extensão da resposta de Ignacio Vazquez-Abrams

>>> u'aあä'.encode('ascii', 'ignore')
'a'

Às vezes, é desejável remover acentos dos caracteres e imprimir o formulário base. Isso pode ser realizado com

>>> import unicodedata
>>> unicodedata.normalize('NFKD', u'aあä').encode('ascii', 'ignore')
'aa'

Você também pode traduzir outros caracteres (como pontuação) para seus equivalentes mais próximos, por exemplo, o caractere unicode RIGHT SINGLE COOTATION MARK não é convertido em um APOSTROPHE ascii durante a codificação.

>>> print u'\u2019'
’
>>> unicodedata.name(u'\u2019')
'RIGHT SINGLE QUOTATION MARK'
>>> u'\u2019'.encode('ascii', 'ignore')
''
# Note we get an empty string back
>>> u'\u2019'.replace(u'\u2019', u'\'').encode('ascii', 'ignore')
"'"

Embora existam maneiras mais eficientes de conseguir isso. Consulte esta pergunta para obter mais detalhes Onde está o banco de dados "melhor ASCII para este Unicode" do Python?


4
Ambos úteis na abordagem da pergunta que foi feita e práticos para resolver questões que possam estar subjacentes à pergunta. Esta é uma resposta modelo para esse tipo de pergunta.
— Shanusmagnus

96

Use o unidecode - até converte caracteres estranhos em ascii instantaneamente e até converte chinês em ascii fonético.

$ pip install unidecode

então:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'

3
Halle-Freakin-lujah - seu tempo sobre eu encontrei uma resposta que funcionou para mim
— Aurielle Perlmann

10
Voto por valor divertido. Observe que isso manipula palavras em todos os idiomas acentuados. Škoda não é Skoda. Skoda provavelmente significa algo nojento com enguias e hovercrafts.
— 194 Sylvain

1
Eu estive vasculhando a internet para dias até agora .... obrigado, muito obrigado
— Stephen

23

Eu uso essa função auxiliar em todos os meus projetos. Se ele não pode converter o unicode, ele o ignora. Isso está vinculado a uma biblioteca de django, mas com um pouco de pesquisa você pode ignorá-la.

from django.utils import encoding

def convert_unicode_to_string(x):
    """
    >>> convert_unicode_to_string(u'ni\xf1era')
    'niera'
    """
    return encoding.smart_str(x, encoding='ascii', errors='ignore')

Eu não recebo mais erros unicode depois de usá-lo.


10
Isso está suprimindo o problema, não diagnosticando e corrigindo. É como dizer "Depois de cortar os pés, não tenho mais problemas com calos e joanetes".
— quer

10
Concordo que está suprimindo o problema. Parece que é isso que a questão é depois. Veja a nota dele: "Posso simplesmente eliminar quaisquer bytes de código que estejam causando o problema em vez de obter um erro?"
— Gattster 03/03/10

3
este é exatamente o mesmo que simplesmente chamando "some-string" .encode ( 'ascii', 'ignorar')
— Joshua Burns,

17
Não posso dizer o quanto estou cansado de alguém fazer uma pergunta sobre o SO e obter todas essas respostas de pregação. "Meu carro não liga." "Por que você quer dar partida no seu carro? Você deveria andar em vez disso." Pare com isso!
— Shanusmagnus

8
@JohnMachin Ninguém se importa. Eu não me importo com o que as pessoas retardadas colocam nos feeds RSS, se for algum personagem que não seja ascii, pode ser truncado. O problema deles. Eu só quero que o python realmente engula e lide com ele, não me dê erros toda vez que eu especificar 'ignorar'. Quem diabos inventou essa merda ?!
— user1244215

10

Para consoles quebrados como cmd.exee saída HTML, você sempre pode usar:

my_unicode_string.encode('ascii','xmlcharrefreplace')

Isso preservará todos os caracteres não-ascii e os imprimirá em ASCII puro e em HTML.

AVISO : Se você usar isso no código de produção para evitar erros, provavelmente haverá algo errado no seu código . O único caso de uso válido para isso é a impressão em um console não unicode ou a fácil conversão para entidades HTML em um contexto HTML.

E, finalmente, se você estiver no Windows e usar o cmd.exe, poderá digitar chcp 65001para ativar a saída utf-8 (funciona com a fonte do Lucida Console). Você pode precisar adicionar myUnicodeString.encode('utf8').


6

Você escreveu "" "Presumo que isso significa que o HTML contém alguma tentativa incorreta de unicode em algum lugar." ""

NÃO se espera que o HTML contenha qualquer tipo de "tentativa de unicode", bem formada ou não. Deve necessariamente conter caracteres Unicode codificados em alguma codificação, que geralmente é fornecida antecipadamente ... procure por "charset".

Você parece estar assumindo que o conjunto de caracteres é UTF-8 ... com que fundamento? O byte "\ xA0" mostrado na sua mensagem de erro indica que você pode ter um conjunto de caracteres de byte único, por exemplo, cp1252.

Se você não conseguir entender a declaração no início do HTML, tente usar chardet para descobrir qual é a provável codificação.

Por que você marcou sua pergunta com "regex"?

Atualize depois de substituir sua pergunta inteira por uma não-pergunta:

html = urllib.urlopen(link).read()
# html refers to a str object. To get unicode, you need to find out
# how it is encoded, and decode it.

html.encode("utf8","ignore")
# problem 1: will fail because html is a str object;
# encode works on unicode objects so Python tries to decode it using 
# 'ascii' and fails
# problem 2: even if it worked, the result will be ignored; it doesn't 
# update html in situ, it returns a function result.
# problem 3: "ignore" with UTF-n: any valid unicode object 
# should be encodable in UTF-n; error implies end of the world,
# don't try to ignore it. Don't just whack in "ignore" willy-nilly,
# put it in only with a comment explaining your very cogent reasons for doing so.
# "ignore" with most other encodings: error implies that you are mistaken
# in your choice of encoding -- same advice as for UTF-n :-)
# "ignore" with decode latin1 aka iso-8859-1: error implies end of the world.
# Irrespective of error or not, you are probably mistaken
# (needing e.g. cp1252 or even cp850 instead) ;-)

4

Se você tiver uma cadeia line, poderá usar o .encode([encoding], [errors='strict'])método para converter cadeias de tipos.

line = 'my big string'

line.encode('ascii', 'ignore')

Para obter mais informações sobre como lidar com ASCII e unicode em Python, este é um site realmente útil: https://docs.python.org/2/howto/unicode.html


1
Isso não funciona quando você tem um caractere não ascii como ü na string.
— sajid

4

Acho que a resposta está lá, mas apenas em partes, o que dificulta a solução rápida do problema, como

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 2818: ordinal not in range(128)

Vamos dar um exemplo: suponha que eu tenha um arquivo com alguns dados no seguinte formato (contendo caracteres ascii e não-ascii)

1/10/17, 21:36 - Terra: Bem-vindo

e queremos ignorar e preservar apenas caracteres ascii.

Este código fará:

import unicodedata
fp  = open(<FILENAME>)
for line in fp:
    rline = line.strip()
    rline = unicode(rline, "utf-8")
    rline = unicodedata.normalize('NFKD', rline).encode('ascii','ignore')
    if len(rline) != 0:
        print rline

e digite (rline) lhe dará

>type(rline) 
<type 'str'>

Isso também funciona para os (não padronizados) casos "ascii estendida"
— Oliver Zendel

1
unicodestring = '\xa0'

decoded_str = unicodestring.decode("windows-1252")
encoded_str = decoded_str.encode('ascii', 'ignore')

Funciona para mim


-5

Parece que você está usando o python 2.x. O Python 2.x usa como padrão ascii e não sabe sobre Unicode. Daí a exceção.

Basta colar a linha abaixo após shebang, ele vai funcionar

# -*- coding: utf-8 -*-

O codingcomentário não é um remédio mágico. Você precisa saber por que o erro está sendo gerado, isso só corrige as coisas quando há caracteres incorretos em sua fonte Python. Esse não parece ser o caso desta pergunta.
— Mark Ransom
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.