Konwertuj Unicode na ASCII bez błędów w Pythonie

178

Mój kod po prostu zeskrobuje stronę internetową, a następnie konwertuje ją na Unicode.

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

Ale dostaję 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)

Zakładam, że to znaczy, że HTML zawiera jakąś źle sformułowaną próbę gdzieś na Unicode. Czy mogę po prostu upuścić wszystkie bajty kodu, które powodują problem, zamiast otrzymać błąd?

lustro
źródło
2
Uważam, że odrzucenie ważnych znaków jest błędem! (Również, gdzie jest pytanie?)
Arafangion
Wygląda na to, że na stronie internetowej wystąpił „brak przerwy”? musiałby być poprzedzony c2bajtem lub prawdopodobnie wystąpiłby błąd dekodowania: hexutf8.com/?q=C2A0
jar

Odpowiedzi:

105

Aktualizacja 2018:

Od lutego 2018 r. Stosowanie kompresji takich jak gzipstało się dość popularne (korzysta z niej około 73% wszystkich witryn, w tym duże witryny, takie jak Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow i Stack Exchange Network).
Jeśli wykonasz proste dekodowanie, jak w oryginalnej odpowiedzi, z odpowiedzią spakowaną gzipem, pojawi się błąd podobny do tego:

UnicodeDecodeError: kodek „utf8” nie może zdekodować bajtu 0x8b na pozycji 1: nieoczekiwany bajt kodu

Aby zdekodować odpowiedź z gzpipem, musisz dodać następujące moduły (w Pythonie 3):

import gzip
import io

Uwaga: w Pythonie 2 używałbyś StringIOzamiastio

Następnie możesz przeanalizować zawartość w następujący sposób:

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

Ten kod odczytuje odpowiedź i umieszcza bajty w buforze. gzipModuł odczytuje się bufor za pomocą GZipFilefunkcji. Następnie spakowany plik gzip można ponownie wczytać do bajtów i na końcu zdekodować do normalnie czytelnego tekstu.

Oryginalna odpowiedź z 2010 r .:

Czy możemy uzyskać rzeczywistą wartość używaną do link?

Ponadto zwykle napotykamy ten problem tutaj, gdy próbujemy .encode()już zakodowany ciąg bajtów. Więc możesz spróbować najpierw go zdekodować, jak w

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

Jako przykład:

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

Niepowodzenie

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

Podczas:

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

Sukces bez błędów. Zwróć uwagę, że użyłem przykładu „windows-1252” . Dostałem to od Chardeta i miałem 0,5 pewności, że to prawda! (cóż, jak w przypadku łańcucha o długości 1 znaku, czego oczekujesz) Powinieneś zmienić to na kodowanie zwracanego ciągu bajtów z .urlopen().read()na to, co dotyczy pobranej zawartości.

Innym problemem, jaki widzę, jest to, że .encode()metoda string zwraca zmodyfikowany ciąg i nie modyfikuje źródła w miejscu. Więc jest to trochę bezużyteczne, self.response.out.write(html)ponieważ html nie jest zakodowanym ciągiem z html.encode (jeśli to jest to, do czego pierwotnie dążyłeś).

Jak zasugerował Ignacio, sprawdź na źródłowej stronie internetowej rzeczywiste kodowanie zwróconego ciągu znaków z read(). Znajduje się w jednym z metatagów lub w nagłówku ContentType odpowiedzi. Użyj tego jako parametru dla .decode().

Należy jednak pamiętać, że nie należy zakładać, że inni programiści są wystarczająco odpowiedzialni, aby upewnić się, że deklaracje nagłówka i / lub zestawu metaznaków odpowiadają rzeczywistej zawartości. (Co jest PITA, tak, należy wiedzieć, że był jednym z tych wcześniej).

Vin-G
źródło
1
W twoim przykładzie myślę, że chciałeś, aby ostatnia linia była encoded_str = decoded_str.encode("utf8")
Ajith Antony
1
Próbowałem w Pythonie 2.7.15 i otrzymałem ten komunikat raise IOError, 'Not a gzipped file'. Jaka jest wina, którą popełniłem?
Hyun-geun Kim
222
>>> u'aあä'.encode('ascii', 'ignore')
'a'

