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?

o espelho
fonte
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).

Vin-G
fonte
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

Ignacio Vazquez-Abrams
fonte
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?

Peter Gibson
fonte
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'
Nimo
fonte
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.

Gattster
fonte
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').

ccpizza
fonte
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) ;-)
John Machin
fonte
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

Jama22
fonte
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'>
Somum
fonte
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

HimalayanCoder
fonte
-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 -*-
Haroon Rashedu
fonte
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