BeautifulSoup 4 преобразует объекты HTML в Unicode, но получает ненужные символы при использовании печати

Я пытаюсь очистить текст из Интернета, используя BeautifulSoup 4, чтобы проанализировать его. Я столкнулся с проблемой при выводе обработанного bs4 текста на консоль. Всякий раз, когда я нажимаю на символ, который изначально был объектом HTML, например ', я получаю мусорные символы на консоли. Я считаю, что bs4 правильно преобразует эти объекты в юникод, потому что, если я попытаюсь использовать другую кодировку для вывода текста, он будет жаловаться на отсутствие соответствующего отображения юникода для символа (например, u'’.) Я не уверен, почему функция печати путается с этими символами. Я пробовал менять шрифты, что меняет символы мусора, и нахожусь на компьютере с Windows 7 с американо-английским языком. Вот мой код для справки, любая помощь приветствуется. Заранее спасибо!

#!/usr/bin/python
import json
import urllib2
import cookielib
import bs4

cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))

url = "http://api.nytimes.com/svc/search/v2/articlesearch.json?q=Tiguan\
&page=0&api-key=blah"
response = opener.open(url)
articles = response.read()
decoded = json.loads(articles)

totalpages = decoded['response']['meta']['hits']/10

for page in range(totalpages + 1):
    if page>0:
        url = "http://api.nytimes.com/svc/search/v2/articlesearch.json?\
q=Tiguan&page=" + str(page) + "&api-key=blah"
        response = opener.open(url)
        articles = response.read()
        decoded = json.loads(articles)
    for url in decoded['response']['docs']:
        print url['web_url']
        urlstring = url['web_url']
        art = opener.open(urlstring)
        soup = bs4.BeautifulSoup(art.read())
        goodstuff = soup.findAll('nyt_text')
        for tag in goodstuff:
            print tag.prettify().encode("UTF")

person DaWisePug    schedule 17.12.2013    source источник
comment
связанные: Python, Unicode и консоль Windows   -  person jfs    schedule 17.12.2013
comment
Кстати, вы не вместе. Обама тоже это понял   -  person jfs    schedule 17.12.2013
comment
@ J.F.Sebastian: Я несколько раз почти помечал вопрос как дубликат этого вопроса, но в нем полно ответов, которые выглядят правильными, а другие нет. Нам действительно нужно где-то, где собраны все различные неуклюжие обходные пути, объясняются проблемы с каждым из них и ясно указывается, что если вы не перестанете использовать Windows или Python 2.x, эти неуклюжие обходные пути будут настолько хороши, насколько вы собираетесь их получить…   -  person abarnert    schedule 18.12.2013
comment
@abarnert: обычно я просто печатаю Unicode и устанавливаю соответствующие PYTHONIOENCODING, например, utf-8 для файлов, каналов и ascii:xmlcharrefreplace, чтобы избежать мусора в консоли. bugs.python.org/issue1602 ошеломляет (ссылка взята из моего комментария к этому вопросу).   -  person jfs    schedule 18.12.2013
comment
@ J.F.Sebastian: Ну, xmlcharrefreplace не совсем удобочитаем/удобен для конечного пользователя. В любом случае, некоторые будущие версии Python будут использовать что-то вроде объектов консоли Windows из этой проблемы (но должным образом интегрированных с модулем io) для вывода UTF-16 в Windows, после чего проблема исчезнет (за исключением людей с некоторыми старыми версиями). и специализированные Unix-системы, которые либо знают, что делают, либо им все равно). Но пока люди придерживаются Python 2.x, не имеет значения, что 3.5 решил проблему…   -  person abarnert    schedule 18.12.2013


Ответы (2)


Проблема не имеет ничего общего ни с bs4, ни с объектами HTML, ни с чем-то еще. Вы можете воспроизвести точно такое же поведение в большинстве систем Windows с помощью однострочной программы для печати тех же символов, которые отображаются как мусор, когда вы пытаетесь их напечатать, например:

print u'\u2019'.encode('UTF-8')

Проблема здесь в том, что, как и в подавляющем большинстве систем Windows (и больше никто не использует в 2013 году), ваш набор символов по умолчанию не UTF-8, а что-то вроде CP1252.

Итак, когда вы кодируете свои строки Unicode в UTF-8 и печатаете эти байты на консоли, консоль интерпретирует их как CP1252. Что в данном случае означает, что вы получаете ’ вместо .

Смена шрифтов не поможет. Кодировка UTF-8 для \u2013 — это три байта \xe2, \x80 и \x99, а значение этих трех байтов в CP1252 — â, и .

Если вы хотите кодировать вручную для консоли, вам нужно кодировать правильный набор символов, который фактически используется вашей консолью. Вы можете получить это как sys.stdout.encoding.

Конечно, вы можете получить исключение, пытаясь закодировать вещи для правильного набора символов, потому что 8-битные наборы символов, такие как CP1252, могут обрабатывать только около 240 из 110 000 символов в Unicode. Единственный способ справиться с этим — использовать аргумент errors для encode, чтобы либо игнорировать их, либо заменять их заменяющими символами.