Zdekoduj otrzymany ciąg, używając zestawu znaków w odpowiednim metatagu w odpowiedzi lub w Content-Typenagłówku, a następnie zakoduj.

Metoda encode(encoding, errors)akceptuje niestandardowe programy obsługi dla błędów. Poza tym wartościami domyślnymi ignoresą:

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

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

Ignacio Vazquez-Abrams
źródło
119

Jako rozszerzenie odpowiedzi Ignacio Vazquez-Abramsa

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

Czasami pożądane jest usunięcie akcentów ze znaków i wydrukowanie formy podstawowej. Można to osiągnąć za pomocą

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

Możesz również chcieć przetłumaczyć inne znaki (takie jak interpunkcja) na ich najbliższe odpowiedniki, na przykład znak Unicode PRAWEGO POJEDYNCZEGO CYTATU nie jest konwertowany na ascii APOSTROPHE podczas kodowania.

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

Chociaż istnieją skuteczniejsze sposoby na osiągnięcie tego. Zobacz to pytanie, aby uzyskać więcej informacji. Gdzie jest „najlepszy ASCII dla tej bazy danych Unicode” w Pythonie?

Peter Gibson
źródło
4
Obie są pomocne w odpowiedzi na zadane pytanie i praktyczne w rozwiązywaniu problemów, które mogą leżeć u podstaw zadanego pytania. To jest wzorowa odpowiedź na tego rodzaju pytanie.
shanusmagnus
96

Używaj unidecode - natychmiast konwertuje nawet dziwne znaki na ascii, a nawet konwertuje chiński na fonetyczny ascii.

$ pip install unidecode

następnie:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
Nimo
źródło
3
halle-freakin-lujah - najwyższy czas znaleźć odpowiedź, która zadziałała
Aurielle Perlmann
10
Głosowano za dobrą zabawą. Zwróć uwagę, że powoduje to zmianę słów we wszystkich akcentowanych językach. Škoda to nie Skoda. Skoda najprawdopodobniej oznacza coś obrzydliwego z węgorzem i poduszkowcami.
Sylvain
1
Przeszukuję internet od wielu dni ... dziękuję, bardzo dziękuję
Stephen
23

Używam tej funkcji pomocnika we wszystkich moich projektach. Jeśli nie może przekonwertować Unicode, ignoruje go. To wiąże się z biblioteką django, ale przy odrobinie badań można ją ominąć.

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

Po użyciu tego nie otrzymuję już żadnych błędów Unicode.

Gattster
źródło
10
To jest TŁUMIENIE problemu, a nie diagnozowanie i naprawianie. To tak, jakby powiedzieć: „Po odcięciu nóg nie mam już problemów z odciskami i haluksami”.
John Machin
10
Zgadzam się, że to tłumi problem. Wygląda na to, że właśnie o to chodzi. Spójrz na jego notatkę: „Czy zamiast otrzymać błąd, mogę po prostu porzucić te bajty kodu, które powodują problem?”
Gattster
3
to jest dokładnie to samo, co zwykłe wywołanie „jakiegoś ciągu” .encode („ascii”, „ignore”)
Joshua Burns
17
Nie potrafię wam powiedzieć, jak bardzo jestem zmęczony, gdy ktoś zadaje pytanie na temat SO i otrzymuje wszystkie te kaznodziejskie odpowiedzi. „Mój samochód się nie uruchamia”. - Dlaczego chcesz odpalić samochód? Zamiast tego powinieneś iść. Przestań!
shanusmagnus
8
@JohnMachin Nikogo to nie obchodzi. Nie obchodzi mnie, co upośledzeni ludzie umieszczają w kanałach RSS, jeśli to jakaś postać nie w ascii, można ją skrócić. Ich problem. Chcę tylko, aby Python faktycznie go zdławił i sobie z tym poradził, a nie wyświetlał mi błędów za każdym razem, gdy określam „ignoruj”. Kto do diabła wymyślił to gówno ?!
user1244215
10

