Перетворити Unicode в ASCII без помилок у Python


178

Мій код просто скребкує веб-сторінку, а потім перетворює на Unicode.

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

Але я отримую 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)

Я припускаю, що означає, що HTML містить десь неправильно сформовану спробу Unicode. Чи можу я просто відкинути будь-які байти коду, які спричиняють проблему, а не помилку?


2
Я вважаю це помилкою, якщо важливі символи відкидаються! (Також де питання?)
Арафангіон

Здається, ви, можливо, на веб-сторінці зіткнулися з "простором"? вам повинен передувати c2байт, інакше,
jar

Відповіді:


105

Оновлення 2018 року:

Станом на лютий 2018 року використання таких компресій gzipстало досить популярним (близько 73% всіх веб-сайтів використовують його, включаючи великі сайти, такі як Google, YouTube, Yahoo, Wikipedia, Reddit, Stack Overflow та Stack Exchange Network).
Якщо ви виконаєте просте декодування, як в оригінальній відповіді з gzipped відповіддю, ви отримаєте помилку, подібну до цього:

UnicodeDecodeError: 'utf8' кодек не може декодувати байт 0x8b в позиції 1: несподіваний байт коду

Для декодування відповіді gzpipped вам потрібно додати наступні модулі (в Python 3):

import gzip
import io

Примітка. У Python 2 ви використовуєте StringIOзамість цьогоio

Тоді ви можете розібрати вміст так:

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

Цей код зчитує відповідь і розміщує байти в буфері. Потім gzipмодуль зчитує буфер за допомогоюGZipFile функції. Після цього gzipped файл можна знов прочитати в байти і в кінці розшифрувати до нормально читаного тексту.

Оригінальний відповідь від 2010 року:

Чи можемо ми використати фактичне значення link?

Крім того, ми зазвичай стикаємося з цією проблемою тут, коли ми намагаємося .encode()вже закодований рядок байтів. Тому ви можете спробувати розшифрувати його спочатку як в

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

Як приклад:

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

Не вдається з

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

Поки:

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

Успіх без помилок. Зауважте, що "windows-1252" - це те, що я використовував як приклад . Я отримав це від шардета, і він мав 0,5 впевненості, що це правильно! (ну, як задано рядком довжиною 1 символу, що ви очікуєте) Ви повинні змінити це на кодування рядка байтів, повернене з.urlopen().read() того, що стосується вмісту, який ви отримали.

Ще одна проблема, яку я бачу там, полягає в тому, що .encode()метод string повертає модифіковану рядок і не змінює джерело на місці. Так що марно мати, self.response.out.write(html)оскільки html - це не закодований рядок з html.encode (якщо саме це ви спочатку прагнули).

Як запропонував Ігнасіо, перевірте вихідну веб-сторінку на предмет фактичного кодування повернутого рядка з read(). Це або в одному з тегів Meta, або у заголовку ContentType у відповіді. Використовуйте це тоді як параметр для .decode().

Однак зауважте, що не слід вважати, що інші розробники несуть достатню відповідальність, щоб переконатися, що декларації заголовка та / або мета символів відповідають дійсному вмісту. (Що таке ПІТА, так, я маю знати, я був одним із таких раніше).


1
У вашому прикладі я думаю, що ви мали на увазі останній рядок encoded_str = decoded_str.encode("utf8")
Айфіт Ентоні

1
Я спробував у Python 2.7.15, і мені надійшло це повідомлення raise IOError, 'Not a gzipped file'. У чому я винна?
Х’юн-гюн Кім

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

Розшифруйте рядок, який ви отримаєте назад, використовуючи або діаграму у відповідному metaтезі у відповіді, або в Content-Typeзаголовку, а потім кодуйте.

Метод encode(encoding, errors)приймає власні обробники помилок. Крім того, типовими значеннями ignoreє:

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

Див. Https://docs.python.org/3/library/stdtypes.html#str.encode


119

Як продовження відповіді Ігнасіо Васкеса-Абрамса

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

Іноді бажано видалити наголоси з символів та надрукувати основну форму. Це можна досягти за допомогою

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

Ви також можете перевести інші символи (такі, як розділові знаки), до їх найближчих еквівалентів, наприклад, знак ЮНІКОДУ ПРИМІТКИ ОДНОГО ПИТАННЯ ПРИМІТКИ не перетворюється на ASCII APOSTROPHE при кодуванні.

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

Хоча існують більш ефективні способи цього досягти. Детальніше див. У цьому запитанні. Де Python є "найкращим ASCII для цієї бази даних Unicode"?


4
І корисні для вирішення поставленого питання, і практичні для вирішення питань, які можуть лежати в основі заданого питання. Це типова відповідь на таке питання.
shanusmagnus

96

Використовуйте unidecode - він навіть перетворює дивні символи в ascii миттєво і навіть перетворює китайську мову на фонетичну ascii.

$ pip install unidecode

тоді:

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

3
halle-freakin-lujah - о часі я знайшов відповідь, яка працювала на мене
Aurielle Perlmann

