Convertir Unicode a ASCII sin errores en Python

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 bash mal formado en Unicode en algún lugar. ¿Puedo simplemente eliminar los bytes de código que están causando el problema en lugar de obtener un error?

Actualización 2018:

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

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

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

 import gzip import io 

Nota: en Python 2 StringIO lugar de io

Entonces puedes 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 módulo gzip luego lee el búfer usando la función GZipFile . Después de eso, el archivo gzip puede ser leído nuevamente en bytes y decodificado a un texto normalmente legible al final.

Respuesta original de 2010:

¿Podemos obtener el valor real utilizado para el link ?

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

 html = urllib.urlopen(link).read() unicode_str = html.decode() 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") 

Lo consigue sin error. Tenga en cuenta que “windows-1252” es algo que usé como ejemplo . ¡Conseguí esto de Chardet y tenía 0.5 confianza de que es correcto! (bueno, como se indica con una cadena de 1 carácter, ¿qué esperas?) Debes cambiar eso a la encoding de la cadena de bytes devuelta desde .urlopen().read() a lo que se aplica al contenido que recuperaste.

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

Como sugirió Ignacio, consulte la página web de origen para ver la encoding real de la cadena devuelta desde read() . Se encuentra en una de las tags Meta o en el encabezado de ContentType en la respuesta. Use eso entonces como el parámetro para .decode() .

Sin embargo, tenga en cuenta que no se debe asumir que otros desarrolladores son lo suficientemente responsables como para asegurarse de que el encabezado y / o las declaraciones del conjunto de caracteres meta coincidan con el contenido real. (Que es un PITA, sí, debería saberlo, yo fui uno de ellos antes).

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

EDITAR:

Decodifique la cadena que obtiene, usando el conjunto de caracteres en la meta apropiada en la respuesta o en el encabezado Content-Type , luego codifique.

El método encode() acepta otros valores como “ignorar”. Por ejemplo: ‘reemplazar’, ‘xmlcharrefreplace’, ‘backslashreplace’. Consulte https://docs.python.org/3/library/stdtypes.html#str.encode

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

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

A veces es conveniente 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' 

Es posible que también desee traducir otros caracteres (como la puntuación) a sus equivalentes más cercanos, por ejemplo, el carácter unicode de RING SINGLE QUOTATION MARK no se convierte en un APOSTROPHE ascii cuando se codifica.

 >>> 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. Consulte esta pregunta para obtener más detalles. ¿Dónde se encuentra el mejor ASCII de Python para este Unicode?

Use unidecode – incluso convierte caracteres extraños a ascii instantáneamente, e incluso convierte chino a ascii fonético.

 $ pip install unidecode 

entonces:

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

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

 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 de Unicode después de usar esto.

Para consolas rotas como cmd.exe y HTML, siempre puede usar:

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

Esto conservará todos los caracteres que no son ASCII y los imprimirá 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 algún error en su código . El único caso de uso válido para esto es imprimir en una consola que no sea Unicode o una conversión fácil a entidades HTML en un contexto HTML.

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

Usted escribió “” “Supongo que eso significa que el HTML contiene algún bash mal formado de unicode en algún lugar.” “”

No se espera que el HTML contenga ningún tipo de “bash de Unicode”, bien formado o no. Debe contener necesariamente caracteres Unicode codificados en alguna encoding, que generalmente se suministra por adelantado … busque “charset”.

Parece que estás asumiendo que el conjunto de caracteres es UTF-8 … ¿por qué motivo? El byte “\ xA0” que se muestra en su mensaje de error indica que puede tener un juego de caracteres de un solo byte, por ejemplo, cp1252.

Si no puede entender la statement al comienzo del código HTML, intente usar chardet para averiguar cuál es la encoding más probable.

¿Por qué has etiquetado tu pregunta con “expresiones regulares”?

Actualice después de que haya reemplazado 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 eg cp1252 or even cp850 instead) ;-) 

Si tiene una line cadena, puede usar el .encode([encoding], [errors='strict']) para que las cadenas conviertan los tipos de encoding.

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

Creo que la respuesta está ahí, pero solo en partes y piezas, 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 los caracteres ASCII.

Este código hará:

 import unicodedata fp = open() 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 type (rline) te dará

 >type(rline)  
 unicodestring = '\xa0' decoded_str = unicodestring.decode("windows-1252") encoded_str = decoded_str.encode('ascii', 'ignore') 

Funciona para mi

Parece que estás usando python 2.x. Python 2.x utiliza de forma predeterminada ascii y no conoce Unicode. De ahí la excepción.

Solo pega la línea de abajo después de shebang, funcionará

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