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
состоит из символов, а не байтов. Вы не должны кодировать это. Если вы попытаетесь это сделать, вы создадите байтовую строку, и когда вы попытаетесь воспринимать это как символы, произойдут плохие вещи.
Есть два способа обойти это:
- Используйте сырые недекодированные байты, которые хранятся в
r.content
, как предложил Мартейн. Затем вы можете расшифровать их сами, чтобы превратить их в персонажей. - Позволять
requests
сделать декодирование, но просто убедитесь, что он использует правильный кодек. Поскольку в этом случае вы знаете, что это UTF-8, вы можете установитьr.encoding = 'utf-8'
, Если вы делаете это до доступаr.text
тогда, когда вы делаете доступr.text
, он будет правильно декодирован, и вы получите строку символов. Вам не нужно возиться с кодировками символов вообще.
Кстати, Python 3 несколько облегчает поддержание разницы между символьными строками и байтовыми строками, потому что он требует использования разных типов объектов для их представления.
В вашем коде есть пара ошибок:
Прежде всего, ваша попытка перекодировать текст не нужна. Запросы могут дать вам исходную кодировку страницы, а 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")
Во-вторых, у вас есть проблема с кодировкой. Вы, вероятно, пытаетесь визуализировать результаты на терминале. Что вы получите, так это представление символов в тексте в юникоде для каждого символа, который не входит в набор 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")