Между тем, если вы еще не читали HOWTO по Unicode, вам действительно нужно это сделать. Особенно, если вы планируете придерживаться Python 2.x и Windows.


Если вам интересно, почему некоторые программы командной строки, кажется, могут обойти эти проблемы: решение Microsoft проблемы набора символов заключается в создании целого параллельного набора API, которые используют 16-битные символы вместо 8-битных, и эти API всегда используют UTF-16. К сожалению, многие вещи, такие как переносимые оболочки stdio, которые Microsoft предоставляет для взаимодействия с консолью и на которые опирается Python 2.x, имеют только 8-битный API. То есть проблема вообще не решена. Python 3.x больше не использует эти оболочки, и постоянно ведутся дискуссии о том, чтобы некоторые будущие версии говорили UTF-16 с консолью. Но даже если это произойдет в 3.4 (что кажется очень маловероятным), это не поможет вам, пока вы используете 2.x.

person abarnert    schedule 17.12.2013
comment
Да, спасибо за ответ. Я попытался установить кодовую страницу консоли, но это тоже не сработало. Мой выбор версии Python был определен администратором Linux-сервера, который предоставляет моя школа, где я в конечном итоге хочу запустить свою программу. Мой выбор Windows обусловлен тем, что я использую предоставленный на работе ноутбук для выполнения школьных заданий. Я думаю, что со мной все будет в порядке, как только я перенесу свою программу на сервер, но это создает некоторую боль во время разработки. Может быть, я посмотрю на альтернативные консоли и посмотрю, будет ли лучше работать с юникодом. Еще раз спасибо за отличное объяснение! - person DaWisePug; 17.12.2013
comment
@DaWisePug: Что именно вы имеете в виду, говоря, что я пытался установить кодовую страницу консоли? Если вы на самом деле переключите свою кодовую страницу Windows OEM на UTF-8 или обманом заставите cmd.exe использовать UTF-8 (любое из этих действий может сломать все остальные вещи) или используете сторонний эмулятор консоли, совместимый с UTF-8 (есть ли какие-либо, кроме настройки X-сервера и запуска чего-то вроде rxvt?), ваш код должен работать. Но я подозреваю, что вы ничего из этого не делали. - person abarnert; 18.12.2013
comment
Я попытался обмануть cmd.exe, введя команду chcp 65001. Это не сработало. - person DaWisePug; 19.12.2013
comment
@DaWisePug: Вы прочитали все комментарии к проблеме 1602 или в любом другом месте, где вы нашли информацию о cp65001? ? Вам нужно создать новое окно консоли с cmd /u, чтобы оно могло обрабатывать вывод Unicode до того, как вы chcp 65001 его (в противном случае оно в конечном итоге преобразует cp65001 в cp1252 и ошибается, когда это не работает). Вы можете делать обе вещи одновременно с помощью cmd /u /k chcp 65001, что даст вам консоль, где вы сможете распечатать CP65001 байт. Что по-прежнему может не работать для символов, отличных от BMP, но, по крайней мере, дает вам BMP. - person abarnert; 19.12.2013
comment
@DaWisePug: Кроме того, вам, вероятно, придется SET PYTHONIOENCODING=utf-8 (потому что Python понятия не имеет, что такое cp65001) и выбрать шрифт Unicode, такой как консоль Lucida, и ваш ввод (stdin и/или sys.argv) все еще может быть испорчен, потому что драйвер ввода консоли может в конечном итоге отправить символы CP1252 на вашу консоль CP65001, и… Это большой беспорядок. - person abarnert; 19.12.2013

ответ @abarnert содержит хорошее объяснение проблемы.

В вашем конкретном случае вы можете просто передать параметр encoding в prettify() вместо utf-8 по умолчанию.

Если вы печатаете на консоль, вы можете попробовать распечатать Unicode напрямую:

print soup.prettify(encoding=None, formatter='html') # print Unicode

Может произойти сбой. Если вы пройдете ascii; тогда BeautifulSoup может использовать числовые ссылки на символы вместо символов, отличных от ascii:

print soup.prettify('ascii', formatter='html')

Предполагается, что текущая кодовая страница Windows является кодировкой на основе ascii (большинство из них). Это также должно работать, если вывод перенаправляется в файл или другую программу через конвейер.

Для переносимости вы всегда можете напечатать Unicode (encoding=None выше) и использовать PYTHONIOENCODING для получения соответствующей кодировки символов, например, utf-8 для файлов, каналов и ascii:xmlcharrefreplace, чтобы избежать мусора в консоли.

person jfs    schedule 17.12.2013
comment
Результатом этого стала печать объектов HTML в виде простого текста на консоли, что могло привести к полезному обходному пути. Спасибо! - person DaWisePug; 17.12.2013
comment
@DaWisePug: именованные персонажи связаны с formatter='html'. Удалите его, если это нежелательно. - person jfs; 18.12.2013
comment
@DaWisePug: я обновил ответ, упомянув более общее решение: распечатать Unicode, использовать PYTHONIOENCODING для конкретного случая. - person jfs; 18.12.2013