Konvertieren Sie Unicode ohne Fehler in Python in ASCII

177

Mein Code kratzt nur eine Webseite und konvertiert sie dann in Unicode.

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

Aber ich bekomme ein 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)

Ich gehe davon aus, dass der HTML-Code irgendwo einen falsch geformten Unicode-Versuch enthält. Kann ich einfach die Codebytes löschen, die das Problem verursachen, anstatt einen Fehler zu erhalten?

der Spiegel
quelle
2
Ich halte es für einen Fehler, wenn wichtige Zeichen verworfen werden! (Auch wo ist die Frage?)
Arafangion
Scheint, als hätten Sie auf der Webseite einen "No Break Space" gefunden? müsste ein c2Byte vorangestellt werden, sonst würde wahrscheinlich ein Dekodierungsfehler auftreten: hexutf8.com/?q=C2A0
jar

Antworten:

105

Update 2018:

Ab Februar 2018 mit Kompressionen wie gziphat sich zu sehr beliebt (rund 73% aller Websites nutzen es, darunter große Websites wie Google, YouTube, Yahoo, Wikipedia, Reddit, Stack - Überlauf und Stapel Exchange Network Sites).
Wenn Sie eine einfache Dekodierung wie in der ursprünglichen Antwort mit einer gezippten Antwort durchführen, wird ein Fehler wie der folgende angezeigt:

UnicodeDecodeError: Der Codec 'utf8' kann das Byte 0x8b an Position 1 nicht dekodieren: Unerwartetes Codebyte

Um eine gzpipped-Antwort zu dekodieren, müssen Sie die folgenden Module hinzufügen (in Python 3):

import gzip
import io

Hinweis: In Python 2 würden Sie StringIOanstelle von verwendenio

Dann können Sie den Inhalt folgendermaßen analysieren:

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

Dieser Code liest die Antwort und legt die Bytes in einem Puffer ab. Das gzipModul liest dann den Puffer mit der GZipFileFunktion. Danach kann die komprimierte Datei wieder in Bytes eingelesen und am Ende in normal lesbaren Text dekodiert werden.

Ursprüngliche Antwort von 2010:

Können wir den tatsächlichen Wert erhalten, für den verwendet wird? link ?

Außerdem tritt dieses Problem normalerweise hier auf, wenn wir versuchen, .encode()eine bereits codierte Bytezeichenfolge zu verwenden. Sie könnten also versuchen, es zuerst wie in zu dekodieren

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

Als Beispiel:

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

Schlägt mit

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

Während:

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

Gelingt ohne Fehler. Beachten Sie, dass "Windows-1252" etwas ist, das ich als Beispiel verwendet habe . Ich habe das von Chardet bekommen und es hatte 0,5 Vertrauen, dass es richtig ist! (Nun, wie bei einer Zeichenfolge mit einer Länge von 1 Zeichen angegeben, was erwarten Sie?) Sie sollten dies in die Codierung der zurückgegebenen Byte-Zeichenfolge ändern.urlopen().read() Ihnen abgerufenen Inhalt gilt.

Ein weiteres Problem, das ich dort sehe, ist, dass die .encode()Zeichenfolgenmethode die geänderte Zeichenfolge zurückgibt und die Quelle nicht an Ort und Stelle ändert. Es ist also nutzlos, self.response.out.write(html)wenn HTML nicht die codierte Zeichenfolge aus html.encode ist (wenn Sie dies ursprünglich angestrebt haben).

Überprüfen Sie, wie von Ignacio vorgeschlagen, die Quellwebseite auf die tatsächliche Codierung der zurückgegebenen Zeichenfolge von read(). Es befindet sich entweder in einem der Meta-Tags oder im ContentType-Header in der Antwort. Verwenden Sie das dann als Parameter für .decode().

Beachten Sie jedoch, dass nicht davon ausgegangen werden sollte, dass andere Entwickler verantwortlich genug sind, um sicherzustellen, dass die Deklarationen für Header und / oder Metazeichensätze mit dem tatsächlichen Inhalt übereinstimmen. (Was eine PITA ist, ja, ich sollte wissen, ich war einer von denen vorher).