10
Запропоновано для цікавого значення. Зауважте, що це манглівські слова на всіх наголошених мовах. Шкода - це не Шкода. Шкода, швидше за все, означає щось грубе з вуграми та повітряними суднами.
Sylvain

1
Я блукав в Інтернеті цілими днями .... дякую, спасибі велике
Стівен

23

Я використовую цю помічну функцію протягом усіх своїх проектів. Якщо він не може перетворити unicode, він ігнорує його. Це пов'язано з бібліотекою джанго, але, маючи невеликі дослідження, ви можете його обійти.

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

Я більше не отримую помилок Unicode після використання цього.


10
Це - НАДАДЖЕННЯ проблеми, а не діагностування та виправлення. Це як би сказати "Після того, як я відрізав ноги, у мене більше не виникають проблеми з мозолями та гронами".
Джон Махін

10
Я згоден, це пригнічує проблему. Здається, саме це питання, однак, після цього. Подивіться на його замітку: "Чи можу я просто скинути будь-які байти коду, що викликають проблему, замість того, щоб отримати помилку?"
Геттстер

3
це точно так само, як просто викликати "деякий рядок" .encode ('ascii', 'ignore')
Джошуа Бернс

17
Я не можу сказати, наскільки я втомився від того, щоб хтось ставив питання про ТА, і отримував усі ці проповідницькі відповіді. "Моя машина не заводиться". "Чому ви хочете завести свій автомобіль? Ви повинні ходити пішки". Зупини це!
shanusmagnus

8
@JohnMachin Нікого не хвилює. Мені не байдуже, що люди з обмеженими глупствами вкладають у RSS-канали, якщо це якийсь персонаж не в ascii, він може бути усічений. Їх проблема. Я просто хочу, щоб Python насправді задушив його і вирішував, а не давав мені помилок щоразу, коли я вказував "ignore". Хто чорт придумав це лайно ?!
user1244215

10

Для зламаних консолей, таких як cmd.exeта вихід HTML, ви завжди можете використовувати:

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

Це дозволить зберегти всі символи, що не мають права ascii, зробивши їх для друку в чистих ASCII та форматі в HTML.

УВАГА : Якщо ви використовуєте це у виробничому коді, щоб уникнути помилок, швидше за все, у вашому коді щось не так . Єдиним правильним випадком використання для цього є друк на консолі без унікоду або просте перетворення в HTML-об'єкти в контексті HTML.

І нарешті, якщо ви перебуваєте у Windows та використовуєте cmd.exe, тоді ви можете ввести, chcp 65001щоб увімкнути вихід utf-8 (працює з шрифтом Lucida Console). Можливо, вам доведеться додати myUnicodeString.encode('utf8').


6

Ви написали "" "Я припускаю, що це означає, що HTML містить десь неправильно сформовану спробу unicode."

НЕ очікується, що HTML містить будь-яку "спробу unicode", добре сформовану чи ні. Потрібно містити символи Unicode, закодовані в деякому кодуванні, яке зазвичай постачається спереду ... шукайте "charset".

Ви, здається, припускаєте, що набір даних - UTF-8 ... на яких підставах? Байт "\ xA0", який відображається у вашому повідомленні про помилку, вказує на те, що у вас може бути однобайтова карта, наприклад cp1252.

Якщо ви не можете отримати сенс у декларації на початку HTML, спробуйте скористатися графіком щоб дізнатися, що таке ймовірне кодування.

Чому ви позначили своє питання "regex"?

Оновіть після того, як ви замінили все запитання на без запитання:

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

4

Якщо у вас є рядок line, ви можете використовувати .encode([encoding], [errors='strict'])метод для рядків для перетворення типів кодування.

line = 'my big string'

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

Для отримання додаткової інформації про обробку ASCII та unicode в Python це справді корисний сайт: https://docs.python.org/2/howto/unicode.html


1
Це не працює, якщо у вас є символ non ascii, наприклад ü у рядку.
sajid

4

Я думаю, що відповідь є, але лише в шматочках і шматочках, що ускладнює швидке вирішення такої проблеми, як

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

Візьмемо приклад. Припустимо, у мене є файл, який містить деякі дані у такій формі (що містить символи ascii та non-ascii)

1.10.17, 21:36 - Земля: Ласкаво просимо ��

і ми хочемо ігнорувати та зберігати лише символи ascii.

Цей код:

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

і тип (rline) дасть вам

>type(rline) 
<type 'str'>

Це також працює для (нестандартних) випадків "розширеної
ассії


-5

Схоже, ви використовуєте python 2.x. Python 2.x за замовчуванням до ascii, і він не знає про Unicode. Звідси виняток.

Просто вставте нижній рядок після shebang, це спрацює

# -*- coding: utf-8 -*-

codingКоментар не чарівна панацея. Вам потрібно знати, чому генерується помилка, це виправляє лише ті випадки, коли у вашому джерелі Python є погані символи. Це, мабуть, не в цьому питанні.
Марк Викуп
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.