Convertir Unicode en ASCII sans erreurs en Python

180

Mon code gratte simplement une page Web, puis la convertit en Unicode.

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

Mais j'obtiens 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 HTML contient une tentative mal formée d'Unicode quelque part. Puis-je simplement supprimer les octets de code qui causent le problème au lieu d'obtenir une erreur?

le miroir
la source
2
Je considère que c'est une erreur si des caractères importants sont supprimés! (Aussi, où est la question?)
Arafangion
On dirait que vous avez peut-être rencontré un "espace sans pause" dans la page Web? devrait être précédé d'un c2octet ou vous obtiendrez probablement une erreur de décodage: hexutf8.com/?q=C2A0
jar

Réponses:

106

Mise à jour 2018:

En février 2018, l'utilisation de compressions comme celles-ci gzipest devenue très populaire (environ 73% de tous les sites Web l'utilisent, y compris les grands sites comme Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow et Stack Exchange Network).
Si vous effectuez un décodage simple comme dans la réponse d'origine avec une réponse gzippée, vous obtiendrez une erreur similaire ou similaire à celle-ci:

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

Afin de décoder une réponse gzpipped, vous devez ajouter les modules suivants (en Python 3):

import gzip
import io

Remarque: dans Python 2, vous utiliseriez à la StringIOplace deio

Ensuite, vous pouvez analyser le contenu comme ceci:

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 gzipmodule lit ensuite le tampon à l'aide de la GZipFilefonction. Après cela, le fichier gzippé peut à nouveau être lu en octets et décodé en texte normalement lisible à la fin.

Réponse originale de 2010:

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

De plus, nous rencontrons généralement ce problème ici lorsque nous essayons .encode()une chaîne d'octets déjà codé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éussit sans erreur. Notez que "windows-1252" est quelque chose que j'ai utilisé comme exemple . J'ai eu ceci de chardet et il avait 0,5 confiance que c'était juste! (enfin, comme indiqué avec une chaîne de 1 caractère, à quoi vous attendez-vous?) Vous devriez changer cela pour l'encodage de la chaîne d'octets renvoyée par .urlopen().read()ce qui s'applique au contenu que vous avez récupéré.

Un autre problème que je vois là-bas est que la .encode()méthode de chaîne 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)comme html n'est pas la chaîne encodée de html.encode (si c'est ce que vous visiez à l'origine).

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

Notez cependant qu'il ne faut pas supposer que les autres développeurs sont suffisamment responsables pour s'assurer que les déclarations d'en-tête et / ou de jeu de méta caractères correspondent au contenu réel. ( Ce qui est un PITA, ouais, je sais, je suis un de ceux avant).

Vin-G
la source
1
Dans votre exemple, je pense que vous vouliez que la dernière ligne soit encoded_str = decoded_str.encode("utf8")
Ajith Antony
1
J'ai essayé en Python 2.7.15 et j'ai reçu ce message raise IOError, 'Not a gzipped file'. Quelle est la faute que j'ai faite?
Hyun-geun Kim
224
>>> u'aあä'.encode('ascii', 'ignore')
'a'

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

La méthode encode(encoding, errors)accepte des gestionnaires personnalisés pour les erreurs. Les valeurs par défaut, en outre ignore, sont:

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

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

Ignacio Vazquez-Abrams
la source
121

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. Cela peut être accompli avec

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

Vous pouvez également traduire d'autres caractères (tels que la ponctuation) en leurs équivalents les plus proches, par exemple, le caractère unicode DROIT UNIQUE QUOTATION MARQUE 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 pour y parvenir. Voir cette question pour plus de détails. Où se trouve la "meilleure base de données ASCII de Python pour cette Unicode"?

Peter Gibson
la source
4
À la fois utiles pour répondre à la question posée et pratiques pour aborder les problèmes qui pourraient sous-tendre la question posée. C'est une réponse modèle pour ce genre de question.
shanusmagnus
99

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

$ pip install unidecode

