Данный пост посвящен извечной проблеме всех питонистов — кодировкам. Недавно я получил письмо, в котором мой знакомый жаловался на то, что у него в программе получаются строчки вида::
Вы заметили что что-то не так? И я вот. Строчки как бы уникодные, но внутри них закодированные utf-8 байты. Что-то здесь не так. Разбираясь дальше и потребовав скрипт, которые такое генерирует, становится понятно, что данные берутся из веба. Вполне обычным способом через
Вообще
Но тут немного другой случай. Тут используется парсер html. И именно в нем происходят эти неприятные метаморфозы со строками.
Я решил понять в чем же всё-таки и дело и как побороть такое поведение.
Для начала, я сходил на yandex.ru и посмотрел что за html там отдается. Кодировка контента utf8. Сразу что бросилось в глаза — это отсутствие декларации кодировки Он не обязателен, но всё же довольно часто используется. Сделав похожий html:
и засунув его в
s — это как раз и есть строчка «Привет мир», выдранная через xpath. Как видно, она в не раскодированном виде. По большому счету это проблему можно решить на месте. Есть такой специальный кодек raw-unicode-escape, который из такой строчки сделает байтовую но тоже без конвертации:
Но такое решение плохое. Надо как-то заставить
Что будет если указать кодировку в нелюбимом мною мета-заголовке html?
Всё сразу встаёт на свои места:
Конечно логичней было бы брать информацию о кодировке из http заголовков, но для lxml.html протокол по которому пришли данные загадка и он не может на него опираться.
Ещё один способ решения — это уже на вход lxml.html давать не байтовую строчку, а уникод (если вы конечно точно знаете кодировку сами):
На мой взгляд было бы правильней, чтобы
Будьте бдительны.
u'\xd0\x9a\xd1\x83\xd1\x80\xd1\x83\xd0\xbc\xd0\xbe\xd1\x87'
Вы заметили что что-то не так? И я вот. Строчки как бы уникодные, но внутри них закодированные utf-8 байты. Что-то здесь не так. Разбираясь дальше и потребовав скрипт, которые такое генерирует, становится понятно, что данные берутся из веба. Вполне обычным способом через
urllib
и потом скармливаются в lxml.html
для разбора. Поскольку urllib
оперирует только байтовыми строками, то он не мог их так превратить в уникод, а значит во всем виноват lxml
.Вообще
lxml
очень крутая библиотека — и быстрая, и функциональная, и умеет мимикрировать интерфейсом под ElementTree
, и взаимодействовать с BeatifulSoup
. Она давно уже пользуется популярностью у питонистов, когда надо как-то удобно работать с xml
.Но тут немного другой случай. Тут используется парсер html. И именно в нем происходят эти неприятные метаморфозы со строками.
Я решил понять в чем же всё-таки и дело и как побороть такое поведение.
Для начала, я сходил на yandex.ru и посмотрел что за html там отдается. Кодировка контента utf8. Сразу что бросилось в глаза — это отсутствие декларации кодировки Он не обязателен, но всё же довольно часто используется. Сделав похожий html:
data = """<html>
<head>
</head>
<body>Привет мир</body>
</html>"""
html = lxml.html.document_fromstring(data)
и засунув его в
lxml.html
, получил, увы, уже ожидаемый результат:>>> s
u'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82 \xd0\xbc\xd0\xb8\xd1\x80'
>>> print s
ÐÑÐ¸Ð²ÐµÑ Ð¼
s — это как раз и есть строчка «Привет мир», выдранная через xpath. Как видно, она в не раскодированном виде. По большому счету это проблему можно решить на месте. Есть такой специальный кодек raw-unicode-escape, который из такой строчки сделает байтовую но тоже без конвертации:
>>> print s.encode('raw-unicode-escape')
Привет мир
Но такое решение плохое. Надо как-то заставить
lxml.html
не издеваться над не-ASCII символами.Что будет если указать кодировку в нелюбимом мною мета-заголовке html?
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>Привет мир</body>
</html>
Всё сразу встаёт на свои места:
>>> print s
Привет мир
Конечно логичней было бы брать информацию о кодировке из http заголовков, но для lxml.html протокол по которому пришли данные загадка и он не может на него опираться.
Ещё один способ решения — это уже на вход lxml.html давать не байтовую строчку, а уникод (если вы конечно точно знаете кодировку сами):
>>> html = lxml.html.document_fromstring(data.decode('utf-8'))
...
>>> print s
Привет мир
На мой взгляд было бы правильней, чтобы
lxml.html
не пытался «выжить любой ценой» и портить контент, а явным образом сообщать о том что не задана кодировка — как кстати он же и поступает в случае разбора xml. Но в любом случае обходные пути есть.Будьте бдительны.