Документация Beautiful Soup
===========================
.. image:: 6.1.jpg
:align: right
:alt: "Лакей Карась начал с того, что вытащил из-под мышки огромный конверт (чуть ли не больше его самого)."
`Beautiful Soup The Dormouse's story Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well. ...
#
# The Dormouse's story
#
#
# Once upon a time there were three little sisters; and their names were
#
# Elsie
#
# ,
#
# Lacie
#
# and
#
# Tillie
#
# ; and they lived at the bottom of a well.
#
# ...
# The Dormouse's story Back to the homepage Back to the homepage The Dormouse's story Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well. ... The Dormouse's story " и так далее. Beautiful Soup предлагает инструменты для реконструирование
первоначального разбора документа.
.. _element-generators:
``.next_element`` и ``.previous_element``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Атрибут ``.next_element`` строки или тега указывает на то,
что было разобрано непосредственно после него. Это могло бы быть тем же, что и
``.next_sibling``, но обычно результат резко отличается.
Возьмем последний тег в фрагменте из «Алисы в стране чудес». Его
``.next_sibling`` является строкой: конец предложения, которое было
прервано началом тега ::
last_a_tag = soup.find("a", id="link3")
last_a_tag
# Tillie
last_a_tag.next_sibling
# '; and they lived at the bottom of a well.'
Но ``.next_element`` этого тега — это то, что было разобрано
сразу после тега , `не` остальная часть этого предложения:
это слово "Tillie"::
last_a_tag.next_element
# u'Tillie'
Это потому, что в оригинальной разметке слово «Tillie» появилось
перед точкой с запятой. Парсер обнаружил тег , затем
слово «Tillie», затем закрывающий тег , затем точку с запятой и оставшуюся
часть предложения. Точка с запятой находится на том же уровне, что и тег , но
слово «Tillie» встретилось первым.
Атрибут ``.previous_element`` является полной противоположностью
``.next_element``. Он указывает на элемент, который был встречен при разборе
непосредственно перед текущим::
last_a_tag.previous_element
# u' and\n'
last_a_tag.previous_element.next_element
# Tillie
``.next_elements`` и ``.previous_elements``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Вы уже должны были уловить идею. Вы можете использовать их для перемещения
вперед или назад по документу, в том порядке, в каком он был разобран парсером::
for element in last_a_tag.next_elements:
print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# ... The Dormouse's story Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well. ... ::
soup.find_all(has_class_but_no_id)
# [ The Dormouse's story Once upon a time there were... ... . Она не выбирает теги ,
поскольку в них определены и атрибут "class" , и атрибут "id". Она не выбирает
теги вроде и The Dormouse's story с CSS-классом "title"?
Давайте посмотрим на аргументы ``find_all()``.
.. _name:
Аргумент ``name``
^^^^^^^^^^^^^^^^^
Передайте значение для аргумента ``name``, и вы скажете Beautiful Soup
рассматривать только теги с определенными именами. Текстовые строки будут игнорироваться, так же как и
теги, имена которых не соответствуют заданным.
Вот простейший пример использования::
soup.find_all("title")
# [ The Dormouse's story Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well. является
непрямым родителем строки, и наш поиск тоже его
находит. Где-то в документе есть тег с классом CSS "title",
но он не является родительским для строки, так что мы не можем найти
его с помощью ``find_parents()``.
Вы могли заметить связь между ``find_parent()``,
``find_parents()`` и атрибутами `.parent`_ и `.parents`_,
упомянутыми ранее. Связь очень сильная. Эти методы поиска
на самом деле используют ``.parents``, чтобы перебрать все родительские элементы и проверить
каждый из них на соответствие заданному фильтру.
``find_next_siblings()`` и ``find_next_sibling()``
--------------------------------------------------
Сигнатура: find_next_siblings(:ref:`name ... The Dormouse's story ... , хотя он находится
в другой части дерева, чем тег , с которого мы начали. Для этих
методов имеет значение только то, что элемент соответствует фильтру и
появляется в документе позже, чем тот элемент, с которого начали поиск.
``find_all_previous()`` и ``find_previous()``
---------------------------------------------
Сигнатура: find_all_previous(:ref:`name Once upon a time there were three little sisters; ... The Dormouse's story , содержащий тег , с которого мы
начали. Это не так уж удивительно: мы смотрим на все теги,
которые появляются в документе раньше, чем тот, с которого мы начали. Тег
, содержащий тег , должен был появиться до тега , который
в нем содержится.
Селекторы CSS
-------------
Начиная с версии 4.7.0, Beautiful Soup поддерживает большинство селекторов CSS4 благодаря
проекту `SoupSieve
... I wish I was bold. I wish I was bold. A one A one, a two
# A one
# , a two
#
# A one, a two
# The law firm of Dewey, Cheatem, & Howe The law firm of Dewey, Cheatem, & Howe Il a dit <<Sacré bleu!>>
# Il a dit <<Sacré bleu!>>
#
# Il a dit <<Sacré bleu!>>
#
# Il a dit <
# IL A DIT <Extremely bold
Атрибуты
^^^^^^^^
У тега может быть любое количество атрибутов. Тег ```` имеет атрибут "id", значение которого равно
"boldest". Вы можете получить доступ к атрибутам тега, обращаясь с тегом как
со словарем::
tag['id']
# u'boldest'
Вы можете получить доступ к этому словарю напрямую как к ``.attrs``::
tag.attrs
# {u'id': 'boldest'}
Вы можете добавлять, удалять и изменять атрибуты тега. Опять же, это
делается путем обращения с тегом как со словарем::
tag['id'] = 'verybold'
tag['another-attribute'] = 1
tag
#
del tag['id']
del tag['another-attribute']
tag
#
tag['id']
# KeyError: 'id'
print(tag.get('id'))
# None
.. _multivalue:
Многозначные атрибуты
&&&&&&&&&&&&&&&&&&&&&
В HTML 4 определено несколько атрибутов, которые могут иметь множество значений. В HTML 5
пара таких атрибутов удалена, но определено еще несколько. Самый распространённый из
многозначных атрибутов — это ``class`` (т. е. тег может иметь более
одного класса CSS). Среди прочих ``rel``, ``rev``, ``accept-charset``,
``headers`` и ``accesskey``. Beautiful Soup представляет значение(я)
многозначного атрибута в виде списка::
css_soup = BeautifulSoup('')
css_soup.p['class']
# ["body"]
css_soup = BeautifulSoup('')
css_soup.p['class']
# ["body", "strikeout"]
Если атрибут `выглядит` так, будто он имеет более одного значения, но это не
многозначный атрибут, определенный какой-либо версией HTML-
стандарта, Beautiful Soup оставит атрибут как есть::
id_soup = BeautifulSoup('')
id_soup.p['id']
# 'my id'
Когда вы преобразовываете тег обратно в строку, несколько значений атрибута
объединяются::
rel_soup = BeautifulSoup('No longer bold
``NavigableString`` поддерживает большинство функций, описанных в
разделах `Навигация по дереву`_ и `Поиск по дереву`_, но
не все. В частности, поскольку строка не может ничего содержать (в том смысле,
в котором тег может содержать строку или другой тег), строки не поддерживают
атрибуты ``.contents`` и ``.string`` или метод ``find()``.
Если вы хотите использовать ``NavigableString`` вне Beautiful Soup,
вам нужно вызвать метод ``unicode()``, чтобы превратить ее в обычную для Python
строку Unicode. Если вы этого не сделаете, ваша строка будет тащить за собой
ссылку на все дерево разбора Beautiful Soup, даже когда вы
закончите использовать Beautiful Soup. Это большой расход памяти.
``BeautifulSoup``
-----------------
Объект ``BeautifulSoup`` представляет разобранный документ как единое
целое. В большинстве случаев вы можете рассматривать его как объект
:ref:`Tag`. Это означает, что он поддерживает большинство методов, описанных
в разделах `Навигация по дереву`_ и `Поиск по дереву`_.
Вы также можете передать объект ``BeautifulSoup`` в один из методов,
перечисленных в разделе `Изменение дерева`_, по аналогии с передачей объекта :ref:`Tag`. Это
позволяет вам делать такие вещи, как объединение двух разобранных документов::
doc = BeautifulSoup("Extremely bold
del tag['class']
del tag['id']
tag
# Extremely bold
Изменение ``.string``
---------------------
Если вы замените значение атрибута ``.string`` новой строкой, содержимое тега будет
заменено на эту строку::
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
tag = soup.a
tag.string = "New link text."
tag
# New link text.
Будьте осторожны: если тег содержит другие теги, они и все их
содержимое будет уничтожено.
``append()``
------------
Вы можете добавить содержимое тега с помощью ``Tag.append()``. Это работает
точно так же, как ``.append()`` для списка в Python::
soup = BeautifulSoup("Foo")
soup.a.append("Bar")
soup
# FooBar
soup.a.contents
# [u'Foo', u'Bar']
``extend()``
------------
Начиная с версии Beautiful Soup 4.7.0, ``Tag`` также поддерживает метод
``.extend()``, который работает так же, как вызов ``.extend()`` для
списка в Python::
soup = BeautifulSoup("Soup")
soup.a.extend(["'s", " ", "on"])
soup
# Soup's on
soup.a.contents
# [u'Soup', u''s', u' ', u'on']
``NavigableString()`` и ``.new_tag()``
--------------------------------------
Если вам нужно добавить строку в документ, нет проблем — вы можете передать
строку Python в ``append()`` или вызвать
конструктор ``NavigableString``::
soup = BeautifulSoup("")
tag = soup.b
tag.append("Hello")
new_string = NavigableString(" there")
tag.append(new_string)
tag
# Hello there.
tag.contents
# [u'Hello', u' there']
Если вы хотите создать комментарий или другой подкласс
``NavigableString``, просто вызовите конструктор::
from bs4 import Comment
new_comment = Comment("Nice to see you.")
tag.append(new_comment)
tag
# Hello there
tag.contents
# [u'Hello', u' there', u'Nice to see you.']
(Это новая функция в Beautiful Soup 4.4.0.)
Что делать, если вам нужно создать совершенно новый тег? Наилучшим решением будет
вызвать фабричный метод ``BeautifulSoup.new_tag()``::
soup = BeautifulSoup("")
original_tag = soup.b
new_tag = soup.new_tag("a", href="http://www.example.com")
original_tag.append(new_tag)
original_tag
#
new_tag.string = "Link text."
original_tag
# Link text.
Нужен только первый аргумент, имя тега.
``insert()``
------------
``Tag.insert()`` похож на ``Tag.append()``, за исключением того, что новый элемент
не обязательно добавляется в конец родительского
``.contents``. Он добавится в любое место, номер которого
вы укажете. Это работает в точности как ``.insert()`` в списке Python::
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
tag = soup.a
tag.insert(1, "but did not endorse ")
tag
# I linked to but did not endorse example.com
tag.contents
# [u'I linked to ', u'but did not endorse', example.com]
``insert_before()`` и ``insert_after()``
----------------------------------------
Метод ``insert_before()`` вставляет теги или строки непосредственно
перед чем-то в дереве разбора::
soup = BeautifulSoup("stop")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.string.insert_before(tag)
soup.b
# Don'tstop
Метод ``insert_after()`` вставляет теги или строки непосредственно
после чего-то в дереве разбора::
div = soup.new_tag('div')
div.string = 'ever'
soup.b.i.insert_after(" you ", div)
soup.b
# Don't you
")
print(soup.encode(formatter="html"))
#
print(soup.encode(formatter="html5"))
#
Если вы передадите ``formatter=None``, Beautiful Soup вообще не будет менять
строки на выходе. Это самый быстрый вариант, но он может привести
к тому, что Beautiful Soup будет генерировать невалидный HTML / XML::
print(soup.prettify(formatter=None))
#
#
#
. Этот парсер также добавляет пустой тег
в документ. Вот тот же документ, разобранный с помощью встроенного в Python парсера HTML:: BeautifulSoup("", "html.parser") # Как и html5lib, этот парсер игнорирует закрывающий тег . В отличие от html5lib, этот парсер не делает попытки создать правильно оформленный HTML- документ, добавив тег . В отличие от lxml, он даже не добавляет тег . Поскольку документ ```` невалиден, ни один из этих способов нельзя назвать "правильным". Парсер html5lib использует способы, которые являются частью стандарта HTML5, поэтому он может претендовать на то, что его подход самый "правильный", но правомерно использовать любой из трех методов. Различия между парсерами могут повлиять на ваш скрипт. Если вы планируете распространять ваш скрипт или запускать его на нескольких машинах, вам нужно указать парсер в конструкторе ``BeautifulSoup``. Это уменьшит вероятность того, что ваши пользователи при разборе документа получат результат, отличный от вашего. Кодировки ========= Любой документ HTML или XML написан в определенной кодировке, такой как ASCII или UTF-8. Но когда вы загрузите этот документ в Beautiful Soup, вы обнаружите, что он был преобразован в Unicode:: markup = "Sacr\xe9 bleu!
''' soup = BeautifulSoup(markup) print(soup.prettify()) # # # # # ## Sacré bleu! #
# # Обратите внимание, что тег был переписан, чтобы отразить тот факт, что теперь документ кодируется в UTF-8. Если вы не хотите кодировку UTF-8, вы можете передать другую в ``prettify()``:: print(soup.prettify("latin-1")) # # # # ... Вы также можете вызвать encode() для объекта ``BeautifulSoup`` или любого элемента в супе, как если бы это была строка Python:: soup.p.encode("latin-1") # 'Sacr\xe9 bleu!
' soup.p.encode("utf-8") # 'Sacr\xc3\xa9 bleu!
' Любые символы, которые не могут быть представлены в выбранной вами кодировке, будут преобразованы в числовые коды мнемоник XML. Вот документ, который включает в себя Unicode-символ SNOWMAN (снеговик):: markup = u"\N{SNOWMAN}" snowman_soup = BeautifulSoup(markup) tag = snowman_soup.b Символ SNOWMAN может быть частью документа UTF-8 (он выглядит так: ☃), но в ISO-Latin-1 или ASCII нет представления для этого символа, поэтому для этих кодировок он конвертируется в "☃": print(tag.encode("utf-8")) # ☃ print tag.encode("latin-1") # ☃ print tag.encode("ascii") # ☃ Unicode, Dammit --------------- Вы можете использовать Unicode, Dammit без Beautiful Soup. Он полезен в тех случаях. когда у вас есть данные в неизвестной кодировке, и вы просто хотите, чтобы они преобразовались в Unicode:: from bs4 import UnicodeDammit dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!") print(dammit.unicode_markup) # Sacré bleu! dammit.original_encoding # 'utf-8' Догадки Unicode, Dammit станут намного точнее, если вы установите библиотеки Python ``chardet`` или ``cchardet``. Чем больше данных вы даете Unicode, Dammit, тем точнее он определит кодировку. Если у вас есть собственные предположения относительно возможных кодировок, вы можете передать их в виде списка:: dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"]) print(dammit.unicode_markup) # Sacré bleu! dammit.original_encoding # 'latin-1' В Unicode, Dammit есть две специальные функции, которые Beautiful Soup не использует. Парные кавычки ^^^^^^^^^^^^^^ Вы можете использовать Unicode, Dammit, чтобы конвертировать парные кавычки (Microsoft smart quotes) в мнемоники HTML или XML:: markup = b"I just \x93love\x94 Microsoft Word\x92s smart quotes
" UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup # u'I just “love” Microsoft Word’s smart quotes
' UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup # u'I just “love” Microsoft Word’s smart quotes
' Вы также можете конвертировать парные кавычки в обычные кавычки ASCII:: UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup # u'I just "love" Microsoft Word\'s smart quotes
' Надеюсь, вы найдете эту функцию полезной, но Beautiful Soup не использует ее. Beautiful Soup по умолчанию конвертирует парные кавычки в символы Unicode, как и все остальное:: UnicodeDammit(markup, ["windows-1252"]).unicode_markup # u'I just \u201clove\u201d Microsoft Word\u2019s smart quotes
' Несогласованные кодировки ^^^^^^^^^^^^^^^^^^^^^^^^^ Иногда документ кодирован в основном в UTF-8, но содержит символы Windows-1252, такие как, опять-таки, парные кавычки. Такое бывает, когда веб-сайт содержит данные из нескольких источников. Вы можете использовать ``UnicodeDammit.detwingle()``, чтобы превратить такой документ в чистый UTF-8. Вот простой пример:: snowmen = (u"\N{SNOWMAN}" * 3) quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}") doc = snowmen.encode("utf8") + quote.encode("windows_1252") В этом документе бардак. Снеговики в UTF-8, а парные кавычки в Windows-1252. Можно отображать или снеговиков, или кавычки, но не то и другое одновременно:: print(doc) # ☃☃☃�I like snowmen!� print(doc.decode("windows-1252")) # ☃☃☃“I like snowmen!” Декодирование документа как UTF-8 вызывает ``UnicodeDecodeError``, а декодирование его как Windows-1252 выдаст тарабарщину. К счастью, ``UnicodeDammit.detwingle()`` преобразует строку в чистый UTF-8, позволяя затем декодировать его в Unicode и отображать снеговиков и кавычки одновременно:: new_doc = UnicodeDammit.detwingle(doc) print(new_doc.decode("utf8")) # ☃☃☃“I like snowmen!” ``UnicodeDammit.detwingle()`` знает только, как обрабатывать Windows-1252, встроенный в UTF-8 (и наоборот, мне кажется), но это наиболее общий случай. Обратите внимание, что нужно вызывать ``UnicodeDammit.detwingle()`` для ваших данных перед передачей в конструктор ``BeautifulSoup`` или ``UnicodeDammit``. Beautiful Soup предполагает, что документ имеет единую кодировку, какой бы она ни была. Если вы передадите ему документ, который содержит как UTF-8, так и Windows-1252, скорее всего, он решит, что весь документ кодируется в Windows-1252, и это будет выглядеть как ``☃☃☃“I like snowmen!”``. ``UnicodeDammit.detwingle()`` — это новое в Beautiful Soup 4.1.0. Нумерация строк =============== Парсеры ``html.parser`` и ``html5lib`` могут отслеживать, где в исходном документе был найден каждый тег. Вы можете получить доступ к этой информации через ``Tag.sourceline`` (номер строки) и ``Tag.sourcepos`` (позиция начального тега в строке):: markup = "Paragraph 1
\nParagraph 2
" soup = BeautifulSoup(markup, 'html.parser') for tag in soup.find_all('p'): print(tag.sourceline, tag.sourcepos, tag.string) # (1, 0, u'Paragraph 1') # (2, 3, u'Paragraph 2') Обратите внимание, что два парсера понимают ``sourceline`` и ``sourcepos`` немного по-разному. Для html.parser эти числа представляет позицию начального знака "<". Для html5lib эти числа представляют позицию конечного знака ">":: soup = BeautifulSoup(markup, 'html5lib') for tag in soup.find_all('p'): print(tag.sourceline, tag.sourcepos, tag.string) # (2, 1, u'Paragraph 1') # (3, 7, u'Paragraph 2') Вы можете отключить эту функцию, передав ``store_line_numbers = False`` в конструктор ``BeautifulSoup``:: markup = "Paragraph 1
\nParagraph 2
" soup = BeautifulSoup(markup, 'html.parser', store_line_numbers=False) soup.p.sourceline # None Эта функция является новой в 4.8.1, и парсеры, основанные на lxml, не поддерживают ее. Проверка объектов на равенство ============================== Beautiful Soup считает, что два объекта ``NavigableString`` или ``Tag`` равны, если они представлены в одинаковой разметке HTML или XML. В этом примере два тега рассматриваются как равные, даже если они находятся в разных частях дерева объекта, потому что они оба выглядят как ``pizza``:: markup = "I want pizza and more pizza!
" soup = BeautifulSoup(markup, 'html.parser') first_b, second_b = soup.find_all('b') print first_b == second_b # True print first_b.previous_element == second_b.previous_element # False Если вы хотите выяснить, указывают ли две переменные на один и тот же объект, используйте `is`:: print first_b is second_b # False Копирование объектов Beautiful Soup =================================== Вы можете использовать ``copy.copy()`` для создания копии любого ``Tag`` или ``NavigableString``:: import copy p_copy = copy.copy(soup.p) print p_copy #I want pizza and more pizza!
Копия считается равной оригиналу, так как у нее такая же разметка, что и у оригинала, но это другой объект:: print soup.p == p_copy # True print soup.p is p_copy # False Единственная настоящая разница в том, что копия полностью отделена от исходного дерева объекта Beautiful Soup, как если бы в отношении нее вызвали метод ``extract()``:: print p_copy.parent # None Это потому, что два разных объекта ``Tag`` не могут занимать одно и то же пространство в одно и то же время. Разбор части документа ====================== Допустим, вы хотите использовать Beautiful Soup, чтобы посмотреть на теги в документе. Было бы бесполезной тратой времени и памяти разобирать весь документ и затем снова проходить по нему в поисках тегов . Намного быстрее изначательно игнорировать все, что не является тегом . Класс ``SoupStrainer`` позволяет выбрать, какие части входящего документ разбирать. Вы просто создаете ``SoupStrainer`` и передаете его в конструктор ``BeautifulSoup`` в качестве аргумента ``parse_only``. (Обратите внимание, что *эта функция не будет работать, если вы используете парсер html5lib*. Если вы используете html5lib, будет разобран весь документ, независимо от обстоятельств. Это потому что html5lib постоянно переставляет части дерева разбора в процессе работы, и если какая-то часть документа не попала в дерево разбора, все рухнет. Чтобы избежать путаницы, в примерах ниже я принудительно использую встроенный в Python парсер HTML.) ``SoupStrainer`` ---------------- Класс ``SoupStrainer`` принимает те же аргументы, что и типичный метод из раздела `Поиск по дереву`_: :ref:`nameThe Dormouse's story
Once upon a time there were three little sisters; and their names were Elsie, Lacie and Tillie; and they lived at the bottom of a well.
...
""" print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify()) # # Elsie # # # Lacie # # # Tillie # print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify()) # # Lacie # print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify()) # Elsie # , # Lacie # and # Tillie # ... # Вы также можете передать ``SoupStrainer`` в любой из методов. описанных в разделе `Поиск по дереву`_. Может, это не безумно полезно, но я решил упомянуть:: soup = BeautifulSoup(html_doc) soup.find_all(only_short_strings) # [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie', # u'\n\n', u'...', u'\n'] Устранение неисправностей ========================= .. _diagnose: ``diagnose()`` -------------- Если у вас возникли проблемы с пониманием того, что Beautiful Soup делает с документом, передайте документ в функцию ``Diagnose()``. (Новое в Beautiful Soup 4.2.0.) Beautiful Soup выведет отчет, показывающий, как разные парсеры обрабатывают документ, и сообщит вам, если отсутствует парсер, который Beautiful Soup мог бы использовать:: from bs4.diagnose import diagnose with open("bad.html") as fp: data = fp.read() diagnose(data) # Diagnostic running on Beautiful Soup 4.2.0 # Python version 2.7.3 (default, Aug 1 2012, 05:16:07) # I noticed that html5lib is not installed. Installing it may help. # Found lxml version 2.3.2.0 # # Trying to parse your data with html.parser # Here's what html.parser did with the document: # ... Простой взгляд на вывод diagnose() может показать, как решить проблему. Если это и не поможет, вы можете скопировать вывод ``Diagnose()``, когда обратитесь за помощью. Ошибки при разборе документа ---------------------------- Существует два вида ошибок разбора. Есть сбои, когда вы подаете документ в Beautiful Soup, и это поднимает исключение, обычно ``HTMLParser.HTMLParseError``. И есть неожиданное поведение, когда дерево разбора Beautiful Soup сильно отличается от документа, использованного для создания дерева. Практически никогда источником этих проблемы не бывает Beautiful Soup. Это не потому, что Beautiful Soup так прекрасно написан. Это потому, что Beautiful Soup не содержит кода, который бы разбирал документ. Beautiful Soup опирается на внешние парсеры. Если один парсер не подходит для разбора документа, лучшим решением будет попробовать другой парсер. В разделе `Установка парсера`_ вы найдете больше информации и таблицу сравнения парсеров. Наиболее распространенные ошибки разбора — это ``HTMLParser.HTMLParseError: malformed start tag`` и ``HTMLParser.HTMLParseError: bad end tag``. Они оба генерируются встроенным в Python парсером HTML, и решением будет :ref:`установить lxml или html5lib.