1
Документация Beautiful Soup
2
===========================
6
:alt: "Лакей Карась начал с того, что вытащил из-под мышки огромный конверт (чуть ли не больше его самого)."
8
`Beautiful Soup <http://www.crummy.com/software/BeautifulSoup/>`_ — это
9
библиотека Python для извлечения данных из файлов HTML и XML. Она работает
10
с вашим любимым парсером, чтобы дать вам естественные способы навигации,
11
поиска и изменения дерева разбора. Она обычно экономит программистам
14
Эти инструкции иллюстрируют все основные функции Beautiful Soup 4
15
на примерах. Я покажу вам, для чего нужна библиотека, как она работает,
16
как ее использовать, как заставить ее делать то, что вы хотите, и что нужно делать, когда она
17
не оправдывает ваши ожидания.
19
Примеры в этой документации работают одинаково на Python 2.7
22
Возможно, вы ищете документацию для `Beautiful Soup 3
23
<http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html>`_.
24
Если это так, имейте в виду, что Beautiful Soup 3 больше не
25
развивается, и что поддержка этой версии будет прекращена
26
31 декабря 2020 года или немногим позже. Если вы хотите узнать о различиях между Beautiful Soup 3
27
и Beautiful Soup 4, читайте раздел `Перенос кода на BS4`_.
29
Эта документация переведена на другие языки
30
пользователями Beautiful Soup:
32
* `这篇文档当然还有中文版. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/>`_
33
* このページは日本語で利用できます(`外部リンク <http://kondou.com/BS4/>`_)
34
* `이 문서는 한국어 번역도 가능합니다. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.ko/>`_
35
* `Este documento também está disponível em Português do Brasil. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.ptbr/>`_
41
Если у вас есть вопросы о Beautiful Soup или возникли проблемы,
42
`отправьте сообщение в дискуссионную группу
43
<https://groups.google.com/forum/?fromgroups#!forum/beautifulsoup>`_. Если
44
ваша проблема связана с разбором HTML-документа, не забудьте упомянуть,
45
:ref:`что говорит о нем функция diagnose() <diagnose>`.
50
Вот HTML-документ, который я буду использовать в качестве примера в этой
51
документации. Это фрагмент из `«Алисы в стране чудес»`::
54
<html><head><title>The Dormouse's story</title></head>
56
<p class="title"><b>The Dormouse's story</b></p>
58
<p class="story">Once upon a time there were three little sisters; and their names were
59
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
60
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
61
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
62
and they lived at the bottom of a well.</p>
64
<p class="story">...</p>
67
Прогон документа через Beautiful Soup дает нам
68
объект ``BeautifulSoup``, который представляет документ в виде
69
вложенной структуры данных::
71
from bs4 import BeautifulSoup
72
soup = BeautifulSoup (html_doc, 'html.parser')
74
print(soup.prettify())
78
# The Dormouse's story
84
# The Dormouse's story
88
# Once upon a time there were three little sisters; and their names were
89
# <a class="sister" href="http://example.com/elsie" id="link1">
93
# <a class="sister" href="http://example.com/lacie" id="link2">
97
# <a class="sister" href="http://example.com/tillie" id="link3">
100
# ; and they lived at the bottom of a well.
108
Вот несколько простых способов навигации по этой структуре данных::
111
# <title>The Dormouse's story</title>
117
# u'The Dormouse's story'
119
soup.title.parent.name
123
# <p class="title"><b>The Dormouse's story</b></p>
129
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
132
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
133
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
134
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
136
soup.find(id="link3")
137
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
139
Одна из распространенных задач — извлечь все URL-адреса, найденные на странице в тегах <a>::
141
for link in soup.find_all('a'):
142
print(link.get('href'))
143
# http://example.com/elsie
144
# http://example.com/lacie
145
# http://example.com/tillie
147
Другая распространенная задача — извлечь весь текст со страницы::
149
print(soup.get_text())
150
# The Dormouse's story
152
# The Dormouse's story
154
# Once upon a time there were three little sisters; and their names were
158
# and they lived at the bottom of a well.
162
Это похоже на то, что вам нужно? Если да, продолжайте читать.
164
Установка Beautiful Soup
165
========================
167
Если вы используете последнюю версию Debian или Ubuntu Linux, вы можете
168
установить Beautiful Soup с помощью системы управления пакетами:
170
:kbd:`$ apt-get install python-bs4` (для Python 2)
172
:kbd:`$ apt-get install python3-bs4` (для Python 3)
174
Beautiful Soup 4 публикуется через PyPi, поэтому, если вы не можете установить библиотеку
175
с помощью системы управления пакетами, можно установить с помощью ``easy_install`` или
176
``pip``. Пакет называется ``beautifulsoup4``. Один и тот же пакет
177
работает как на Python 2, так и на Python 3. Убедитесь, что вы используете версию
178
``pip`` или ``easy_install``, предназначенную для вашей версии Python (их можно назвать
179
``pip3`` и ``easy_install3`` соответственно, если вы используете Python 3).
181
:kbd:`$ easy_install beautifulsoup4`
183
:kbd:`$ pip install beautifulsoup4`
185
(``BeautifulSoup`` — это, скорее всего, `не тот` пакет, который вам нужен. Это
186
предыдущий основной релиз, `Beautiful Soup 3`_. Многие программы используют
187
BS3, так что он все еще доступен, но если вы пишете новый код,
188
нужно установить ``beautifulsoup4``.)
190
Если у вас не установлены ``easy_install`` или ``pip``, вы можете
191
`скачать архив с исходным кодом Beautiful Soup 4
192
<http://www.crummy.com/software/BeautifulSoup/download/4.x/>`_ и
193
установить его с помощью ``setup.py``.
195
:kbd:`$ python setup.py install`
197
Если ничего не помогает, лицензия на Beautiful Soup позволяет
198
упаковать библиотеку целиком вместе с вашим приложением. Вы можете скачать
199
tar-архив, скопировать из него в кодовую базу вашего приложения каталог ``bs4``
200
и использовать Beautiful Soup, не устанавливая его вообще.
202
Я использую Python 2.7 и Python 3.2 для разработки Beautiful Soup, но библиотека
203
должна работать и с более поздними версиями Python.
205
Проблемы после установки
206
------------------------
208
Beautiful Soup упакован как код Python 2. Когда вы устанавливаете его для
209
использования с Python 3, он автоматически конвертируется в код Python 3. Если
210
вы не устанавливаете библиотеку в виде пакета, код не будет сконвертирован. Были
211
также сообщения об установке неправильной версии на компьютерах с
214
Если выводится сообщение ``ImportError`` "No module named HTMLParser", ваша
215
проблема в том, что вы используете версию кода на Python 2, работая на
218
Если выводится сообщение ``ImportError`` "No module named html.parser", ваша
219
проблема в том, что вы используете версию кода на Python 3, работая на
222
В обоих случаях лучше всего полностью удалить Beautiful
223
Soup с вашей системы (включая любой каталог, созданный
224
при распаковке tar-архива) и запустить установку еще раз.
226
Если выводится сообщение ``SyntaxError`` "Invalid syntax" в строке
227
``ROOT_TAG_NAME = u'[document]'``, вам нужно конвертировать код из Python 2
228
в Python 3. Вы можете установить пакет:
230
:kbd:`$ python3 setup.py install`
232
или запустить вручную Python-скрипт ``2to3``
235
:kbd:`$ 2to3-3.2 -w bs4`
237
.. _parser-installation:
243
Beautiful Soup поддерживает парсер HTML, включенный в стандартную библиотеку Python,
244
а также ряд сторонних парсеров на Python.
245
Одним из них является `парсер lxml <http://lxml.de/>`_. В зависимости от ваших настроек,
246
вы можете установить lxml с помощью одной из следующих команд:
248
:kbd:`$ apt-get install python-lxml`
250
:kbd:`$ easy_install lxml`
252
:kbd:`$ pip install lxml`
254
Другая альтернатива — написанный исключительно на Python `парсер html5lib
255
<http://code.google.com/p/html5lib/>`_, который разбирает HTML таким же образом,
256
как это делает веб-браузер. В зависимости от ваших настроек, вы можете установить html5lib
257
с помощью одной из этих команд:
259
:kbd:`$ apt-get install python-html5lib`
261
:kbd:`$ easy_install html5lib`
263
:kbd:`$ pip install html5lib`
265
Эта таблица суммирует преимущества и недостатки каждого парсера:
267
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
268
| Парсер | Типичное использование | Преимущества | Недостатки |
269
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
270
| html.parser от Python| ``BeautifulSoup(markup, "html.parser")`` | * Входит в комплект | * Не такой быстрый, как |
271
| | | * Приличная скорость | lxml, более строгий, |
272
| | | * Нестрогий (по крайней мере, | чем html5lib. |
273
| | | в Python 2.7.3 и 3.2.) | |
274
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
275
| HTML-парсер в lxml | ``BeautifulSoup(markup, "lxml")`` | * Очень быстрый | * Внешняя зависимость |
276
| | | * Нестрогий | от C |
277
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
278
| XML-парсер в lxml | ``BeautifulSoup(markup, "lxml-xml")`` | * Очень быстрый | * Внешняя зависимость |
279
| | ``BeautifulSoup(markup, "xml")`` | * Единственный XML-парсер, | от C |
280
| | | который сейчас поддерживается| |
281
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
282
| html5lib | ``BeautifulSoup(markup, "html5lib")`` | * Очень нестрогий | * Очень медленный |
283
| | | * Разбирает страницы так же, | * Внешняя зависимость |
284
| | | как это делает браузер | от Python |
285
| | | * Создает валидный HTML5 | |
286
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
288
Я рекомендую по возможности установить и использовать lxml для быстродействия. Если вы
289
используете версию Python 2 более раннюю, чем 2.7.3, или версию Python 3
290
более раннюю, чем 3.2.2, `необходимо` установить lxml или
291
html5lib, потому что встроенный в Python парсер HTML просто недостаточно хорош в старых
294
Обратите внимание, что если документ невалиден, различные парсеры будут генерировать
295
дерево Beautiful Soup для этого документа по-разному. Ищите подробности в разделе `Различия
301
Чтобы разобрать документ, передайте его в
302
конструктор ``BeautifulSoup``. Вы можете передать строку или открытый дескриптор файла::
304
from bs4 import BeautifulSoup
306
with open("index.html") as fp:
307
soup = BeautifulSoup(fp)
309
soup = BeautifulSoup("<html>data</html>")
311
Первым делом документ конвертируется в Unicode, а HTML-мнемоники
312
конвертируются в символы Unicode::
314
BeautifulSoup("Sacré bleu!")
315
<html><head></head><body>Sacré bleu!</body></html>
317
Затем Beautiful Soup анализирует документ, используя лучший из доступных
318
парсеров. Библиотека будет использовать HTML-парсер, если вы явно не укажете,
319
что нужно использовать XML-парсер. (См. `Разбор XML`_.)
324
Beautiful Soup превращает сложный HTML-документ в сложное дерево
325
объектов Python. Однако вам придется иметь дело только с четырьмя
326
`видами` объектов: ``Tag``, ``NavigableString``, ``BeautifulSoup``
334
Объект ``Tag`` соответствует тегу XML или HTML в исходном документе::
336
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
339
# <class 'bs4.element.Tag'>
341
У объекта Tag (далее «тег») много атрибутов и методов, и я расскажу о большинстве из них
342
в разделах `Навигация по дереву`_ и `Поиск по дереву`_. На данный момент наиболее
343
важными особенностями тега являются его имя и атрибуты.
348
У каждого тега есть имя, доступное как ``.name``::
353
Если вы измените имя тега, это изменение будет отражено в любой HTML-
354
разметке, созданной Beautiful Soup::
356
tag.name = "blockquote"
358
# <blockquote class="boldest">Extremely bold</blockquote>
363
У тега может быть любое количество атрибутов. Тег ``<b
364
id = "boldest">`` имеет атрибут "id", значение которого равно
365
"boldest". Вы можете получить доступ к атрибутам тега, обращаясь с тегом как
371
Вы можете получить доступ к этому словарю напрямую как к ``.attrs``::
376
Вы можете добавлять, удалять и изменять атрибуты тега. Опять же, это
377
делается путем обращения с тегом как со словарем::
379
tag['id'] = 'verybold'
380
tag['another-attribute'] = 1
382
# <b another-attribute="1" id="verybold"></b>
385
del tag['another-attribute']
396
Многозначные атрибуты
397
&&&&&&&&&&&&&&&&&&&&&
399
В HTML 4 определено несколько атрибутов, которые могут иметь множество значений. В HTML 5
400
пара таких атрибутов удалена, но определено еще несколько. Самый распространённый из
401
многозначных атрибутов — это ``class`` (т. е. тег может иметь более
402
одного класса CSS). Среди прочих ``rel``, ``rev``, ``accept-charset``,
403
``headers`` и ``accesskey``. Beautiful Soup представляет значение(я)
404
многозначного атрибута в виде списка::
406
css_soup = BeautifulSoup('<p class="body"></p>')
410
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
412
# ["body", "strikeout"]
414
Если атрибут `выглядит` так, будто он имеет более одного значения, но это не
415
многозначный атрибут, определенный какой-либо версией HTML-
416
стандарта, Beautiful Soup оставит атрибут как есть::
418
id_soup = BeautifulSoup('<p id="my id"></p>')
422
Когда вы преобразовываете тег обратно в строку, несколько значений атрибута
425
rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
428
rel_soup.a['rel'] = ['index', 'contents']
430
# <p>Back to the <a rel="index contents">homepage</a></p>
432
Вы можете отключить объединение, передав ``multi_valued_attributes = None`` в качестве
433
именованного аргумента в конструктор ``BeautifulSoup``::
435
no_list_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html', multi_valued_attributes=None)
436
no_list_soup.p['class']
439
Вы можете использовать ``get_attribute_list``, того чтобы получить значение в виде списка,
440
независимо от того, является ли атрибут многозначным или нет::
442
id_soup.p.get_attribute_list('id')
445
Если вы разбираете документ как XML, многозначных атрибутов не будет::
447
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
451
Опять же, вы можете поменять настройку, используя аргумент ``multi_valued_attributes``::
453
class_is_multi= { '*' : 'class'}
454
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml', multi_valued_attributes=class_is_multi)
456
# [u'body', u'strikeout']
458
Вряд ли вам это пригодится, но если все-таки будет нужно, руководствуйтесь значениями
459
по умолчанию. Они реализуют правила, описанные в спецификации HTML::
461
from bs4.builder import builder_registry
462
builder_registry.lookup('html').DEFAULT_CDATA_LIST_ATTRIBUTES
468
Строка соответствует фрагменту текста в теге. Beautiful Soup
469
использует класс ``NavigableString`` для хранения этих фрагментов текста::
474
# <class 'bs4.element.NavigableString'>
476
``NavigableString`` похожа на строку Unicode в Python, не считая того,
477
что она также поддерживает некоторые функции, описанные в
478
разделах `Навигация по дереву`_ и `Поиск по дереву`_. Вы можете конвертировать
479
``NavigableString`` в строку Unicode с помощью ``unicode()``::
481
unicode_string = unicode(tag.string)
487
Вы не можете редактировать строку непосредственно, но вы можете заменить одну строку
488
другой, используя :ref:`replace_with()`::
490
tag.string.replace_with("No longer bold")
492
# <blockquote>No longer bold</blockquote>
494
``NavigableString`` поддерживает большинство функций, описанных в
495
разделах `Навигация по дереву`_ и `Поиск по дереву`_, но
496
не все. В частности, поскольку строка не может ничего содержать (в том смысле,
497
в котором тег может содержать строку или другой тег), строки не поддерживают
498
атрибуты ``.contents`` и ``.string`` или метод ``find()``.
500
Если вы хотите использовать ``NavigableString`` вне Beautiful Soup,
501
вам нужно вызвать метод ``unicode()``, чтобы превратить ее в обычную для Python
502
строку Unicode. Если вы этого не сделаете, ваша строка будет тащить за собой
503
ссылку на все дерево разбора Beautiful Soup, даже когда вы
504
закончите использовать Beautiful Soup. Это большой расход памяти.
509
Объект ``BeautifulSoup`` представляет разобранный документ как единое
510
целое. В большинстве случаев вы можете рассматривать его как объект
511
:ref:`Tag`. Это означает, что он поддерживает большинство методов, описанных
512
в разделах `Навигация по дереву`_ и `Поиск по дереву`_.
514
Вы также можете передать объект ``BeautifulSoup`` в один из методов,
515
перечисленных в разделе `Изменение дерева`_, по аналогии с передачей объекта :ref:`Tag`. Это
516
позволяет вам делать такие вещи, как объединение двух разобранных документов::
518
doc = BeautifulSoup("<document><content/>INSERT FOOTER HERE</document", "xml")
519
footer = BeautifulSoup("<footer>Here's the footer</footer>", "xml")
520
doc.find(text="INSERT FOOTER HERE").replace_with(footer)
521
# u'INSERT FOOTER HERE'
523
# <?xml version="1.0" encoding="utf-8"?>
524
# <document><content/><footer>Here's the footer</footer></document>
526
Поскольку объект ``BeautifulSoup`` не соответствует действительному
527
HTML- или XML-тегу, у него нет имени и атрибутов. Однако иногда
528
бывает полезно взглянуть на ``.name`` объекта ``BeautifulSoup``, поэтому ему было присвоено специальное «имя»
529
``.name`` "[document]"::
534
Комментарии и другие специфичные строки
535
---------------------------------------
537
``Tag``, ``NavigableString`` и ``BeautifulSoup`` охватывают почти
538
все, с чем вы столкнётесь в файле HTML или XML, но осталось
539
ещё немного. Пожалуй, единственное, о чем стоит волноваться,
542
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
543
soup = BeautifulSoup(markup)
544
comment = soup.b.string
546
# <class 'bs4.element.Comment'>
548
Объект ``Comment`` — это просто особый тип ``NavigableString``::
551
# u'Hey, buddy. Want to buy a used parser'
553
Но когда он появляется как часть HTML-документа, ``Comment``
554
отображается со специальным форматированием::
556
print(soup.b.prettify())
558
# <!--Hey, buddy. Want to buy a used parser?-->
561
Beautiful Soup определяет классы для всего, что может появиться в
562
XML-документе: ``CData``, ``ProcessingInstruction``,
563
``Declaration`` и ``Doctype``. Как и ``Comment``, эти классы
564
являются подклассами ``NavigableString``, которые добавляют что-то еще к
565
строке. Вот пример, который заменяет комментарий блоком
568
from bs4 import CData
569
cdata = CData("A CDATA block")
570
comment.replace_with(cdata)
572
print(soup.b.prettify())
574
# <![CDATA[A CDATA block]]>
581
Вернемся к HTML-документу с фрагментом из «Алисы в стране чудес»::
584
<html><head><title>The Dormouse's story</title></head>
586
<p class="title"><b>The Dormouse's story</b></p>
588
<p class="story">Once upon a time there were three little sisters; and their names were
589
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
590
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
591
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
592
and they lived at the bottom of a well.</p>
594
<p class="story">...</p>
597
from bs4 import BeautifulSoup
598
soup = BeautifulSoup (html_doc, 'html.parser')
600
Я буду использовать его в качестве примера, чтобы показать, как перейти от одной части
606
Теги могут содержать строки и другие теги. Эти элементы являются
607
дочерними (`children`) для тега. Beautiful Soup предоставляет множество различных атрибутов для
608
навигации и перебора дочерних элементов.
610
Обратите внимание, что строки Beautiful Soup не поддерживают ни один из этих
611
атрибутов, потому что строка не может иметь дочерних элементов.
613
Навигация с использованием имен тегов
614
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
616
Самый простой способ навигации по дереву разбора — это указать имя
617
тега, который вам нужен. Если вы хотите получить тег <head>, просто напишите ``soup.head``::
620
# <head><title>The Dormouse's story</title></head>
623
# <title>The Dormouse's story</title>
625
Вы можете повторять этот трюк многократно, чтобы подробнее рассмотреть определенную часть
626
дерева разбора. Следующий код извлекает первый тег <b> внутри тега <body>::
629
# <b>The Dormouse's story</b>
631
Использование имени тега в качестве атрибута даст вам только `первый` тег с таким
635
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
637
Если вам нужно получить `все` теги <a> или что-нибудь более сложное,
638
чем первый тег с определенным именем, вам нужно использовать один из
639
методов, описанных в разделе `Поиск по дереву`_, такой как `find_all()`::
642
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
643
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
644
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
646
``.contents`` и ``.children``
647
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
649
Дочерние элементы доступны в списке под названием ``.contents``::
653
# <head><title>The Dormouse's story</title></head>
656
[<title>The Dormouse's story</title>]
658
title_tag = head_tag.contents[0]
660
# <title>The Dormouse's story</title>
662
# [u'The Dormouse's story']
664
Сам объект ``BeautifulSoup`` имеет дочерние элементы. В этом случае
665
тег <html> является дочерним для объекта ``BeautifulSoup``::
669
soup.contents[0].name
672
У строки нет ``.contents``, потому что она не может содержать
675
text = title_tag.contents[0]
677
# AttributeError: У объекта 'NavigableString' нет атрибута 'contents'
679
Вместо того, чтобы получать дочерние элементы в виде списка, вы можете перебирать их
680
с помощью генератора ``.children``::
682
for child in title_tag.children:
684
# The Dormouse's story
689
Атрибуты ``.contents`` и ``.children`` применяются только в отношении
690
`непосредственных` дочерних элементов тега. Например, тег <head> имеет только один непосредственный
691
дочерний тег <title>::
694
# [<title>The Dormouse's story</title>]
696
Но у самого тега <title> есть дочерний элемент: строка "The Dormouse's
697
story". В некотором смысле эта строка также является дочерним элементом
698
тега <head>. Атрибут ``.descendants`` позволяет перебирать `все`
699
дочерние элементы тега рекурсивно: его непосредственные дочерние элементы, дочерние элементы
700
дочерних элементов и так далее::
702
for child in head_tag.descendants:
704
# <title>The Dormouse's story</title>
705
# The Dormouse's story
707
У тега <head> есть только один дочерний элемент, но при этом у него два потомка:
708
тег <title> и его дочерний элемент. У объекта ``BeautifulSoup``
709
только один прямой дочерний элемент (тег <html>), зато множество
712
len(list(soup.children))
714
len(list(soup.descendants))
722
Если у тега есть только один дочерний элемент, и это ``NavigableString``,
723
его можно получить через ``.string``::
726
# u'The Dormouse's story'
728
Если единственным дочерним элементом тега является другой тег, и у этого `другого` тега есть строка
729
``.string``, то считается, что родительский тег содержит ту же строку
730
``.string``, что и дочерний тег::
733
# [<title>The Dormouse's story</title>]
736
# u'The Dormouse's story'
738
Если тег содержит больше чем один элемент, то становится неясным, какая из строк
739
``.string`` относится и к родительскому тегу, поэтому ``.string`` родительского тега имеет значение
742
print(soup.html.string)
745
.. _string-generators:
747
``.strings`` и ``.stripped_strings``
748
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
750
Если внутри тега есть более одного элемента, вы все равно можете посмотреть только на
751
строки. Используйте генератор ``.strings``::
753
for string in soup.strings:
755
# u"The Dormouse's story"
757
# u"The Dormouse's story"
759
# u'Once upon a time there were three little sisters; and their names were\n'
765
# u';\nand they lived at the bottom of a well.'
770
В этих строках много лишних пробелов, которые вы можете
771
удалить, используя генератор ``.stripped_strings``::
773
for string in soup.stripped_strings:
775
# u"The Dormouse's story"
776
# u"The Dormouse's story"
777
# u'Once upon a time there were three little sisters; and their names were'
783
# u';\nand they lived at the bottom of a well.'
786
Здесь строки, состоящие исключительно из пробелов, игнорируются, а
787
пробелы в начале и конце строк удаляются.
792
В продолжение аналогии с «семейным деревом», каждый тег и каждая строка имеет
793
родителя (`parent`): тег, который его содержит.
800
Вы можете получить доступ к родительскому элементу с помощью атрибута ``.parent``. В
801
примере документа с фрагментом из «Алисы в стране чудес» тег <head> является родительским
804
title_tag = soup.title
806
# <title>The Dormouse's story</title>
808
# <head><title>The Dormouse's story</title></head>
810
Строка заголовка сама имеет родителя: тег <title>, содержащий
813
title_tag.string.parent
814
# <title>The Dormouse's story</title>
816
Родительским элементом тега верхнего уровня, такого как <html>, является сам объект
820
type(html_tag.parent)
821
# <class 'bs4.BeautifulSoup'>
823
И ``.parent`` объекта ``BeautifulSoup`` определяется как None::
833
Вы можете перебрать всех родителей элемента с помощью
834
``.parents``. В следующем примере ``.parents`` используется для перемещения от тега <a>,
835
закопанного глубоко внутри документа, до самого верха документа::
839
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
840
for parent in link.parents:
854
Рассмотрим простой документ::
856
sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
857
print(sibling_soup.prettify())
871
Тег <b> и тег <c> находятся на одном уровне: они оба непосредственные
872
дочерние элементы одного и того же тега. Мы называем их `одноуровневые`. Когда документ
873
красиво отформатирован, одноуровневые элементы выводятся с одинаковым отступом. Вы
874
также можете использовать это отношение в написанном вами коде.
876
``.next_sibling`` и ``.previous_sibling``
877
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
879
Вы можете использовать ``.next_sibling`` и ``.previous_sibling`` для навигации
880
между элементами страницы, которые находятся на одном уровне дерева разбора::
882
sibling_soup.b.next_sibling
885
sibling_soup.c.previous_sibling
888
У тега <b> есть ``.next_sibling``, но нет ``.previous_sibling``,
889
потому что нет ничего до тега <b> `на том же уровне
890
дерева`. По той же причине у тега <c> есть ``.previous_sibling``,
891
но нет ``.next_sibling``::
893
print(sibling_soup.b.previous_sibling)
895
print(sibling_soup.c.next_sibling)
898
Строки "text1" и "text2" `не являются` одноуровневыми, потому что они не
899
имеют общего родителя::
901
sibling_soup.b.string
904
print(sibling_soup.b.string.next_sibling)
907
В реальных документах ``.next_sibling`` или ``.previous_sibling``
908
тега обычно будет строкой, содержащей пробелы. Возвращаясь к
909
фрагменту из «Алисы в стране чудес»::
911
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
912
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
913
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
915
Вы можете подумать, что ``.next_sibling`` первого тега <a>
916
должен быть второй тег <a>. Но на самом деле это строка: запятая и
917
перевод строки, отделяющий первый тег <a> от второго::
921
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
926
Второй тег <a> на самом деле является ``.next_sibling`` запятой ::
928
link.next_sibling.next_sibling
929
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
931
.. _sibling-generators:
933
``.next_siblings`` и ``.previous_siblings``
934
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
936
Вы можете перебрать одноуровневые элементы данного тега с помощью ``.next_siblings`` или
937
``.previous_siblings``::
939
for sibling in soup.a.next_siblings:
942
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
944
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
945
# u'; and they lived at the bottom of a well.'
948
for sibling in soup.find(id="link3").previous_siblings:
951
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
953
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
954
# u'Once upon a time there were three little sisters; and their names were\n'
957
Проход вперед и назад
958
---------------------
960
Взгляните на начало фрагмента из «Алисы в стране чудес»::
962
<html><head><title>The Dormouse's story</title></head>
963
<p class="title"><b>The Dormouse's story</b></p>
965
HTML-парсер берет эту строку символов и превращает ее в
966
серию событий: "открыть тег <html>", "открыть тег <head>", "открыть
967
тег <html>", "добавить строку", "закрыть тег <title>", "открыть
968
тег <p>" и так далее. Beautiful Soup предлагает инструменты для реконструирование
969
первоначального разбора документа.
971
.. _element-generators:
973
``.next_element`` и ``.previous_element``
974
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
976
Атрибут ``.next_element`` строки или тега указывает на то,
977
что было разобрано непосредственно после него. Это могло бы быть тем же, что и
978
``.next_sibling``, но обычно результат резко отличается.
980
Возьмем последний тег <a> в фрагменте из «Алисы в стране чудес». Его
981
``.next_sibling`` является строкой: конец предложения, которое было
982
прервано началом тега <a>::
984
last_a_tag = soup.find("a", id="link3")
986
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
988
last_a_tag.next_sibling
989
# '; and they lived at the bottom of a well.'
991
Но ``.next_element`` этого тега <a> — это то, что было разобрано
992
сразу после тега <a>, `не` остальная часть этого предложения:
995
last_a_tag.next_element
998
Это потому, что в оригинальной разметке слово «Tillie» появилось
999
перед точкой с запятой. Парсер обнаружил тег <a>, затем
1000
слово «Tillie», затем закрывающий тег </a>, затем точку с запятой и оставшуюся
1001
часть предложения. Точка с запятой находится на том же уровне, что и тег <a>, но
1002
слово «Tillie» встретилось первым.
1004
Атрибут ``.previous_element`` является полной противоположностью
1005
``.next_element``. Он указывает на элемент, который был встречен при разборе
1006
непосредственно перед текущим::
1008
last_a_tag.previous_element
1010
last_a_tag.previous_element.next_element
1011
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
1013
``.next_elements`` и ``.previous_elements``
1014
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1016
Вы уже должны были уловить идею. Вы можете использовать их для перемещения
1017
вперед или назад по документу, в том порядке, в каком он был разобран парсером::
1019
for element in last_a_tag.next_elements:
1020
print(repr(element))
1022
# u';\nand they lived at the bottom of a well.'
1024
# <p class="story">...</p>
1032
Beautiful Soup определяет множество методов поиска по дереву разбора,
1033
но они все очень похожи. Я буду долго объяснять, как работают
1034
два самых популярных метода: ``find()`` и ``find_all()``. Прочие
1035
методы принимают практически те же самые аргументы, поэтому я расскажу
1038
И опять, я буду использовать фрагмент из «Алисы в стране чудес» в качестве примера::
1041
<html><head><title>The Dormouse's story</title></head>
1043
<p class="title"><b>The Dormouse's story</b></p>
1045
<p class="story">Once upon a time there were three little sisters; and their names were
1046
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
1047
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
1048
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
1049
and they lived at the bottom of a well.</p>
1051
<p class="story">...</p>
1054
from bs4 import BeautifulSoup
1055
soup = BeautifulSoup (html_doc, 'html.parser')
1057
Передав фильтр в аргумент типа ``find_all()``, вы можете
1058
углубиться в интересующие вас части документа.
1063
Прежде чем подробно рассказывать о ``find_all()`` и подобных методах, я
1064
хочу показать примеры различных фильтров, которые вы можете передать в эти
1065
методы. Эти фильтры появляются снова и снова в
1066
поисковом API. Вы можете использовать их для фильтрации по имени тега,
1067
по его атрибутам, по тексту строки или по некоторой их
1075
Самый простой фильтр — это строка. Передайте строку в метод поиска, и
1076
Beautiful Soup выполнит поиск соответствия этой строке. Следующий
1077
код находит все теги <b> в документе::
1080
# [<b>The Dormouse's story</b>]
1082
Если вы передадите байтовую строку, Beautiful Soup будет считать, что строка
1083
кодируется в UTF-8. Вы можете избежать этого, передав вместо нее строку Unicode.
1085
.. _a regular expression:
1087
Регулярное выражение
1088
^^^^^^^^^^^^^^^^^^^^
1090
Если вы передадите объект с регулярным выражением, Beautiful Soup отфильтрует результаты
1091
в соответствии с этим регулярным выражением, используя его метод ``search()``. Следующий код
1092
находит все теги, имена которых начинаются с буквы "b"; в нашем
1093
случае это теги <body> и <b>::
1096
for tag in soup.find_all(re.compile("^b")):
1101
Этот код находит все теги, имена которых содержат букву "t"::
1103
for tag in soup.find_all(re.compile("t")):
1113
Если вы передадите список, Beautiful Soup разрешит совпадение строк
1114
с `любым` элементом из этого списка. Следующий код находит все теги <a>
1117
soup.find_all(["a", "b"])
1118
# [<b>The Dormouse's story</b>,
1119
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1120
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1121
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1128
Значение ``True`` подходит везде, где возможно.. Следующий код находит `все`
1129
теги в документе, но не текстовые строки::
1131
for tag in soup.find_all(True):
1150
Если ничто из перечисленного вам не подходит, определите функцию, которая
1151
принимает элемент в качестве единственного аргумента. Функция должна вернуть
1152
``True``, если аргумент подходит, и ``False``, если нет.
1154
Вот функция, которая возвращает ``True``, если в теге определен атрибут "class",
1155
но не определен атрибут "id"::
1157
def has_class_but_no_id(tag):
1158
return tag.has_attr('class') and not tag.has_attr('id')
1160
Передайте эту функцию в ``find_all()``, и вы получите все
1163
soup.find_all(has_class_but_no_id)
1164
# [<p class="title"><b>The Dormouse's story</b></p>,
1165
# <p class="story">Once upon a time there were...</p>,
1166
# <p class="story">...</p>]
1168
Эта функция выбирает только теги <p>. Она не выбирает теги <a>,
1169
поскольку в них определены и атрибут "class" , и атрибут "id". Она не выбирает
1170
теги вроде <html> и <title>, потому что в них не определен атрибут
1173
Если вы передаете функцию для фильтрации по определенному атрибуту, такому как
1174
``href``, аргументом, переданным в функцию, будет
1175
значение атрибута, а не весь тег. Вот функция, которая находит все теги ``a``,
1176
у которых атрибут ``href`` *не* соответствует регулярному выражению::
1178
def not_lacie(href):
1179
return href and not re.compile("lacie").search(href)
1180
soup.find_all(href=not_lacie)
1181
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1182
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1184
Функция может быть настолько сложной, насколько вам нужно. Вот
1185
функция, которая возвращает ``True``, если тег окружен строковыми
1188
from bs4 import NavigableString
1189
def surrounded_by_strings(tag):
1190
return (isinstance(tag.next_element, NavigableString)
1191
and isinstance(tag.previous_element, NavigableString))
1193
for tag in soup.find_all(surrounded_by_strings):
1201
Теперь мы готовы подробно рассмотреть методы поиска.
1206
Сигнатура: find_all(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
1207
<recursive>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1209
Метод ``find_all()`` просматривает потомков тега и
1210
извлекает `всех` потомков, которые соответствую вашим фильтрам. Я привел несколько
1211
примеров в разделе `Виды фильтров`_, а вот еще несколько::
1213
soup.find_all("title")
1214
# [<title>The Dormouse's story</title>]
1216
soup.find_all("p", "title")
1217
# [<p class="title"><b>The Dormouse's story</b></p>]
1220
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1221
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1222
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1224
soup.find_all(id="link2")
1225
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1228
soup.find(string=re.compile("sisters"))
1229
# u'Once upon a time there were three little sisters; and their names were\n'
1231
Кое-что из этого нам уже знакомо, но есть и новое. Что означает
1232
передача значения для ``string`` или ``id``? Почему
1233
``find_all ("p", "title")`` находит тег <p> с CSS-классом "title"?
1234
Давайте посмотрим на аргументы ``find_all()``.
1241
Передайте значение для аргумента ``name``, и вы скажете Beautiful Soup
1242
рассматривать только теги с определенными именами. Текстовые строки будут игнорироваться, так же как и
1243
теги, имена которых не соответствуют заданным.
1245
Вот простейший пример использования::
1247
soup.find_all("title")
1248
# [<title>The Dormouse's story</title>]
1250
В разделе `Виды фильтров`_ говорилось, что значением ``name`` может быть
1251
`строка`_, `регулярное выражение`_, `список`_, `функция`_ или
1256
Именованные аргументы
1257
^^^^^^^^^^^^^^^^^^^^^
1259
Любой нераспознанный аргумент будет превращен в фильтр
1260
по атрибуту тега. Если вы передаете значение для аргумента с именем ``id``,
1261
Beautiful Soup будет фильтровать по атрибуту "id" каждого тега::
1263
soup.find_all(id='link2')
1264
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1266
Если вы передадите значение для ``href``, Beautiful Soup отфильтрует
1267
по атрибуту "href" каждого тега::
1269
soup.find_all(href=re.compile("elsie"))
1270
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1272
Для фильтрации по атрибуту может использоваться `строка`_, `регулярное
1273
выражение`_, `список`_, `функция`_ или значение `True`_.
1275
Следующий код находит все теги, атрибут ``id`` которых имеет значение,
1276
независимо от того, что это за значение::
1278
soup.find_all(id=True)
1279
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1280
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1281
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1283
Вы можете отфильтровать несколько атрибутов одновременно, передав более одного
1284
именованного аргумента::
1286
soup.find_all(href=re.compile("elsie"), id='link1')
1287
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
1289
Некоторые атрибуты, такие как атрибуты data-* в HTML 5, имеют имена, которые
1290
нельзя использовать в качестве имен именованных аргументов::
1292
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
1293
data_soup.find_all(data-foo="value")
1294
# SyntaxError: keyword can't be an expression
1296
Вы можете использовать эти атрибуты в поиске, поместив их в
1297
словарь и передав словарь в ``find_all()`` как
1298
аргумент ``attrs``::
1300
data_soup.find_all(attrs={"data-foo": "value"})
1301
# [<div data-foo="value">foo!</div>]
1303
Нельзя использовать именованный аргумент для поиска в HTML по элементу "name",
1304
потому что Beautiful Soup использует аргумент ``name`` для имени
1305
самого тега. Вместо этого вы можете передать элемент "name" вместе с его значением в
1306
составе аргумента ``attrs``::
1308
name_soup = BeautifulSoup('<input name="email"/>')
1309
name_soup.find_all(name="email")
1311
name_soup.find_all(attrs={"name": "email"})
1312
# [<input name="email"/>]
1319
Очень удобно искать тег с определенным классом CSS, но
1320
имя атрибута CSS, "class", является зарезервированным словом в
1321
Python. Использование ``class`` в качестве именованного аргумента приведет к синтаксической
1322
ошибке. Начиная с Beautiful Soup 4.1.2, вы можете выполнять поиск по классу CSS, используя
1323
именованный аргумент ``class_``::
1325
soup.find_all("a", class_="sister")
1326
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1327
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1328
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1330
Как и с любым именованным аргументом, вы можете передать в качестве значения ``class_`` строку, регулярное
1331
выражение, функцию или ``True``::
1333
soup.find_all(class_=re.compile("itl"))
1334
# [<p class="title"><b>The Dormouse's story</b></p>]
1336
def has_six_characters(css_class):
1337
return css_class is not None and len(css_class) == 6
1339
soup.find_all(class_=has_six_characters)
1340
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1341
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1342
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1344
Помните, что один тег может иметь :ref:`несколько значений <multivalue>`
1345
для атрибута "class". Когда вы ищете тег, который
1346
соответствует определенному классу CSS, вы ищете соответствие `любому` из его
1349
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
1350
css_soup.find_all("p", class_="strikeout")
1351
# [<p class="body strikeout"></p>]
1353
css_soup.find_all("p", class_="body")
1354
# [<p class="body strikeout"></p>]
1356
Можно искать точное строковое значение атрибута ``class``::
1358
css_soup.find_all("p", class_="body strikeout")
1359
# [<p class="body strikeout"></p>]
1361
Но поиск вариантов строкового значения не сработает::
1363
css_soup.find_all("p", class_="strikeout body")
1366
Если вы хотите искать теги, которые соответствуют двум или более классам CSS,
1367
следует использовать селектор CSS::
1369
css_soup.select("p.strikeout.body")
1370
# [<p class="body strikeout"></p>]
1372
В старых версиях Beautiful Soup, в которых нет ярлыка ``class_``
1373
можно использовать трюк с аргументом ``attrs``, упомянутый выше. Создайте
1374
словарь, значение которого для "class" является строкой (или регулярным
1375
выражением, или чем угодно еще), которую вы хотите найти::
1377
soup.find_all("a", attrs={"class": "sister"})
1378
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1379
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1380
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1387
С помощью ``string`` вы можете искать строки вместо тегов. Как и в случае с
1388
``name`` и именованными аргументами, передаваться может `строка`_,
1389
`регулярное выражение`_, `список`_, `функция`_ или значения `True`_.
1390
Вот несколько примеров::
1392
soup.find_all(string="Elsie")
1395
soup.find_all(string=["Tillie", "Elsie", "Lacie"])
1396
# [u'Elsie', u'Lacie', u'Tillie']
1398
soup.find_all(string=re.compile("Dormouse"))
1399
[u"The Dormouse's story", u"The Dormouse's story"]
1401
def is_the_only_string_within_a_tag(s):
1402
"""Return True if this string is the only child of its parent tag."""
1403
return (s == s.parent.string)
1405
soup.find_all(string=is_the_only_string_within_a_tag)
1406
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
1408
Хотя значение типа ``string`` предназначено для поиска строк, вы можете комбинировать его с
1409
аргументами, которые находят теги: Beautiful Soup найдет все теги, в которых
1410
``.string`` соответствует вашему значению для ``string``. Следующий код находит все теги <a>,
1411
у которых ``.string`` равно "Elsie"::
1413
soup.find_all("a", string="Elsie")
1414
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
1416
Аргумент ``string`` — это новое в Beautiful Soup 4.4.0. В ранних
1417
версиях он назывался ``text``::
1419
soup.find_all("a", text="Elsie")
1420
# [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
1427
``find_all()`` возвращает все теги и строки, которые соответствуют вашим
1428
фильтрам. Это может занять некоторое время, если документ большой. Если вам не
1429
нужны `все` результаты, вы можете указать их предельное число — ``limit``. Это
1430
работает так же, как ключевое слово LIMIT в SQL. Оно говорит Beautiful Soup
1431
прекратить собирать результаты после того, как их найдено определенное количество.
1433
В фрагменте из «Алисы в стране чудес» есть три ссылки, но следующий код
1434
находит только первые две::
1436
soup.find_all("a", limit=2)
1437
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1438
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1442
Аргумент ``recursive``
1443
^^^^^^^^^^^^^^^^^^^^^^
1445
Если вы вызовете ``mytag.find_all()``, Beautiful Soup проверит всех
1446
потомков ``mytag``: его дочерние элементы, дочерние элементы дочерних элементов, и
1447
так далее. Если вы хотите, чтобы Beautiful Soup рассматривал только непосредственных потомков (дочерние элементы),
1448
вы можете передать ``recursive = False``. Оцените разницу::
1450
soup.html.find_all("title")
1451
# [<title>The Dormouse's story</title>]
1453
soup.html.find_all("title", recursive=False)
1456
Вот эта часть документа::
1461
The Dormouse's story
1466
Тег <title> находится под тегом <html>, но не `непосредственно`
1467
под тегом <html>: на пути встречается тег <head>. Beautiful Soup
1468
находит тег <title>, когда разрешено просматривать всех потомков
1469
тега <html>, но когда ``recursive=False`` ограничивает поиск
1470
только непосредстввенно дочерними элементами, Beautiful Soup ничего не находит.
1472
Beautiful Soup предлагает множество методов поиска по дереву (они рассмотрены ниже),
1473
и они в основном принимают те же аргументы, что и ``find_all()``: ``name``,
1474
``attrs``, ``string``, ``limit`` и именованные аргументы. Но
1475
с аргументом ``recursive`` все иначе: ``find_all()`` и ``find()`` —
1476
это единственные методы, которые его поддерживают. От передачи ``recursive=False`` в
1477
метод типа ``find_parents()`` не очень много пользы.
1479
Вызов тега похож на вызов ``find_all()``
1480
----------------------------------------
1482
Поскольку ``find_all()`` является самым популярным методом в Beautiful
1483
Soup API, вы можете использовать сокращенную запись. Если относиться к
1484
объекту ``BeautifulSoup`` или объекту ``Tag`` так, будто это
1485
функция, то это похоже на вызов ``find_all()``
1486
с этим объектом. Эти две строки кода эквивалентны::
1491
Эти две строки также эквивалентны::
1493
soup.title.find_all(string=True)
1494
soup.title(string=True)
1499
Сигнатура: find(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
1500
<recursive>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1502
Метод ``find_all()`` сканирует весь документ в поиске
1503
всех результатов, но иногда вам нужен только один. Если вы знаете,
1504
что в документе есть только один тег <body>, нет смысла сканировать
1505
весь документ в поиске остальных. Вместо того, чтобы передавать ``limit=1``
1506
каждый раз, когда вы вызываете ``find_all()``, используйте
1507
метод ``find()``. Эти две строки кода эквивалентны::
1509
soup.find_all('title', limit=1)
1510
# [<title>The Dormouse's story</title>]
1513
# <title>The Dormouse's story</title>
1515
Разница лишь в том, что ``find_all()`` возвращает список, содержащий
1516
единственный результат, а ``find()`` возвращает только сам результат.
1518
Если ``find_all()`` не может ничего найти, он возвращает пустой список. Если
1519
``find()`` не может ничего найти, он возвращает ``None``::
1521
print(soup.find("nosuchtag"))
1524
Помните трюк с ``soup.head.title`` из раздела
1525
`Навигация с использованием имен тегов`_? Этот трюк работает на основе неоднократного вызова ``find()``::
1528
# <title>The Dormouse's story</title>
1530
soup.find("head").find("title")
1531
# <title>The Dormouse's story</title>
1533
``find_parents()`` и ``find_parent()``
1534
--------------------------------------
1536
Сигнатура: find_parents(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1538
Сигнатура: find_parent(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1540
Я долго объяснял, как работают ``find_all()`` и
1541
``find()``. Beautiful Soup API определяет десяток других методов для
1542
поиска по дереву, но пусть вас это не пугает. Пять из этих методов
1543
в целом похожи на ``find_all()``, а другие пять в целом
1544
похожи на ``find()``. Единственное различие в том, по каким частям
1547
Сначала давайте рассмотрим ``find_parents()`` и
1548
``find_parent()``. Помните, что ``find_all()`` и ``find()`` прорабатывают
1549
дерево сверху вниз, просматривая теги и их потомков. ``find_parents()`` и ``find_parent()``
1550
делают наоборот: они идут `снизу вверх`, рассматривая
1551
родительские элементы тега или строки. Давайте испытаем их, начав со строки,
1552
закопанной глубоко в фрагменте из «Алисы в стране чудес»::
1554
a_string = soup.find(string="Lacie")
1558
a_string.find_parents("a")
1559
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1561
a_string.find_parent("p")
1562
# <p class="story">Once upon a time there were three little sisters; and their names were
1563
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1564
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
1565
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
1566
# and they lived at the bottom of a well.</p>
1568
a_string.find_parents("p", class="title")
1571
Один из трех тегов <a> является прямым родителем искомой строки,
1572
так что наш поиск находит его. Один из трех тегов <p> является
1573
непрямым родителем строки, и наш поиск тоже его
1574
находит. Где-то в документе есть тег <p> с классом CSS "title",
1575
но он не является родительским для строки, так что мы не можем найти
1576
его с помощью ``find_parents()``.
1578
Вы могли заметить связь между ``find_parent()``,
1579
``find_parents()`` и атрибутами `.parent`_ и `.parents`_,
1580
упомянутыми ранее. Связь очень сильная. Эти методы поиска
1581
на самом деле используют ``.parents``, чтобы перебрать все родительские элементы и проверить
1582
каждый из них на соответствие заданному фильтру.
1584
``find_next_siblings()`` и ``find_next_sibling()``
1585
--------------------------------------------------
1587
Сигнатура: find_next_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1589
Сигнатура: find_next_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1591
Эти методы используют :ref:`.next_siblings <sibling-generators>` для
1592
перебора одноуровневых элементов для данного элемента в дереве. Метод
1593
``find_next_siblings()`` возвращает все подходящие одноуровневые элементы,
1594
а ``find_next_sibling()`` возвращает только первый из них::
1598
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1600
first_link.find_next_siblings("a")
1601
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1602
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1604
first_story_paragraph = soup.find("p", "story")
1605
first_story_paragraph.find_next_sibling("p")
1606
# <p class="story">...</p>
1608
``find_previous_siblings()`` и ``find_previous_sibling()``
1609
----------------------------------------------------------
1611
Сигнатура: find_previous_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1613
Сигнатура: find_previous_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1615
Эти методы используют :ref:`.previous_siblings <sibling-generators>` для перебора тех одноуровневых элементов,
1616
которые предшествуют данному элементу в дереве разбора. Метод ``find_previous_siblings()``
1617
возвращает все подходящие одноуровневые элементы,, а
1618
а ``find_next_sibling()`` только первый из них::
1620
last_link = soup.find("a", id="link3")
1622
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
1624
last_link.find_previous_siblings("a")
1625
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1626
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1628
first_story_paragraph = soup.find("p", "story")
1629
first_story_paragraph.find_previous_sibling("p")
1630
# <p class="title"><b>The Dormouse's story</b></p>
1633
``find_all_next()`` и ``find_next()``
1634
-------------------------------------
1636
Сигнатура: find_all_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1638
Сигнатура: find_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1640
Эти методы используют :ref:`.next_elements <element-generators>` для
1641
перебора любых тегов и строк, которые встречаются в документе после
1642
элемента. Метод ``find_all_next()`` возвращает все совпадения, а
1643
``find_next()`` только первое::
1647
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1649
first_link.find_all_next(string=True)
1650
# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
1651
# u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']
1653
first_link.find_next("p")
1654
# <p class="story">...</p>
1656
В первом примере нашлась строка "Elsie", хотя она
1657
содержится в теге <a>, с которого мы начали. Во втором примере
1658
нашелся последний тег <p>, хотя он находится
1659
в другой части дерева, чем тег <a>, с которого мы начали. Для этих
1660
методов имеет значение только то, что элемент соответствует фильтру и
1661
появляется в документе позже, чем тот элемент, с которого начали поиск.
1663
``find_all_previous()`` и ``find_previous()``
1664
---------------------------------------------
1666
Сигнатура: find_all_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1668
Сигнатура: find_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1670
Эти методы используют :ref:`.previous_elements <element-generators>` для
1671
перебора любых тегов и строк, которые встречаются в документе до
1672
элемента. Метод ``find_all_previous()`` возвращает все совпадения, а
1673
``find_previous()`` только первое::
1677
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1679
first_link.find_all_previous("p")
1680
# [<p class="story">Once upon a time there were three little sisters; ...</p>,
1681
# <p class="title"><b>The Dormouse's story</b></p>]
1683
first_link.find_previous("title")
1684
# <title>The Dormouse's story</title>
1686
Вызов ``find_all_previous ("p")`` нашел первый абзац в
1687
документе (тот, который с ``class = "title"``), но он также находит
1688
второй абзац, а именно тег <p>, содержащий тег <a>, с которого мы
1689
начали. Это не так уж удивительно: мы смотрим на все теги,
1690
которые появляются в документе раньше, чем тот, с которого мы начали. Тег
1691
<p>, содержащий тег <a>, должен был появиться до тега <a>, который
1697
Начиная с версии 4.7.0, Beautiful Soup поддерживает большинство селекторов CSS4 благодаря
1699
<https://facelessuser.github.io/soupsieve/>`_. Если вы установили Beautiful Soup через ``pip``, одновременно должен был установиться SoupSieve,
1700
так что вам больше ничего не нужно делать.
1702
В ``BeautifulSoup`` есть метод ``.select()``, который использует SoupSieve, чтобы
1703
запустить селектор CSS и вернуть все
1704
подходящие элементы. ``Tag`` имеет похожий метод, который запускает селектор CSS
1705
в отношении содержимого одного тега.
1707
(В более ранних версиях Beautiful Soup тоже есть метод ``.select()``,
1708
но поддерживаются только наиболее часто используемые селекторы CSS.)
1710
В `документации SoupSieve
1711
<https://facelessuser.github.io/soupsieve/>`_ перечислены все
1712
селекторы CSS, которые поддерживаются на данный момент, но вот некоторые из основных:
1714
Вы можете найти теги::
1716
soup.select("title")
1717
# [<title>The Dormouse's story</title>]
1719
soup.select("p:nth-of-type(3)")
1720
# [<p class="story">...</p>]
1722
Найти теги под другими тегами::
1724
soup.select("body a")
1725
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1726
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1727
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1729
soup.select("html head title")
1730
# [<title>The Dormouse's story</title>]
1732
Найти теги `непосредственно` под другими тегами::
1734
soup.select("head > title")
1735
# [<title>The Dormouse's story</title>]
1737
soup.select("p > a")
1738
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1739
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1740
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1742
soup.select("p > a:nth-of-type(2)")
1743
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1745
soup.select("p > #link1")
1746
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1748
soup.select("body > a")
1751
Найти одноуровневые элементы тега::
1753
soup.select("#link1 ~ .sister")
1754
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1755
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1757
soup.select("#link1 + .sister")
1758
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1760
Найти теги по классу CSS::
1762
soup.select(".sister")
1763
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1764
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1765
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1767
soup.select("[class~=sister]")
1768
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1769
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1770
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1774
soup.select("#link1")
1775
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1777
soup.select("a#link2")
1778
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1780
Найти теги, которые соответствуют любому селектору из списка::
1782
soup.select("#link1,#link2")
1783
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1784
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1786
Проверка на наличие атрибута::
1788
soup.select('a[href]')
1789
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1790
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1791
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1793
Найти теги по значению атрибута::
1795
soup.select('a[href="http://example.com/elsie"]')
1796
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1798
soup.select('a[href^="http://example.com/"]')
1799
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1800
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1801
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1803
soup.select('a[href$="tillie"]')
1804
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1806
soup.select('a[href*=".com/el"]')
1807
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1809
Есть также метод ``select_one()``, который находит только
1810
первый тег, соответствующий селектору::
1812
soup.select_one(".sister")
1813
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1815
Если вы разобрали XML, в котором определены пространства имен, вы можете использовать их в
1818
from bs4 import BeautifulSoup
1819
xml = """<tag xmlns:ns1="http://namespace1/" xmlns:ns2="http://namespace2/">
1820
<ns1:child>I'm in namespace 1</ns1:child>
1821
<ns2:child>I'm in namespace 2</ns2:child>
1823
soup = BeautifulSoup(xml, "xml")
1825
soup.select("child")
1826
# [<ns1:child>I'm in namespace 1</ns1:child>, <ns2:child>I'm in namespace 2</ns2:child>]
1828
soup.select("ns1|child", namespaces=namespaces)
1829
# [<ns1:child>I'm in namespace 1</ns1:child>]
1831
При обработке селектора CSS, который использует пространства имен, Beautiful Soup
1832
использует сокращения пространства имен, найденные при разборе
1833
документа. Вы можете заменить сокращения своими собственными, передав словарь
1836
namespaces = dict(first="http://namespace1/", second="http://namespace2/")
1837
soup.select("second|child", namespaces=namespaces)
1838
# [<ns1:child>I'm in namespace 2</ns1:child>]
1840
Все эти селекторы CSS удобны для тех, кто уже
1841
знаком с синтаксисом селекторов CSS. Вы можете сделать все это с помощью
1842
Beautiful Soup API. И если CSS селекторы — это все, что вам нужно, вам следует
1843
использовать парсер lxml: так будет намного быстрее. Но вы можете
1844
`комбинировать` селекторы CSS с Beautiful Soup API.
1849
Основная сила Beautiful Soup в поиске по дереву разбора, но вы
1850
также можете изменить дерево и записать свои изменения в виде нового HTML или
1853
Изменение имен тегов и атрибутов
1854
--------------------------------
1856
Я говорил об этом раньше, в разделе `Атрибуты`_, но это стоит повторить. Вы
1857
можете переименовать тег, изменить значения его атрибутов, добавить новые
1858
атрибуты и удалить атрибуты::
1860
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
1863
tag.name = "blockquote"
1864
tag['class'] = 'verybold'
1867
# <blockquote class="verybold" id="1">Extremely bold</blockquote>
1872
# <blockquote>Extremely bold</blockquote>
1874
Изменение ``.string``
1875
---------------------
1877
Если вы замените значение атрибута ``.string`` новой строкой, содержимое тега будет
1878
заменено на эту строку::
1880
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
1881
soup = BeautifulSoup(markup)
1884
tag.string = "New link text."
1886
# <a href="http://example.com/">New link text.</a>
1888
Будьте осторожны: если тег содержит другие теги, они и все их
1889
содержимое будет уничтожено.
1894
Вы можете добавить содержимое тега с помощью ``Tag.append()``. Это работает
1895
точно так же, как ``.append()`` для списка в Python::
1897
soup = BeautifulSoup("<a>Foo</a>")
1898
soup.a.append("Bar")
1901
# <html><head></head><body><a>FooBar</a></body></html>
1908
Начиная с версии Beautiful Soup 4.7.0, ``Tag`` также поддерживает метод
1909
``.extend()``, который работает так же, как вызов ``.extend()`` для
1912
soup = BeautifulSoup("<a>Soup</a>")
1913
soup.a.extend(["'s", " ", "on"])
1916
# <html><head></head><body><a>Soup's on</a></body></html>
1918
# [u'Soup', u''s', u' ', u'on']
1920
``NavigableString()`` и ``.new_tag()``
1921
--------------------------------------
1923
Если вам нужно добавить строку в документ, нет проблем — вы можете передать
1924
строку Python в ``append()`` или вызвать
1925
конструктор ``NavigableString``::
1927
soup = BeautifulSoup("<b></b>")
1930
new_string = NavigableString(" there")
1931
tag.append(new_string)
1933
# <b>Hello there.</b>
1935
# [u'Hello', u' there']
1937
Если вы хотите создать комментарий или другой подкласс
1938
``NavigableString``, просто вызовите конструктор::
1940
from bs4 import Comment
1941
new_comment = Comment("Nice to see you.")
1942
tag.append(new_comment)
1944
# <b>Hello there<!--Nice to see you.--></b>
1946
# [u'Hello', u' there', u'Nice to see you.']
1948
(Это новая функция в Beautiful Soup 4.4.0.)
1950
Что делать, если вам нужно создать совершенно новый тег? Наилучшим решением будет
1951
вызвать фабричный метод ``BeautifulSoup.new_tag()``::
1953
soup = BeautifulSoup("<b></b>")
1954
original_tag = soup.b
1956
new_tag = soup.new_tag("a", href="http://www.example.com")
1957
original_tag.append(new_tag)
1959
# <b><a href="http://www.example.com"></a></b>
1961
new_tag.string = "Link text."
1963
# <b><a href="http://www.example.com">Link text.</a></b>
1965
Нужен только первый аргумент, имя тега.
1970
``Tag.insert()`` похож на ``Tag.append()``, за исключением того, что новый элемент
1971
не обязательно добавляется в конец родительского
1972
``.contents``. Он добавится в любое место, номер которого
1973
вы укажете. Это работает в точности как ``.insert()`` в списке Python::
1975
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
1976
soup = BeautifulSoup(markup)
1979
tag.insert(1, "but did not endorse ")
1981
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
1983
# [u'I linked to ', u'but did not endorse', <i>example.com</i>]
1985
``insert_before()`` и ``insert_after()``
1986
----------------------------------------
1988
Метод ``insert_before()`` вставляет теги или строки непосредственно
1989
перед чем-то в дереве разбора::
1991
soup = BeautifulSoup("<b>stop</b>")
1992
tag = soup.new_tag("i")
1993
tag.string = "Don't"
1994
soup.b.string.insert_before(tag)
1996
# <b><i>Don't</i>stop</b>
1998
Метод ``insert_after()`` вставляет теги или строки непосредственно
1999
после чего-то в дереве разбора::
2001
div = soup.new_tag('div')
2003
soup.b.i.insert_after(" you ", div)
2005
# <b><i>Don't</i> you <div>ever</div> stop</b>
2007
# [<i>Don't</i>, u' you', <div>ever</div>, u'stop']
2012
``Tag.clear()`` удаляет содержимое тега::
2014
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2015
soup = BeautifulSoup(markup)
2020
# <a href="http://example.com/"></a>
2025
``PageElement.extract()`` удаляет тег или строку из дерева. Он
2026
возвращает тег или строку, которая была извлечена::
2028
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2029
soup = BeautifulSoup(markup)
2032
i_tag = soup.i.extract()
2035
# <a href="http://example.com/">I linked to</a>
2038
# <i>example.com</i>
2043
К этому моменту у вас фактически есть два дерева разбора: одно в
2044
объекте ``BeautifulSoup``, который вы использовали, чтобы разобрать документ, другое в
2045
теге, который был извлечен. Вы можете далее вызывать ``extract`` в отношении
2046
дочернего элемента того тега, который был извлечен::
2048
my_string = i_tag.string.extract()
2052
print(my_string.parent)
2061
``Tag.decompose()`` удаляет тег из дерева, а затем `полностью
2062
уничтожает его вместе с его содержимым`::
2064
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2065
soup = BeautifulSoup(markup)
2071
# <a href="http://example.com/">I linked to</a>
2079
``PageElement.extract()`` удаляет тег или строку из дерева
2080
и заменяет его тегом или строкой по вашему выбору::
2082
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2083
soup = BeautifulSoup(markup)
2086
new_tag = soup.new_tag("b")
2087
new_tag.string = "example.net"
2088
a_tag.i.replace_with(new_tag)
2091
# <a href="http://example.com/">I linked to <b>example.net</b></a>
2093
``replace_with()`` возвращает тег или строку, которые были заменены, так что
2094
вы можете изучить его или добавить его обратно в другую часть дерева.
2099
``PageElement.wrap()`` обертывает элемент в указанный вами тег. Он
2100
возвращает новую обертку::
2102
soup = BeautifulSoup("<p>I wish I was bold.</p>")
2103
soup.p.string.wrap(soup.new_tag("b"))
2104
# <b>I wish I was bold.</b>
2106
soup.p.wrap(soup.new_tag("div")
2107
# <div><p><b>I wish I was bold.</b></p></div>
2109
Это новый метод в Beautiful Soup 4.0.5.
2114
``Tag.unwrap()`` — это противоположность ``wrap()``. Он заменяет весь тег на
2115
его содержимое. Этим методом удобно очищать разметку::
2117
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2118
soup = BeautifulSoup(markup)
2123
# <a href="http://example.com/">I linked to example.com</a>
2125
Как и ``replace_with()``, ``unwrap()`` возвращает тег,
2126
который был заменен.
2131
После вызова ряда методов, которые изменяют дерево разбора, у вас может оказаться несколько объектов ``NavigableString`` подряд. У Beautiful Soup с этим нет проблем, но поскольку такое не случается со свежеразобранным документом, вам может показаться неожиданным следующее поведение::
2133
soup = BeautifulSoup("<p>A one</p>")
2134
soup.p.append(", a two")
2137
# [u'A one', u', a two']
2139
print(soup.p.encode())
2140
# <p>A one, a two</p>
2142
print(soup.p.prettify())
2148
Вы можете вызвать ``Tag.smooth()``, чтобы очистить дерево разбора путем объединения смежных строк::
2155
print(soup.p.prettify())
2160
``smooth()`` — это новый метод в Beautiful Soup 4.8.0.
2165
.. _.prettyprinting:
2167
Красивое форматирование
2168
-----------------------
2170
Метод ``prettify()`` превратит дерево разбора Beautiful Soup в
2171
красиво отформатированную строку Unicode, где каждый
2172
тег и каждая строка выводятся на отдельной строчке::
2174
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2175
soup = BeautifulSoup(markup)
2177
# '<html>\n <head>\n </head>\n <body>\n <a href="http://example.com/">\n...'
2179
print(soup.prettify())
2184
# <a href="http://example.com/">
2193
Вы можете вызвать ``prettify()`` для объекта ``BeautifulSoup`` верхнего уровня
2194
или для любого из его объектов ``Tag``::
2196
print(soup.a.prettify())
2197
# <a href="http://example.com/">
2204
Без красивого форматирования
2205
----------------------------
2207
Если вам нужна просто строка, без особого форматирования, вы можете вызвать
2208
``unicode()`` или ``str()`` для объекта ``BeautifulSoup`` или объекта ``Tag``
2212
# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
2215
# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
2217
Функция ``str()`` возвращает строку, кодированную в UTF-8. Для получения более подробной информации см.
2220
Вы также можете вызвать ``encode()`` для получения байтовой строки, и ``decode()``,
2221
чтобы получить Unicode.
2223
.. _output_formatters:
2225
Средства форматирования вывода
2226
------------------------------
2228
Если вы дадите Beautiful Soup документ, который содержит HTML-мнемоники, такие как
2229
"&lquot;", они будут преобразованы в символы Unicode::
2231
soup = BeautifulSoup("“Dammit!” he said.")
2233
# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
2235
Если затем преобразовать документ в строку, символы Unicode
2236
будет кодироваться как UTF-8. Вы не получите обратно HTML-мнемоники::
2239
# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
2241
По умолчанию единственные символы, которые экранируются при выводе — это чистые
2242
амперсанды и угловые скобки. Они превращаются в «&», «<»
2243
и ">", чтобы Beautiful Soup случайно не сгенерировал
2244
невалидный HTML или XML::
2246
soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>")
2248
# <p>The law firm of Dewey, Cheatem, & Howe</p>
2250
soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
2252
# <a href="http://example.com/?foo=val1&bar=val2">A link</a>
2254
Вы можете изменить это поведение, указав для
2255
аргумента ``formatter`` одно из значений: ``prettify()``, ``encode()`` или
2256
``decode()``. Beautiful Soup распознает пять возможных значений
2259
Значение по умолчанию — ``formatter="minimal"``. Строки будут обрабатываться
2260
ровно настолько, чтобы Beautiful Soup генерировал валидный HTML / XML::
2262
french = "<p>Il a dit <<Sacré bleu!>></p>"
2263
soup = BeautifulSoup(french)
2264
print(soup.prettify(formatter="minimal"))
2268
# Il a dit <<Sacré bleu!>>
2273
Если вы передадите ``formatter = "html"``, Beautiful Soup преобразует
2274
символы Unicode в HTML-мнемоники, когда это возможно::
2276
print(soup.prettify(formatter="html"))
2280
# Il a dit <<Sacré bleu!>>
2285
Если вы передаете ``formatter="html5"``, это то же самое, что
2286
``formatter="html"``, только Beautiful Soup будет
2287
пропускать закрывающую косую черту в пустых тегах HTML, таких как "br"::
2289
soup = BeautifulSoup("<br>")
2291
print(soup.encode(formatter="html"))
2292
# <html><body><br/></body></html>
2294
print(soup.encode(formatter="html5"))
2295
# <html><body><br></body></html>
2297
Если вы передадите ``formatter=None``, Beautiful Soup вообще не будет менять
2298
строки на выходе. Это самый быстрый вариант, но он может привести
2299
к тому, что Beautiful Soup будет генерировать невалидный HTML / XML::
2301
print(soup.prettify(formatter=None))
2305
# Il a dit <<Sacré bleu!>>
2310
link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
2311
print(link_soup.a.encode(formatter=None))
2312
# <a href="http://example.com/?foo=val1&bar=val2">A link</a>
2314
Если вам нужен более сложный контроль над выводом, вы можете
2315
использовать класс ``Formatter`` из Beautiful Soup. Вот как можно
2316
преобразовать строки в верхний регистр, независимо от того, находятся ли они в текстовом узле или в
2319
from bs4.formatter import HTMLFormatter
2322
formatter = HTMLFormatter(uppercase)
2324
print(soup.prettify(formatter=formatter))
2328
# IL A DIT <<SACRÉ BLEU!>>
2333
print(link_soup.a.prettify(formatter=formatter))
2334
# <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
2338
Подклассы ``HTMLFormatter`` или ``XMLFormatter`` дают еще
2339
больший контроль над выводом. Например, Beautiful Soup сортирует
2340
атрибуты в каждом теге по умолчанию::
2342
attr_soup = BeautifulSoup(b'<p z="1" m="2" a="3"></p>')
2343
print(attr_soup.p.encode())
2344
# <p a="3" m="2" z="1"></p>
2346
Чтобы выключить сортировку по умолчанию, вы можете создать подкласс на основе метода ``Formatter.attributes()``,
2347
который контролирует, какие атрибуты выводятся и в каком
2348
порядке. Эта реализация также отфильтровывает атрибут с именем "m",
2349
где бы он ни появился::
2351
class UnsortedAttributes(HTMLFormatter):
2352
def attributes(self, tag):
2353
for k, v in tag.attrs.items():
2357
print(attr_soup.p.encode(formatter=UnsortedAttributes()))
2358
# <p z="1" a="3"></p>
2360
Последнее предостережение: если вы создаете объект ``CData``, текст внутри
2361
этого объекта всегда представлен `как есть, без какого-либо
2362
форматирования`. Beautiful Soup вызовет вашу функцию для замены мнемоник,
2363
на тот случай, если вы написали функцию, которая подсчитывает
2364
все строки в документе или что-то еще, но он будет игнорировать
2365
возвращаемое значение::
2367
from bs4.element import CData
2368
soup = BeautifulSoup("<a></a>")
2369
soup.a.string = CData("one < three")
2370
print(soup.a.prettify(formatter="xml"))
2372
# <![CDATA[one < three]]>
2379
Если вам нужна только текстовая часть документа или тега, вы можете использовать
2380
метод ``get_text()``. Он возвращает весь текст документа или
2381
тега в виде единственной строки Unicode::
2383
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
2384
soup = BeautifulSoup(markup)
2387
u'\nI linked to example.com\n'
2391
Вы можете указать строку, которая будет использоваться для объединения текстовых фрагментов
2394
# soup.get_text("|")
2395
u'\nI linked to |example.com|\n'
2397
Вы можете сказать Beautiful Soup удалять пробелы в начале и
2398
конце каждого текстового фрагмента::
2400
# soup.get_text("|", strip=True)
2401
u'I linked to|example.com'
2403
Но в этом случае вы можете предпочесть использовать генератор :ref:`.stripped_strings <string-generators>`
2404
и затем обработать текст самостоятельно::
2406
[text for text in soup.stripped_strings]
2407
# [u'I linked to', u'example.com']
2412
Если вам нужно просто разобрать HTML, вы можете скинуть разметку в
2413
конструктор ``BeautifulSoup``, и, скорее всего, все будет в порядке. Beautiful
2414
Soup подберет для вас парсер и проанализирует данные. Но есть
2415
несколько дополнительных аргументов, которые вы можете передать конструктору, чтобы изменить
2416
используемый парсер.
2418
Первым аргументом конструктора ``BeautifulSou`` является строка или
2419
открытый дескриптор файла — сама разметка, которую вы хотите разобрать. Второй аргумент — это
2420
`как` вы хотите, чтобы разметка была разобрана.
2422
Если вы ничего не укажете, будет использован лучший HTML-парсер из тех,
2423
которые установлены. Beautiful Soup оценивает парсер lxml как лучший, за ним идет
2424
html5lib, затем встроенный парсер Python. Вы можете переопределить используемый парсер,
2425
указав что-то из следующего:
2427
* Какой тип разметки вы хотите разобрать. В данный момент поддерживаются:
2428
"html", "xml" и "html5".
2430
* Имя библиотеки парсера, которую вы хотите использовать. В данный момент поддерживаются
2431
"lxml", "html5lib" и "html.parser" (встроенный в Python
2434
В разделе `Установка парсера`_ вы найдете сравнительную таблицу поддерживаемых парсеров.
2436
Если у вас не установлен соответствующий парсер, Beautiful Soup
2437
проигнорирует ваш запрос и выберет другой парсер. На текущий момент единственный
2438
поддерживаемый парсер XML — это lxml. Если у вас не установлен lxml, запрос на
2439
парсер XML ничего не даст, и запрос "lxml" тоже
2442
Различия между парсерами
2443
------------------------
2445
Beautiful Soup представляет один интерфейс для разных
2446
парсеров, но парсеры неодинаковы. Разные парсеры создадут
2447
различные деревья разбора из одного и того же документа. Самые большие различия будут
2448
между парсерами HTML и парсерами XML. Вот короткий
2449
документ, разобранный как HTML::
2451
BeautifulSoup("<a><b /></a>")
2452
# <html><head></head><body><a><b></b></a></body></html>
2454
Поскольку пустой тег <b /> не является валидным кодом HTML, парсер превращает его в
2457
Вот тот же документ, который разобран как XML (для его запуска нужно, чтобы был
2458
установлен lxml). Обратите внимание, что пустой тег <b /> остается, и
2459
что в документ добавляется объявление XML вместо
2462
BeautifulSoup("<a><b /></a>", "xml")
2463
# <?xml version="1.0" encoding="utf-8"?>
2466
Есть также различия между парсерами HTML. Если вы даете Beautiful
2467
Soup идеально оформленный документ HTML, эти различия не будут
2468
иметь значения. Один парсер будет быстрее другого, но все они будут давать
2469
структуру данных, которая выглядит точно так же, как оригинальный
2472
Но если документ оформлен неидеально, различные парсеры
2473
дадут разные результаты. Вот короткий невалидный документ, разобранный с помощью
2474
HTML-парсера lxml. Обратите внимание, что висячий тег </p> просто
2477
BeautifulSoup("<a></p>", "lxml")
2478
# <html><body><a></a></body></html>
2480
Вот тот же документ, разобранный с помощью html5lib::
2482
BeautifulSoup("<a></p>", "html5lib")
2483
# <html><head></head><body><a><p></p></a></body></html>
2485
Вместо того, чтобы игнорировать висячий тег </p>, html5lib добавляет
2486
открывающй тег <p>. Этот парсер также добавляет пустой тег <head> в
2489
Вот тот же документ, разобранный с помощью встроенного в Python
2492
BeautifulSoup("<a></p>", "html.parser")
2495
Как и html5lib, этот парсер игнорирует закрывающий тег </p>. В отличие от
2496
html5lib, этот парсер не делает попытки создать правильно оформленный HTML-
2497
документ, добавив тег <body>. В отличие от lxml, он даже не
2498
добавляет тег <html>.
2500
Поскольку документ ``<a></p>`` невалиден, ни один из этих способов
2501
нельзя назвать "правильным". Парсер html5lib использует способы,
2502
которые являются частью стандарта HTML5, поэтому он может претендовать на то, что его подход
2503
самый "правильный", но правомерно использовать любой из трех методов.
2505
Различия между парсерами могут повлиять на ваш скрипт. Если вы планируете
2506
распространять ваш скрипт или запускать его на нескольких
2507
машинах, вам нужно указать парсер в
2508
конструкторе ``BeautifulSoup``. Это уменьшит вероятность того, что ваши пользователи при разборе
2509
документа получат результат, отличный от вашего.
2514
Любой документ HTML или XML написан в определенной кодировке, такой как ASCII
2515
или UTF-8. Но когда вы загрузите этот документ в Beautiful Soup, вы
2516
обнаружите, что он был преобразован в Unicode::
2518
markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
2519
soup = BeautifulSoup(markup)
2521
# <h1>Sacré bleu!</h1>
2525
Это не волшебство. (Хотя это было бы здорово, конечно.) Beautiful Soup использует
2526
подбиблиотеку под названием `Unicode, Dammit`_ для определения кодировки документа
2527
и преобразования ее в Unicode. Кодировка, которая была автоматически определена, содержится в значении
2528
атрибута ``.original_encoding`` объекта ``BeautifulSoup``::
2530
soup.original_encoding
2533
Unicode, Dammit чаще всего угадывает правильно, но иногда
2534
делает ошибки. Иногда он угадывает правильно только после
2535
побайтового поиска по документу, что занимает очень много времени. Если
2536
вы вдруг уже знаете кодировку документа, вы можете избежать
2537
ошибок и задержек, передав кодировку конструктору ``BeautifulSoup``
2538
как аргумент ``from_encoding``.
2540
Вот документ, написанный на ISO-8859-8. Документ настолько короткий, что
2541
Unicode, Dammit не может разобраться и неправильно идентифицирует кодировку как
2544
markup = b"<h1>\xed\xe5\xec\xf9</h1>"
2545
soup = BeautifulSoup(markup)
2548
soup.original_encoding
2551
Мы можем все исправить, передав правильный ``from_encoding``::
2553
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
2556
soup.original_encoding
2559
Если вы не знаете правильную кодировку, но видите, что
2560
Unicode, Dammit определяет ее неправильно, вы можете передать ошибочные варианты в
2561
``exclude_encodings``::
2563
soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"])
2566
soup.original_encoding
2569
Windows-1255 не на 100% подходит, но это совместимое
2570
надмножество ISO-8859-8, так что догадка почти верна. (``exclude_encodings``
2571
— это новая функция в Beautiful Soup 4.4.0.)
2573
В редких случаях (обычно когда документ UTF-8 содержит текст в
2574
совершенно другой кодировке) единственным способом получить Unicode может оказаться
2575
замена некоторых символов специальным символом Unicode
2576
"REPLACEMENT CHARACTER" (U+FFFD, �). Если Unicode, Dammit приходится это сделать,
2577
он установит атрибут ``.contains_replacement_characters``
2578
в ``True`` для объектов ``UnicodeDammit`` или ``BeautifulSoup``. Это
2579
даст понять, что представление в виде Unicode не является точным
2580
представление оригинала, и что некоторые данные потерялись. Если документ
2581
содержит �, но ``.contains_replacement_characters`` равен ``False``,
2582
вы будете знать, что � был в тексте изначально (как в этом
2583
параграфе), а не служит заменой отсутствующим данным.
2588
Когда вы пишете документ из Beautiful Soup, вы получаете документ в UTF-8,
2589
даже если он изначально не был в UTF-8. Вот
2590
документ в кодировке Latin-1::
2595
<meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
2598
<p>Sacr\xe9 bleu!</p>
2603
soup = BeautifulSoup(markup)
2604
print(soup.prettify())
2607
# <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
2616
Обратите внимание, что тег <meta> был переписан, чтобы отразить тот факт, что
2617
теперь документ кодируется в UTF-8.
2619
Если вы не хотите кодировку UTF-8, вы можете передать другую в ``prettify()``::
2621
print(soup.prettify("latin-1"))
2624
# <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
2627
Вы также можете вызвать encode() для объекта ``BeautifulSoup`` или любого
2628
элемента в супе, как если бы это была строка Python::
2630
soup.p.encode("latin-1")
2631
# '<p>Sacr\xe9 bleu!</p>'
2633
soup.p.encode("utf-8")
2634
# '<p>Sacr\xc3\xa9 bleu!</p>'
2636
Любые символы, которые не могут быть представлены в выбранной вами кодировке, будут
2637
преобразованы в числовые коды мнемоник XML. Вот документ,
2638
который включает в себя Unicode-символ SNOWMAN (снеговик)::
2640
markup = u"<b>\N{SNOWMAN}</b>"
2641
snowman_soup = BeautifulSoup(markup)
2642
tag = snowman_soup.b
2644
Символ SNOWMAN может быть частью документа UTF-8 (он выглядит
2645
так: ☃), но в ISO-Latin-1 или
2646
ASCII нет представления для этого символа, поэтому для этих кодировок он конвертируется в "☃":
2648
print(tag.encode("utf-8"))
2651
print tag.encode("latin-1")
2654
print tag.encode("ascii")
2660
Вы можете использовать Unicode, Dammit без Beautiful Soup. Он полезен в тех случаях.
2661
когда у вас есть данные в неизвестной кодировке, и вы просто хотите, чтобы они
2662
преобразовались в Unicode::
2664
from bs4 import UnicodeDammit
2665
dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
2666
print(dammit.unicode_markup)
2668
dammit.original_encoding
2671
Догадки Unicode, Dammit станут намного точнее, если вы установите
2672
библиотеки Python ``chardet`` или ``cchardet``. Чем больше данных вы
2673
даете Unicode, Dammit, тем точнее он определит кодировку. Если у вас есть
2674
собственные предположения относительно возможных кодировок, вы можете передать
2677
dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
2678
print(dammit.unicode_markup)
2680
dammit.original_encoding
2683
В Unicode, Dammit есть две специальные функции, которые Beautiful Soup не
2689
Вы можете использовать Unicode, Dammit, чтобы конвертировать парные кавычки (Microsoft smart quotes) в
2690
мнемоники HTML или XML::
2692
markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
2694
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
2695
# u'<p>I just “love” Microsoft Word’s smart quotes</p>'
2697
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
2698
# u'<p>I just “love” Microsoft Word’s smart quotes</p>'
2700
Вы также можете конвертировать парные кавычки в обычные кавычки ASCII::
2702
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
2703
# u'<p>I just "love" Microsoft Word\'s smart quotes</p>'
2705
Надеюсь, вы найдете эту функцию полезной, но Beautiful Soup не
2706
использует ее. Beautiful Soup по умолчанию
2707
конвертирует парные кавычки в символы Unicode, как и
2710
UnicodeDammit(markup, ["windows-1252"]).unicode_markup
2711
# u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'
2713
Несогласованные кодировки
2714
^^^^^^^^^^^^^^^^^^^^^^^^^
2716
Иногда документ кодирован в основном в UTF-8, но содержит символы Windows-1252,
2717
такие как, опять-таки, парные кавычки. Такое бывает,
2718
когда веб-сайт содержит данные из нескольких источников. Вы можете использовать
2719
``UnicodeDammit.detwingle()``, чтобы превратить такой документ в чистый
2720
UTF-8. Вот простой пример::
2722
snowmen = (u"\N{SNOWMAN}" * 3)
2723
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
2724
doc = snowmen.encode("utf8") + quote.encode("windows_1252")
2726
В этом документе бардак. Снеговики в UTF-8, а парные кавычки
2727
в Windows-1252. Можно отображать или снеговиков, или кавычки, но не
2728
то и другое одновременно::
2731
# ☃☃☃�I like snowmen!�
2733
print(doc.decode("windows-1252"))
2734
# ☃☃☃“I like snowmen!”
2736
Декодирование документа как UTF-8 вызывает ``UnicodeDecodeError``, а
2737
декодирование его как Windows-1252 выдаст тарабарщину. К счастью,
2738
``UnicodeDammit.detwingle()`` преобразует строку в чистый UTF-8,
2739
позволяя затем декодировать его в Unicode и отображать снеговиков и кавычки
2742
new_doc = UnicodeDammit.detwingle(doc)
2743
print(new_doc.decode("utf8"))
2744
# ☃☃☃“I like snowmen!”
2746
``UnicodeDammit.detwingle()`` знает только, как обрабатывать Windows-1252,
2747
встроенный в UTF-8 (и наоборот, мне кажется), но это наиболее
2750
Обратите внимание, что нужно вызывать ``UnicodeDammit.detwingle()`` для ваших данных
2751
перед передачей в конструктор ``BeautifulSoup`` или
2752
``UnicodeDammit``. Beautiful Soup предполагает, что документ имеет единую
2753
кодировку, какой бы она ни была. Если вы передадите ему документ, который
2754
содержит как UTF-8, так и Windows-1252, скорее всего, он решит, что весь
2755
документ кодируется в Windows-1252, и это будет выглядеть как
2756
``☃☃☃“I like snowmen!”``.
2758
``UnicodeDammit.detwingle()`` — это новое в Beautiful Soup 4.1.0.
2763
Парсеры ``html.parser`` и ``html5lib`` могут отслеживать, где в
2764
исходном документе был найден каждый тег. Вы можете получить доступ к этой
2765
информации через ``Tag.sourceline`` (номер строки) и ``Tag.sourcepos``
2766
(позиция начального тега в строке)::
2768
markup = "<p\n>Paragraph 1</p>\n <p>Paragraph 2</p>"
2769
soup = BeautifulSoup(markup, 'html.parser')
2770
for tag in soup.find_all('p'):
2771
print(tag.sourceline, tag.sourcepos, tag.string)
2772
# (1, 0, u'Paragraph 1')
2773
# (2, 3, u'Paragraph 2')
2775
Обратите внимание, что два парсера понимают
2776
``sourceline`` и ``sourcepos`` немного по-разному. Для html.parser эти числа
2777
представляет позицию начального знака "<". Для html5lib
2778
эти числа представляют позицию конечного знака ">"::
2780
soup = BeautifulSoup(markup, 'html5lib')
2781
for tag in soup.find_all('p'):
2782
print(tag.sourceline, tag.sourcepos, tag.string)
2783
# (2, 1, u'Paragraph 1')
2784
# (3, 7, u'Paragraph 2')
2786
Вы можете отключить эту функцию, передав ``store_line_numbers = False``
2787
в конструктор ``BeautifulSoup``::
2789
markup = "<p\n>Paragraph 1</p>\n <p>Paragraph 2</p>"
2790
soup = BeautifulSoup(markup, 'html.parser', store_line_numbers=False)
2794
Эта функция является новой в 4.8.1, и парсеры, основанные на lxml, не
2797
Проверка объектов на равенство
2798
==============================
2800
Beautiful Soup считает, что два объекта ``NavigableString`` или ``Tag``
2801
равны, если они представлены в одинаковой разметке HTML или XML. В этом
2802
примере два тега <b> рассматриваются как равные, даже если они находятся
2803
в разных частях дерева объекта, потому что они оба выглядят как
2806
markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>"
2807
soup = BeautifulSoup(markup, 'html.parser')
2808
first_b, second_b = soup.find_all('b')
2809
print first_b == second_b
2812
print first_b.previous_element == second_b.previous_element
2815
Если вы хотите выяснить, указывают ли две переменные на один и тот же
2816
объект, используйте `is`::
2818
print first_b is second_b
2821
Копирование объектов Beautiful Soup
2822
===================================
2824
Вы можете использовать ``copy.copy()`` для создания копии любого ``Tag`` или
2825
``NavigableString``::
2828
p_copy = copy.copy(soup.p)
2830
# <p>I want <b>pizza</b> and more <b>pizza</b>!</p>
2832
Копия считается равной оригиналу, так как у нее
2833
такая же разметка, что и у оригинала, но это другой объект::
2835
print soup.p == p_copy
2838
print soup.p is p_copy
2841
Единственная настоящая разница в том, что копия полностью отделена от
2842
исходного дерева объекта Beautiful Soup, как если бы в отношении нее вызвали
2843
метод ``extract()``::
2848
Это потому, что два разных объекта ``Tag`` не могут занимать одно и то же
2849
пространство в одно и то же время.
2852
Разбор части документа
2853
======================
2855
Допустим, вы хотите использовать Beautiful Soup, чтобы посмотреть на
2856
теги <a> в документе. Было бы бесполезной тратой времени и памяти разобирать весь документ и
2857
затем снова проходить по нему в поисках тегов <a>. Намного быстрее
2858
изначательно игнорировать все, что не является тегом <a>. Класс
2859
``SoupStrainer`` позволяет выбрать, какие части входящего
2860
документ разбирать. Вы просто создаете ``SoupStrainer`` и передаете его в
2861
конструктор ``BeautifulSoup`` в качестве аргумента ``parse_only``.
2863
(Обратите внимание, что *эта функция не будет работать, если вы используете парсер html5lib*.
2864
Если вы используете html5lib, будет разобран весь документ, независимо
2865
от обстоятельств. Это потому что html5lib постоянно переставляет части дерева разбора
2866
в процессе работы, и если какая-то часть документа не
2867
попала в дерево разбора, все рухнет. Чтобы избежать путаницы, в
2868
примерах ниже я принудительно использую встроенный в Python
2874
Класс ``SoupStrainer`` принимает те же аргументы, что и типичный
2875
метод из раздела `Поиск по дереву`_: :ref:`name <name>`, :ref:`attrs
2876
<attrs>`, :ref:`string <string>` и :ref:`**kwargs <kwargs>`. Вот
2877
три объекта ``SoupStrainer``::
2879
from bs4 import SoupStrainer
2881
only_a_tags = SoupStrainer("a")
2883
only_tags_with_id_link2 = SoupStrainer(id="link2")
2885
def is_short_string(string):
2886
return len(string) < 10
2888
only_short_strings = SoupStrainer(string=is_short_string)
2890
Вернемся к фрагменту из «Алисы в стране чудес»
2891
и увидим, как выглядит документ, когда он разобран с этими
2892
тремя объектами ``SoupStrainer``::
2895
<html><head><title>The Dormouse's story</title></head>
2897
<p class="title"><b>The Dormouse's story</b></p>
2899
<p class="story">Once upon a time there were three little sisters; and their names were
2900
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
2901
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
2902
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
2903
and they lived at the bottom of a well.</p>
2905
<p class="story">...</p>
2908
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
2909
# <a class="sister" href="http://example.com/elsie" id="link1">
2912
# <a class="sister" href="http://example.com/lacie" id="link2">
2915
# <a class="sister" href="http://example.com/tillie" id="link3">
2919
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
2920
# <a class="sister" href="http://example.com/lacie" id="link2">
2924
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
2933
Вы также можете передать ``SoupStrainer`` в любой из методов. описанных в разделе
2934
`Поиск по дереву`_. Может, это не безумно полезно, но я
2937
soup = BeautifulSoup(html_doc)
2938
soup.find_all(only_short_strings)
2939
# [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
2940
# u'\n\n', u'...', u'\n']
2942
Устранение неисправностей
2943
=========================
2950
Если у вас возникли проблемы с пониманием того, что Beautiful Soup делает с
2951
документом, передайте документ в функцию ``Diagnose()``. (Новое в
2952
Beautiful Soup 4.2.0.) Beautiful Soup выведет отчет, показывающий,
2953
как разные парсеры обрабатывают документ, и сообщит вам, если
2954
отсутствует парсер, который Beautiful Soup мог бы использовать::
2956
from bs4.diagnose import diagnose
2957
with open("bad.html") as fp:
2961
# Diagnostic running on Beautiful Soup 4.2.0
2962
# Python version 2.7.3 (default, Aug 1 2012, 05:16:07)
2963
# I noticed that html5lib is not installed. Installing it may help.
2964
# Found lxml version 2.3.2.0
2966
# Trying to parse your data with html.parser
2967
# Here's what html.parser did with the document:
2970
Простой взгляд на вывод diagnose() может показать, как решить
2971
проблему. Если это и не поможет, вы можете скопировать вывод ``Diagnose()``, когда
2972
обратитесь за помощью.
2974
Ошибки при разборе документа
2975
----------------------------
2977
Существует два вида ошибок разбора. Есть сбои,
2978
когда вы подаете документ в Beautiful Soup, и это поднимает
2979
исключение, обычно ``HTMLParser.HTMLParseError``. И есть
2980
неожиданное поведение, когда дерево разбора Beautiful Soup сильно
2981
отличается от документа, использованного для создания дерева.
2983
Практически никогда источником этих проблемы не бывает Beautiful
2984
Soup. Это не потому, что Beautiful Soup так прекрасно
2985
написан. Это потому, что Beautiful Soup не содержит
2986
кода, который бы разбирал документ. Beautiful Soup опирается на внешние парсеры. Если один парсер
2987
не подходит для разбора документа, лучшим решением будет попробовать
2988
другой парсер. В разделе `Установка парсера`_ вы найдете больше информации
2989
и таблицу сравнения парсеров.
2991
Наиболее распространенные ошибки разбора — это ``HTMLParser.HTMLParseError:
2992
malformed start tag`` и ``HTMLParser.HTMLParseError: bad end
2993
tag``. Они оба генерируются встроенным в Python парсером HTML,
2994
и решением будет :ref:`установить lxml или
2995
html5lib. <parser-installation>`
2997
Наиболее распространенный тип неожиданного поведения — когда вы не можете найти
2998
тег, который точно есть в документе. Вы видели его на входе, но
2999
``find_all()`` возвращает ``[]``, или ``find()`` возвращает ``None``. Это
3000
еще одна распространенная проблема со встроенным в Python парсером HTML, который
3001
иногда пропускает теги, которые он не понимает. Опять же, решение заключается в
3002
:ref:`установке lxml или html5lib <parser-installation>`.
3004
Проблемы несоответствия версий
3005
------------------------------
3007
* ``SyntaxError: Invalid syntax`` (в строке ``ROOT_TAG_NAME =
3008
u'[document]'``) — вызвано запуском версии Beautiful Soup на Python 2
3009
под Python 3 без конвертации кода.
3011
* ``ImportError: No module named HTMLParser`` — вызвано запуском
3012
версия Beautiful Soup на Python 3 под Python 2.
3014
* ``ImportError: No module named html.parser`` — вызвано запуском
3015
версия Beautiful Soup на Python 2 под Python 3.
3017
* ``ImportError: No module named BeautifulSoup`` — вызвано запуском
3018
кода Beautiful Soup 3 в системе, где BS3
3019
не установлен. Или код писали на Beautiful Soup 4, не зная, что
3020
имя пакета сменилось на ``bs4``.
3022
* ``ImportError: No module named bs4`` — вызвано запуском
3023
кода Beautiful Soup 4 в системе, где BS4 не установлен.
3030
По умолчанию Beautiful Soup разбирает документы как HTML. Чтобы разобрать
3031
документ в виде XML, передайте "xml" в качестве второго аргумента
3032
в конструктор ``BeautifulSoup``::
3034
soup = BeautifulSoup(markup, "xml")
3036
Вам также нужно будет :ref:`установить lxml <parser-installation>`.
3038
Другие проблемы с парсерами
3039
---------------------------
3041
* Если ваш скрипт работает на одном компьютере, но не работает на другом, или работает в одной
3042
виртуальной среде, но не в другой, или работает вне виртуальной
3043
среды, но не внутри нее, это, вероятно, потому что в двух
3044
средах разные библиотеки парсеров. Например,
3045
вы могли разработать скрипт на компьютере с установленным lxml,
3046
а затем попытались запустить его на компьютере, где установлен только
3047
html5lib. Читайте в разделе `Различия между парсерами`_, почему это
3048
важно, и исправляйте проблемы, указывая конкретную библиотеку парсера
3049
в конструкторе ``BeautifulSoup``.
3051
* Поскольку `HTML-теги и атрибуты нечувствительны к регистру
3052
<http://www.w3.org/TR/html5/syntax.html#syntax>`_, все три HTML-
3053
парсера конвертируют имена тегов и атрибутов в нижний регистр. Таким образом,
3054
разметка <TAG></TAG> преобразуется в <tag></tag>. Если вы хотите
3055
сохранить смешанный или верхний регистр тегов и атрибутов, вам нужно
3056
:ref:`разобрать документ как XML <parsing-xml>`.
3063
* ``UnicodeEncodeError: 'charmap' codec can't encode character
3064
u'\xfoo' in position bar`` (или практически любая другая ошибка
3065
``UnicodeEncodeError``) — это не проблема с Beautiful Soup.
3066
Эта проблема проявляется в основном в двух ситуациях. Во-первых, когда вы пытаетесь
3067
вывести символ Unicode, который ваша консоль не может отобразить, потому что не знает, как.
3068
(Смотрите `эту страницу в Python вики
3069
<http://wiki.python.org/moin/PrintFails>`_.) Во-вторых, когда
3070
вы пишете в файл и передаете символ Unicode, который
3071
не поддерживается вашей кодировкой по умолчанию. В этом случае самым простым
3072
решением будет явное кодирование строки Unicode в UTF-8 с помощью
3073
``u.encode("utf8")``.
3075
* ``KeyError: [attr]`` — вызывается при обращении к ``tag['attr']``, когда
3076
в искомом теге не определен атрибут ``attr``. Наиболее
3077
типичны ошибки ``KeyError: 'href'`` и ``KeyError:
3078
'class'``. Используйте ``tag.get('attr')``, если вы не уверены, что ``attr``
3079
определен — так же, как если бы вы работали со словарем Python.
3081
* ``AttributeError: 'ResultSet' object has no attribute 'foo'`` — это
3082
обычно происходит тогда, когда вы ожидаете, что ``find_all()`` вернет
3083
один тег или строку. Но ``find_all()`` возвращает *список* тегов
3084
и строк в объекте ``ResultSet``. Вам нужно перебрать
3085
список и поискать ``.foo`` в каждом из элементов. Или, если вам действительно
3086
нужен только один результат, используйте ``find()`` вместо
3089
* ``AttributeError: 'NoneType' object has no attribute 'foo'`` — это
3090
обычно происходит, когда вы вызываете ``find()`` и затем пытаетесь
3091
получить доступ к атрибуту ``.foo``. Но в вашем случае
3092
``find()`` не нашел ничего, поэтому вернул ``None`` вместо
3093
того, чтобы вернуть тег или строку. Вам нужно выяснить, почему
3094
``find()`` ничего не возвращает.
3096
Повышение производительности
3097
----------------------------
3099
Beautiful Soup никогда не будет таким же быстрым, как парсеры, на основе которых он
3100
работает. Если время отклика критично, если вы платите за компьютерное время
3101
по часам, или если есть какая-то другая причина, почему компьютерное время
3102
важнее программистского, стоит забыть о Beautiful Soup
3103
и работать непосредственно с `lxml <http://lxml.de/>`_.
3105
Тем не менее, есть вещи, которые вы можете сделать, чтобы ускорить Beautiful Soup. Если
3106
вы не используете lxml в качестве основного парсера, самое время
3107
:ref:`начать <parser-installation>`. Beautiful Soup разбирает документы
3108
значительно быстрее с lxml, чем с html.parser или html5lib.
3110
Вы можете значительно ускорить распознавание кодировок, установив
3111
библиотеку `cchardet <http://pypi.python.org/pypi/cchardet/>`_.
3113
`Разбор части документа`_ не сэкономит много времени в процессе разбора,
3114
но может сэкономить много памяти, что сделает
3115
`поиск` по документу намного быстрее.
3121
Beautiful Soup 3 — предыдущая версия, и она больше
3122
активно не развивается. На текущий момент Beautiful Soup 3 поставляется со всеми основными
3123
дистрибутивами Linux:
3125
:kbd:`$ apt-get install python-beautifulsoup`
3127
Он также публикуется через PyPi как ``BeautifulSoup``:
3129
:kbd:`$ easy_install BeautifulSoup`
3131
:kbd:`$ pip install BeautifulSoup`
3133
Вы можете скачать `tar-архив Beautiful Soup 3.2.0
3134
<http://www.crummy.com/software/BeautifulSoup/bs3/download/3.x/BeautifulSoup-3.2.0.tar.gz>`_.
3136
Если вы запустили ``easy_install beautifulsoup`` или ``easy_install
3137
BeautifulSoup``, но ваш код не работает, значит, вы ошибочно установили Beautiful
3138
Soup 3. Вам нужно запустить ``easy_install beautifulsoup4``.
3140
Архивная документация для Beautiful Soup 3 доступна `онлайн
3141
<http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html>`_.
3146
Большая часть кода, написанного для Beautiful Soup 3, будет работать и в Beautiful
3147
Soup 4 с одной простой заменой. Все, что вам нужно сделать, это изменить
3148
имя пакета c ``BeautifulSoup`` на ``bs4``. Так что это::
3150
from BeautifulSoup import BeautifulSoup
3154
from bs4 import BeautifulSoup
3156
* Если выводится сообщение ``ImportError`` "No module named BeautifulSoup", ваша
3157
проблема в том, что вы пытаетесь запустить код Beautiful Soup 3, в то время как
3158
у вас установлен Beautiful Soup 4.
3160
* Если выводится сообщение ``ImportError`` "No module named bs4", ваша проблема
3161
в том, что вы пытаетесь запустить код Beautiful Soup 4, в то время как
3162
у вас установлен Beautiful Soup 3.
3164
Хотя BS4 в основном обратно совместим с BS3, большинство
3165
методов BS3 устарели и получили новые имена, чтобы `соответствовать PEP 8
3166
<http://www.python.org/dev/peps/pep-0008/>`_. Некоторые
3167
из переименований и изменений нарушают обратную совместимость.
3169
Вот что нужно знать, чтобы перейти с BS3 на BS4:
3174
Beautiful Soup 3 использовал модуль Python ``SGMLParser``, который теперь
3175
устарел и был удален в Python 3.0. Beautiful Soup 4 по умолчанию использует
3176
``html.parser``, но вы можете подключить lxml или html5lib
3177
вместо него. Вы найдете таблицу сравнения парсеров в разделе `Установка парсера`_.
3179
Поскольку ``html.parser`` — это не то же, что ``SGMLParser``, вы
3180
можете обнаружить, что Beautiful Soup 4 дает другое дерево разбора, чем
3181
Beautiful Soup 3. Если вы замените html.parser
3182
на lxml или html5lib, может оказаться, что дерево разбора опять
3183
изменилось. Если такое случится, вам придется обновить код,
3184
чтобы разобраться с новым деревом.
3189
* ``renderContents`` -> ``encode_contents``
3190
* ``replaceWith`` -> ``replace_with``
3191
* ``replaceWithChildren`` -> ``unwrap``
3192
* ``findAll`` -> ``find_all``
3193
* ``findAllNext`` -> ``find_all_next``
3194
* ``findAllPrevious`` -> ``find_all_previous``
3195
* ``findNext`` -> ``find_next``
3196
* ``findNextSibling`` -> ``find_next_sibling``
3197
* ``findNextSiblings`` -> ``find_next_siblings``
3198
* ``findParent`` -> ``find_parent``
3199
* ``findParents`` -> ``find_parents``
3200
* ``findPrevious`` -> ``find_previous``
3201
* ``findPreviousSibling`` -> ``find_previous_sibling``
3202
* ``findPreviousSiblings`` -> ``find_previous_siblings``
3203
* ``getText`` -> ``get_text``
3204
* ``nextSibling`` -> ``next_sibling``
3205
* ``previousSibling`` -> ``previous_sibling``
3207
Некоторые аргументы конструктора Beautiful Soup были переименованы по
3210
* ``BeautifulSoup(parseOnlyThese=...)`` -> ``BeautifulSoup(parse_only=...)``
3211
* ``BeautifulSoup(fromEncoding=...)`` -> ``BeautifulSoup(from_encoding=...)``
3213
Я переименовал один метод для совместимости с Python 3:
3215
* ``Tag.has_key()`` -> ``Tag.has_attr()``
3217
Я переименовал один атрибут, чтобы использовать более точную терминологию:
3219
* ``Tag.isSelfClosing`` -> ``Tag.is_empty_element``
3221
Я переименовал три атрибута, чтобы избежать использования зарезервированных слов
3222
в Python. В отличие от других, эти изменения *не являются обратно
3223
совместимыми*. Если вы использовали эти атрибуты в BS3, ваш код не сработает
3224
на BS4, пока вы их не измените.
3226
* ``UnicodeDammit.unicode`` -> ``UnicodeDammit.unicode_markup``
3227
* ``Tag.next`` -> ``Tag.next_element``
3228
* ``Tag.previous`` -> ``Tag.previous_element``
3233
Я дал генераторам PEP 8-совместимые имена и преобразовал их в
3236
* ``childGenerator()`` -> ``children``
3237
* ``nextGenerator()`` -> ``next_elements``
3238
* ``nextSiblingGenerator()`` -> ``next_siblings``
3239
* ``previousGenerator()`` -> ``previous_elements``
3240
* ``previousSiblingGenerator()`` -> ``previous_siblings``
3241
* ``recursiveChildGenerator()`` -> ``descendants``
3242
* ``parentGenerator()`` -> ``parents``
3244
Так что вместо этого::
3246
for parent in tag.parentGenerator():
3249
Вы можете написать это::
3251
for parent in tag.parents:
3254
(Хотя старый код тоже будет работать.)
3256
Некоторые генераторы выдавали ``None`` после их завершения и
3257
останавливались. Это была ошибка. Теперь генераторы просто останавливаются.
3259
Добавились два генератора: :ref:`.strings и
3260
.stripped_strings <string-generators>`.
3263
объекты NavigableString, а ``.stripped_strings`` выдает строки Python,
3264
у которых удалены пробелы.
3269
Больше нет класса ``BeautifulStoneSoup`` для разбора XML. Чтобы
3270
разобрать XML, нужно передать "xml" в качестве второго аргумента
3271
в конструктор ``BeautifulSoup``. По той же причине
3272
конструктор ``BeautifulSoup`` больше не распознает
3273
аргумент ``isHTML``.
3275
Улучшена обработка пустых тегов
3276
XML. Ранее при разборе XML нужно было явно указать,
3277
какие теги считать пустыми элементами. Аргумент ``SelfClosingTags``
3278
больше не распознается. Вместо этого
3279
Beautiful Soup считает пустым элементом любой тег без содержимого. Если
3280
вы добавляете в тег дочерний элемент, тег больше не считается
3286
Входящие мнемоники HTML или XML всегда преобразуются в
3287
соответствующие символы Unicode. В Beautiful Soup 3 было несколько
3288
перекрывающих друг друга способов взаимодействия с мнемониками. Эти способы
3289
удалены. Конструктор ``BeautifulSoup`` больше не распознает
3290
аргументы ``smartQuotesTo`` и ``convertEntities``. (В `Unicode,
3291
Dammit`_ все еще присутствует ``smart_quotes_to``, но по умолчанию парные кавычки
3292
преобразуются в Unicode). Константы ``HTML_ENTITIES``,
3293
``XML_ENTITIES`` и ``XHTML_ENTITIES`` были удалены, так как они
3294
служили для настройки функции, которой больше нет (преобразование отдельных мнемоник в
3297
Если вы хотите на выходе преобразовать символы Unicode обратно в мнемоники HTML,
3298
а не превращать Unicode в символы UTF-8, вам нужно
3299
использовать :ref:`средства форматирования вывода <output_formatters>`.
3304
:ref:`Tag.string <.string>` теперь работает рекурсивно. Если тег А
3305
содержит только тег B и ничего больше, тогда значение A.string будет таким же, как
3306
B.string. (Раньше это был None.)
3308
`Многозначные атрибуты`_, такие как ``class``, теперь в качестве значений имеют списки строк,
3309
а не строки. Это может повлиять на поиск
3312
Если вы передадите в один из методов ``find*`` одновременно :ref:`string <string>` `и`
3313
специфичный для тега аргумент, такой как :ref:`name <name>`, Beautiful Soup будет
3314
искать теги, которые, во-первых, соответствуют специфичным для тега критериям, и, во-вторых, имеют
3315
:ref:`Tag.string <.string>`, соответствующий заданному вами значению :ref:`string
3316
<string>`. Beautiful Soup `не` найдет сами строки. Ранее
3317
Beautiful Soup игнорировал аргументы, специфичные для тегов, и искал
3320
Конструктор ``BeautifulSoup`` больше не распознает
3321
аргумент `markupMassage`. Теперь это задача парсера —
3322
обрабатывать разметку правильно.
3324
Редко используемые альтернативные классы парсеров, такие как
3325
``ICantBelieveItsBeautifulSoup`` и ``BeautifulSOAP``,
3326
удалены. Теперь парсер решает, что делать с неоднозначной
3329
Метод ``prettify()`` теперь возвращает строку Unicode, а не байтовую строку.
3331
Перевод документации
3332
====================
3334
Переводы документации Beautiful Soup очень
3335
приветствуются. Перевод должен быть лицензирован по лицензии MIT,
3336
так же, как сам Beautiful Soup и англоязычная документация к нему.
3338
Есть два способа передать ваш перевод:
3340
1. Создайте ветку репозитория Beautiful Soup, добавьте свой
3341
перевод и предложите слияние с основной веткой — так же,
3342
как вы предложили бы изменения исходного кода.
3343
2. Отправьте `в дискуссионную группу Beautiful Soup <https://groups.google.com/forum/?fromgroups#!forum/beautifulsoup>`_
3344
сообщение со ссылкой на
3345
ваш перевод, или приложите перевод к сообщению.
3347
Используйте существующие переводы документации на китайский или португальский в качестве образца. В
3348
частности, переводите исходный файл ``doc/source/index.rst`` вместо
3349
того, чтобы переводить HTML-версию документации. Это позволяет
3350
публиковать документацию в разных форматах, не
3356
Перевод на русский язык: `authoress <mailto:geekwriter@yandex.ru>`_
3358
Дата перевода: февраль 2020
3360
Перевод выполнен с `оригинала на английском языке <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_.