puis:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
Nimo
la source
4
halle-freakin-lujah - il est temps que je trouve une réponse qui a fonctionné pour moi
Aurielle Perlmann
10
J'ai voté pour une valeur amusante. Notez que cela déforme les mots dans toutes les langues accentuées. Škoda n'est pas Skoda. Skoda signifie probablement quelque chose de dégoûtant avec les anguilles et les aéroglisseurs.
Sylvain
1
J'ai parcouru Internet pendant des jours jusqu'à maintenant .... merci, merci beaucoup
Stephen
23

J'utilise cette fonction d'aide 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 le 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 n'obtiens plus d'erreurs Unicode après avoir utilisé cela.

Gattster
la source
10
C'est SUPPRIMER le problème, pas diagnostiquer et réparer. C'est comme dire "Après avoir coupé mes pieds, je n'ai plus de problèmes avec les cors et les oignons".
John Machin
10
Je suis d'accord que cela supprime le problème. Il semble que ce soit la question après. Regardez sa note: "Puis-je simplement supprimer les octets de code qui causent le problème au lieu d'obtenir une erreur?"
Gattster
3
c'est exactement la même chose que d'appeler simplement "some-string" .encode ('ascii', 'ignore')
Joshua Burns
17
Je ne peux pas vous dire à quel point je suis fatigué que quelqu'un pose une question sur SO et obtienne toutes ces réponses prêches. "Ma voiture ne démarre pas." "Pourquoi veux-tu démarrer ta voiture? Tu devrais marcher à la place." Arrête ça!
shanusmagnus
8
@JohnMachin Personne ne s'en soucie. Je me fiche de la merde retardée que les gens mettent dans les flux RSS, si c'est un caractère qui n'est pas en ascii, il peut être tronqué. Leur problème. Je veux juste que python l'étrangle et le traite, ne me donne pas d'erreurs à chaque fois que je spécifie «ignorer». Qui diable a inventé cette merde?!
user1244215
10

Pour les consoles cassées comme la cmd.exesortie HTML, vous pouvez toujours utiliser:

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

Cela conservera tous les caractères non-ascii tout en les rendant imprimables en ASCII pur et en HTML.

AVERTISSEMENT : si vous utilisez ceci dans le code de production pour éviter les erreurs, il y a probablement quelque chose qui ne va pas dans votre code . Le seul cas d'utilisation valable pour cela est l'impression sur une console non-Unicode ou la conversion facile en entités HTML dans un contexte HTML.

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

ccpizza
la source
6

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

Le HTML ne doit PAS contenir de "tentative d'Unicode", bien formée ou non. Il doit nécessairement contenir des caractères Unicode encodés dans un encodage, qui est généralement fourni à l'avance ... cherchez "charset".

Vous semblez supposer que le jeu de caractères est UTF-8 ... pour quelles raisons? L'octet "\ xA0" qui apparaît 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 HTML, essayez d'utiliser chardet pour découvrir quel est le codage 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) ;-)
John Machin
la source
4

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

line = 'my big string'

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

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

Jama22
la source
1
Cela ne fonctionne pas lorsque vous avez un caractère non ascii comme ü dans la chaîne.
sajid le
4

Je pense que la réponse est là, mais seulement par 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 contenant des données sous la forme suivante (contenant des caractères ascii et non-ascii)

1/10/17, 21:36 - Terre: Bienvenue ��

et nous voulons ignorer et conserver uniquement 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'>
Somum
la source
Cela fonctionne également pour les cas (non standardisés) "ascii étendu"
Oliver Zendel
1
unicodestring = '\xa0'

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

Travaille pour moi

HimalayanCoder
la source
-5

On dirait que vous utilisez python 2.x. Python 2.x utilise par défaut ascii et il ne connaît pas Unicode. D'où l'exception.

Collez simplement la ligne ci-dessous après shebang, cela fonctionnera

# -*- coding: utf-8 -*-
Haroon Rashedu
la source
Le codingcommentaire n'est pas une panacée magique. Vous devez savoir pourquoi l'erreur est générée, cela ne corrige les choses que lorsqu'il y a de mauvais caractères dans votre source Python. Cela ne semble pas être le cas pour cette question.
Mark Ransom