W przypadku zepsutych konsol, takich jak cmd.exei wyjścia HTML, zawsze możesz użyć:

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

Pozwoli to zachować wszystkie znaki inne niż ASCII, jednocześnie umożliwiając ich drukowanie w czystym ASCII i HTML.

OSTRZEŻENIE : Jeśli użyjesz tego w kodzie produkcyjnym, aby uniknąć błędów, najprawdopodobniej w Twoim kodzie jest coś nie tak . Jedynym prawidłowym przypadkiem użycia jest drukowanie do konsoli innej niż Unicode lub łatwa konwersja do jednostek HTML w kontekście HTML.

I wreszcie, jeśli jesteś w chcp 65001systemie Windows i używasz cmd.exe, możesz wpisać, aby włączyć wyjście utf-8 (działa z czcionką Lucida Console). Może być konieczne dodanie myUnicodeString.encode('utf8').

ccpizza
źródło
6

Napisałeś "" "Zakładam, że oznacza to, że HTML zawiera gdzieś źle sformułowaną próbę unicode." ""

Oczekuje się, że kod HTML NIE będzie zawierał żadnego rodzaju „próby wprowadzenia kodu Unicode”, poprawnie sformułowanego lub nie. Musi z konieczności zawierać znaki Unicode zakodowane w jakimś kodowaniu, które jest zwykle dostarczane z góry ... poszukaj "charset".

Wydaje się, że zakładasz, że zestaw znaków to UTF-8… na jakiej podstawie? Bajt „\ xA0” wyświetlany w komunikacie o błędzie wskazuje, że możesz mieć jednobajtowy zestaw znaków, np. Cp1252.

Jeśli nie możesz wyciągnąć żadnego sensu z deklaracji na początku HTML, spróbuj użyć programu chardet, aby dowiedzieć się, jakie jest prawdopodobne kodowanie.

Dlaczego oznaczyłeś swoje pytanie „wyrażeniem regularnym”?

Zaktualizuj po zastąpieniu całego pytania pytaniem innym niż pytanie:

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
źródło
4

Jeśli masz ciąg line, możesz użyć .encode([encoding], [errors='strict'])metody dla ciągów do konwersji typów kodowania.

line = 'my big string'

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

Aby uzyskać więcej informacji na temat obsługi ASCII i Unicode w Pythonie, jest to naprawdę przydatna witryna: https://docs.python.org/2/howto/unicode.html

Jama22
źródło
1
To nie działa, jeśli w ciągu znaków występuje znak inny niż ASCII, taki jak ü.
sajid
4

Myślę, że odpowiedź jest, ale tylko w kawałkach, co utrudnia szybkie rozwiązanie problemu, np

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

Weźmy przykład, załóżmy, że mam plik, który ma pewne dane w następującej formie (zawierającej znaki ascii i non-ascii)

10.01.17, 21:36 - Ziemia: Witamy ��

a my chcemy zignorować i zachować tylko znaki ascii.

Ten kod zrobi:

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

i wpisz (rline)

>type(rline) 
<type 'str'>
Somum
źródło
Działa to również w przypadku (niestandaryzowanych) „rozszerzonych ascii”
Oliver Zendel
1
unicodestring = '\xa0'

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

Pracuje dla mnie

HimalayanCoder
źródło
-5

Wygląda na to, że używasz Pythona 2.x. Python 2.x domyślnie używa ascii i nie ma informacji o Unicode. Stąd wyjątek.

Po prostu wklej poniższą linię po shebang, zadziała

# -*- coding: utf-8 -*-
Haroon Rashedu
źródło
Ten codingkomentarz nie jest magicznym lekarstwem na wszystko. Musisz wiedzieć, dlaczego generowany jest błąd, to rozwiązuje problem tylko wtedy, gdy w źródle Pythona są złe znaki. Wydaje się, że tak nie jest w przypadku tego pytania.
Mark Ransom