Converti Unicode in ASCII senza errori in Python


178

Il mio codice semplicemente raschia una pagina Web, quindi la converte in Unicode.

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

Ma ottengo 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)

Presumo che ciò significhi che l'HTML contiene qualche tentativo errato di Unicode da qualche parte. Posso semplicemente eliminare qualsiasi byte di codice che causa il problema invece di ottenere un errore?


2
Lo considero un errore se i personaggi importanti vengono scartati! (Inoltre, dov'è la domanda?)
Arafangion

Sembra che potresti aver riscontrato uno "spazio di non interruzione" nella pagina Web? dovrebbe essere preceduto da un c2byte o si otterrebbe probabilmente un errore di decodifica: hexutf8.com/?q=C2A0
jar

Risposte:


105

Aggiornamento 2018:

A partire da febbraio 2018, l'utilizzo di compressioni come gzipè diventato abbastanza popolare (circa il 73% di tutti i siti Web lo utilizza, inclusi siti di grandi dimensioni come Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow e Stack Exchange Network).
Se esegui una decodifica semplice come nella risposta originale con una risposta compressa, otterrai un errore simile o simile a questo:

UnicodeDecodeError: il codec 'utf8' non può decodificare byte 0x8b in posizione 1: byte di codice imprevisto

Per decodificare una risposta gzpipped è necessario aggiungere i seguenti moduli (in Python 3):

import gzip
import io

Nota: in Python 2 useresti StringIOinvece diio

Quindi puoi analizzare il contenuto in questo modo:

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

Questo codice legge la risposta e inserisce i byte in un buffer. Il gzipmodulo legge quindi il buffer utilizzando la GZipFilefunzione. Successivamente, il file gzipped può essere nuovamente letto in byte e decodificato alla fine in testo normalmente leggibile.

Risposta originale del 2010:

Possiamo ottenere il valore effettivo utilizzato per link?

Inoltre, di solito incontriamo questo problema qui quando proviamo a .encode()una stringa di byte già codificata. Quindi potresti provare a decodificarlo prima come in

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

Come esempio:

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

Non riesce con

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

Mentre:

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

Succede senza errori. Nota che "windows-1252" è qualcosa che ho usato come esempio . Ho preso questo da chardet e aveva la sicurezza di avere ragione! (bene, come indicato con una stringa di 1 carattere, cosa ti aspetti) Dovresti cambiarlo con la codifica della stringa di byte restituita da .urlopen().read()ciò che si applica al contenuto che hai recuperato.

Un altro problema che vedo è che il .encode()metodo string restituisce la stringa modificata e non modifica l'origine in atto. Quindi è un po 'inutile avere self.response.out.write(html)come html non è la stringa codificata da html.encode (se è quello che stavi mirando originariamente).

Come suggerito da Ignacio, controlla la pagina web di origine per la codifica effettiva della stringa restituita da read(). È in uno dei meta tag o nell'intestazione ContentType nella risposta. Usalo quindi come parametro per .decode().

Si noti tuttavia che non si deve presumere che altri sviluppatori siano abbastanza responsabili da assicurarsi che l'intestazione e / o le dichiarazioni del set di caratteri meta corrispondano al contenuto effettivo. (Che è una valle di lacrime, sì, dovrei so, io ero uno di quelli prima).


1
Nel tuo esempio penso che volevi dire che l'ultima riga fosse encoded_str = decoded_str.encode("utf8")
Ajith Antony,

1
Ho provato in Python 2.7.15 e ho ricevuto questo messaggio raise IOError, 'Not a gzipped file'. Qual è la colpa che ho fatto?
Hyun-geun Kim,

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

Decodifica la stringa restituita, utilizzando il set di caratteri nel metatag appropriato nella risposta o Content-Typenell'intestazione, quindi codifica.

Il metodo encode(encoding, errors)accetta gestori personalizzati per errori. I valori predefiniti, inoltre ignore, sono:

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

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


119

Come estensione della risposta di Ignacio Vazquez-Abrams

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

A volte è desiderabile rimuovere gli accenti dai caratteri e stampare il modulo di base. Questo può essere realizzato con

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

Potresti anche voler tradurre altri caratteri (come la punteggiatura) nei loro equivalenti più vicini, ad esempio il carattere unicode DESTRA SINGOLO PREVENTIVO non viene convertito in un APOSTROPHE ascii durante la 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')
"'"

Sebbene ci siano modi più efficienti per raggiungere questo obiettivo. Vedi questa domanda per maggiori dettagli Dov'è il database "ASCII migliore per questo Unicode" di Python?


4
Sia utile nell'affrontare la domanda posta, sia pratico per affrontare i problemi che potrebbero essere alla base della domanda posta. Questa è una risposta modello per questo tipo di domanda.
shanusmagnus,

96

Usa unidecode : converte persino i caratteri strani in ascii all'istante e converte persino i cinesi in ascii fonetici.

$ pip install unidecode

poi:

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

3
halle-freakin-lujah - è giunto il momento che ho trovato una risposta che ha funzionato per me
Aurielle Perlmann

10
Eseguito l'upgrade per un valore divertente. Si noti che questo manipola le parole in tutte le lingue accentuate. Škoda non è Skoda. Skoda molto probabilmente significa qualcosa di disgustoso con anguille e hovercraft.
Sylvain,

1
Ho cercato su internet per giorni fino ad ora .... grazie, grazie mille
Stephen,

23

Uso questa funzione di supporto in tutti i miei progetti. Se non è in grado di convertire l'unicode, lo ignora. Questo si lega a una libreria di django, ma con un po 'di ricerca potresti aggirarlo.

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

Non utilizzo più errori Unicode dopo aver usato questo.


10
Questo è SOPPRESSIONE del problema, non diagnosi e correzione. È come dire "Dopo che mi sono tagliato i piedi, non ho più problemi con semi e semi".
John Machin

10
Sono d'accordo che sta sopprimendo il problema. Sembra che sia quello che la domanda è dopo però. Guarda la sua nota: "Posso semplicemente eliminare qualsiasi byte di codice che causa il problema invece di ottenere un errore?"
Gattster

3
è esattamente lo stesso che chiamare semplicemente "some-string" .encode ('ascii', 'ignore')
Joshua Burns,

17
Non posso dirti quanto sono stanco di qualcuno che faccia una domanda su SO e ottenga tutte queste risposte predicate. "La mia macchina non parte." "Perché vuoi avviare la tua macchina? Dovresti invece camminare." Smettila!
shanusmagnus,

8
@JohnMachin A nessuno importa. Non mi interessa quale schifezza ritardata metta nei feed RSS, se è un personaggio che non è in ASCII può essere troncato. Il loro problema Voglio solo che Python lo soffra e lo gestisca, non mi dia errori ogni volta che specifico "ignora". Chi diavolo è venuto fuori con quella merda ?!
user1244215

10

Per console rotte come cmd.exee output HTML puoi sempre usare:

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

Ciò conserverà tutti i caratteri non ascii rendendoli stampabili in ASCII puro e in HTML.

ATTENZIONE : se lo usi nel codice di produzione per evitare errori, molto probabilmente c'è qualcosa di sbagliato nel tuo codice . L'unico caso d'uso valido per questo è la stampa su una console non unicode o una facile conversione in entità HTML in un contesto HTML.

E infine, se sei su Windows e usi cmd.exe, puoi digitare chcp 65001per abilitare l'output utf-8 (funziona con il font Lucida Console). Potrebbe essere necessario aggiungere myUnicodeString.encode('utf8').


6

Hai scritto "" "Suppongo che ciò significhi che l'HTML contiene qualche tentativo errato di unicode da qualche parte." ""

L'HTML NON dovrebbe contenere alcun tipo di "tentativo di unicode", ben formato o meno. Deve necessariamente contenere caratteri Unicode codificati in qualche codifica, che di solito viene fornita in primo piano ... cercare "charset".

Sembra che tu stia supponendo che il set di caratteri sia UTF-8 ... per quali motivi? Il byte "\ xA0" visualizzato nel messaggio di errore indica che è possibile disporre di un set di caratteri a byte singolo, ad esempio cp1252.

Se non riesci a ottenere alcun senso dalla dichiarazione all'inizio dell'HTML, prova a utilizzare chardet per scoprire qual è la probabile codifica.

Perché hai taggato la tua domanda con "regex"?

Aggiorna dopo aver sostituito l'intera domanda con una non-domanda:

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 si dispone di una stringa line, è possibile utilizzare il .encode([encoding], [errors='strict'])metodo per le stringhe per convertire i tipi di codifica.

line = 'my big string'

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

Per ulteriori informazioni sulla gestione di ASCII e unicode in Python, questo è un sito davvero utile: https://docs.python.org/2/howto/unicode.html


1
Questo non funziona quando nella stringa è presente un carattere non ascii come ü.
Sajid

4

Penso che la risposta sia lì, ma solo a pezzi, il che rende difficile risolvere rapidamente il problema come

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

Facciamo un esempio, supponiamo di avere un file che ha alcuni dati nel seguente formato (contenente caratteri ASCII e non ASCII)

10/01/17, 21:36 - Terra: benvenuto ��

e vogliamo ignorare e preservare solo i caratteri ASCII.

Questo codice 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 digitare (rline) ti darà

>type(rline) 
<type 'str'>

Questo funziona anche per i casi "non standardizzati" di ascii estesa
Oliver Zendel,

1
unicodestring = '\xa0'

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

Per me va bene


-5

Sembra che tu stia usando Python 2.x. Python 2.x è impostato su ASCII e non è a conoscenza di Unicode. Da qui l'eccezione.

Basta incollare la riga sotto dopo shebang, funzionerà

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

Il codingcommento non è una cura magica. Devi sapere perché viene generato l'errore, questo risolve le cose solo quando ci sono caratteri cattivi nel tuo sorgente Python. Non sembra essere il caso di questa domanda.
Mark Ransom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.