Convierta Unicode a ASCII sin errores en Python

178

Mi código simplemente raspa una página web, luego la convierte a Unicode.

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

Pero me sale un 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)

Supongo que eso significa que el HTML contiene algún intento mal formado en Unicode en alguna parte. ¿Puedo descartar los bytes de código que causan el problema en lugar de obtener un error?

el espejo
fuente
2
¡Considero que es un error si se descartan caracteres importantes! (Además, ¿dónde está la pregunta?)
Arafangion
Parece que puede haber encontrado un "espacio libre" en la página web. tendría que ir precedido de un c2byte o probablemente obtendría un error de decodificación: hexutf8.com/?q=C2A0
jar

Respuestas:

105

Actualización 2018:

A partir de febrero de 2018, el uso de compresiones se gzipha vuelto bastante popular (alrededor del 73% de todos los sitios web lo usan, incluidos sitios grandes como Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow y Stack Exchange Network).
Si realiza una decodificación simple como en la respuesta original con una respuesta comprimida, obtendrá un error similar o similar a este:

UnicodeDecodeError: el códec 'utf8' no puede decodificar el byte 0x8b en la posición 1: byte de código inesperado

Para decodificar una respuesta gzpipped, debe agregar los siguientes módulos (en Python 3):

import gzip
import io

Nota: En Python 2 usarías en StringIOlugar deio

Luego puede analizar el contenido de esta manera:

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 lee la respuesta y coloca los bytes en un búfer. El gzipmódulo luego lee el búfer utilizando la GZipFilefunción. Después de eso, el archivo comprimido puede leerse nuevamente en bytes y descodificarse al texto normalmente legible al final.

Respuesta original de 2010:

¿Podemos obtener el valor real utilizado link?

Además, generalmente encontramos este problema aquí cuando intentamos .encode()una cadena de bytes ya codificada. Entonces, puede intentar decodificarlo primero como en

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

Como ejemplo:

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

Falla con

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

Mientras:

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

Tiene éxito sin error. Tenga en cuenta que "windows-1252" es algo que utilicé como ejemplo . ¡ Obtuve esto de chardet y tenía 0.5 confianza de que es correcto! (bueno, como se da con una cadena de 1 carácter, qué espera) Debe cambiar eso a la codificación de la cadena de bytes devuelta de .urlopen().read()lo que se aplica al contenido que recuperó.

Otro problema que veo allí es que el .encode()método de cadena devuelve la cadena modificada y no modifica la fuente en su lugar. Por lo tanto, es inútil tenerlo, self.response.out.write(html)ya que html no es la cadena codificada de html.encode (si eso es lo que originalmente buscabas).

Como sugirió Ignacio, revise la página web de origen para la codificación real de la cadena devuelta read(). Está en una de las etiquetas Meta o en el encabezado ContentType en la respuesta. Use eso entonces como parámetro para .decode().

Sin embargo, tenga en cuenta que no debe suponerse que otros desarrolladores son lo suficientemente responsables como para asegurarse de que las declaraciones del encabezado y / o del conjunto de caracteres meta coincidan con el contenido real. (¿Qué es un PITA, sí, debería saber, que era uno de los de antes).

Vin-G
fuente
1
En su ejemplo, creo que se refería a la última línea encoded_str = decoded_str.encode("utf8")
Ajith Antony
1
Lo intenté en Python 2.7.15 y recibí este mensaje raise IOError, 'Not a gzipped file'. ¿Cuál es la culpa que hice?
Hyun-geun Kim
222
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Decodifica la cadena que obtienes, usando el juego de caracteres en la metaetiqueta apropiada en la respuesta o en el Content-Typeencabezado, luego codifica.

El método encode(encoding, errors)acepta manejadores personalizados para errores. Los valores predeterminados, además ignore, son:

>>> 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'

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

Ignacio Vazquez-Abrams
fuente
119

Como una extensión a la respuesta de Ignacio Vázquez-Abrams

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

A veces es deseable eliminar los acentos de los caracteres e imprimir el formulario base. Esto se puede lograr con

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

También es posible que desee traducir otros caracteres (como la puntuación) a sus equivalentes más cercanos, por ejemplo, el carácter unicode MARCA DE COTIZACIÓN SENCILLA DERECHA no se convierte en un APÓSTROFO ASCII al codificar.

>>> 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')
"'"

Aunque hay formas más eficientes de lograr esto. Vea esta pregunta para más detalles ¿Dónde está la base de datos "mejor ASCII para este Unicode" de Python?

Peter Gibson
fuente
44
Tanto útiles para abordar la pregunta que se hizo, como prácticos para abordar los problemas que podrían estar subyacentes a la pregunta formulada. Esta es una respuesta modelo para este tipo de preguntas.
shanusmagnus
96

Use unidecode : incluso convierte caracteres extraños en ascii al instante, e incluso convierte chino en ascii fonético.

$ pip install unidecode

