LINUX.ORG.RU

Python3 + lxml.html + windows-1251 + xpath = а где русский-то?

 , ,


0

2

Окончательно обессилив, прошу помощи у сообщества! Получил html-страницу в кодировке windows-1251, сохранил её в базу Django в текстовое поле. Все операции на машине в utf-8. Затем с этим html хочу получить через xpath нужный мне кусок html. Получение простого текста получается отлично. А в html вместо русских символов - кракозявки или двухсимвольные штуки.

Нашел в сети рекомендацию как передать lxml «правильную» кодировку того, что ему дают, через BeautifulSoup. Без толку. :( Кодировку я и так знаю и работа этого кода без bs4 ничем не отличается по выводу на терминал. Текст в консоли полученный от text_content() печатается отлично. Но вот html - нет. Помогите!

from lxml import html, etree
from bs4 import UnicodeDammit
content = my_page_html
doc = UnicodeDammit(content, is_html=True)
parser = html.HTMLParser(encoding=doc.original_encoding)
root = html.document_fromstring(content, parser=parser)
page_elements = root.xpath(page_queue.page.xpath_content)
title = root.find('.//title').text_content()
print(title)  # тут все отображается отлично
print(etree.tostring(root, pretty_print=True))
# а тут вместо русского - мешанина, и добавление 
# , encoding='windows-1251'
# дает только изменение вида мешанины с козявок на двухсимвольные штуки

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

Сразу после получения контента переконверчивай его в утв-8, каким-нить iconv или что там у вас в джангу завезли.

deep-purple ★★★★★ ()
Ответ на: комментарий от deep-purple

А как это по-вашему должно выглядеть на чистом Питоне 3?

Получаю я данные с помощью библиотеки request.

Дальше мне надо полученному тексту сделать .encode('utf-8') или весь html документ переконвертировать и переписать ему метатеги на utf-8?

slobodyan ()

Странно, но без каких либо изменений исходный данных у меня прекрасно заработала следующая конструкция:

from grab import Grab
g = Grab(str(my_page_html).encode('cp1251'))
for element in g.doc.select(page_queue.page.xpath_content):
                print(element.html())
slobodyan ()
Ответ на: комментарий от slobodyan

Без понятия — я в пистон не умею. Ну а чисто архитектурно, нужно заточить алгоритм разбора под утф-8 и не париться. Ну а если через реквест прилетит что-то не в утф (там же может быть и не только цп-1251), то на это должен быть отдельный ф-ционал, который переконвертить во внутренний формат перед дальшейшими действиями.

deep-purple ★★★★★ ()

Если же отвлечься от unicodedammit и прочего, то есть такой нюанс:

козявки
двухсимвольные штуки

Потому что tostring хочет знать кодировку вывода. Если у тебя нет xml declaration, то дефолтом будет ascii, а в ascii все левые символы кодируются в «козявки». Не помню, что он считает за xml declaration в случае с html parser, проверять лень.

Defaults to ASCII encoding without XML declaration. This
behaviour can be configured with the keyword arguments 'encoding'
(string) and 'xml_declaration' (bool). Note that changing the
encoding to a non UTF-8 compatible encoding will enable a
declaration by default.

С указанной же кодировкой они кодируются как надо, но на выходе получается bytes, потому что python3. Если ты хочешь print, то тебе придётся полученный bytes декодировать обратно в string. Это на самом деле логичнее, чем кажется на первый взгляд. Почитай https://docs.python.org/3/howto/unicode.html, без этого никак.

Ну а вот тупой пример сходу:

% echo '<html><head><title>Кодировка cp1251 дико устарела, чувак</title></head></html>' | iconv -t cp1251 > /tmp/1251.html
from lxml import html, etree

with open('/tmp/1251.html', 'r', encoding='cp1251') as f:
    content = f.read()
    parser = html.HTMLParser()
    root = html.document_fromstring(content, parser=parser)
    title = root.find('.//title').text_content()
    print(title)
    print(etree.tostring(root, pretty_print=True, encoding='utf-8').decode('utf-8'))
% python enc.py
Кодировка cp1251 дико устарела, чувак
<html>
  <head>
    <title>Кодировка cp1251 дико устарела, чувак</title>
  </head>
</html>

Тащемта, сработает и с cp1251 в последней строке, это не важно.

risenshnobel ★★★ ()
Ответ на: комментарий от risenshnobel

Огромное спасибо за обстоятельный ответ!!

А как мне сделать то-же но не с байтовой последовательностью из файла, а с записью из таблицы, в которой все по-умолчанию в utf-8? Ну или просто с некоей строковой переменной, которая в ... utf, наверное. Вся локаль-то в UTF. Только страница эта в win-1251

slobodyan ()
Ответ на: комментарий от slobodyan

То есть в таблице битые данные и ты получил оттуда Unicode-строку, которая скастовалась в юникод с неверной кодировкой? Обратно переведи в bytes: .encode('utf-8'), потом в юникод: .decode('cp1251').

x3al ★★★★★ ()
Ответ на: комментарий от risenshnobel

Благодаря Вам всё получилось!!

Это из-за моего незнания азов. :(

Вот такой код отлично работает с моими строчками page_queue_cache.html (html код страницы) и page_queue.page.xpath_content (путь к контенту страницы):

from lxml import html, etree
            
parser = html.HTMLParser()
root = html.document_fromstring(page_queue_cache.html, parser=parser)
nodes = root.xpath(page_queue.page.xpath_content)
for node in nodes:
    print(etree.tostring(node, pretty_print=True, encoding='utf-8').decode('utf-8'))
slobodyan ()
Ответ на: комментарий от x3al

Вроде с нормальной, раз печатает в консоль её нормально. Перекодировки по Вашей формуле приводят к ошибкам по поводу неизвестного символа.

Плюс я заметил, что в выводе

print(etree.tostring(node, pretty_print=True, encoding='utf-8').decode('utf-8'))
регулярно присутствует некая кракозявка такого вида в конце некоторых строк:
<br/>&#13;

При работе с Grab такого не было.

slobodyan ()
Ответ на: комментарий от slobodyan

А как мне сделать то-же но не с байтовой последовательностью из файла, а с записью из таблицы, в которой все по-умолчанию в utf-8?

Библиотека для работы с БД обычно перекодирует bytes в str сама, и ничего декодировать не надо. Хотя проверить надо.

Ну или просто с некоей строковой переменной, которая в ... utf, наверное. Вся локаль-то в UTF.

Почитай ещё документацию, там всё немного не так. Внутри питона str кодируется в юникод, но не utf-8. Но это не важно, во что он там кодируется, суть в том, что есть некое внутреннее представление строк (тот самый str) и тип данных, который позволяет обмениваться данными с «внешним миром» (bytes). Из системы (файлы, сокеты и т.д.) ты получаешь bytes, далее декодируешь в str, далее, при записи, обратно делаешь bytes (тут я всё упрощаю, но не суть).

В некоторых случаях питон делает это за тебя, например, тот же open по-дефолту пытается декодировать данные, используя системную кодировку. Иногда это плохо, и надо указывать кодировку вручную. При выводе print в консоль кодирует сам на глаз, но иногда это неверно работает, и так далее.

В общем, там всё достаточно понятно, но в этом стоит разобраться.

risenshnobel ★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.