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

Mój kod po prostu zeskrobuje stronę internetową, a następnie konwertuje ją do 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 oznacza to, że HTML zawiera gdzieś błędnie uformowaną próbę Unicode. Czy mogę po prostu porzucić bajty kodu, które powodują problem, zamiast uzyskać błąd?

Author: Martin Thoma, 2010-03-02

11 answers

[[18]}2018 aktualizacja:

Od lutego 2018 r. używanie kompresji, takich jak gzip, stało się dość popularne (korzysta z niego około 73% wszystkich stron internetowych, w tym duże witryny, takie jak Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow i Stack Exchange Network sites).
Jeśli wykonasz proste dekodowanie, jak w oryginalnej odpowiedzi z odpowiedzią gzipped, otrzymasz błąd podobny lub podobny do tego: {]}

UnicodeDecodeError: kodek 'utf8' nie może dekodować bajtu 0x8b w pozycji 1: nieoczekiwany bajt kodu

Aby zdekodować odpowiedź GZP należy dodać następujące moduły (w Pythonie 3):

import gzip
import io

Uwaga: w Pythonie 2 używasz StringIO zamiast io

Następnie możesz przetworzyć 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. Moduł gzip odczytuje następnie bufor za pomocą funkcji GZipFile. Następnie plik gzipped może być ponownie wczytany do bajtów i dekodowany do normalnie czytelnego tekstu na końcu.

Oryginalna odpowiedź z 2010 roku:

Czy możemy uzyskać rzeczywistą wartość użytą dla link?

Ponadto, zwykle napotykamy ten problem tutaj, gdy próbujemy .encode() już zakodowany łańcuch bajtów. Więc możesz spróbować najpierw go odkodować 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")

Fails with

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

While:

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

Udaje się bez błędu. Należy pamiętać ,że "windows-1252" jest czymś, co używany jako przykład . Mam to od chardet i miał 0,5 pewności, że to prawda! (cóż, jak podano z ciągiem o długości 1 znaków, czego oczekujesz) powinieneś zmienić to na kodowanie ciągu bajtów zwróconego z .urlopen().read() na to, co odnosi się do pobranej zawartości.

Inny problem widzę tam jest to, że metoda .encode() string zwraca zmodyfikowany łańcuch i nie modyfikuje źródła w miejscu. Więc to trochę bezużyteczne mieć self.response.out.write(html) jako html nie jest zakodowanym ciągiem z html.koduj (jeśli to jest to, do czego pierwotnie dążyłeś).

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

Należy jednak pamiętać, że nie należy zakładać, że inni programiści są na tyle odpowiedzialni, aby upewnić się, że nagłówek i / lub meta zestaw znaków deklaracje są zgodne z rzeczywistą treścią. (Co jest PITA, tak, powinienem wiedzieć, ja był jeden z nich wcześniej).

 93
Author: Vin-G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-24 17:25:50
>>> u'aあä'.encode('ascii', 'ignore')
'a'

EDIT:

Dekoduj otrzymany ciąg znaków, używając zestawu znaków w odpowiednim znaczniku meta w odpowiedzi lub w nagłówku Content-Type, a następnie Zakoduj.

Metoda encode() przyjmuje inne wartości jako "ignoruj". Na przykład: 'replace', 'xmlcharrefreplace', 'backslashreplace'. Zobacz https://docs.python.org/3/library/stdtypes.html#str.encode

 189
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-28 08:31:13

Jako rozszerzenie do odpowiedzi Ignacio Vazqueza-Abramsa

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

Czasami pożądane jest usunięcie akcentów ze znaków i wydrukowanie formy bazowej. 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 prawy pojedynczy cudzysłów znak unicode nie jest konwertowany do apostrofu ascii 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ż są bardziej efektywne sposoby na / align = "left" / Zobacz to pytanie po więcej szczegółów gdzie jest baza danych Pythona "najlepsze ASCII dla tego Unicode"?

 106
Author: Peter Gibson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:11

Użycie unidecode - to nawet konwertuje dziwne znaki do ascii natychmiast, a nawet konwertuje chiński do fonetycznego ascii.

$ pip install unidecode

Wtedy:

>>> from unidecode import unidecode
>>> unidecode(u'北京')
'Bei Jing'
>>> unidecode(u'Škoda')
'Skoda'
 76
Author: Nimo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-27 18:26:09

Używam tej funkcji pomocnika we wszystkich moich projektach. Jeśli nie może przekonwertować unicode, ignoruje go. Łączy się to z biblioteką django, ale przy odrobinie badań możesz 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')

Nie dostaję już żadnych błędów unicode po użyciu tego.

 24
Author: Gattster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-03 00:02:07

Dla zepsutych konsol, takich jak cmd.exe i wyjście HTML, zawsze możesz użyć:

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

To zachowa wszystkie znaki spoza ascii, czyniąc je drukowalnymi w czystych ASCII i W HTML.

Ostrzeżenie: Jeśli używasz tego w kodzie produkcyjnym, aby uniknąć błędów, najprawdopodobniej jest coś nie tak w Twoim kodzie . Jedynym prawidłowym przypadkiem użycia jest drukowanie na konsoli innej niż unicode lub łatwa konwersja na encje HTML w kontekście HTML.

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

 8
Author: ccpizza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-22 14:20:11

Napisałeś "" zakładam, że oznacza to, że HTML zawiera gdzieś błędnie uformowaną próbę Unicode."""

Oczekuje się, że HTML nie będzie zawierał żadnego rodzaju" próby Unicode", dobrze uformowanego lub nie. Musi z konieczności zawierać znaki Unicode zakodowane w pewnym kodowaniu, które jest zwykle dostarczane z przodu ... Szukaj "charset".

Wydaje się, że zakładasz, że zestaw znaków to UTF-8 ... na jakiej podstawie? Bajt "\xa0 " pokazany w komunikacie o błędzie wskazuje, że możesz mieć kod jednobajtowy, np. cp1252.

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

Dlaczego otagowałeś swoje pytanie "regex"?

Update po zastąpieniu całego pytania bez pytania:

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
Author: John Machin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-03 03:46:55

Jeśli masz łańcuch znaków line, możesz użyć metody .encode([encoding], [errors='strict']) 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 strona: https://docs.python.org/2/howto/unicode.html

 4
Author: Jama22,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-10 07:34:48

Myślę, że odpowiedź jest tam, ale tylko w kawałkach i kawałkach, co sprawia, że trudno szybko rozwiązać problem, taki jak

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ące znaki ascii i nie-ascii)

1/10/17, 21:36 - Ziemia: Witamy ï¿½ï ¿½

I chcemy ignorować 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 typ (rline) da ci

>type(rline) 
<type 'str'>
 3
Author: Somum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-15 14:41:03
unicodestring = '\xa0'

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

Działa dla mnie

 1
Author: HimalayanCoder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-05 08:00:29

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

Po prostu wklej poniższy wiersz po shebang, to będzie działać

# -*- coding: utf-8 -*-
 -4
Author: Haroon Rashedu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-03 20:04:28