Проблемы с кодировкой Python и BeautifulSoup - PullRequest
23 голосов
/ 28 августа 2011

Я пишу сканер с Python, используя BeautifulSoup, и все шло гладко, пока я не наткнулся на этот сайт:

http://www.elnorte.ec/

Я получаю содержимое с запросамибиблиотека:

r = requests.get('http://www.elnorte.ec/')
content = r.content

Если я распечатаю переменную содержимого в этот момент, все испанские специальные символы, кажется, работают нормально.Однако, как только я пытаюсь передать переменную содержимого в BeautifulSoup, все это портится:

soup = BeautifulSoup(content)
print(soup)
...
<a class="blogCalendarToday" href="/component/blog_calendar/?year=2011&amp;month=08&amp;day=27&amp;modid=203" title="1009 artículos en este día">
...

По-видимому, он искажает все испанские специальные символы (акценты и еще много чего).Я попытался сделать content.decode ('utf-8'), content.decode ('latin-1'), также попытался возиться с параметром fromEncoding для BeautifulSoup, установив его в fromEncoding = 'utf-8' и fromEncoding= 'latin-1', но все еще без игры в кости.

Любые указатели будут высоко оценены.

Ответы [ 5 ]

23 голосов
/ 28 августа 2011

В вашем случае эта страница содержит неверные данные utf-8, что приводит к путанице в BeautifulSoup и заставляет думать, что на вашей странице используется windows-1252, вы можете сделать этот трюк:

soup = BeautifulSoup.BeautifulSoup(content.decode('utf-8','ignore'))

, выполнив это, вы откажетесьлюбые неправильные символы из исходного кода и BeautifulSoup будут правильно угадывать кодировку.

Вы можете заменить «игнорировать» на «заменить» и проверить текст на «?»символы, чтобы увидеть, что было отброшено.

На самом деле это очень сложная задача - написать сканер, который может каждый раз угадывать кодировку страницы с вероятностью 100% (в настоящее время браузеры очень хороши в этом), вы можете использовать такие модули, как 'chardet ', но, например, в вашем случае он будет угадывать кодировку как ISO-8859-2, что тоже не правильно.

Если вам действительно нужно иметь возможность получить кодировку для любой страницы, которую пользователь может предоставить- вам следует либо создать многоуровневую (попробуйте utf-8, попробуйте latin1, try и т. д.) функцию обнаружения (как мы это делали в нашем проекте), либо использовать некоторый код обнаружения из firefox или chromium в качестве модуля C.

18 голосов
/ 28 августа 2011

не могли бы вы попробовать:

r = urllib.urlopen('http://www.elnorte.ec/')
x = BeautifulSoup.BeautifulSoup(r.read)
r.close()

print x.prettify('latin-1')

Я получаю правильный вывод. О, в этом особом случае вы могли бы также x.__str__(encoding='latin1').

Полагаю, это связано с тем, что содержимое соответствует ISO-8859-1 (5), а мета-http-эквивалентный тип содержимого неправильно говорит "UTF-8".

Не могли бы вы подтвердить?

4 голосов
/ 11 августа 2017

Вы можете попробовать это, это работает для любой кодировки

from bs4 import BeautifulSoup
from bs4.dammit import EncodingDetector
headers = {"User-Agent": USERAGENT}
resp = requests.get(url, headers=headers)
http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
encoding = html_encoding or http_encoding
soup = BeautifulSoup(resp.content, 'lxml', from_encoding=encoding)
2 голосов
/ 03 декабря 2016

Я бы предложил более методичный подход к защите от дурака.

# 1. get the raw data 
raw = urllib.urlopen('http://www.elnorte.ec/').read()

# 2. detect the encoding and convert to unicode 
content = toUnicode(raw)    # see my caricature for toUnicode below

# 3. pass unicode to beautiful soup. 
soup = BeautifulSoup(content)


def toUnicode(s):
    if type(s) is unicode:
        return s
    elif type(s) is str:
        d = chardet.detect(s)
        (cs, conf) = (d['encoding'], d['confidence'])
        if conf > 0.80:
            try:
                return s.decode( cs, errors = 'replace' )
            except Exception as ex:
                pass 
    # force and return only ascii subset
    return unicode(''.join( [ i if ord(i) < 128 else ' ' for i in s ]))

Вы можете рассуждать, что бы вы ни бросали, он всегда будет отправлять действительный юникод на bs.

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

Метод проб и ошибок не работает в коде - слишком много комбинаций: -)

2 голосов
/ 03 июля 2012

Первый ответ правильный, некоторые функции эффективны.

    def __if_number_get_string(number):
        converted_str = number
        if isinstance(number, int) or \
            isinstance(number, float):
                converted_str = str(number)
        return converted_str


    def get_unicode(strOrUnicode, encoding='utf-8'):
        strOrUnicode = __if_number_get_string(strOrUnicode)
        if isinstance(strOrUnicode, unicode):
            return strOrUnicode
        return unicode(strOrUnicode, encoding, errors='ignore')

    def get_string(strOrUnicode, encoding='utf-8'):
        strOrUnicode = __if_number_get_string(strOrUnicode)
        if isinstance(strOrUnicode, unicode):
            return strOrUnicode.encode(encoding)
        return strOrUnicode
...