Vin-G
quelle
1
In Ihrem Beispiel, denke ich, wollten Sie die letzte Zeile sein encoded_str = decoded_str.encode("utf8")
Ajith Antony
1
Ich habe es in Python 2.7.15 versucht und diese Nachricht erhalten raise IOError, 'Not a gzipped file'. Was ist der Fehler, den ich gemacht habe?
Hyun-geun Kim
221
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Dekodieren Sie die Zeichenfolge, die Sie zurückerhalten, entweder mit dem Zeichensatz im entsprechenden metaTag in der Antwort oder im Content-TypeHeader und codieren Sie sie dann.

Die Methode encode(encoding, errors)akzeptiert benutzerdefinierte Handler für Fehler. Die Standardwerte ignoresind außerdem:

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

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

Ignacio Vazquez-Abrams
quelle
119

Als Erweiterung der Antwort von Ignacio Vazquez-Abrams

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

Manchmal ist es wünschenswert, Akzente aus Zeichen zu entfernen und das Basisformular zu drucken. Dies kann mit erreicht werden

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

Möglicherweise möchten Sie auch andere Zeichen (z. B. Interpunktion) in die nächstgelegenen Entsprechungen übersetzen. Beispielsweise wird das Unicode-Zeichen RIGHT SINGLE QUOTATION MARK beim Codieren nicht in ein ASCII-APOSTROPHE konvertiert.

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

Es gibt zwar effizientere Möglichkeiten, dies zu erreichen. Weitere Informationen finden Sie in dieser Frage. Wo befindet sich Pythons "beste ASCII-Datenbank für diesen Unicode"?

Peter Gibson
quelle
4
Sowohl hilfreich bei der Beantwortung der gestellten Frage als auch praktisch bei der Beantwortung von Problemen, die der gestellten Frage zugrunde liegen könnten. Dies ist eine vorbildliche Antwort auf diese Art von Frage.
Shanusmagnus
96

Verwenden Sie Unidecode - es konvertiert sogar seltsame Zeichen sofort in ASCII und konvertiert sogar Chinesisch in phonetische ASCII.

$ pip install unidecode

dann:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
Nimo
quelle
3
halle-freakin-lujah - es ist an der Zeit, eine Antwort zu finden, die für mich
funktioniert
10
Upvoted für Spaßwert. Beachten Sie, dass dies Wörter in allen akzentuierten Sprachen entstellt. Škoda ist kein Skoda. Skoda bedeutet höchstwahrscheinlich etwas Grobes mit Aalen und Luftkissenfahrzeugen.
Sylvain
1
Ich habe bis jetzt tagelang das Internet
Stephen
23

Ich benutze diese Hilfsfunktion in allen meinen Projekten. Wenn der Unicode nicht konvertiert werden kann, wird er ignoriert. Dies knüpft an eine Django-Bibliothek an, aber mit ein wenig Recherche könnte man sie umgehen.

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

Ich erhalte nach dieser Verwendung keine Unicode-Fehler mehr.

Gattster
quelle
10
Das heißt, das Problem zu unterdrücken, nicht zu diagnostizieren und zu beheben. Es ist wie zu sagen "Nachdem ich meine Füße abgeschnitten habe, habe ich keine Probleme mehr mit Hühneraugen und Ballen".
John Machin
10
Ich bin damit einverstanden, dass es das Problem unterdrückt. Es scheint jedoch so, als ob die Frage danach ist. Schauen Sie sich seine Notiz an: "Kann ich einfach die Codebytes löschen, die das Problem verursachen, anstatt einen Fehler zu erhalten?"
Gattster
3
Dies ist genau das gleiche wie das einfache Aufrufen von "some-string" .encode ('ascii', 'ignore')
Joshua Burns
17
Ich kann Ihnen nicht sagen, wie müde ich von jemandem bin, der eine Frage zu SO stellt und all diese predigenden Antworten erhält. "Mein Auto startet nicht." "Warum willst du dein Auto starten? Du solltest stattdessen gehen." Hör auf!
Shanusmagnus
8
@ JohnMachin Niemand kümmert sich darum. Es ist mir egal, welchen verzögerten Mist Leute in RSS-Feeds einfügen, wenn es sich um ein Zeichen handelt, das nicht in ASCII enthalten ist, kann es abgeschnitten werden. Ihr Problem. Ich möchte nur, dass Python es tatsächlich erstickt und damit umgeht, und gebe mir nicht jedes Mal Fehler, wenn ich "Ignorieren" spezifiziere. Wer zum Teufel hat sich diese Scheiße ausgedacht?!
user1244215
10

