Конвертировать 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, да, я должен знать, я был одним из них раньше).

Vin-G
источник
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'
Nimo
источник
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 после использования этого.

Gattster
источник
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').

ccpizza
источник
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

Jama22
источник
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'>
Somum
источник
Это также работает для (нестандартных) случаев "расширенной ascii"
Оливер
1
unicodestring = '\xa0'

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

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

HimalayanCoder
источник
-5

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

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

# -*- coding: utf-8 -*-
Харун Рашеду
источник
codingКомментарий не волшебная панацея. Вам нужно знать, почему генерируется ошибка, это только исправляет ситуацию, когда в исходном коде Python есть плохие символы. Похоже, это не относится к этому вопросу.
Марк Рэнсом