Конвертировать 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байт, или вы, вероятно, получите ошибку декодирования: hexutf8.com/?q=C2A0
— 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» - это то, что я использовал в качестве примера . Я получил это от Chardet, и у него было 0,5 уверенности, что это правильно! (ну, как и в случае со строкой длиной в 1 символ, что вы ожидаете) Вы должны изменить это на кодирование строки байтов, возвращаемой .urlopen().read()на то, что относится к содержимому, которое вы получили.

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

Как предложил Игнасио, проверьте исходную веб-страницу на предмет фактической кодировки возвращаемой строки из read(). Это либо в одном из мета-тегов, либо в заголовке ContentType в ответе. Используйте это тогда как параметр для .decode().

Однако обратите внимание, что не следует предполагать, что другие разработчики несут достаточную ответственность, чтобы убедиться, что объявления заголовка и / или набора метасимволов соответствуют фактическому содержанию. (Что такое PITA, да, я должен знать, я был одним из них раньше).


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'

Вы также можете захотеть перевести другие символы (например, знаки пунктуации) в их ближайшие эквиваленты, например, символ юникода RIGHT SINGLE QUOTATION MARK не конвертируется в 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')
"'"

Хотя есть более эффективные способы сделать это. См. Этот вопрос для получения более подробной информации. Где находится «лучшая ASCII для этого Unicode» Python?


4
Как полезно для решения вопроса, который был задан, так и практично для решения вопросов, которые могут лежать в основе задаваемого вопроса. Это примерный ответ на этот вопрос.
— Шанусмагнус

96

Используйте unidecode - он даже мгновенно преобразует странные символы в ascii и даже преобразует китайский язык в фонетический ascii.

$ pip install unidecode

затем:

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

3
halle-freakin-lujah - самое время найти ответ, который сработал для меня
— Ауриэль Перлманн

10
Проголосовал за интересную ценность. Обратите внимание, что это искажает слова на всех акцентированных языках. Шкода это не Шкода. Skoda, скорее всего, означает что-то грубое с угрями и судами на воздушной подушке.
— Сильвен

1
Я искал в интернете несколько дней до сих пор .... спасибо, большое спасибо
— Стивен

23

Я использую эту вспомогательную функцию во всех моих проектах. Если он не может конвертировать юникод, он игнорирует его. Это связано с библиотекой Django, но с небольшим исследованием вы можете обойти это.

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
это точно так же, как простой вызов "some-string" .encode ('ascii', 'ignore')
— Джошуа Бернс

17
Я не могу сказать вам, как я устал от того, что кто-то задает вопрос о СО и получает все эти проповеднические ответы. «Моя машина не заводится». «Почему ты хочешь завести свою машину? Ты должен идти вместо этого». Останови это!
— Шанусмагнус

8
@JohnMachin Никому нет дела. Мне безразлично, что за дурацкие дураки люди помещают в RSS-каналы, если это какой-то персонаж не в ascii, его можно усечь. Их проблема. Я просто хочу, чтобы python на самом деле подавил его и справился с ним, а не выдавал мне ошибки каждый раз, когда я указываю «игнорировать». Кто, черт возьми, придумал это дерьмо ?!
— 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 содержит какую-то неправильно сформированную попытку где-то использовать юникод." ""

Не предполагается, что HTML-код будет содержать какую-либо «попытку в кодировке Unicode», правильную или нет. Он должен обязательно содержать символы Unicode, закодированные в некоторой кодировке, которая обычно предоставляется заранее ... ищите "charset".

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

Если вы не можете понять смысл объявления в начале HTML-кода, попробуйте использовать chardet, чтобы узнать, какова вероятная кодировка.

Почему вы пометили свой вопрос "регулярное выражение"?

Обновите после того, как вы заменили весь свой вопрос без вопроса:

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
Это не работает, если в строке есть символ, отличный от ascii, например ü.
— Саджид

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

Это также работает для (нестандартных) случаев "расширенной ascii"
— Оливер

1
unicodestring = '\xa0'

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

Работает для меня


-5

Похоже, вы используете Python 2.x. Python 2.x по умолчанию ascii и не знает о Unicode. Отсюда и исключение.

Просто вставьте строку ниже после Шебанга, она будет работать

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

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