it-swarm.dev

Converter Unicode para ASCII sem erros no Python

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

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

Mas eu obtenho 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)

Eu suponho que significa que o HTML contém alguma tentativa mal formada em Unicode em algum lugar. Eu posso simplesmente descartar o código que está causando o problema em vez de receber um erro?

163
themirror

Atualização 2018:

A partir de fevereiro de 2018, o uso de compressões como gzip tornou-se bastante popular (cerca de 73% de todos os sites o utilizam, incluindo sites grandes como Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow e Stack Exchange Network sites).
Se você fizer uma simples decodificação como na resposta original com uma resposta gzipada, receberá um erro parecido ou similar 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 StringIO em vez de io

Então você pode analisar o conteúdo assim:

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 módulo gzip lê o buffer usando a função GZipFile. Depois disso, o arquivo gzipado 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 para link?

Além disso, geralmente encontramos esse problema aqui quando tentamos .encode() uma string de byte já codificada. Então 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")

Sucedem sem erros. Note que "windows-1252" é algo que usei como exemplo . Eu tenho isso de chardet e tinha 0,5 confiança de que está certo! (bem, conforme fornecido com uma string de 1 caractere, o que você espera) Você deve mudar isso para a codificação da string de byte retornada de .urlopen().read() para o que se aplica ao conteúdo que você recuperou.

Outro problema que vejo é que o método string .encode() retorna a string modificada e não modifica a fonte no lugar. Então é meio inútil ter self.response.out.write(html) como html não é a string codificada de html.encode (se é isso que você estava originalmente procurando).

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

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

99
Vin-G
>>> u'aあä'.encode('ascii', 'ignore')
'a'

EDIT:

Decodifique a string que você recebe, usando o conjunto de caracteres na tag meta apropriada na resposta ou no cabeçalho Content-Type e, em seguida, codifique.

O método encode() aceita outros valores como "ignore". Por exemplo: 'replace', 'xmlcharrefreplace', 'backslashreplace'. Veja https://docs.python.org/3/library/stdtypes.html#str.encode

200
Ignacio Vazquez-Abrams

Como uma extensão da resposta de Ignacio Vazquez-Abrams

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

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

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

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

>>> 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. Veja esta questão para mais detalhes Onde está o melhor banco de dados do Python?ASCII para este banco de dados Unicode?

113
Peter Gibson

Use nidecode - ele até converte caracteres estranhos para ascii instantaneamente, e até converte chineses em ascii fonéticos.

$ pip install unidecode

então:

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

Eu uso essa função auxiliar em todos os meus projetos. Se não for possível converter o unicode, ele será ignorado. Isso liga-se a uma biblioteca do Django, mas com um pouco de pesquisa você pode contorná-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')

Não consigo mais erros de unicode depois de usar isso.

24
Gattster

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

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

Isso preservará todos os caracteres não-ascii ao torná-los imprimíveis em pure ASCII e em HTML.

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

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

9
ccpizza

Você escreveu "" "Eu suponho que significa que o HTML contém alguma tentativa mal formada no unicode em algum lugar." ""

Não se espera que o HTML contenha qualquer tipo de "tentativa de unicode", bem formado ou não. Ele deve necessariamente conter caracteres Unicode codificados em alguma codificação, que geralmente é fornecida na frente ... procure por "charset".

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

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

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

Atualize depois de ter substituído toda a sua pergunta 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) ;-)
5
John Machin

Se você tiver uma string line, você pode usar o método .encode([encoding], [errors='strict']) para que as strings convertam os tipos de codificação.

line = 'my big string'

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

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

4
Jama22

Eu acho que a resposta está lá, mas apenas em pedaços, o que torna difícil corrigir rapidamente o 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 que tenha alguns dados no seguinte formato (contendo caracteres ascii e non-ascii)

1/10/17, 21:36 - Terra: Boas Vindas

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'>
3
Somum
unicodestring = '\xa0'

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

Funciona para mim

1
HimalayanCoder