Python правильная кодировка сайта (Beautiful Soup)

Я пытаюсь загрузить html-страницу и вывести текст, хотя я правильно получаю веб-страницу, BeautifulSoup каким-то образом разрушает кодировку.

Источник:

# -*- coding: utf-8 -*-
import requests
from BeautifulSoup import BeautifulSoup

url = "http://www.columbia.edu/~fdc/utf8/"
r = requests.get(url)

encodedText = r.text.encode("utf-8")
soup = BeautifulSoup(encodedText)
text =  str(soup.findAll(text=True))
print text.decode("utf-8")

Вывод выдержки:

...Odenw\xc3\xa4lderisch...

это должно быть Оденвальдериш

4 ответа

Решение

Вы делаете две ошибки; вы неправильно обрабатываете кодирование и рассматриваете список результатов как нечто, что можно безопасно преобразовать в строку без потери информации.

Прежде всего, не используйте response.text! Здесь не виновата BeautifulSoup, вы перекодируете моджибаке. requests библиотека по умолчанию будет использовать кодировку Latin-1 для text/* типы контента, когда сервер явно не указывает кодировку, потому что стандарт HTTP утверждает, что это значение по умолчанию.

См. Раздел " Кодирование " Расширенной документации:

Единственный раз, когда Запросы не будут делать это, если в заголовках HTTP нет явной кодировки и Content-Type заголовок содержит text, В этой ситуации RFC 2616 указывает, что кодировка по умолчанию должна бытьISO-8859-1, Запросы следует спецификации в этом случае. Если вам требуется другая кодировка, вы можете вручную установить Response.encoding собственности, или использовать сырье Response.content,

Жирный акцент мой.

Пройдите в response.content сырые данные вместо:

soup = BeautifulSoup(r.content)

Я вижу, что вы используете BeautifulSoup 3. Вы действительно хотите перейти на BeautifulSoup 4; версия 3 была прекращена в 2012 году и содержит несколько ошибок. Установите beautifulsoup4 проект и использование from bs4 import BeautifulSoup,

BeautifulSoup 4 обычно отлично справляется с поиском правильной кодировки, используемой при разборе, либо из HTML <meta> тег или статистический анализ предоставленных байтов. Если сервер предоставляет набор символов, вы все равно можете передать его в BeautifulSoup из ответа, но сначала выполните тестирование, если requests используется по умолчанию:

encoding = r.encoding if 'charset' in r.headers.get('content-type', '').lower() else None
soup = BeautifulSoup(r.content, from_encoding=encoding)

Наконец, что не менее важно, с BeautifulSoup 4, вы можете извлечь весь текст со страницы, используя soup.get_text():

text = soup.get_text()
print text

Вместо этого вы конвертируете список результатов (возвращаемое значение soup.findAll()) в строку. Это никогда не может работать, потому что контейнеры в Python используют repr() для каждого элемента в списке для создания строки отладки, а для строк это означает, что вы получаете escape-последовательности для всего, что не является печатаемым символом ASCII.

Это не вина BeautifulSoup. Вы можете увидеть это, распечатав encodedText, прежде чем вы будете использовать BeautifulSoup: не-ASCII символы уже бессмысленны.

Проблема в том, что вы путаете байты и символы. Для хорошего обзора различия прочитайте одну из статей Джоэла, но суть в том, что байты - это, в общем, байты (группы из 8 битов без какого-либо дополнительного значения), тогда как символы - это то, что составляет строки текста. Кодирование превращает символы в байты, а декодирование превращает байты обратно в символы.

Посмотрите на requests документация показывает, что r.text состоит из символов, а не байтов. Вы не должны кодировать это. Если вы попытаетесь это сделать, вы создадите байтовую строку, и когда вы попытаетесь воспринимать это как символы, произойдут плохие вещи.

Есть два способа обойти это:

  1. Используйте сырые недекодированные байты, которые хранятся в r.content, как предложил Мартейн. Затем вы можете расшифровать их сами, чтобы превратить их в персонажей.
  2. Позволять requests сделать декодирование, но просто убедитесь, что он использует правильный кодек. Поскольку в этом случае вы знаете, что это UTF-8, вы можете установить r.encoding = 'utf-8', Если вы делаете это до доступа r.textтогда, когда вы делаете доступ r.text, он будет правильно декодирован, и вы получите строку символов. Вам не нужно возиться с кодировками символов вообще.

Кстати, Python 3 несколько облегчает поддержание разницы между символьными строками и байтовыми строками, потому что он требует использования разных типов объектов для их представления.

В вашем коде есть пара ошибок:

  1. Прежде всего, ваша попытка перекодировать текст не нужна. Запросы могут дать вам исходную кодировку страницы, а BeautifulSoup может взять эту информацию и выполнить саму расшифровку:

    # -*- coding: utf-8 -*-
    import requests
    from BeautifulSoup import BeautifulSoup
    
    url = "http://www.columbia.edu/~fdc/utf8/"
    r = requests.get(url)
    
    soup = BeautifulSoup(r.text, "html5lib")
    
  2. Во-вторых, у вас есть проблема с кодировкой. Вы, вероятно, пытаетесь визуализировать результаты на терминале. Что вы получите, так это представление символов в тексте в юникоде для каждого символа, который не входит в набор ASCII. Вы можете проверить результаты, как это:

    res = [item.encode("ascii","ignore") for item in soup.find_all(text=True)]
    

вы можете сделать это путем декодирования response.content с помощью utf-8

      soup = BeautifulSoup(r.content.decode("utf-8"),  "html.parser")
Другие вопросы по тегам