Für defekte Konsolen wie cmd.exeund HTML-Ausgabe können Sie immer verwenden:

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

Dadurch bleiben alle Nicht-ASCII-Zeichen erhalten, während sie in reinem ASCII und HTML gedruckt werden können.

WARNUNG : Wenn Sie dies im Produktionscode verwenden, um Fehler zu vermeiden, stimmt höchstwahrscheinlich etwas in Ihrem Code nicht . Der einzig gültige Anwendungsfall hierfür ist das Drucken auf eine Nicht-Unicode-Konsole oder die einfache Konvertierung in HTML-Entitäten in einem HTML-Kontext.

Wenn Sie unter Windows arbeiten und cmd.exe verwenden, können Sie chcp 65001eingeben, um die Ausgabe von utf-8 zu aktivieren (funktioniert mit der Schriftart Lucida Console). Möglicherweise müssen Sie hinzufügen myUnicodeString.encode('utf8').

ccpizza
quelle
6

Sie haben "" geschrieben. Ich nehme an, das bedeutet, dass der HTML-Code irgendwo einen falsch geformten Unicode-Versuch enthält. ""

Es wird NICHT erwartet, dass der HTML-Code irgendeine Art von "Unicode-Versuch" enthält, ob wohlgeformt oder nicht. Es muss notwendigerweise Unicode-Zeichen enthalten, die in einer Codierung codiert sind, die normalerweise im Voraus bereitgestellt wird. Suchen Sie nach "Zeichensatz".

Sie scheinen anzunehmen, dass der Zeichensatz UTF-8 ist ... aus welchen Gründen? Das in Ihrer Fehlermeldung angezeigte Byte "\ xA0" zeigt an, dass Sie möglicherweise einen Einzelbyte-Zeichensatz haben, z. B. cp1252.

Wenn die Deklaration zu Beginn des HTML- Codes keinen Sinn ergibt , versuchen Sie es mit Chardet herauszufinden, wie wahrscheinlich die Codierung ist.

Warum haben Sie Ihre Frage mit "Regex" markiert?

Aktualisieren Sie, nachdem Sie Ihre gesamte Frage durch eine Nicht-Frage ersetzt haben:

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

Wenn Sie eine Zeichenfolge haben line, können Sie die .encode([encoding], [errors='strict'])Methode für Zeichenfolgen verwenden, um Codierungstypen zu konvertieren.

line = 'my big string'

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

Weitere Informationen zum Umgang mit ASCII und Unicode in Python finden Sie auf dieser wirklich nützlichen Website: https://docs.python.org/2/howto/unicode.html

Jama22
quelle
1
Dies funktioniert nicht, wenn Sie ein Nicht-ASCII-Zeichen wie ü in der Zeichenfolge haben.
Sajid
4

Ich denke, die Antwort ist da, aber nur in Teilen, was es schwierig macht, das Problem schnell zu beheben, wie z

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

Nehmen wir ein Beispiel: Angenommen, ich habe eine Datei mit einigen Daten in der folgenden Form (mit ASCII- und Nicht-ASCII-Zeichen).

10.01.17, 21:36 Uhr - Land: Willkommen ��

und wir wollen nur ASCII-Zeichen ignorieren und beibehalten.

Dieser Code reicht aus:

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

und geben Sie (rline) ein

>type(rline) 
<type 'str'>
Somum
quelle
Dies funktioniert auch für die (nicht standardisierten) "Extended Ascii" -Fälle
Oliver Zendel
1
unicodestring = '\xa0'

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

Funktioniert bei mir

HimalayanCoder
quelle
-5

Sieht so aus, als würden Sie Python 2.x verwenden. Python 2.x ist standardmäßig ASCII und kennt Unicode nicht. Daher die Ausnahme.

Fügen Sie einfach die folgende Zeile nach Shebang ein, es wird funktionieren

# -*- coding: utf-8 -*-
Haroon Rashedu
quelle
Der codingKommentar ist kein magisches Allheilmittel. Sie müssen wissen, warum der Fehler generiert wird. Dies behebt Probleme nur, wenn Ihre Python-Quelle fehlerhafte Zeichen enthält. Dies scheint bei dieser Frage nicht der Fall zu sein.
Mark Ransom