web-dev-qa-db-fra.com

Convertir Unicode en ASCII sans erreurs dans Python

Mon code supprime simplement une page Web, puis le convertit en Unicode.

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

Mais je reçois 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)

Je suppose que cela signifie que le code HTML contient une tentative mal faite d’Unicode quelque part. Puis-je simplement supprimer les octets de code qui causent le problème au lieu d’obtenir une erreur?

167
themirror

Mise à jour 2018:

Depuis février 2018, l'utilisation de compressions telles que gzip est devenue très populaire (environ 73% des sites Web l'utilisent, y compris des sites volumineux tels que Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow et les sites du réseau d'échange de piles).
Si vous effectuez un décodage simple comme dans la réponse originale avec une réponse compressée, vous obtiendrez une erreur similaire ou similaire à ceci:

UnicodeDecodeError: le codec 'utf8' ne peut pas décoder l'octet 0x8b en position 1: octet de code inattendu

Pour décoder une réponse gzpipped, vous devez ajouter les modules suivants (dans Python 3):

import gzip
import io

Remarque: Dans Python 2, vous utiliseriez StringIO au lieu de io

Ensuite, vous pouvez analyser le contenu comme suit:

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

Ce code lit la réponse et place les octets dans un tampon. Le module gzip lit ensuite le tampon à l'aide de la fonction GZipFile. Après cela, le fichier gzippé peut être lu à nouveau en octets et décodé en texte lisible normalement.

Réponse originale de 2010:

Pouvons-nous obtenir la valeur réelle utilisée pour link?

De plus, nous rencontrons généralement ce problème ici lorsque nous essayons de .encode() une chaîne d'octets déjà encodée. Vous pouvez donc essayer de le décoder d’abord comme dans

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

Par exemple:

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

Échoue avec

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

Tandis que:

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

Réussite sans erreur. Notez que "windows-1252" est quelque chose que j'ai utilisé comme exemple . Je l'ai eu de chardet et il avait une confiance de 0.5 que c'est juste! (et bien, comme prévu avec une chaîne de 1 caractère), vous devriez changer cela pour l'encodage de la chaîne d'octets renvoyée de .urlopen().read() par ce qui s'applique au contenu que vous avez récupéré.

Un autre problème que je vois ici est que la méthode de chaîne .encode() renvoie la chaîne modifiée et ne modifie pas la source en place. Il est donc inutile d’avoir self.response.out.write(html), car html n’est pas la chaîne encodée à partir de html.encode (si c’est ce que vous visiez à l’origine).

Comme Ignacio l’a suggéré, vérifiez sur la page Web source le codage réel de la chaîne renvoyée par read(). Il se trouve soit dans l'une des balises Meta, soit dans l'en-tête ContentType de la réponse. Utilisez-le comme paramètre pour .decode().

Notez toutefois qu'il ne faut pas présumer que les autres développeurs sont suffisamment responsables pour s'assurer que les déclarations d'en-tête et/ou de méta caractères correspondent au contenu réel. (Ce qui est un PITA, oui, je devrais savoir, je était un de ceux avant).

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

EDIT:

Décodez la chaîne que vous récupérez, en utilisant le jeu de caractères de la balise appropriée meta de la réponse ou dans l'en-tête Content-Type, puis codez.

La méthode encode() accepte les autres valeurs comme "ignorer". Par exemple: 'replace', 'xmlcharrefreplace', 'backslashreplace'. Voir https://docs.python.org/3/library/stdtypes.html#str.encode

202

En prolongement de la réponse d'Ignacio Vazquez-Abrams

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

Il est parfois souhaitable de supprimer les accents des caractères et d’imprimer le formulaire de base. Ceci peut être accompli avec

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

Vous pouvez également vouloir traduire d'autres caractères (tels que la ponctuation) en leurs équivalents les plus proches, par exemple, le caractère unicode DROIT SINGLE QUOTATION MARK n'est pas converti en APOSTROPHE ascii lors de l'encodage.

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

Bien qu'il existe des moyens plus efficaces d'y parvenir. Voir cette question pour plus de détails Où est la "meilleure ASCII de cette base de données Unicode" de Python?

114
Peter Gibson

Utilisez nidecode - il convertit même des caractères étranges en ascii instantanément, et convertit même le chinois en ascii phonétique.

$ pip install unidecode

ensuite:

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

J'utilise cette fonction d'assistance dans tous mes projets. S'il ne peut pas convertir l'unicode, il l'ignore. Ceci est lié à une bibliothèque Django, mais avec un peu de recherche, vous pouvez la contourner.

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

Je ne reçois plus aucune erreur unicode après avoir utilisé cela.

23
Gattster

Pour les consoles brisées comme cmd.exe et la sortie HTML, vous pouvez toujours utiliser:

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

Cela préservera tous les caractères non-ascii tout en les rendant imprimables en pure ASCII et en HTML.

WARNING: Si vous utilisez ceci dans le code de production pour éviter les erreurs, il est fort probable qu'il y ait un problème dans votre code . Le seul cas d’utilisation valide est l’impression sur une console non-unicode ou la conversion facile en entités HTML dans un contexte HTML.

Enfin, si vous utilisez Windows et que vous utilisez cmd.exe, vous pouvez taper chcp 65001 pour activer la sortie utf-8 (fonctionne avec la police Lucida Console). Vous devrez peut-être ajouter myUnicodeString.encode('utf8').

10
ccpizza

Vous avez écrit "" "Je suppose que cela signifie que le code HTML contient une tentative mal faite d’unicode quelque part." "

Le code HTML ne devrait contenir aucun type de "tentative d'unicode", correctement formé ou non. Il doit obligatoirement contenir des caractères Unicode encodés dans un encodage, ce qui est généralement fourni à l’avance ... recherchez "charset".

Vous semblez supposer que le jeu de caractères est UTF-8 ... pour quels motifs? L'octet "\ xA0" indiqué dans votre message d'erreur indique que vous pouvez avoir un jeu de caractères à un octet, par exemple. cp1252.

Si vous ne parvenez pas à comprendre la déclaration au début du code HTML, essayez d’utiliser chardet pour déterminer l’encodage probable.

Pourquoi avez-vous tagué votre question avec "regex"?

Mettez à jour après avoir remplacé toute votre question par une non-question:

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

Si vous avez une chaîne line, vous pouvez utiliser la méthode .encode([encoding], [errors='strict']) pour que les chaînes convertissent les types de codage.

line = 'my big string'

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

Pour plus d'informations sur la gestion de ASCII et de l'unicode en Python, ce site est très utile: https://docs.python.org/2/howto/unicode.html

4
Jama22

Je pense que la réponse est là, mais seulement en morceaux, ce qui rend difficile de résoudre rapidement le problème tel que

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

Prenons un exemple, supposons que j’ai un fichier qui contient des données sous la forme suivante (contenant des caractères ascii et non ascii)

1/10/17, 21:36 - Land: Bienvenue

et nous voulons ignorer et ne conserver que les caractères ascii.

Ce code fera:

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

et tapez (rline) vous donnera

>type(rline) 
<type 'str'>
4
Somum
unicodestring = '\xa0'

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

Travaille pour moi

1
HimalayanCoder