luego:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
Nimo
fuente
3
halle-freakin-lujah - ya es hora de que encuentre una respuesta que funcionó para mí
Aurielle Perlmann
10
Votado por valor divertido. Tenga en cuenta que esto altera las palabras en todos los idiomas acentuados. Škoda no es Skoda. Skoda probablemente significa algo asqueroso con anguilas y aerodeslizadores.
Sylvain
1
He estado recorriendo Internet durante días hasta ahora ... gracias, muchas gracias
Stephen
23

Utilizo esta función auxiliar en todos mis proyectos. Si no puede convertir el Unicode, lo ignora. Esto se vincula con una biblioteca de django, pero con un poco de investigación podría evitarlo.

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

Ya no recibo ningún error Unicode después de usar esto.

Gattster
fuente
10
Eso es SUPRIMIR el problema, no diagnosticar y solucionar. Es como decir "Después de cortarme los pies, ya no tengo problemas con callos y juanetes".
John Machin
10
Estoy de acuerdo en que está suprimiendo el problema. Parece que eso es lo que busca la pregunta. Mire su nota: "¿Puedo soltar los bytes de código que causan el problema en lugar de obtener un error?"
Gattster
3
esto es exactamente lo mismo que simplemente llamar "alguna cadena" .encode ('ascii', 'ignorar')
Joshua Burns
17
No puedo decirte cuán cansado estoy de que alguien haga una pregunta sobre SO y obtenga todas estas respuestas de predicación. "Mi auto no arranca". "¿Por qué quieres encender tu auto? Deberías caminar en su lugar". ¡Para!
shanusmagnus
8
@JohnMachin A nadie le importa. No me importa qué basura retardada ponga la gente en los canales RSS, si se trata de un personaje que no está en ASCII, se puede truncar. Su problema Solo quiero que Python lo ahogue y lo maneje, no me dé errores cada vez que especifique 'ignorar'. ¿A quién demonios se le ocurrió esa mierda?
user1244215
10

Para consolas rotas como cmd.exey salida HTML siempre puede usar:

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

Esto preservará todos los caracteres no ascii mientras los hace imprimibles en ASCII puro y en HTML.

ADVERTENCIA : Si usa esto en el código de producción para evitar errores, lo más probable es que haya algo mal en su código . El único caso de uso válido para esto es imprimir en una consola no Unicode o una conversión fácil a entidades HTML en un contexto HTML.

Y, por último, si está en Windows y utiliza cmd.exe, puede escribir chcp 65001para habilitar la salida utf-8 (funciona con la fuente de la consola Lucida). Es posible que deba agregar myUnicodeString.encode('utf8').

ccpizza
fuente
6

Escribiste "" "Supongo que eso significa que el HTML contiene algún intento mal formado de unicode en alguna parte.

NO se espera que el HTML contenga ningún tipo de "intento de unicode", bien formado o no. Debe contener necesariamente caracteres Unicode codificados en alguna codificación, que generalmente se proporciona por adelantado ... busque "charset".

Parece estar asumiendo que el juego de caracteres es UTF-8 ... ¿por qué motivos? El byte "\ xA0" que se muestra en su mensaje de error indica que puede tener un conjunto de caracteres de un solo byte, por ejemplo, cp1252.

Si no puede entender la declaración al comienzo del HTML, intente usar chardet para averiguar cuál es la codificación probable.

¿Por qué has etiquetado tu pregunta con "regex"?

Actualice después de reemplazar toda su pregunta con una no pregunta:

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
fuente
4

Si tiene una cadena line, puede usar el .encode([encoding], [errors='strict'])método para cadenas para convertir tipos de codificación.

line = 'my big string'

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

Para obtener más información sobre el manejo de ASCII y Unicode en Python, este es un sitio realmente útil: https://docs.python.org/2/howto/unicode.html

Jama22
fuente
1
Esto no funciona cuando tiene un carácter no ascii como ü en la cadena.
Sajid
4

Creo que la respuesta está ahí, pero solo en partes, lo que dificulta la solución rápida del problema, como

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

Tomemos un ejemplo, supongamos que tengo un archivo que tiene algunos datos en la siguiente forma (que contiene caracteres ascii y no ascii)

1/10/17, 21:36 - Tierra: Bienvenido ��

y queremos ignorar y preservar solo caracteres ascii.

Este código hará:

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

y escriba (rline) le dará

>type(rline) 
<type 'str'>
Somum
fuente
Esto también funciona para los casos (no estandarizados) de "ascii extendido"
Oliver Zendel
1
unicodestring = '\xa0'

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

Funciona para mi

HimalayaCoder
fuente
-5

Parece que estás usando python 2.x. Python 2.x tiene como valor predeterminado ascii y no sabe acerca de Unicode. De ahí la excepción.

Simplemente pegue la siguiente línea después de shebang, funcionará

# -*- coding: utf-8 -*-
Haroon Rashedu
fuente
El codingcomentario no es una cura mágica. Necesita saber por qué se genera el error, esto solo soluciona las cosas cuando hay caracteres malos en su fuente de Python. Ese no parece ser el caso para esta pregunta.
Mark Ransom el