~leonardr/beautifulsoup/bs4

« back to all changes in this revision

Viewing changes to doc.ru/source/bs4ru.rst

  • Committer: Leonard Richardson
  • Date: 2020-04-04 22:22:11 UTC
  • Revision ID: leonardr@segfault.org-20200404222211-inp8kns635yu6pyy
Added a Russian translation by 'authoress' to the repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
Документация Beautiful Soup 
 
2
===========================
 
3
 
 
4
.. image:: 6.1.jpg
 
5
   :align: right
 
6
   :alt: "Лакей Карась начал с того, что вытащил из-под мышки огромный конверт (чуть ли не больше его самого)."
 
7
 
 
8
`Beautiful Soup <http://www.crummy.com/software/BeautifulSoup/>`_ — это
 
9
библиотека Python для извлечения данных из файлов HTML и XML. Она работает
 
10
с вашим любимым парсером, чтобы дать вам естественные способы навигации,
 
11
поиска и изменения дерева разбора. Она обычно экономит программистам
 
12
часы и дни работы.
 
13
 
 
14
Эти инструкции иллюстрируют все основные функции Beautiful Soup 4
 
15
на примерах. Я покажу вам, для чего нужна библиотека, как она работает,
 
16
как ее использовать, как заставить ее делать то, что вы хотите, и что нужно делать, когда она
 
17
не оправдывает ваши ожидания.
 
18
 
 
19
Примеры в этой документации работают одинаково на Python 2.7
 
20
и Python 3.2.
 
21
 
 
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`_.
 
28
 
 
29
Эта документация переведена на другие языки
 
30
пользователями Beautiful Soup:
 
31
 
 
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/>`_
 
36
 
 
37
 
 
38
Техническая поддержка
 
39
---------------------
 
40
 
 
41
Если у вас есть вопросы о Beautiful Soup или возникли проблемы,
 
42
`отправьте сообщение в дискуссионную группу
 
43
<https://groups.google.com/forum/?fromgroups#!forum/beautifulsoup>`_. Если
 
44
ваша проблема связана с разбором HTML-документа, не забудьте упомянуть,
 
45
:ref:`что говорит о нем функция diagnose() <diagnose>`.
 
46
 
 
47
Быстрый старт
 
48
=============
 
49
 
 
50
Вот HTML-документ, который я буду использовать в качестве примера в этой
 
51
документации. Это фрагмент из `«Алисы в стране чудес»`::
 
52
 
 
53
 html_doc = """
 
54
 <html><head><title>The Dormouse's story</title></head>
 
55
 <body>
 
56
 <p class="title"><b>The Dormouse's story</b></p>
 
57
 
 
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>
 
63
 
 
64
 <p class="story">...</p>
 
65
 """
 
66
 
 
67
Прогон документа через Beautiful Soup дает нам
 
68
объект ``BeautifulSoup``, который представляет документ в виде
 
69
вложенной структуры данных::
 
70
 
 
71
 from bs4 import BeautifulSoup
 
72
 soup = BeautifulSoup (html_doc, 'html.parser')
 
73
 
 
74
 print(soup.prettify())
 
75
 # <html>
 
76
 #  <head>
 
77
 #   <title>
 
78
 #    The Dormouse's story
 
79
 #   </title>
 
80
 #  </head>
 
81
 #  <body>
 
82
 #   <p class="title">
 
83
 #    <b>
 
84
 #     The Dormouse's story
 
85
 #    </b>
 
86
 #   </p>
 
87
 #   <p class="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">
 
90
 #     Elsie
 
91
 #    </a>
 
92
 #    ,
 
93
 #    <a class="sister" href="http://example.com/lacie" id="link2">
 
94
 #     Lacie
 
95
 #    </a>
 
96
 #    and
 
97
 #    <a class="sister" href="http://example.com/tillie" id="link3">
 
98
 #     Tillie
 
99
 #    </a>
 
100
 #    ; and they lived at the bottom of a well.
 
101
 #   </p>
 
102
 #   <p class="story">
 
103
 #    ...
 
104
 #   </p>
 
105
 #  </body>
 
106
 # </html>
 
107
 
 
108
Вот несколько простых способов навигации по этой структуре данных::
 
109
 
 
110
 soup.title
 
111
 # <title>The Dormouse's story</title>
 
112
 
 
113
 soup.title.name
 
114
 # u'title'
 
115
 
 
116
 soup.title.string
 
117
 # u'The Dormouse's story'
 
118
 
 
119
 soup.title.parent.name
 
120
 # u'head'
 
121
 
 
122
 soup.p
 
123
 # <p class="title"><b>The Dormouse's story</b></p>
 
124
 
 
125
 soup.p['class']
 
126
 # u'title'
 
127
 
 
128
 soup.a
 
129
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
130
 
 
131
 soup.find_all('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>]
 
135
 
 
136
 soup.find(id="link3")
 
137
 # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
 
138
 
 
139
Одна из распространенных задач — извлечь все URL-адреса, найденные на странице в тегах <a>::
 
140
 
 
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
 
146
 
 
147
Другая распространенная задача — извлечь весь текст со страницы::
 
148
 
 
149
 print(soup.get_text())
 
150
 # The Dormouse's story
 
151
 #
 
152
 # The Dormouse's story
 
153
 #
 
154
 # Once upon a time there were three little sisters; and their names were
 
155
 # Elsie,
 
156
 # Lacie and
 
157
 # Tillie;
 
158
 # and they lived at the bottom of a well.
 
159
 #
 
160
 # ...
 
161
 
 
162
Это похоже на то, что вам нужно? Если да, продолжайте читать.
 
163
 
 
164
Установка Beautiful Soup
 
165
========================
 
166
 
 
167
Если вы используете последнюю версию Debian или Ubuntu Linux, вы можете
 
168
установить Beautiful Soup с помощью системы управления пакетами:
 
169
 
 
170
:kbd:`$ apt-get install python-bs4` (для Python 2)
 
171
 
 
172
:kbd:`$ apt-get install python3-bs4` (для Python 3)
 
173
 
 
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).
 
180
 
 
181
:kbd:`$ easy_install beautifulsoup4`
 
182
 
 
183
:kbd:`$ pip install beautifulsoup4`
 
184
 
 
185
(``BeautifulSoup`` — это, скорее всего, `не тот` пакет, который вам нужен. Это
 
186
предыдущий основной релиз, `Beautiful Soup 3`_. Многие программы используют
 
187
BS3, так что он все еще доступен, но если вы пишете новый код,
 
188
нужно установить ``beautifulsoup4``.)
 
189
 
 
190
Если у вас не установлены ``easy_install`` или ``pip``, вы можете
 
191
`скачать архив с исходным кодом Beautiful Soup 4
 
192
<http://www.crummy.com/software/BeautifulSoup/download/4.x/>`_ и
 
193
установить его с помощью ``setup.py``.
 
194
 
 
195
:kbd:`$ python setup.py install`
 
196
 
 
197
Если ничего не помогает, лицензия на Beautiful Soup позволяет
 
198
упаковать библиотеку целиком вместе с вашим приложением. Вы можете скачать
 
199
tar-архив, скопировать из него в кодовую базу вашего приложения каталог ``bs4``
 
200
и использовать Beautiful Soup, не устанавливая его вообще.
 
201
 
 
202
Я использую Python 2.7 и Python 3.2 для разработки Beautiful Soup, но библиотека
 
203
должна работать и с более поздними версиями Python.
 
204
 
 
205
Проблемы после установки
 
206
------------------------
 
207
 
 
208
Beautiful Soup упакован как код Python 2. Когда вы устанавливаете его для
 
209
использования с Python 3, он автоматически конвертируется в код Python 3. Если
 
210
вы не устанавливаете библиотеку в виде пакета, код не будет сконвертирован. Были
 
211
также сообщения об установке неправильной версии на компьютерах с
 
212
Windows.
 
213
 
 
214
Если выводится сообщение ``ImportError`` "No module named HTMLParser", ваша
 
215
проблема в том, что вы используете версию кода на Python 2, работая на
 
216
Python 3.
 
217
 
 
218
Если выводится сообщение ``ImportError`` "No module named html.parser", ваша
 
219
проблема в том, что вы используете версию кода на Python 3, работая на
 
220
Python 2.
 
221
 
 
222
В обоих случаях лучше всего полностью удалить Beautiful
 
223
Soup  с вашей системы (включая любой каталог, созданный
 
224
при распаковке tar-архива) и запустить установку еще раз.
 
225
 
 
226
Если выводится сообщение ``SyntaxError`` "Invalid syntax" в строке
 
227
``ROOT_TAG_NAME = u'[document]'``, вам нужно конвертировать код из Python 2
 
228
в Python 3. Вы можете установить пакет:
 
229
 
 
230
:kbd:`$ python3 setup.py install`
 
231
 
 
232
или запустить вручную Python-скрипт ``2to3``
 
233
в каталоге ``bs4``:
 
234
 
 
235
:kbd:`$ 2to3-3.2 -w bs4`
 
236
 
 
237
.. _parser-installation:
 
238
 
 
239
 
 
240
Установка парсера
 
241
-----------------
 
242
 
 
243
Beautiful Soup поддерживает парсер HTML, включенный в стандартную библиотеку Python,
 
244
а также ряд сторонних парсеров на Python.
 
245
Одним из них является `парсер lxml <http://lxml.de/>`_. В зависимости от ваших настроек,
 
246
вы можете установить lxml с помощью одной из следующих команд:
 
247
 
 
248
:kbd:`$ apt-get install python-lxml`
 
249
 
 
250
:kbd:`$ easy_install lxml`
 
251
 
 
252
:kbd:`$ pip install lxml`
 
253
 
 
254
Другая альтернатива — написанный исключительно на Python `парсер html5lib
 
255
<http://code.google.com/p/html5lib/>`_, который разбирает HTML таким же образом,
 
256
как это делает веб-браузер. В зависимости от ваших настроек, вы можете установить html5lib
 
257
с помощью одной из этих команд:
 
258
 
 
259
:kbd:`$ apt-get install python-html5lib`
 
260
 
 
261
:kbd:`$ easy_install html5lib`
 
262
 
 
263
:kbd:`$ pip install html5lib`
 
264
 
 
265
Эта таблица суммирует преимущества и недостатки каждого парсера:
 
266
 
 
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
+----------------------+--------------------------------------------+--------------------------------+--------------------------+
 
287
 
 
288
Я рекомендую по возможности установить и использовать lxml для быстродействия. Если вы
 
289
используете версию Python 2 более раннюю, чем 2.7.3, или версию Python 3
 
290
более раннюю, чем 3.2.2, `необходимо` установить lxml или
 
291
html5lib, потому что встроенный в Python парсер HTML просто недостаточно хорош в старых
 
292
версиях.
 
293
 
 
294
Обратите внимание, что если документ невалиден, различные парсеры будут генерировать
 
295
дерево Beautiful Soup для этого документа по-разному. Ищите подробности в разделе `Различия
 
296
между парсерами`_.
 
297
 
 
298
Приготовление супа
 
299
==================
 
300
 
 
301
Чтобы разобрать документ, передайте его в
 
302
конструктор ``BeautifulSoup``. Вы можете передать строку или открытый дескриптор файла::
 
303
 
 
304
 from bs4 import BeautifulSoup
 
305
 
 
306
 with open("index.html") as fp:
 
307
     soup = BeautifulSoup(fp)
 
308
 
 
309
 soup = BeautifulSoup("<html>data</html>")
 
310
 
 
311
Первым делом документ конвертируется в Unicode, а HTML-мнемоники
 
312
конвертируются в символы Unicode::
 
313
 
 
314
 BeautifulSoup("Sacr&eacute; bleu!")
 
315
 <html><head></head><body>Sacré bleu!</body></html>
 
316
 
 
317
Затем Beautiful Soup анализирует документ, используя лучший из доступных
 
318
парсеров. Библиотека будет использовать HTML-парсер, если вы явно не укажете,
 
319
что нужно использовать XML-парсер. (См. `Разбор XML`_.)
 
320
 
 
321
Виды объектов
 
322
=============
 
323
 
 
324
Beautiful Soup превращает сложный HTML-документ в сложное дерево
 
325
объектов Python. Однако вам придется иметь дело только с четырьмя
 
326
`видами` объектов: ``Tag``, ``NavigableString``, ``BeautifulSoup``
 
327
и ``Comment``.
 
328
 
 
329
.. _Tag:
 
330
 
 
331
``Tag``
 
332
-------
 
333
 
 
334
Объект ``Tag`` соответствует тегу XML или HTML в исходном документе::
 
335
 
 
336
 soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
 
337
 tag = soup.b
 
338
 type(tag)
 
339
 # <class 'bs4.element.Tag'>
 
340
 
 
341
У объекта Tag (далее «тег») много атрибутов и методов, и я расскажу о большинстве из них
 
342
в разделах `Навигация по дереву`_ и `Поиск по дереву`_. На данный момент наиболее
 
343
важными особенностями тега являются его имя и атрибуты.
 
344
 
 
345
Имя
 
346
^^^
 
347
 
 
348
У каждого тега есть имя, доступное как ``.name``::
 
349
 
 
350
 tag.name
 
351
 # u'b'
 
352
 
 
353
Если вы измените имя тега, это изменение будет отражено в любой HTML-
 
354
разметке, созданной Beautiful Soup::
 
355
 
 
356
 tag.name = "blockquote"
 
357
 tag
 
358
 # <blockquote class="boldest">Extremely bold</blockquote>
 
359
 
 
360
Атрибуты
 
361
^^^^^^^^
 
362
 
 
363
У тега может быть любое количество атрибутов. Тег ``<b
 
364
id = "boldest">`` имеет атрибут "id", значение которого равно
 
365
"boldest". Вы можете получить доступ к атрибутам тега, обращаясь с тегом как
 
366
со словарем::
 
367
 
 
368
 tag['id']
 
369
 # u'boldest'
 
370
 
 
371
Вы можете получить доступ к этому словарю напрямую как к ``.attrs``::
 
372
 
 
373
 tag.attrs
 
374
 # {u'id': 'boldest'}
 
375
 
 
376
Вы можете добавлять, удалять и изменять атрибуты тега. Опять же, это
 
377
делается путем обращения с тегом как со словарем::
 
378
 
 
379
 tag['id'] = 'verybold'
 
380
 tag['another-attribute'] = 1
 
381
 tag
 
382
 # <b another-attribute="1" id="verybold"></b>
 
383
 
 
384
 del tag['id']
 
385
 del tag['another-attribute']
 
386
 tag
 
387
 # <b></b>
 
388
 
 
389
 tag['id']
 
390
 # KeyError: 'id'
 
391
 print(tag.get('id'))
 
392
 # None
 
393
 
 
394
.. _multivalue:
 
395
 
 
396
Многозначные атрибуты
 
397
&&&&&&&&&&&&&&&&&&&&&
 
398
 
 
399
В HTML 4 определено несколько атрибутов, которые могут иметь множество значений. В HTML 5
 
400
пара таких атрибутов удалена, но определено еще несколько. Самый распространённый из
 
401
многозначных атрибутов — это ``class`` (т. е. тег может иметь более
 
402
одного класса CSS). Среди прочих ``rel``, ``rev``, ``accept-charset``,
 
403
``headers`` и ``accesskey``. Beautiful Soup представляет значение(я)
 
404
многозначного атрибута в виде списка::
 
405
 
 
406
 css_soup = BeautifulSoup('<p class="body"></p>')
 
407
 css_soup.p['class']
 
408
 # ["body"]
 
409
  
 
410
 css_soup = BeautifulSoup('<p class="body strikeout"></p>')
 
411
 css_soup.p['class']
 
412
 # ["body", "strikeout"]
 
413
 
 
414
Если атрибут `выглядит` так, будто он имеет более одного значения, но это не
 
415
многозначный атрибут, определенный какой-либо версией HTML-
 
416
стандарта, Beautiful Soup оставит атрибут как есть::
 
417
 
 
418
 id_soup = BeautifulSoup('<p id="my id"></p>')
 
419
 id_soup.p['id']
 
420
 # 'my id'
 
421
 
 
422
Когда вы преобразовываете тег обратно в строку, несколько значений атрибута
 
423
объединяются::
 
424
 
 
425
 rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
 
426
 rel_soup.a['rel']
 
427
 # ['index']
 
428
 rel_soup.a['rel'] = ['index', 'contents']
 
429
 print(rel_soup.p)
 
430
 # <p>Back to the <a rel="index contents">homepage</a></p>
 
431
 
 
432
Вы можете отключить объединение, передав ``multi_valued_attributes = None`` в качестве
 
433
именованного аргумента в конструктор ``BeautifulSoup``::
 
434
 
 
435
  no_list_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html', multi_valued_attributes=None)
 
436
  no_list_soup.p['class']
 
437
  # u'body strikeout'
 
438
 
 
439
Вы можете использовать ``get_attribute_list``, того чтобы получить значение в виде списка,
 
440
независимо от того, является ли атрибут многозначным или нет::
 
441
 
 
442
  id_soup.p.get_attribute_list('id')
 
443
  # ["my id"]
 
444
 
 
445
Если вы разбираете документ как XML, многозначных атрибутов не будет::
 
446
 
 
447
 xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
 
448
 xml_soup.p['class']
 
449
 # u'body strikeout'
 
450
 
 
451
Опять же, вы можете поменять настройку, используя аргумент ``multi_valued_attributes``::
 
452
 
 
453
  class_is_multi= { '*' : 'class'}
 
454
  xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml', multi_valued_attributes=class_is_multi)
 
455
  xml_soup.p['class']
 
456
  # [u'body', u'strikeout']
 
457
 
 
458
Вряд ли вам это пригодится, но если все-таки будет нужно, руководствуйтесь значениями
 
459
по умолчанию. Они реализуют правила, описанные в спецификации HTML::
 
460
 
 
461
  from bs4.builder import builder_registry
 
462
  builder_registry.lookup('html').DEFAULT_CDATA_LIST_ATTRIBUTES
 
463
 
 
464
  
 
465
``NavigableString``
 
466
-------------------
 
467
 
 
468
Строка соответствует фрагменту текста в теге. Beautiful Soup
 
469
использует класс ``NavigableString`` для хранения этих фрагментов текста::
 
470
 
 
471
 tag.string
 
472
 # u'Extremely bold'
 
473
 type(tag.string)
 
474
 # <class 'bs4.element.NavigableString'>
 
475
 
 
476
``NavigableString`` похожа на строку Unicode в Python, не считая того,
 
477
что она также поддерживает некоторые функции, описанные в
 
478
разделах `Навигация по дереву`_ и `Поиск по дереву`_. Вы можете конвертировать
 
479
``NavigableString`` в строку Unicode с помощью ``unicode()``::
 
480
 
 
481
 unicode_string = unicode(tag.string)
 
482
 unicode_string
 
483
 # u'Extremely bold'
 
484
 type(unicode_string)
 
485
 # <type 'unicode'>
 
486
 
 
487
Вы не можете редактировать строку непосредственно, но вы можете заменить одну строку
 
488
другой, используя :ref:`replace_with()`::
 
489
 
 
490
 tag.string.replace_with("No longer bold")
 
491
 tag
 
492
 # <blockquote>No longer bold</blockquote>
 
493
 
 
494
``NavigableString`` поддерживает большинство функций, описанных в
 
495
разделах `Навигация по дереву`_ и `Поиск по дереву`_, но
 
496
не все. В частности, поскольку строка не может ничего содержать (в том смысле,
 
497
в котором тег может содержать строку или другой тег), строки не поддерживают
 
498
атрибуты ``.contents`` и ``.string`` или метод ``find()``.
 
499
 
 
500
Если вы хотите использовать ``NavigableString`` вне Beautiful Soup,
 
501
вам нужно вызвать метод ``unicode()``, чтобы превратить ее в обычную для Python
 
502
строку Unicode. Если вы этого не сделаете, ваша строка будет тащить за собой
 
503
ссылку на все дерево разбора Beautiful Soup, даже когда вы
 
504
закончите использовать Beautiful Soup. Это большой расход памяти.
 
505
 
 
506
``BeautifulSoup``
 
507
-----------------
 
508
 
 
509
Объект ``BeautifulSoup`` представляет разобранный документ как единое
 
510
целое. В большинстве случаев вы можете рассматривать его как объект
 
511
:ref:`Tag`. Это означает, что он поддерживает большинство методов, описанных
 
512
в разделах `Навигация по дереву`_ и `Поиск по дереву`_.
 
513
 
 
514
Вы также можете передать объект ``BeautifulSoup`` в один из методов,
 
515
перечисленных в разделе `Изменение дерева`_, по аналогии с передачей объекта :ref:`Tag`. Это
 
516
позволяет вам делать такие вещи, как объединение двух разобранных документов::
 
517
 
 
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'
 
522
  print(doc)
 
523
  # <?xml version="1.0" encoding="utf-8"?>
 
524
  # <document><content/><footer>Here's the footer</footer></document>
 
525
 
 
526
Поскольку объект ``BeautifulSoup`` не соответствует действительному
 
527
HTML- или XML-тегу, у него нет имени и атрибутов. Однако иногда
 
528
бывает полезно взглянуть на ``.name`` объекта ``BeautifulSoup``, поэтому ему было присвоено специальное «имя»
 
529
``.name`` "[document]"::
 
530
 
 
531
 soup.name
 
532
 # u'[document]'
 
533
 
 
534
Комментарии и другие специфичные строки
 
535
---------------------------------------
 
536
 
 
537
``Tag``, ``NavigableString`` и ``BeautifulSoup`` охватывают почти
 
538
все, с чем вы столкнётесь в файле HTML или XML, но осталось
 
539
ещё немного. Пожалуй, единственное, о чем стоит волноваться,
 
540
это комментарий::
 
541
 
 
542
 markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
 
543
 soup = BeautifulSoup(markup)
 
544
 comment = soup.b.string
 
545
 type(comment)
 
546
 # <class 'bs4.element.Comment'>
 
547
 
 
548
Объект ``Comment`` — это просто особый тип ``NavigableString``::
 
549
 
 
550
 comment
 
551
 # u'Hey, buddy. Want to buy a used parser'
 
552
 
 
553
Но когда он появляется как часть HTML-документа, ``Comment``
 
554
отображается со специальным форматированием::
 
555
 
 
556
 print(soup.b.prettify())
 
557
 # <b>
 
558
 #  <!--Hey, buddy. Want to buy a used parser?-->
 
559
 # </b>
 
560
 
 
561
Beautiful Soup определяет классы для всего, что может появиться в
 
562
XML-документе: ``CData``, ``ProcessingInstruction``,
 
563
``Declaration`` и ``Doctype``. Как и ``Comment``, эти классы
 
564
являются подклассами ``NavigableString``, которые добавляют что-то еще к
 
565
строке. Вот пример, который заменяет комментарий блоком
 
566
CDATA::
 
567
 
 
568
 from bs4 import CData
 
569
 cdata = CData("A CDATA block")
 
570
 comment.replace_with(cdata)
 
571
 
 
572
 print(soup.b.prettify())
 
573
 # <b>
 
574
 #  <![CDATA[A CDATA block]]>
 
575
 # </b>
 
576
 
 
577
 
 
578
Навигация по дереву
 
579
===================
 
580
 
 
581
Вернемся к HTML-документу с фрагментом из «Алисы в стране чудес»::
 
582
 
 
583
 html_doc = """
 
584
 <html><head><title>The Dormouse's story</title></head>
 
585
 <body>
 
586
 <p class="title"><b>The Dormouse's story</b></p>
 
587
 
 
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>
 
593
 
 
594
 <p class="story">...</p>
 
595
 """
 
596
 
 
597
 from bs4 import BeautifulSoup
 
598
 soup = BeautifulSoup (html_doc, 'html.parser')
 
599
 
 
600
Я буду использовать его в качестве примера, чтобы показать, как перейти от одной части
 
601
документа к другой.
 
602
 
 
603
Проход сверху вниз
 
604
------------------
 
605
 
 
606
Теги могут содержать строки и другие теги. Эти элементы являются
 
607
дочерними (`children`) для тега. Beautiful Soup предоставляет множество различных атрибутов для
 
608
навигации и перебора дочерних элементов.
 
609
 
 
610
Обратите внимание, что строки Beautiful Soup не поддерживают ни один из этих
 
611
атрибутов, потому что строка не может иметь дочерних элементов.
 
612
 
 
613
Навигация с использованием имен тегов
 
614
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
615
 
 
616
Самый простой способ навигации по дереву разбора — это указать имя
 
617
тега, который вам нужен. Если вы хотите получить тег <head>, просто напишите ``soup.head``::
 
618
 
 
619
 soup.head
 
620
 # <head><title>The Dormouse's story</title></head>
 
621
 
 
622
 soup.title
 
623
 # <title>The Dormouse's story</title>
 
624
 
 
625
Вы можете повторять этот трюк многократно, чтобы подробнее рассмотреть определенную часть
 
626
дерева разбора. Следующий код извлекает первый тег <b> внутри тега <body>::
 
627
 
 
628
 soup.body.b
 
629
 # <b>The Dormouse's story</b>
 
630
 
 
631
Использование имени тега в качестве атрибута даст вам только `первый` тег с таким
 
632
именем::
 
633
 
 
634
 soup.a
 
635
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
636
 
 
637
Если вам нужно получить `все` теги <a> или что-нибудь более сложное,
 
638
чем первый тег с определенным именем, вам нужно использовать один из
 
639
методов, описанных в разделе `Поиск по дереву`_, такой как `find_all()`::
 
640
 
 
641
 soup.find_all('a')
 
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>]
 
645
 
 
646
``.contents`` и ``.children``
 
647
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
648
 
 
649
Дочерние элементы доступны в списке под названием ``.contents``::
 
650
 
 
651
 head_tag = soup.head
 
652
 head_tag
 
653
 # <head><title>The Dormouse's story</title></head>
 
654
 
 
655
 head_tag.contents
 
656
 [<title>The Dormouse's story</title>]
 
657
 
 
658
 title_tag = head_tag.contents[0]
 
659
 title_tag
 
660
 # <title>The Dormouse's story</title>
 
661
 title_tag.contents
 
662
 # [u'The Dormouse's story']
 
663
 
 
664
Сам объект ``BeautifulSoup`` имеет дочерние элементы. В этом случае
 
665
тег <html> является дочерним для объекта ``BeautifulSoup``::
 
666
 
 
667
 len(soup.contents)
 
668
 # 1
 
669
 soup.contents[0].name
 
670
 # u'html'
 
671
 
 
672
У строки нет ``.contents``, потому что она не может содержать
 
673
ничего::
 
674
 
 
675
 text = title_tag.contents[0]
 
676
 text.contents
 
677
 # AttributeError: У объекта 'NavigableString' нет атрибута 'contents'
 
678
 
 
679
Вместо того, чтобы получать дочерние элементы в виде списка, вы можете перебирать их
 
680
с помощью генератора ``.children``::
 
681
 
 
682
 for child in title_tag.children:
 
683
     print(child)
 
684
 # The Dormouse's story
 
685
 
 
686
``.descendants``
 
687
^^^^^^^^^^^^^^^^
 
688
 
 
689
Атрибуты ``.contents`` и ``.children`` применяются только в отношении
 
690
`непосредственных` дочерних элементов тега. Например, тег <head> имеет только один непосредственный
 
691
дочерний тег <title>::
 
692
 
 
693
 head_tag.contents
 
694
 # [<title>The Dormouse's story</title>]
 
695
 
 
696
Но у самого тега <title> есть дочерний элемент: строка "The Dormouse's
 
697
story". В некотором смысле эта строка также является дочерним элементом
 
698
тега <head>. Атрибут ``.descendants`` позволяет перебирать `все`
 
699
дочерние элементы тега рекурсивно: его непосредственные дочерние элементы, дочерние элементы
 
700
дочерних элементов и так далее::
 
701
 
 
702
 for child in head_tag.descendants:
 
703
     print(child)
 
704
 # <title>The Dormouse's story</title>
 
705
 # The Dormouse's story
 
706
 
 
707
У тега <head> есть только один дочерний элемент, но при этом у него два потомка:
 
708
тег <title> и его дочерний элемент. У объекта ``BeautifulSoup``
 
709
только один прямой дочерний элемент (тег <html>), зато множество
 
710
потомков::
 
711
 
 
712
 len(list(soup.children))
 
713
 # 1
 
714
 len(list(soup.descendants))
 
715
 # 25
 
716
 
 
717
.. _.string:
 
718
 
 
719
``.string``
 
720
^^^^^^^^^^^
 
721
 
 
722
Если у тега есть только один дочерний элемент, и это ``NavigableString``,
 
723
его можно получить через ``.string``::
 
724
 
 
725
 title_tag.string
 
726
 # u'The Dormouse's story'
 
727
 
 
728
Если единственным дочерним элементом тега является другой тег, и у этого `другого` тега есть строка
 
729
``.string``, то считается, что родительский тег содержит ту же строку
 
730
``.string``, что и дочерний тег::
 
731
 
 
732
 head_tag.contents
 
733
 # [<title>The Dormouse's story</title>]
 
734
 
 
735
 head_tag.string
 
736
 # u'The Dormouse's story'
 
737
 
 
738
Если тег содержит больше чем один элемент, то становится неясным, какая из строк
 
739
``.string`` относится и к родительскому тегу, поэтому ``.string`` родительского тега имеет значение
 
740
``None``::
 
741
 
 
742
 print(soup.html.string)
 
743
 # None
 
744
 
 
745
.. _string-generators:
 
746
 
 
747
``.strings`` и ``.stripped_strings``
 
748
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
749
 
 
750
Если внутри тега есть более одного элемента, вы все равно можете посмотреть только на
 
751
строки. Используйте генератор ``.strings``::
 
752
 
 
753
 for string in soup.strings:
 
754
     print(repr(string))
 
755
 # u"The Dormouse's story"
 
756
 # u'\n\n'
 
757
 # u"The Dormouse's story"
 
758
 # u'\n\n'
 
759
 # u'Once upon a time there were three little sisters; and their names were\n'
 
760
 # u'Elsie'
 
761
 # u',\n'
 
762
 # u'Lacie'
 
763
 # u' and\n'
 
764
 # u'Tillie'
 
765
 # u';\nand they lived at the bottom of a well.'
 
766
 # u'\n\n'
 
767
 # u'...'
 
768
 # u'\n'
 
769
 
 
770
В этих строках много лишних пробелов, которые вы можете
 
771
удалить, используя генератор ``.stripped_strings``::
 
772
 
 
773
 for string in soup.stripped_strings:
 
774
     print(repr(string))
 
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'
 
778
 # u'Elsie'
 
779
 # u','
 
780
 # u'Lacie'
 
781
 # u'and'
 
782
 # u'Tillie'
 
783
 # u';\nand they lived at the bottom of a well.'
 
784
 # u'...'
 
785
 
 
786
Здесь строки, состоящие исключительно из пробелов, игнорируются, а
 
787
пробелы в начале и конце строк удаляются.
 
788
 
 
789
Проход снизу вверх
 
790
------------------
 
791
 
 
792
В продолжение аналогии с «семейным деревом», каждый тег и каждая строка имеет
 
793
родителя (`parent`): тег, который его содержит.
 
794
 
 
795
.. _.parent:
 
796
 
 
797
``.parent``
 
798
^^^^^^^^^^^
 
799
 
 
800
Вы можете получить доступ к родительскому элементу с помощью атрибута ``.parent``. В
 
801
примере документа с фрагментом из «Алисы в стране чудес» тег <head> является родительским
 
802
для тега <title>::
 
803
 
 
804
 title_tag = soup.title
 
805
 title_tag
 
806
 # <title>The Dormouse's story</title>
 
807
 title_tag.parent
 
808
 # <head><title>The Dormouse's story</title></head>
 
809
 
 
810
Строка заголовка сама имеет родителя: тег <title>, содержащий
 
811
ее::
 
812
 
 
813
 title_tag.string.parent
 
814
 # <title>The Dormouse's story</title>
 
815
 
 
816
Родительским элементом тега верхнего уровня, такого как <html>, является сам объект
 
817
``BeautifulSoup``::
 
818
 
 
819
 html_tag = soup.html
 
820
 type(html_tag.parent)
 
821
 # <class 'bs4.BeautifulSoup'>
 
822
 
 
823
И ``.parent`` объекта ``BeautifulSoup`` определяется как None::
 
824
 
 
825
 print(soup.parent)
 
826
 # None
 
827
 
 
828
.. _.parents:
 
829
 
 
830
``.parents``
 
831
^^^^^^^^^^^^
 
832
 
 
833
Вы можете перебрать всех родителей элемента с помощью
 
834
``.parents``. В следующем примере ``.parents`` используется для перемещения от тега <a>,
 
835
закопанного глубоко внутри документа, до самого верха документа::
 
836
 
 
837
 link = soup.a
 
838
 link
 
839
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
840
 for parent in link.parents:
 
841
     if parent is None:
 
842
         print(parent)
 
843
     else:
 
844
         print(parent.name)
 
845
 # p
 
846
 # body
 
847
 # html
 
848
 # [document]
 
849
 # None
 
850
 
 
851
Перемещение вбок
 
852
----------------
 
853
 
 
854
Рассмотрим простой документ::
 
855
 
 
856
 sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></b></a>")
 
857
 print(sibling_soup.prettify())
 
858
 # <html>
 
859
 #  <body>
 
860
 #   <a>
 
861
 #    <b>
 
862
 #     text1
 
863
 #    </b>
 
864
 #    <c>
 
865
 #     text2
 
866
 #    </c>
 
867
 #   </a>
 
868
 #  </body>
 
869
 # </html>
 
870
 
 
871
Тег <b> и тег <c> находятся на одном уровне: они оба непосредственные
 
872
дочерние элементы одного и того же тега. Мы называем их `одноуровневые`. Когда документ
 
873
красиво отформатирован, одноуровневые элементы выводятся с одинаковым  отступом. Вы
 
874
также можете использовать это отношение в написанном вами коде.
 
875
 
 
876
``.next_sibling`` и ``.previous_sibling``
 
877
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
878
 
 
879
Вы можете использовать ``.next_sibling`` и ``.previous_sibling`` для навигации
 
880
между элементами страницы, которые находятся на одном уровне дерева разбора::
 
881
 
 
882
 sibling_soup.b.next_sibling
 
883
 # <c>text2</c>
 
884
 
 
885
 sibling_soup.c.previous_sibling
 
886
 # <b>text1</b>
 
887
 
 
888
У тега <b> есть ``.next_sibling``, но нет ``.previous_sibling``,
 
889
потому что нет ничего до тега <b> `на том же уровне
 
890
дерева`. По той же причине у тега <c> есть ``.previous_sibling``,
 
891
но нет ``.next_sibling``::
 
892
 
 
893
 print(sibling_soup.b.previous_sibling)
 
894
 # None
 
895
 print(sibling_soup.c.next_sibling)
 
896
 # None
 
897
 
 
898
Строки "text1" и "text2" `не являются` одноуровневыми, потому что они не
 
899
имеют общего родителя::
 
900
 
 
901
 sibling_soup.b.string
 
902
 # u'text1'
 
903
 
 
904
 print(sibling_soup.b.string.next_sibling)
 
905
 # None
 
906
 
 
907
В реальных документах ``.next_sibling`` или ``.previous_sibling``
 
908
тега обычно будет строкой, содержащей пробелы. Возвращаясь к
 
909
фрагменту из «Алисы в стране чудес»::
 
910
 
 
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>
 
914
 
 
915
Вы можете подумать, что ``.next_sibling`` первого тега <a>
 
916
должен быть второй тег <a>. Но на самом деле это строка: запятая и
 
917
перевод строки, отделяющий первый тег <a> от второго::
 
918
 
 
919
 link = soup.a
 
920
 link
 
921
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
922
 
 
923
 link.next_sibling
 
924
 # u',\n'
 
925
 
 
926
Второй тег <a> на самом деле является ``.next_sibling`` запятой ::
 
927
 
 
928
 link.next_sibling.next_sibling
 
929
 # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
 
930
 
 
931
.. _sibling-generators:
 
932
 
 
933
``.next_siblings`` и ``.previous_siblings``
 
934
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
935
 
 
936
Вы можете перебрать одноуровневые элементы данного тега с помощью ``.next_siblings`` или
 
937
``.previous_siblings``::
 
938
 
 
939
 for sibling in soup.a.next_siblings:
 
940
     print(repr(sibling))
 
941
 # u',\n'
 
942
 # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
 
943
 # u' and\n'
 
944
 # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
 
945
 # u'; and they lived at the bottom of a well.'
 
946
 # None
 
947
 
 
948
 for sibling in soup.find(id="link3").previous_siblings:
 
949
     print(repr(sibling))
 
950
 # ' and\n'
 
951
 # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
 
952
 # u',\n'
 
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'
 
955
 # None
 
956
 
 
957
Проход вперед и назад
 
958
---------------------
 
959
 
 
960
Взгляните на начало фрагмента из «Алисы в стране чудес»::
 
961
 
 
962
 <html><head><title>The Dormouse's story</title></head>
 
963
 <p class="title"><b>The Dormouse's story</b></p>
 
964
 
 
965
HTML-парсер берет эту строку символов и превращает ее в
 
966
серию событий: "открыть тег <html>", "открыть тег <head>", "открыть
 
967
тег <html>", "добавить строку", "закрыть тег <title>", "открыть
 
968
тег <p>" и так далее. Beautiful Soup предлагает инструменты для реконструирование
 
969
первоначального разбора документа.
 
970
 
 
971
.. _element-generators:
 
972
 
 
973
``.next_element`` и ``.previous_element``
 
974
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
975
 
 
976
Атрибут ``.next_element`` строки или тега указывает на то,
 
977
что было разобрано непосредственно после него. Это могло бы быть тем же, что и
 
978
``.next_sibling``, но обычно результат резко отличается.
 
979
 
 
980
Возьмем последний тег <a> в фрагменте из «Алисы в стране чудес». Его
 
981
``.next_sibling`` является строкой: конец предложения, которое было
 
982
прервано началом тега <a>::
 
983
 
 
984
 last_a_tag = soup.find("a", id="link3")
 
985
 last_a_tag
 
986
 # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
 
987
 
 
988
 last_a_tag.next_sibling
 
989
 # '; and they lived at the bottom of a well.'
 
990
 
 
991
Но ``.next_element`` этого тега <a> — это то, что было разобрано
 
992
сразу после тега <a>, `не` остальная часть этого предложения:
 
993
это слово "Tillie"::
 
994
 
 
995
 last_a_tag.next_element
 
996
 # u'Tillie'
 
997
 
 
998
Это потому, что в оригинальной разметке слово «Tillie» появилось
 
999
перед точкой с запятой. Парсер обнаружил тег <a>, затем
 
1000
слово «Tillie», затем закрывающий тег </a>, затем точку с запятой и оставшуюся
 
1001
часть предложения. Точка с запятой находится на том же уровне, что и тег <a>, но
 
1002
слово «Tillie» встретилось первым.
 
1003
 
 
1004
Атрибут ``.previous_element`` является полной противоположностью
 
1005
``.next_element``. Он указывает на элемент, который был встречен при разборе
 
1006
непосредственно перед текущим::
 
1007
 
 
1008
 last_a_tag.previous_element
 
1009
 # u' and\n'
 
1010
 last_a_tag.previous_element.next_element
 
1011
 # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
 
1012
 
 
1013
``.next_elements`` и ``.previous_elements``
 
1014
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
1015
 
 
1016
Вы уже должны были уловить идею. Вы можете использовать их для перемещения
 
1017
вперед или назад по документу, в том порядке, в каком он был разобран парсером::
 
1018
 
 
1019
 for element in last_a_tag.next_elements:
 
1020
     print(repr(element))
 
1021
 # u'Tillie'
 
1022
 # u';\nand they lived at the bottom of a well.'
 
1023
 # u'\n\n'
 
1024
 # <p class="story">...</p>
 
1025
 # u'...'
 
1026
 # u'\n'
 
1027
 # None
 
1028
 
 
1029
Поиск по дереву
 
1030
===============
 
1031
 
 
1032
Beautiful Soup определяет множество методов поиска по дереву разбора,
 
1033
но они все очень похожи. Я буду долго объяснять, как работают
 
1034
два самых популярных метода: ``find()`` и ``find_all()``. Прочие
 
1035
методы принимают практически те же самые аргументы, поэтому я расскажу
 
1036
о них вкратце.
 
1037
 
 
1038
И опять, я буду использовать фрагмент из «Алисы в стране чудес» в качестве примера::
 
1039
 
 
1040
 html_doc = """
 
1041
 <html><head><title>The Dormouse's story</title></head>
 
1042
 <body>
 
1043
 <p class="title"><b>The Dormouse's story</b></p>
 
1044
 
 
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>
 
1050
 
 
1051
 <p class="story">...</p>
 
1052
 """
 
1053
 
 
1054
 from bs4 import BeautifulSoup
 
1055
 soup = BeautifulSoup (html_doc, 'html.parser')
 
1056
 
 
1057
Передав фильтр в аргумент типа ``find_all()``, вы можете
 
1058
углубиться в интересующие вас части документа.
 
1059
 
 
1060
Виды фильтров
 
1061
-------------
 
1062
 
 
1063
Прежде чем подробно рассказывать о ``find_all()`` и подобных методах, я
 
1064
хочу показать примеры различных фильтров, которые вы можете передать в эти
 
1065
методы. Эти фильтры появляются снова и снова в
 
1066
поисковом API. Вы можете использовать их для фильтрации по имени тега,
 
1067
по его атрибутам, по тексту строки или по некоторой их
 
1068
комбинации.
 
1069
 
 
1070
.. _a string:
 
1071
 
 
1072
Строка
 
1073
^^^^^^
 
1074
 
 
1075
Самый простой фильтр — это строка. Передайте строку в метод поиска, и
 
1076
Beautiful Soup выполнит поиск соответствия этой строке. Следующий
 
1077
код находит все теги <b> в документе::
 
1078
 
 
1079
 soup.find_all('b')
 
1080
 # [<b>The Dormouse's story</b>]
 
1081
 
 
1082
Если вы передадите байтовую строку, Beautiful Soup будет считать, что строка
 
1083
кодируется в UTF-8. Вы можете избежать этого, передав вместо нее строку Unicode.
 
1084
 
 
1085
.. _a regular expression:
 
1086
 
 
1087
Регулярное выражение
 
1088
^^^^^^^^^^^^^^^^^^^^
 
1089
 
 
1090
Если вы передадите объект с регулярным выражением, Beautiful Soup отфильтрует результаты
 
1091
в соответствии с этим регулярным выражением, используя его метод ``search()``. Следующий код
 
1092
находит все теги, имена которых начинаются с буквы "b"; в нашем
 
1093
случае это теги <body> и <b>::
 
1094
 
 
1095
 import re
 
1096
 for tag in soup.find_all(re.compile("^b")):
 
1097
     print(tag.name)
 
1098
 # body
 
1099
 # b
 
1100
 
 
1101
Этот код находит все теги, имена которых содержат букву "t"::
 
1102
 
 
1103
 for tag in soup.find_all(re.compile("t")):
 
1104
     print(tag.name)
 
1105
 # html
 
1106
 # title
 
1107
 
 
1108
.. _a list:
 
1109
 
 
1110
Список
 
1111
^^^^^^
 
1112
 
 
1113
Если вы передадите список, Beautiful Soup разрешит совпадение строк
 
1114
с `любым` элементом из этого списка. Следующий код находит все теги <a>
 
1115
`и` все теги <b>::
 
1116
 
 
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>]
 
1122
 
 
1123
.. _the value True:
 
1124
 
 
1125
``True``
 
1126
^^^^^^^^
 
1127
 
 
1128
Значение ``True`` подходит везде, где возможно.. Следующий код находит `все`
 
1129
теги в документе, но не текстовые строки::
 
1130
 
 
1131
 for tag in soup.find_all(True):
 
1132
     print(tag.name)
 
1133
 # html
 
1134
 # head
 
1135
 # title
 
1136
 # body
 
1137
 # p
 
1138
 # b
 
1139
 # p
 
1140
 # a
 
1141
 # a
 
1142
 # a
 
1143
 # p
 
1144
 
 
1145
.. a function:
 
1146
 
 
1147
Функция
 
1148
^^^^^^^
 
1149
 
 
1150
Если ничто из перечисленного вам не подходит, определите функцию, которая
 
1151
принимает элемент в качестве единственного аргумента. Функция должна вернуть
 
1152
``True``, если аргумент подходит, и ``False``, если нет.
 
1153
 
 
1154
Вот функция, которая возвращает ``True``, если в теге определен атрибут "class",
 
1155
но не определен атрибут "id"::
 
1156
 
 
1157
 def has_class_but_no_id(tag):
 
1158
     return tag.has_attr('class') and not tag.has_attr('id')
 
1159
 
 
1160
Передайте эту функцию в ``find_all()``, и вы получите все
 
1161
теги <p>::
 
1162
 
 
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>]
 
1167
 
 
1168
Эта функция выбирает только теги <p>. Она не выбирает теги <a>,
 
1169
поскольку в них определены и атрибут "class" , и атрибут "id". Она не выбирает
 
1170
теги вроде <html> и <title>, потому что в них не определен атрибут
 
1171
"class".
 
1172
 
 
1173
Если вы передаете функцию для фильтрации по определенному атрибуту, такому как
 
1174
``href``, аргументом, переданным в функцию, будет
 
1175
значение атрибута, а не весь тег. Вот функция, которая находит все теги ``a``,
 
1176
у которых атрибут ``href`` *не* соответствует регулярному выражению::
 
1177
 
 
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>]
 
1183
 
 
1184
Функция может быть настолько сложной, насколько вам нужно. Вот
 
1185
функция, которая возвращает ``True``, если тег окружен строковыми
 
1186
объектами::
 
1187
 
 
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))
 
1192
 
 
1193
 for tag in soup.find_all(surrounded_by_strings):
 
1194
     print tag.name
 
1195
 # p
 
1196
 # a
 
1197
 # a
 
1198
 # a
 
1199
 # p
 
1200
 
 
1201
Теперь мы готовы подробно рассмотреть методы поиска.
 
1202
 
 
1203
``find_all()``
 
1204
--------------
 
1205
 
 
1206
Сигнатура: find_all(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
 
1207
<recursive>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1208
 
 
1209
Метод ``find_all()`` просматривает потомков тега и
 
1210
извлекает `всех` потомков, которые соответствую вашим фильтрам. Я привел несколько
 
1211
примеров в разделе `Виды фильтров`_, а вот еще несколько::
 
1212
 
 
1213
 soup.find_all("title")
 
1214
 # [<title>The Dormouse's story</title>]
 
1215
 
 
1216
 soup.find_all("p", "title")
 
1217
 # [<p class="title"><b>The Dormouse's story</b></p>]
 
1218
 
 
1219
 soup.find_all("a")
 
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>]
 
1223
 
 
1224
 soup.find_all(id="link2")
 
1225
 # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1226
 
 
1227
 import re
 
1228
 soup.find(string=re.compile("sisters"))
 
1229
 # u'Once upon a time there were three little sisters; and their names were\n'
 
1230
 
 
1231
Кое-что из этого нам уже знакомо, но есть и новое. Что означает
 
1232
передача значения для ``string`` или ``id``? Почему
 
1233
``find_all ("p", "title")`` находит тег <p> с CSS-классом "title"?
 
1234
Давайте посмотрим на аргументы ``find_all()``.
 
1235
 
 
1236
.. _name:
 
1237
 
 
1238
Аргумент ``name``
 
1239
^^^^^^^^^^^^^^^^^
 
1240
 
 
1241
Передайте значение для аргумента ``name``, и вы скажете Beautiful Soup
 
1242
рассматривать только теги с определенными именами. Текстовые строки будут игнорироваться, так же как и
 
1243
теги, имена которых не соответствуют заданным.
 
1244
 
 
1245
Вот простейший пример использования::
 
1246
 
 
1247
 soup.find_all("title")
 
1248
 # [<title>The Dormouse's story</title>]
 
1249
 
 
1250
В разделе  `Виды фильтров`_ говорилось, что значением ``name`` может быть
 
1251
`строка`_, `регулярное выражение`_, `список`_, `функция`_ или
 
1252
`True`_.
 
1253
 
 
1254
.. _kwargs:
 
1255
 
 
1256
Именованные аргументы
 
1257
^^^^^^^^^^^^^^^^^^^^^
 
1258
 
 
1259
Любой нераспознанный аргумент будет превращен в фильтр
 
1260
по атрибуту тега. Если вы передаете значение для аргумента с именем ``id``,
 
1261
Beautiful Soup будет фильтровать по атрибуту "id" каждого тега::
 
1262
 
 
1263
 soup.find_all(id='link2')
 
1264
 # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1265
 
 
1266
Если вы передадите значение для ``href``, Beautiful Soup отфильтрует
 
1267
по атрибуту "href" каждого тега::
 
1268
 
 
1269
 soup.find_all(href=re.compile("elsie"))
 
1270
 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
 
1271
 
 
1272
Для фильтрации по атрибуту может использоваться `строка`_, `регулярное
 
1273
выражение`_, `список`_, `функция`_ или значение `True`_.
 
1274
 
 
1275
Следующий код находит все теги, атрибут ``id`` которых имеет значение,
 
1276
независимо от того, что это за значение::
 
1277
 
 
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>]
 
1282
 
 
1283
Вы можете отфильтровать несколько атрибутов одновременно, передав более одного
 
1284
именованного аргумента::
 
1285
 
 
1286
 soup.find_all(href=re.compile("elsie"), id='link1')
 
1287
 # [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
 
1288
 
 
1289
Некоторые атрибуты, такие как атрибуты data-* в HTML 5, имеют имена, которые
 
1290
нельзя использовать в качестве имен именованных аргументов::
 
1291
 
 
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
 
1295
 
 
1296
Вы можете использовать эти атрибуты в поиске, поместив их в
 
1297
словарь и передав словарь в ``find_all()`` как
 
1298
аргумент ``attrs``::
 
1299
 
 
1300
 data_soup.find_all(attrs={"data-foo": "value"})
 
1301
 # [<div data-foo="value">foo!</div>]
 
1302
 
 
1303
Нельзя использовать именованный аргумент для поиска в HTML по элементу "name",
 
1304
потому что Beautiful Soup использует аргумент ``name`` для имени
 
1305
самого тега. Вместо этого вы можете передать элемент "name" вместе с его значением в
 
1306
составе аргумента ``attrs``::
 
1307
 
 
1308
 name_soup = BeautifulSoup('<input name="email"/>')
 
1309
 name_soup.find_all(name="email")
 
1310
 # []
 
1311
 name_soup.find_all(attrs={"name": "email"})
 
1312
 # [<input name="email"/>]
 
1313
 
 
1314
.. _attrs:
 
1315
 
 
1316
Поиск по классу CSS
 
1317
^^^^^^^^^^^^^^^^^^^
 
1318
 
 
1319
Очень удобно искать тег с определенным классом CSS, но
 
1320
имя атрибута CSS, "class", является зарезервированным словом в
 
1321
Python. Использование ``class`` в качестве именованного аргумента приведет к синтаксической
 
1322
ошибке. Начиная с Beautiful Soup 4.1.2, вы можете выполнять поиск по классу CSS, используя
 
1323
именованный аргумент ``class_``::
 
1324
 
 
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>]
 
1329
 
 
1330
Как и с любым именованным аргументом, вы можете передать в качестве значения ``class_`` строку, регулярное
 
1331
выражение, функцию или ``True``::
 
1332
 
 
1333
 soup.find_all(class_=re.compile("itl"))
 
1334
 # [<p class="title"><b>The Dormouse's story</b></p>]
 
1335
 
 
1336
 def has_six_characters(css_class):
 
1337
     return css_class is not None and len(css_class) == 6
 
1338
 
 
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>]
 
1343
 
 
1344
Помните, что один тег может иметь :ref:`несколько значений <multivalue>`
 
1345
для атрибута "class". Когда вы ищете тег, который
 
1346
соответствует определенному классу CSS, вы ищете соответствие `любому` из его
 
1347
классов CSS::
 
1348
 
 
1349
 css_soup = BeautifulSoup('<p class="body strikeout"></p>')
 
1350
 css_soup.find_all("p", class_="strikeout")
 
1351
 # [<p class="body strikeout"></p>]
 
1352
 
 
1353
 css_soup.find_all("p", class_="body")
 
1354
 # [<p class="body strikeout"></p>]
 
1355
 
 
1356
Можно искать точное строковое значение атрибута ``class``::
 
1357
 
 
1358
 css_soup.find_all("p", class_="body strikeout")
 
1359
 # [<p class="body strikeout"></p>]
 
1360
 
 
1361
Но поиск вариантов строкового значения не сработает::
 
1362
 
 
1363
 css_soup.find_all("p", class_="strikeout body")
 
1364
 # []
 
1365
 
 
1366
Если вы хотите искать теги, которые соответствуют двум или более классам CSS,
 
1367
следует использовать селектор CSS::
 
1368
 
 
1369
 css_soup.select("p.strikeout.body")
 
1370
 # [<p class="body strikeout"></p>]
 
1371
 
 
1372
В старых версиях Beautiful Soup, в которых нет ярлыка ``class_``
 
1373
можно использовать трюк  с аргументом ``attrs``, упомянутый выше. Создайте
 
1374
словарь, значение которого для "class" является строкой (или регулярным
 
1375
выражением, или чем угодно еще), которую вы хотите найти::
 
1376
 
 
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>]
 
1381
 
 
1382
.. _string:
 
1383
 
 
1384
Аргумент ``string``
 
1385
^^^^^^^^^^^^^^^^^^^
 
1386
 
 
1387
С помощью ``string`` вы можете искать строки вместо тегов. Как и в случае с
 
1388
``name`` и именованными аргументами, передаваться может `строка`_,
 
1389
`регулярное выражение`_, `список`_, `функция`_ или значения `True`_.
 
1390
Вот несколько примеров::
 
1391
 
 
1392
 soup.find_all(string="Elsie")
 
1393
 # [u'Elsie']
 
1394
 
 
1395
 soup.find_all(string=["Tillie", "Elsie", "Lacie"])
 
1396
 # [u'Elsie', u'Lacie', u'Tillie']
 
1397
 
 
1398
 soup.find_all(string=re.compile("Dormouse"))
 
1399
 [u"The Dormouse's story", u"The Dormouse's story"]
 
1400
 
 
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)
 
1404
 
 
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'...']
 
1407
 
 
1408
Хотя значение типа ``string`` предназначено для поиска строк, вы можете комбинировать его с
 
1409
аргументами, которые находят теги: Beautiful Soup найдет все теги, в которых
 
1410
``.string`` соответствует вашему значению для ``string``. Следующий код находит все теги <a>,
 
1411
у которых ``.string`` равно "Elsie"::
 
1412
 
 
1413
 soup.find_all("a", string="Elsie")
 
1414
 # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
 
1415
 
 
1416
Аргумент ``string`` — это новое в Beautiful Soup 4.4.0. В ранних
 
1417
версиях он назывался ``text``::
 
1418
 
 
1419
 soup.find_all("a", text="Elsie")
 
1420
 # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
 
1421
 
 
1422
.. _limit:
 
1423
 
 
1424
Аргумент ``limit``
 
1425
^^^^^^^^^^^^^^^^^^
 
1426
 
 
1427
``find_all()`` возвращает все теги и строки, которые соответствуют вашим
 
1428
фильтрам. Это может занять некоторое время, если документ большой. Если вам не
 
1429
нужны `все` результаты, вы можете указать их предельное число — ``limit``. Это
 
1430
работает так же, как ключевое слово LIMIT в SQL. Оно говорит Beautiful Soup
 
1431
прекратить собирать результаты после того, как их найдено определенное количество.
 
1432
 
 
1433
В фрагменте из «Алисы в стране чудес» есть три ссылки, но следующий код
 
1434
находит только первые две::
 
1435
 
 
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>]
 
1439
 
 
1440
.. _recursive:
 
1441
 
 
1442
Аргумент ``recursive``
 
1443
^^^^^^^^^^^^^^^^^^^^^^
 
1444
 
 
1445
Если вы вызовете ``mytag.find_all()``, Beautiful Soup проверит всех
 
1446
потомков ``mytag``: его дочерние элементы, дочерние элементы дочерних элементов, и
 
1447
так далее. Если вы хотите, чтобы Beautiful Soup рассматривал только непосредственных потомков (дочерние элементы),
 
1448
вы можете передать ``recursive = False``. Оцените разницу::
 
1449
 
 
1450
 soup.html.find_all("title")
 
1451
 # [<title>The Dormouse's story</title>]
 
1452
 
 
1453
 soup.html.find_all("title", recursive=False)
 
1454
 # []
 
1455
 
 
1456
Вот эта часть документа::
 
1457
 
 
1458
 <html>
 
1459
  <head>
 
1460
   <title>
 
1461
    The Dormouse's story
 
1462
   </title>
 
1463
  </head>
 
1464
 ...
 
1465
 
 
1466
Тег <title> находится под тегом <html>, но не `непосредственно`
 
1467
под тегом <html>: на пути встречается тег <head>. Beautiful Soup
 
1468
находит тег <title>, когда разрешено просматривать всех потомков
 
1469
тега <html>, но когда ``recursive=False`` ограничивает поиск
 
1470
только непосредстввенно дочерними элементами,  Beautiful Soup ничего не находит.
 
1471
 
 
1472
Beautiful Soup предлагает множество методов поиска по дереву (они рассмотрены ниже),
 
1473
и они в основном принимают те же аргументы, что и ``find_all()``: ``name``,
 
1474
``attrs``, ``string``, ``limit`` и именованные аргументы. Но
 
1475
с аргументом ``recursive`` все иначе:  ``find_all()`` и ``find()`` —
 
1476
это единственные методы, которые его поддерживают. От передачи ``recursive=False`` в
 
1477
метод типа ``find_parents()`` не очень много пользы.
 
1478
 
 
1479
Вызов тега похож на вызов ``find_all()``
 
1480
----------------------------------------
 
1481
 
 
1482
Поскольку ``find_all()`` является самым популярным методом в Beautiful
 
1483
Soup API, вы можете использовать сокращенную запись. Если относиться к 
 
1484
объекту  ``BeautifulSoup`` или объекту ``Tag`` так, будто это
 
1485
функция, то это похоже на вызов ``find_all()``
 
1486
с этим объектом. Эти две строки кода эквивалентны::
 
1487
 
 
1488
 soup.find_all("a")
 
1489
 soup("a")
 
1490
 
 
1491
Эти две строки также эквивалентны::
 
1492
 
 
1493
 soup.title.find_all(string=True)
 
1494
 soup.title(string=True)
 
1495
 
 
1496
``find()``
 
1497
----------
 
1498
 
 
1499
Сигнатура: find(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
 
1500
<recursive>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1501
 
 
1502
Метод ``find_all()`` сканирует весь документ в поиске
 
1503
всех результатов, но иногда вам нужен только один. Если вы знаете,
 
1504
что в документе есть только один тег <body>, нет смысла сканировать
 
1505
весь документ в поиске остальных. Вместо того, чтобы передавать ``limit=1``
 
1506
каждый раз, когда вы вызываете ``find_all()``, используйте
 
1507
метод ``find()``. Эти две строки кода эквивалентны::
 
1508
 
 
1509
 soup.find_all('title', limit=1)
 
1510
 # [<title>The Dormouse's story</title>]
 
1511
 
 
1512
 soup.find('title')
 
1513
 # <title>The Dormouse's story</title>
 
1514
 
 
1515
Разница лишь в том, что ``find_all()`` возвращает список, содержащий
 
1516
единственный результат, а ``find()`` возвращает только сам результат.
 
1517
 
 
1518
Если ``find_all()`` не может ничего найти, он возвращает пустой список. Если
 
1519
``find()`` не может ничего найти, он возвращает ``None``::
 
1520
 
 
1521
 print(soup.find("nosuchtag"))
 
1522
 # None
 
1523
 
 
1524
Помните трюк с ``soup.head.title`` из раздела
 
1525
`Навигация с использованием имен тегов`_? Этот трюк работает на основе неоднократного вызова ``find()``::
 
1526
 
 
1527
 soup.head.title
 
1528
 # <title>The Dormouse's story</title>
 
1529
 
 
1530
 soup.find("head").find("title")
 
1531
 # <title>The Dormouse's story</title>
 
1532
 
 
1533
``find_parents()`` и ``find_parent()``
 
1534
--------------------------------------
 
1535
 
 
1536
Сигнатура: find_parents(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1537
 
 
1538
Сигнатура: find_parent(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1539
 
 
1540
Я долго объяснял, как работают ``find_all()`` и
 
1541
``find()``. Beautiful Soup API определяет десяток других методов для
 
1542
поиска по дереву, но пусть вас это не пугает. Пять из этих методов
 
1543
в целом похожи на ``find_all()``, а другие пять в целом
 
1544
похожи на ``find()``. Единственное различие в том, по каким частям
 
1545
дерева они ищут.
 
1546
 
 
1547
Сначала давайте рассмотрим ``find_parents()`` и
 
1548
``find_parent()``. Помните, что ``find_all()`` и ``find()`` прорабатывают
 
1549
дерево сверху вниз, просматривая теги и их потомков. ``find_parents()`` и ``find_parent()``
 
1550
делают наоборот: они идут `снизу вверх`, рассматривая
 
1551
родительские элементы тега или строки. Давайте испытаем их, начав со строки,
 
1552
закопанной глубоко в фрагменте из «Алисы в стране чудес»::
 
1553
 
 
1554
  a_string = soup.find(string="Lacie")
 
1555
  a_string
 
1556
  # u'Lacie'
 
1557
 
 
1558
  a_string.find_parents("a")
 
1559
  # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1560
 
 
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>
 
1567
 
 
1568
  a_string.find_parents("p", class="title")
 
1569
  # []
 
1570
 
 
1571
Один из трех тегов <a> является прямым родителем искомой строки,
 
1572
так что наш поиск находит его. Один из трех тегов <p> является
 
1573
непрямым родителем строки, и наш поиск тоже его
 
1574
находит. Где-то в документе есть тег <p> с классом CSS "title",
 
1575
но он не является родительским для строки, так что мы не можем найти
 
1576
его с помощью ``find_parents()``.
 
1577
 
 
1578
Вы могли заметить связь между ``find_parent()``,
 
1579
``find_parents()`` и атрибутами `.parent`_ и `.parents`_,
 
1580
упомянутыми ранее. Связь очень сильная. Эти методы поиска
 
1581
на самом деле используют ``.parents``, чтобы перебрать все родительские элементы и проверить
 
1582
каждый из них на соответствие заданному фильтру.
 
1583
 
 
1584
``find_next_siblings()`` и ``find_next_sibling()``
 
1585
--------------------------------------------------
 
1586
 
 
1587
Сигнатура: find_next_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1588
 
 
1589
Сигнатура: find_next_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1590
 
 
1591
Эти методы используют :ref:`.next_siblings <sibling-generators>` для
 
1592
перебора одноуровневых элементов для данного элемента в дереве. Метод
 
1593
``find_next_siblings()`` возвращает все  подходящие одноуровневые элементы,
 
1594
а ``find_next_sibling()`` возвращает только первый из них::
 
1595
 
 
1596
 first_link = soup.a
 
1597
 first_link
 
1598
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
1599
 
 
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>]
 
1603
 
 
1604
 first_story_paragraph = soup.find("p", "story")
 
1605
 first_story_paragraph.find_next_sibling("p")
 
1606
 # <p class="story">...</p>
 
1607
 
 
1608
``find_previous_siblings()`` и ``find_previous_sibling()``
 
1609
----------------------------------------------------------
 
1610
 
 
1611
Сигнатура: find_previous_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1612
 
 
1613
Сигнатура: find_previous_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1614
 
 
1615
Эти методы используют :ref:`.previous_siblings <sibling-generators>` для перебора тех одноуровневых элементов,
 
1616
которые предшествуют данному элементу в дереве разбора. Метод ``find_previous_siblings()``
 
1617
возвращает все подходящие одноуровневые элементы,, а
 
1618
а ``find_next_sibling()`` только первый из них::
 
1619
 
 
1620
 last_link = soup.find("a", id="link3")
 
1621
 last_link
 
1622
 # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
 
1623
 
 
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>]
 
1627
 
 
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>
 
1631
 
 
1632
 
 
1633
``find_all_next()`` и ``find_next()``
 
1634
-------------------------------------
 
1635
 
 
1636
Сигнатура: find_all_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1637
 
 
1638
Сигнатура: find_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1639
 
 
1640
Эти методы используют :ref:`.next_elements <element-generators>` для
 
1641
перебора любых тегов и строк, которые встречаются в документе после
 
1642
элемента. Метод ``find_all_next()`` возвращает все совпадения, а
 
1643
``find_next()`` только первое::
 
1644
 
 
1645
 first_link = soup.a
 
1646
 first_link
 
1647
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
1648
 
 
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']
 
1652
 
 
1653
 first_link.find_next("p")
 
1654
 # <p class="story">...</p>
 
1655
 
 
1656
В первом примере нашлась строка "Elsie", хотя она
 
1657
содержится в теге <a>, с которого мы начали. Во втором примере
 
1658
нашелся последний тег <p>, хотя он находится
 
1659
в другой части дерева, чем тег <a>, с которого мы начали. Для этих
 
1660
методов имеет значение только то, что элемент соответствует фильтру и
 
1661
появляется в документе позже, чем тот элемент, с которого начали поиск.
 
1662
 
 
1663
``find_all_previous()`` и ``find_previous()``
 
1664
---------------------------------------------
 
1665
 
 
1666
Сигнатура: find_all_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
 
1667
 
 
1668
Сигнатура: find_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
 
1669
 
 
1670
Эти методы используют :ref:`.previous_elements <element-generators>` для
 
1671
перебора любых тегов и строк, которые встречаются в документе до
 
1672
элемента. Метод ``find_all_previous()`` возвращает все совпадения, а
 
1673
``find_previous()`` только первое::
 
1674
 
 
1675
 first_link = soup.a
 
1676
 first_link
 
1677
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
1678
 
 
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>]
 
1682
 
 
1683
 first_link.find_previous("title")
 
1684
 # <title>The Dormouse's story</title>
 
1685
 
 
1686
Вызов ``find_all_previous ("p")`` нашел первый абзац в
 
1687
документе (тот, который с ``class = "title"``), но он также находит
 
1688
второй абзац, а именно тег <p>, содержащий тег <a>, с которого мы
 
1689
начали. Это не так уж удивительно: мы смотрим на все теги,
 
1690
которые появляются в документе раньше, чем тот, с которого мы начали. Тег
 
1691
<p>, содержащий тег <a>, должен был появиться до тега <a>, который
 
1692
в нем содержится.
 
1693
 
 
1694
Селекторы CSS
 
1695
-------------
 
1696
 
 
1697
Начиная с версии 4.7.0, Beautiful Soup поддерживает большинство селекторов CSS4 благодаря
 
1698
проекту `SoupSieve 
 
1699
<https://facelessuser.github.io/soupsieve/>`_. Если вы установили Beautiful Soup через ``pip``, одновременно должен был установиться SoupSieve,
 
1700
так что вам больше ничего не нужно делать.
 
1701
 
 
1702
В ``BeautifulSoup`` есть метод ``.select()``, который использует SoupSieve, чтобы
 
1703
запустить селектор CSS и вернуть все
 
1704
подходящие элементы. ``Tag`` имеет похожий метод, который запускает селектор CSS
 
1705
в отношении содержимого одного тега.
 
1706
 
 
1707
(В более ранних версиях Beautiful Soup тоже есть метод ``.select()``,
 
1708
но поддерживаются только наиболее часто используемые селекторы CSS.)
 
1709
 
 
1710
В `документации SoupSieve
 
1711
<https://facelessuser.github.io/soupsieve/>`_ перечислены все
 
1712
селекторы CSS, которые поддерживаются на данный момент, но вот некоторые из основных:
 
1713
 
 
1714
Вы можете найти теги::
 
1715
 
 
1716
 soup.select("title")
 
1717
 # [<title>The Dormouse's story</title>]
 
1718
 
 
1719
 soup.select("p:nth-of-type(3)")
 
1720
 # [<p class="story">...</p>]
 
1721
 
 
1722
Найти теги под другими тегами::
 
1723
 
 
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>]
 
1728
 
 
1729
 soup.select("html head title")
 
1730
 # [<title>The Dormouse's story</title>]
 
1731
 
 
1732
Найти теги `непосредственно` под другими тегами::
 
1733
 
 
1734
 soup.select("head > title")
 
1735
 # [<title>The Dormouse's story</title>]
 
1736
 
 
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>]
 
1741
 
 
1742
 soup.select("p > a:nth-of-type(2)")
 
1743
 # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1744
 
 
1745
 soup.select("p > #link1")
 
1746
 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
 
1747
 
 
1748
 soup.select("body > a")
 
1749
 # []
 
1750
 
 
1751
Найти одноуровневые элементы тега::
 
1752
 
 
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>]
 
1756
 
 
1757
 soup.select("#link1 + .sister")
 
1758
 # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1759
 
 
1760
Найти теги по классу CSS::
 
1761
 
 
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>]
 
1766
 
 
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>]
 
1771
 
 
1772
Найти теги по ID::
 
1773
 
 
1774
 soup.select("#link1")
 
1775
 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
 
1776
 
 
1777
 soup.select("a#link2")
 
1778
 # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
 
1779
 
 
1780
Найти теги, которые соответствуют любому селектору из списка::
 
1781
 
 
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>]
 
1785
 
 
1786
Проверка на наличие атрибута::
 
1787
 
 
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>]
 
1792
 
 
1793
Найти теги по значению атрибута::
 
1794
 
 
1795
 soup.select('a[href="http://example.com/elsie"]')
 
1796
 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
 
1797
 
 
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>]
 
1802
 
 
1803
 soup.select('a[href$="tillie"]')
 
1804
 # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
 
1805
 
 
1806
 soup.select('a[href*=".com/el"]')
 
1807
 # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
 
1808
 
 
1809
Есть также метод ``select_one()``, который находит только
 
1810
первый тег, соответствующий селектору::
 
1811
 
 
1812
 soup.select_one(".sister")
 
1813
 # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
 
1814
 
 
1815
Если вы разобрали XML, в котором определены пространства имен, вы можете использовать их в
 
1816
селекторах CSS::
 
1817
 
 
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>
 
1822
 </tag> """
 
1823
 soup = BeautifulSoup(xml, "xml")
 
1824
 
 
1825
 soup.select("child")
 
1826
 # [<ns1:child>I'm in namespace 1</ns1:child>, <ns2:child>I'm in namespace 2</ns2:child>]
 
1827
 
 
1828
 soup.select("ns1|child", namespaces=namespaces)
 
1829
 # [<ns1:child>I'm in namespace 1</ns1:child>]
 
1830
 
 
1831
При обработке селектора CSS, который использует пространства имен, Beautiful Soup
 
1832
использует сокращения пространства имен, найденные при разборе
 
1833
документа. Вы можете заменить сокращения своими собственными, передав словарь
 
1834
сокращений::
 
1835
 
 
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>]
 
1839
 
 
1840
Все эти селекторы CSS удобны для тех, кто уже
 
1841
знаком с синтаксисом селекторов CSS. Вы можете сделать все это с помощью
 
1842
Beautiful Soup API. И если CSS селекторы — это все, что вам нужно, вам следует
 
1843
использовать парсер lxml: так будет намного быстрее. Но вы можете
 
1844
`комбинировать` селекторы CSS с Beautiful Soup API.
 
1845
 
 
1846
Изменение дерева
 
1847
================
 
1848
 
 
1849
Основная сила Beautiful Soup в поиске по дереву разбора, но вы
 
1850
также можете изменить дерево и записать свои изменения в виде нового HTML или
 
1851
XML-документа.
 
1852
 
 
1853
Изменение имен тегов и атрибутов
 
1854
--------------------------------
 
1855
 
 
1856
Я говорил об этом раньше, в разделе `Атрибуты`_, но это стоит повторить. Вы
 
1857
можете переименовать тег, изменить значения его атрибутов, добавить новые
 
1858
атрибуты и удалить атрибуты::
 
1859
 
 
1860
 soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
 
1861
 tag = soup.b
 
1862
 
 
1863
 tag.name = "blockquote"
 
1864
 tag['class'] = 'verybold'
 
1865
 tag['id'] = 1
 
1866
 tag
 
1867
 # <blockquote class="verybold" id="1">Extremely bold</blockquote>
 
1868
 
 
1869
 del tag['class']
 
1870
 del tag['id']
 
1871
 tag
 
1872
 # <blockquote>Extremely bold</blockquote>
 
1873
 
 
1874
Изменение ``.string``
 
1875
---------------------
 
1876
 
 
1877
Если вы замените значение атрибута ``.string`` новой строкой, содержимое тега будет
 
1878
заменено на эту строку::
 
1879
 
 
1880
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
1881
  soup = BeautifulSoup(markup)
 
1882
 
 
1883
  tag = soup.a
 
1884
  tag.string = "New link text."
 
1885
  tag
 
1886
  # <a href="http://example.com/">New link text.</a>
 
1887
  
 
1888
Будьте осторожны: если тег содержит другие теги, они и все их
 
1889
содержимое будет уничтожено.  
 
1890
 
 
1891
``append()``
 
1892
------------
 
1893
 
 
1894
Вы можете добавить содержимое тега с помощью ``Tag.append()``. Это работает
 
1895
точно так же, как ``.append()`` для списка в Python::
 
1896
 
 
1897
   soup = BeautifulSoup("<a>Foo</a>")
 
1898
   soup.a.append("Bar")
 
1899
 
 
1900
   soup
 
1901
   # <html><head></head><body><a>FooBar</a></body></html>
 
1902
   soup.a.contents
 
1903
   # [u'Foo', u'Bar']
 
1904
 
 
1905
``extend()``
 
1906
------------
 
1907
 
 
1908
Начиная с версии Beautiful Soup 4.7.0, ``Tag`` также поддерживает метод
 
1909
``.extend()``, который работает так же, как вызов ``.extend()`` для
 
1910
списка в Python::
 
1911
 
 
1912
   soup = BeautifulSoup("<a>Soup</a>")
 
1913
   soup.a.extend(["'s", " ", "on"])
 
1914
 
 
1915
   soup
 
1916
   # <html><head></head><body><a>Soup's on</a></body></html>
 
1917
   soup.a.contents
 
1918
   # [u'Soup', u''s', u' ', u'on']
 
1919
   
 
1920
``NavigableString()`` и ``.new_tag()``
 
1921
--------------------------------------
 
1922
 
 
1923
Если вам нужно добавить строку в документ, нет проблем — вы можете передать
 
1924
строку Python в ``append()`` или вызвать 
 
1925
конструктор ``NavigableString``::
 
1926
 
 
1927
   soup = BeautifulSoup("<b></b>")
 
1928
   tag = soup.b
 
1929
   tag.append("Hello")
 
1930
   new_string = NavigableString(" there")
 
1931
   tag.append(new_string)
 
1932
   tag
 
1933
   # <b>Hello there.</b>
 
1934
   tag.contents
 
1935
   # [u'Hello', u' there']
 
1936
 
 
1937
Если вы хотите создать комментарий или другой подкласс
 
1938
``NavigableString``, просто вызовите конструктор::
 
1939
 
 
1940
   from bs4 import Comment
 
1941
   new_comment = Comment("Nice to see you.")
 
1942
   tag.append(new_comment)
 
1943
   tag
 
1944
   # <b>Hello there<!--Nice to see you.--></b>
 
1945
   tag.contents
 
1946
   # [u'Hello', u' there', u'Nice to see you.']
 
1947
 
 
1948
(Это новая функция в Beautiful Soup 4.4.0.)
 
1949
 
 
1950
Что делать, если вам нужно создать совершенно новый тег?  Наилучшим решением будет
 
1951
вызвать фабричный метод ``BeautifulSoup.new_tag()``::
 
1952
 
 
1953
   soup = BeautifulSoup("<b></b>")
 
1954
   original_tag = soup.b
 
1955
 
 
1956
   new_tag = soup.new_tag("a", href="http://www.example.com")
 
1957
   original_tag.append(new_tag)
 
1958
   original_tag
 
1959
   # <b><a href="http://www.example.com"></a></b>
 
1960
 
 
1961
   new_tag.string = "Link text."
 
1962
   original_tag
 
1963
   # <b><a href="http://www.example.com">Link text.</a></b>
 
1964
 
 
1965
Нужен только первый аргумент, имя тега.
 
1966
 
 
1967
``insert()``
 
1968
------------
 
1969
 
 
1970
``Tag.insert()`` похож на ``Tag.append()``, за исключением того, что новый элемент
 
1971
не обязательно добавляется в конец родительского
 
1972
``.contents``. Он добавится в любое место, номер которого
 
1973
вы укажете. Это работает в точности как ``.insert()`` в списке Python::
 
1974
 
 
1975
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
1976
  soup = BeautifulSoup(markup)
 
1977
  tag = soup.a
 
1978
 
 
1979
  tag.insert(1, "but did not endorse ")
 
1980
  tag
 
1981
  # <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
 
1982
  tag.contents
 
1983
  # [u'I linked to ', u'but did not endorse', <i>example.com</i>]
 
1984
 
 
1985
``insert_before()`` и ``insert_after()``
 
1986
----------------------------------------
 
1987
 
 
1988
Метод ``insert_before()`` вставляет теги или строки непосредственно
 
1989
перед чем-то в дереве разбора::
 
1990
 
 
1991
   soup = BeautifulSoup("<b>stop</b>")
 
1992
   tag = soup.new_tag("i")
 
1993
   tag.string = "Don't"
 
1994
   soup.b.string.insert_before(tag)
 
1995
   soup.b
 
1996
   # <b><i>Don't</i>stop</b>
 
1997
 
 
1998
Метод ``insert_after()`` вставляет теги или строки непосредственно
 
1999
после чего-то в дереве разбора::
 
2000
 
 
2001
   div = soup.new_tag('div')
 
2002
   div.string = 'ever'
 
2003
   soup.b.i.insert_after(" you ", div)
 
2004
   soup.b
 
2005
   # <b><i>Don't</i> you <div>ever</div> stop</b>
 
2006
   soup.b.contents
 
2007
   # [<i>Don't</i>, u' you', <div>ever</div>, u'stop']
 
2008
 
 
2009
``clear()``
 
2010
-----------
 
2011
 
 
2012
``Tag.clear()`` удаляет содержимое тега::
 
2013
 
 
2014
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2015
  soup = BeautifulSoup(markup)
 
2016
  tag = soup.a
 
2017
 
 
2018
  tag.clear()
 
2019
  tag
 
2020
  # <a href="http://example.com/"></a>
 
2021
 
 
2022
``extract()``
 
2023
-------------
 
2024
 
 
2025
``PageElement.extract()`` удаляет тег или строку из дерева. Он
 
2026
возвращает тег или строку, которая была извлечена::
 
2027
 
 
2028
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2029
  soup = BeautifulSoup(markup)
 
2030
  a_tag = soup.a
 
2031
 
 
2032
  i_tag = soup.i.extract()
 
2033
 
 
2034
  a_tag
 
2035
  # <a href="http://example.com/">I linked to</a>
 
2036
 
 
2037
  i_tag
 
2038
  # <i>example.com</i>
 
2039
 
 
2040
  print(i_tag.parent)
 
2041
  None
 
2042
 
 
2043
К этому моменту у вас фактически есть два дерева разбора: одно в
 
2044
объекте ``BeautifulSoup``, который вы использовали, чтобы разобрать документ, другое в
 
2045
теге, который был извлечен. Вы можете далее вызывать ``extract`` в отношении
 
2046
дочернего элемента того тега, который был извлечен::
 
2047
 
 
2048
  my_string = i_tag.string.extract()
 
2049
  my_string
 
2050
  # u'example.com'
 
2051
 
 
2052
  print(my_string.parent)
 
2053
  # None
 
2054
  i_tag
 
2055
  # <i></i>
 
2056
 
 
2057
 
 
2058
``decompose()``
 
2059
---------------
 
2060
 
 
2061
``Tag.decompose()`` удаляет тег из дерева, а затем `полностью
 
2062
уничтожает его вместе с его содержимым`::
 
2063
 
 
2064
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2065
  soup = BeautifulSoup(markup)
 
2066
  a_tag = soup.a
 
2067
 
 
2068
  soup.i.decompose()
 
2069
 
 
2070
  a_tag
 
2071
  # <a href="http://example.com/">I linked to</a>
 
2072
 
 
2073
 
 
2074
.. _replace_with():
 
2075
 
 
2076
``replace_with()``
 
2077
------------------
 
2078
 
 
2079
``PageElement.extract()`` удаляет тег или строку из дерева
 
2080
и заменяет его тегом или строкой по вашему выбору::
 
2081
 
 
2082
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2083
  soup = BeautifulSoup(markup)
 
2084
  a_tag = soup.a
 
2085
 
 
2086
  new_tag = soup.new_tag("b")
 
2087
  new_tag.string = "example.net"
 
2088
  a_tag.i.replace_with(new_tag)
 
2089
 
 
2090
  a_tag
 
2091
  # <a href="http://example.com/">I linked to <b>example.net</b></a>
 
2092
 
 
2093
``replace_with()`` возвращает тег или строку, которые были заменены, так что
 
2094
вы можете изучить его или добавить его обратно в другую часть дерева.
 
2095
 
 
2096
``wrap()``
 
2097
----------
 
2098
 
 
2099
``PageElement.wrap()`` обертывает элемент в указанный вами тег. Он
 
2100
возвращает новую обертку::
 
2101
 
 
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>
 
2105
 
 
2106
 soup.p.wrap(soup.new_tag("div")
 
2107
 # <div><p><b>I wish I was bold.</b></p></div>
 
2108
 
 
2109
Это новый метод в Beautiful Soup 4.0.5.
 
2110
 
 
2111
``unwrap()``
 
2112
------------
 
2113
 
 
2114
``Tag.unwrap()`` — это противоположность ``wrap()``. Он заменяет весь тег на
 
2115
его содержимое. Этим методом удобно очищать разметку::
 
2116
 
 
2117
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2118
  soup = BeautifulSoup(markup)
 
2119
  a_tag = soup.a
 
2120
 
 
2121
  a_tag.i.unwrap()
 
2122
  a_tag
 
2123
  # <a href="http://example.com/">I linked to example.com</a>
 
2124
 
 
2125
Как и ``replace_with()``, ``unwrap()`` возвращает тег,
 
2126
который был заменен.
 
2127
 
 
2128
``smooth()``
 
2129
------------
 
2130
 
 
2131
После вызова ряда методов, которые изменяют дерево разбора, у вас может оказаться несколько объектов ``NavigableString`` подряд. У Beautiful Soup с этим нет проблем, но поскольку такое не случается со свежеразобранным документом, вам может показаться неожиданным следующее поведение::
 
2132
 
 
2133
  soup = BeautifulSoup("<p>A one</p>")
 
2134
  soup.p.append(", a two")
 
2135
 
 
2136
  soup.p.contents
 
2137
  # [u'A one', u', a two']
 
2138
 
 
2139
  print(soup.p.encode())
 
2140
  # <p>A one, a two</p>
 
2141
 
 
2142
  print(soup.p.prettify())
 
2143
  # <p>
 
2144
  #  A one
 
2145
  #  , a two
 
2146
  # </p>
 
2147
 
 
2148
Вы можете вызвать ``Tag.smooth()``, чтобы очистить дерево разбора путем объединения смежных строк::
 
2149
 
 
2150
 soup.smooth()
 
2151
 
 
2152
 soup.p.contents
 
2153
 # [u'A one, a two']
 
2154
 
 
2155
 print(soup.p.prettify())
 
2156
 # <p>
 
2157
 #  A one, a two
 
2158
 # </p>
 
2159
 
 
2160
``smooth()`` — это новый метод в Beautiful Soup 4.8.0.
 
2161
 
 
2162
Вывод
 
2163
=====
 
2164
 
 
2165
.. _.prettyprinting:
 
2166
 
 
2167
Красивое форматирование
 
2168
-----------------------
 
2169
 
 
2170
Метод ``prettify()`` превратит дерево разбора Beautiful Soup в
 
2171
красиво отформатированную строку Unicode, где каждый
 
2172
тег и каждая строка выводятся на отдельной строчке::
 
2173
 
 
2174
  markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2175
  soup = BeautifulSoup(markup)
 
2176
  soup.prettify()
 
2177
  # '<html>\n <head>\n </head>\n <body>\n  <a href="http://example.com/">\n...'
 
2178
 
 
2179
  print(soup.prettify())
 
2180
  # <html>
 
2181
  #  <head>
 
2182
  #  </head>
 
2183
  #  <body>
 
2184
  #   <a href="http://example.com/">
 
2185
  #    I linked to
 
2186
  #    <i>
 
2187
  #     example.com
 
2188
  #    </i>
 
2189
  #   </a>
 
2190
  #  </body>
 
2191
  # </html>
 
2192
 
 
2193
Вы можете вызвать ``prettify()`` для объекта ``BeautifulSoup`` верхнего уровня
 
2194
или для любого из его объектов ``Tag``::
 
2195
 
 
2196
  print(soup.a.prettify())
 
2197
  # <a href="http://example.com/">
 
2198
  #  I linked to
 
2199
  #  <i>
 
2200
  #   example.com
 
2201
  #  </i>
 
2202
  # </a>
 
2203
 
 
2204
Без красивого форматирования
 
2205
----------------------------
 
2206
 
 
2207
Если вам нужна просто строка, без особого форматирования, вы можете вызвать
 
2208
``unicode()`` или ``str()`` для объекта ``BeautifulSoup`` или объекта ``Tag``
 
2209
внутри::
 
2210
 
 
2211
 str(soup)
 
2212
 # '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
 
2213
 
 
2214
 unicode(soup.a)
 
2215
 # u'<a href="http://example.com/">I linked to <i>example.com</i></a>'
 
2216
 
 
2217
Функция ``str()`` возвращает строку, кодированную в UTF-8. Для получения более подробной информации см. 
 
2218
`Кодировки`_.
 
2219
 
 
2220
Вы также можете вызвать ``encode()`` для получения байтовой строки, и ``decode()``,
 
2221
чтобы получить Unicode.
 
2222
 
 
2223
.. _output_formatters:
 
2224
 
 
2225
Средства форматирования вывода
 
2226
------------------------------
 
2227
 
 
2228
Если вы дадите Beautiful Soup документ, который содержит HTML-мнемоники, такие как
 
2229
"&lquot;", они будут преобразованы в символы Unicode::
 
2230
 
 
2231
 soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.")
 
2232
 unicode(soup)
 
2233
 # u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
 
2234
 
 
2235
Если затем преобразовать документ в строку, символы Unicode
 
2236
будет кодироваться как UTF-8. Вы не получите обратно HTML-мнемоники::
 
2237
 
 
2238
 str(soup)
 
2239
 # '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'
 
2240
 
 
2241
По умолчанию единственные символы, которые экранируются при выводе — это чистые
 
2242
амперсанды и угловые скобки. Они превращаются в «&», «<»
 
2243
и ">", чтобы Beautiful Soup случайно не сгенерировал
 
2244
невалидный HTML или XML::
 
2245
 
 
2246
 soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>")
 
2247
 soup.p
 
2248
 # <p>The law firm of Dewey, Cheatem, &amp; Howe</p>
 
2249
 
 
2250
 soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>')
 
2251
 soup.a
 
2252
 # <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>
 
2253
 
 
2254
Вы можете изменить это поведение, указав для
 
2255
аргумента ``formatter`` одно из значений: ``prettify()``, ``encode()`` или
 
2256
``decode()``. Beautiful Soup распознает пять возможных значений
 
2257
``formatter``.
 
2258
 
 
2259
Значение по умолчанию — ``formatter="minimal"``. Строки будут обрабатываться
 
2260
ровно настолько, чтобы Beautiful Soup генерировал валидный HTML / XML::
 
2261
 
 
2262
 french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
 
2263
 soup = BeautifulSoup(french)
 
2264
 print(soup.prettify(formatter="minimal"))
 
2265
 # <html>
 
2266
 #  <body>
 
2267
 #   <p>
 
2268
 #    Il a dit &lt;&lt;Sacré bleu!&gt;&gt;
 
2269
 #   </p>
 
2270
 #  </body>
 
2271
 # </html>
 
2272
 
 
2273
Если вы передадите ``formatter = "html"``, Beautiful Soup преобразует
 
2274
символы Unicode в HTML-мнемоники, когда это возможно::
 
2275
 
 
2276
 print(soup.prettify(formatter="html"))
 
2277
 # <html>
 
2278
 #  <body>
 
2279
 #   <p>
 
2280
 #    Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;
 
2281
 #   </p>
 
2282
 #  </body>
 
2283
 # </html>
 
2284
 
 
2285
Если вы передаете ``formatter="html5"``, это то же самое, что
 
2286
``formatter="html"``, только Beautiful Soup будет
 
2287
пропускать закрывающую косую черту в пустых тегах HTML, таких как "br"::
 
2288
 
 
2289
 soup = BeautifulSoup("<br>")
 
2290
 
 
2291
 print(soup.encode(formatter="html"))
 
2292
 # <html><body><br/></body></html>
 
2293
 
 
2294
 print(soup.encode(formatter="html5"))
 
2295
 # <html><body><br></body></html>
 
2296
 
 
2297
Если вы передадите ``formatter=None``, Beautiful Soup вообще не будет менять
 
2298
строки на выходе. Это самый быстрый вариант, но он может привести
 
2299
к тому, что Beautiful Soup будет генерировать невалидный HTML / XML::
 
2300
 
 
2301
 print(soup.prettify(formatter=None))
 
2302
 # <html>
 
2303
 #  <body>
 
2304
 #   <p>
 
2305
 #    Il a dit <<Sacré bleu!>>
 
2306
 #   </p>
 
2307
 #  </body>
 
2308
 # </html>
 
2309
 
 
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>
 
2313
 
 
2314
Если вам нужен более сложный контроль над выводом, вы можете
 
2315
использовать класс ``Formatter`` из Beautiful Soup. Вот как можно
 
2316
преобразовать строки в верхний регистр, независимо от того, находятся ли они в текстовом узле или в
 
2317
значении атрибута::
 
2318
 
 
2319
 from bs4.formatter import HTMLFormatter
 
2320
 def uppercase(str):
 
2321
     return str.upper()
 
2322
 formatter = HTMLFormatter(uppercase)
 
2323
 
 
2324
 print(soup.prettify(formatter=formatter))
 
2325
 # <html>
 
2326
 #  <body>
 
2327
 #   <p>
 
2328
 #    IL A DIT <<SACRÉ BLEU!>>
 
2329
 #   </p>
 
2330
 #  </body>
 
2331
 # </html>
 
2332
 
 
2333
 print(link_soup.a.prettify(formatter=formatter))
 
2334
 # <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
 
2335
 #  A LINK
 
2336
 # </a>
 
2337
 
 
2338
Подклассы ``HTMLFormatter`` или ``XMLFormatter`` дают еще
 
2339
больший контроль над выводом. Например, Beautiful Soup сортирует
 
2340
атрибуты в каждом теге по умолчанию::
 
2341
 
 
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>
 
2345
 
 
2346
Чтобы выключить сортировку по умолчанию, вы можете создать подкласс  на основе метода ``Formatter.attributes()``,
 
2347
который контролирует, какие атрибуты выводятся и в каком
 
2348
порядке. Эта реализация также отфильтровывает атрибут с именем "m",
 
2349
где бы он ни появился::
 
2350
 
 
2351
 class UnsortedAttributes(HTMLFormatter):
 
2352
     def attributes(self, tag):
 
2353
         for k, v in tag.attrs.items():
 
2354
             if k == 'm':
 
2355
                 continue
 
2356
             yield k, v
 
2357
 print(attr_soup.p.encode(formatter=UnsortedAttributes())) 
 
2358
 # <p z="1" a="3"></p>
 
2359
 
 
2360
Последнее предостережение: если вы создаете объект ``CData``, текст внутри
 
2361
этого объекта всегда представлен `как есть, без какого-либо
 
2362
форматирования`. Beautiful Soup вызовет вашу функцию для замены мнемоник,
 
2363
на тот случай, если вы написали функцию, которая подсчитывает
 
2364
все строки в документе или что-то еще, но он будет игнорировать
 
2365
возвращаемое значение::
 
2366
 
 
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"))
 
2371
 # <a>
 
2372
 #  <![CDATA[one < three]]>
 
2373
 # </a>
 
2374
 
 
2375
 
 
2376
``get_text()``
 
2377
--------------
 
2378
 
 
2379
Если вам нужна только текстовая часть документа или тега, вы можете использовать
 
2380
метод ``get_text()``. Он возвращает весь текст документа или
 
2381
тега в виде единственной строки Unicode::
 
2382
 
 
2383
  markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
 
2384
  soup = BeautifulSoup(markup)
 
2385
 
 
2386
  soup.get_text()
 
2387
  u'\nI linked to example.com\n'
 
2388
  soup.i.get_text()
 
2389
  u'example.com'
 
2390
 
 
2391
Вы можете указать строку, которая будет использоваться для объединения текстовых фрагментов
 
2392
в единую строку::
 
2393
 
 
2394
 # soup.get_text("|")
 
2395
 u'\nI linked to |example.com|\n'
 
2396
 
 
2397
Вы можете сказать Beautiful Soup удалять пробелы в начале и
 
2398
конце каждого текстового фрагмента::
 
2399
 
 
2400
 # soup.get_text("|", strip=True)
 
2401
 u'I linked to|example.com'
 
2402
 
 
2403
Но в этом случае вы можете предпочесть использовать генератор :ref:`.stripped_strings <string-generators>`
 
2404
и затем обработать текст самостоятельно::
 
2405
 
 
2406
 [text for text in soup.stripped_strings]
 
2407
 # [u'I linked to', u'example.com']
 
2408
 
 
2409
Указание парсера
 
2410
================
 
2411
 
 
2412
Если вам нужно просто разобрать HTML, вы можете скинуть разметку в
 
2413
конструктор ``BeautifulSoup``, и, скорее всего, все будет в порядке. Beautiful
 
2414
Soup подберет для вас парсер и проанализирует данные. Но есть
 
2415
несколько дополнительных аргументов, которые вы можете передать конструктору, чтобы изменить
 
2416
используемый парсер.
 
2417
 
 
2418
Первым аргументом конструктора ``BeautifulSou`` является строка или
 
2419
открытый дескриптор файла — сама разметка, которую вы хотите разобрать. Второй аргумент — это
 
2420
`как` вы хотите, чтобы разметка была разобрана.
 
2421
 
 
2422
Если вы ничего не укажете, будет использован лучший HTML-парсер из тех,
 
2423
которые установлены. Beautiful Soup оценивает парсер lxml как лучший, за ним идет
 
2424
html5lib, затем встроенный парсер Python. Вы можете переопределить используемый парсер,
 
2425
указав что-то из следующего:
 
2426
 
 
2427
* Какой тип разметки вы хотите разобрать. В данный момент поддерживаются:
 
2428
  "html", "xml" и "html5".
 
2429
 
 
2430
* Имя библиотеки парсера, которую вы хотите использовать. В данный момент поддерживаются
 
2431
  "lxml", "html5lib" и "html.parser" (встроенный в Python
 
2432
  парсер HTML).
 
2433
 
 
2434
В разделе `Установка парсера`_ вы найдете сравнительную таблицу поддерживаемых парсеров.
 
2435
 
 
2436
Если у вас не установлен соответствующий парсер, Beautiful Soup
 
2437
проигнорирует ваш запрос и выберет другой парсер. На текущий момент единственный
 
2438
поддерживаемый парсер XML — это lxml. Если у вас не установлен lxml, запрос на
 
2439
парсер XML ничего не даст, и запрос "lxml" тоже
 
2440
не сработает.
 
2441
 
 
2442
Различия между парсерами
 
2443
------------------------
 
2444
 
 
2445
Beautiful Soup представляет один интерфейс для разных
 
2446
парсеров, но парсеры неодинаковы. Разные парсеры создадут
 
2447
различные деревья разбора из одного и того же документа. Самые большие различия будут
 
2448
между парсерами HTML и парсерами XML. Вот короткий
 
2449
документ, разобранный как HTML::
 
2450
 
 
2451
 BeautifulSoup("<a><b /></a>")
 
2452
 # <html><head></head><body><a><b></b></a></body></html>
 
2453
 
 
2454
Поскольку пустой тег <b /> не является валидным кодом HTML, парсер превращает его в
 
2455
пару тегов <b></b>.
 
2456
 
 
2457
Вот тот же документ, который разобран как XML (для его запуска нужно, чтобы был
 
2458
установлен lxml). Обратите внимание, что пустой тег <b /> остается, и
 
2459
что в документ добавляется объявление XML вместо
 
2460
тега <html>::
 
2461
 
 
2462
 BeautifulSoup("<a><b /></a>", "xml")
 
2463
 # <?xml version="1.0" encoding="utf-8"?>
 
2464
 # <a><b/></a>
 
2465
 
 
2466
Есть также различия между парсерами HTML. Если вы даете Beautiful
 
2467
Soup идеально оформленный документ HTML, эти различия не будут
 
2468
иметь значения. Один парсер будет быстрее другого, но все они будут давать
 
2469
структуру данных, которая выглядит точно так же, как оригинальный
 
2470
документ HTML.
 
2471
 
 
2472
Но если документ оформлен неидеально, различные парсеры
 
2473
дадут разные результаты. Вот короткий невалидный документ, разобранный с помощью
 
2474
HTML-парсера lxml. Обратите внимание, что висячий тег </p> просто
 
2475
игнорируется::
 
2476
 
 
2477
 BeautifulSoup("<a></p>", "lxml")
 
2478
 # <html><body><a></a></body></html>
 
2479
 
 
2480
Вот тот же документ, разобранный с помощью html5lib::
 
2481
 
 
2482
 BeautifulSoup("<a></p>", "html5lib")
 
2483
 # <html><head></head><body><a><p></p></a></body></html>
 
2484
 
 
2485
Вместо того, чтобы игнорировать висячий тег </p>, html5lib добавляет
 
2486
открывающй тег <p>. Этот парсер также добавляет пустой тег <head> в
 
2487
документ.
 
2488
 
 
2489
Вот тот же документ, разобранный с помощью встроенного в Python
 
2490
парсера HTML::
 
2491
 
 
2492
 BeautifulSoup("<a></p>", "html.parser")
 
2493
 # <a></a>
 
2494
 
 
2495
Как и html5lib, этот парсер игнорирует закрывающий тег </p>. В отличие от
 
2496
html5lib, этот парсер не делает попытки создать правильно оформленный HTML-
 
2497
документ, добавив тег <body>. В отличие от lxml, он даже не
 
2498
добавляет тег <html>.
 
2499
 
 
2500
Поскольку документ ``<a></p>`` невалиден, ни один из этих способов
 
2501
нельзя назвать "правильным". Парсер html5lib использует способы,
 
2502
которые являются частью стандарта HTML5, поэтому он может претендовать на то, что его подход
 
2503
самый "правильный", но правомерно использовать любой из трех методов.
 
2504
 
 
2505
Различия между парсерами могут повлиять на ваш скрипт. Если вы планируете
 
2506
распространять ваш скрипт или запускать его на нескольких
 
2507
машинах, вам нужно указать парсер в
 
2508
конструкторе ``BeautifulSoup``. Это уменьшит вероятность того, что ваши пользователи при разборе
 
2509
документа получат результат, отличный от вашего.
 
2510
   
 
2511
Кодировки
 
2512
=========
 
2513
 
 
2514
Любой документ HTML или XML написан в определенной кодировке, такой как ASCII
 
2515
или UTF-8.  Но когда вы загрузите этот документ в Beautiful Soup, вы
 
2516
обнаружите, что он был преобразован в Unicode::
 
2517
 
 
2518
 markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
 
2519
 soup = BeautifulSoup(markup)
 
2520
 soup.h1
 
2521
 # <h1>Sacré bleu!</h1>
 
2522
 soup.h1.string
 
2523
 # u'Sacr\xe9 bleu!'
 
2524
 
 
2525
Это не волшебство. (Хотя это было бы здорово, конечно.) Beautiful Soup использует
 
2526
подбиблиотеку под названием `Unicode, Dammit`_ для определения кодировки документа
 
2527
и преобразования ее в Unicode. Кодировка, которая была автоматически определена, содержится в значении
 
2528
атрибута ``.original_encoding`` объекта ``BeautifulSoup``::
 
2529
 
 
2530
 soup.original_encoding
 
2531
 'utf-8'
 
2532
 
 
2533
Unicode, Dammit чаще всего угадывает правильно, но иногда
 
2534
делает ошибки. Иногда он угадывает правильно только после
 
2535
побайтового поиска по документу, что занимает очень много времени. Если
 
2536
вы вдруг уже знаете кодировку документа, вы можете избежать
 
2537
ошибок и задержек, передав кодировку конструктору ``BeautifulSoup``
 
2538
как аргумент ``from_encoding``.
 
2539
 
 
2540
Вот документ, написанный на ISO-8859-8. Документ настолько короткий, что
 
2541
Unicode, Dammit не может разобраться и неправильно идентифицирует кодировку как
 
2542
ISO-8859-7::
 
2543
 
 
2544
 markup = b"<h1>\xed\xe5\xec\xf9</h1>"
 
2545
 soup = BeautifulSoup(markup)
 
2546
 soup.h1
 
2547
 <h1>νεμω</h1>
 
2548
 soup.original_encoding
 
2549
 'ISO-8859-7'
 
2550
 
 
2551
Мы можем все исправить, передав правильный ``from_encoding``::
 
2552
 
 
2553
 soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
 
2554
 soup.h1
 
2555
 <h1>םולש</h1>
 
2556
 soup.original_encoding
 
2557
 'iso8859-8'
 
2558
 
 
2559
Если вы не знаете правильную кодировку, но видите, что
 
2560
Unicode, Dammit определяет ее неправильно, вы можете передать ошибочные варианты в
 
2561
``exclude_encodings``::
 
2562
 
 
2563
 soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"])
 
2564
 soup.h1
 
2565
 <h1>םולש</h1>
 
2566
 soup.original_encoding
 
2567
 'WINDOWS-1255'
 
2568
 
 
2569
Windows-1255 не на 100% подходит, но это совместимое
 
2570
надмножество ISO-8859-8, так что догадка почти верна. (``exclude_encodings``
 
2571
— это новая функция в Beautiful Soup 4.4.0.)
 
2572
 
 
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
параграфе), а не служит заменой отсутствующим данным.
 
2584
 
 
2585
Кодировка вывода
 
2586
----------------
 
2587
 
 
2588
Когда вы пишете документ из Beautiful Soup, вы получаете документ в UTF-8,
 
2589
даже если он изначально не был в UTF-8. Вот
 
2590
документ в кодировке Latin-1::
 
2591
 
 
2592
 markup = b'''
 
2593
  <html>
 
2594
   <head>
 
2595
    <meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
 
2596
   </head>
 
2597
   <body>
 
2598
    <p>Sacr\xe9 bleu!</p>
 
2599
   </body>
 
2600
  </html>
 
2601
 '''
 
2602
 
 
2603
 soup = BeautifulSoup(markup)
 
2604
 print(soup.prettify())
 
2605
 # <html>
 
2606
 #  <head>
 
2607
 #   <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
 
2608
 #  </head>
 
2609
 #  <body>
 
2610
 #   <p>
 
2611
 #    Sacré bleu!
 
2612
 #   </p>
 
2613
 #  </body>
 
2614
 # </html>
 
2615
 
 
2616
Обратите внимание, что тег <meta> был переписан, чтобы отразить тот факт, что
 
2617
теперь документ кодируется в UTF-8.
 
2618
 
 
2619
Если вы не хотите кодировку UTF-8, вы можете передать другую в ``prettify()``::
 
2620
 
 
2621
 print(soup.prettify("latin-1"))
 
2622
 # <html>
 
2623
 #  <head>
 
2624
 #   <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
 
2625
 # ...
 
2626
 
 
2627
Вы также можете вызвать encode() для объекта ``BeautifulSoup`` или любого
 
2628
элемента в супе, как если бы это была строка Python::
 
2629
 
 
2630
 soup.p.encode("latin-1")
 
2631
 # '<p>Sacr\xe9 bleu!</p>'
 
2632
 
 
2633
 soup.p.encode("utf-8")
 
2634
 # '<p>Sacr\xc3\xa9 bleu!</p>'
 
2635
 
 
2636
Любые символы, которые не могут быть представлены в выбранной вами кодировке, будут
 
2637
преобразованы в числовые коды мнемоник XML. Вот документ,
 
2638
который включает в себя Unicode-символ SNOWMAN (снеговик)::
 
2639
 
 
2640
 markup = u"<b>\N{SNOWMAN}</b>"
 
2641
 snowman_soup = BeautifulSoup(markup)
 
2642
 tag = snowman_soup.b
 
2643
 
 
2644
Символ SNOWMAN может быть частью документа UTF-8 (он выглядит
 
2645
так: ☃), но в ISO-Latin-1 или
 
2646
ASCII нет представления для этого символа, поэтому для этих кодировок он конвертируется в "&#9731;":
 
2647
 
 
2648
 print(tag.encode("utf-8"))
 
2649
 # <b>☃</b>
 
2650
 
 
2651
 print tag.encode("latin-1")
 
2652
 # <b>&#9731;</b>
 
2653
 
 
2654
 print tag.encode("ascii")
 
2655
 # <b>&#9731;</b>
 
2656
 
 
2657
Unicode, Dammit
 
2658
---------------
 
2659
 
 
2660
Вы можете использовать Unicode, Dammit без Beautiful Soup. Он полезен в тех случаях.
 
2661
когда у вас есть данные в неизвестной кодировке, и вы просто хотите, чтобы они
 
2662
преобразовались в Unicode::
 
2663
 
 
2664
 from bs4 import UnicodeDammit
 
2665
 dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
 
2666
 print(dammit.unicode_markup)
 
2667
 # Sacré bleu!
 
2668
 dammit.original_encoding
 
2669
 # 'utf-8'
 
2670
 
 
2671
Догадки Unicode, Dammit станут намного точнее, если вы установите
 
2672
библиотеки Python ``chardet`` или ``cchardet``. Чем больше данных вы
 
2673
даете Unicode, Dammit, тем точнее он определит кодировку. Если у вас есть
 
2674
собственные предположения относительно возможных кодировок, вы можете передать
 
2675
их в виде списка::
 
2676
 
 
2677
 dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
 
2678
 print(dammit.unicode_markup)
 
2679
 # Sacré bleu!
 
2680
 dammit.original_encoding
 
2681
 # 'latin-1'
 
2682
 
 
2683
В Unicode, Dammit есть две специальные функции, которые Beautiful Soup не
 
2684
использует.
 
2685
 
 
2686
Парные кавычки
 
2687
^^^^^^^^^^^^^^
 
2688
 
 
2689
Вы можете использовать Unicode, Dammit, чтобы конвертировать парные кавычки (Microsoft smart quotes) в
 
2690
мнемоники HTML или XML::
 
2691
 
 
2692
 markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
 
2693
 
 
2694
 UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
 
2695
 # u'<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'
 
2696
 
 
2697
 UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
 
2698
 # u'<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'
 
2699
 
 
2700
Вы также можете конвертировать парные кавычки в обычные кавычки ASCII::
 
2701
 
 
2702
 UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
 
2703
 # u'<p>I just "love" Microsoft Word\'s smart quotes</p>'
 
2704
 
 
2705
Надеюсь, вы найдете эту функцию полезной, но Beautiful Soup не
 
2706
использует ее. Beautiful Soup по умолчанию
 
2707
конвертирует парные кавычки в символы Unicode, как и
 
2708
все остальное::
 
2709
 
 
2710
 UnicodeDammit(markup, ["windows-1252"]).unicode_markup
 
2711
 # u'<p>I just \u201clove\u201d Microsoft Word\u2019s smart quotes</p>'
 
2712
 
 
2713
Несогласованные кодировки
 
2714
^^^^^^^^^^^^^^^^^^^^^^^^^
 
2715
 
 
2716
Иногда документ кодирован в основном в UTF-8, но содержит символы Windows-1252,
 
2717
такие как, опять-таки, парные кавычки. Такое бывает,
 
2718
когда веб-сайт содержит данные из нескольких источников. Вы можете использовать
 
2719
``UnicodeDammit.detwingle()``, чтобы превратить такой документ в чистый
 
2720
UTF-8. Вот простой пример::
 
2721
 
 
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")
 
2725
 
 
2726
В этом документе бардак. Снеговики в UTF-8, а парные кавычки
 
2727
в Windows-1252. Можно отображать или снеговиков, или кавычки, но не
 
2728
то и другое одновременно::
 
2729
 
 
2730
 print(doc)
 
2731
 # ☃☃☃�I like snowmen!�
 
2732
 
 
2733
 print(doc.decode("windows-1252"))
 
2734
 # â˜ƒâ˜ƒâ˜ƒ“I like snowmen!”
 
2735
 
 
2736
Декодирование документа как UTF-8 вызывает ``UnicodeDecodeError``, а
 
2737
декодирование его как Windows-1252 выдаст тарабарщину. К счастью,
 
2738
``UnicodeDammit.detwingle()`` преобразует строку в чистый UTF-8,
 
2739
позволяя затем декодировать его в Unicode и отображать снеговиков и кавычки
 
2740
одновременно::
 
2741
 
 
2742
 new_doc = UnicodeDammit.detwingle(doc)
 
2743
 print(new_doc.decode("utf8"))
 
2744
 # ☃☃☃“I like snowmen!”
 
2745
 
 
2746
``UnicodeDammit.detwingle()`` знает только, как обрабатывать Windows-1252,
 
2747
встроенный в UTF-8 (и наоборот, мне кажется), но это наиболее
 
2748
общий случай.
 
2749
 
 
2750
Обратите внимание, что нужно вызывать ``UnicodeDammit.detwingle()`` для ваших данных
 
2751
перед передачей в конструктор ``BeautifulSoup`` или
 
2752
``UnicodeDammit``. Beautiful Soup предполагает, что документ имеет единую
 
2753
кодировку, какой бы она ни была. Если вы передадите ему документ, который
 
2754
содержит как UTF-8, так и Windows-1252, скорее всего, он решит, что весь
 
2755
документ кодируется в Windows-1252, и это будет выглядеть как
 
2756
``☃☃☃“I like snowmen!”``.
 
2757
 
 
2758
``UnicodeDammit.detwingle()`` — это новое в Beautiful Soup 4.1.0.
 
2759
 
 
2760
Нумерация строк
 
2761
===============
 
2762
 
 
2763
Парсеры ``html.parser`` и ``html5lib`` могут отслеживать, где в
 
2764
исходном документе был найден каждый тег. Вы можете получить доступ к этой
 
2765
информации через ``Tag.sourceline`` (номер строки) и ``Tag.sourcepos``
 
2766
(позиция начального тега в строке)::
 
2767
 
 
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')
 
2774
 
 
2775
Обратите внимание, что два парсера понимают
 
2776
``sourceline`` и ``sourcepos`` немного по-разному. Для html.parser эти числа
 
2777
представляет позицию начального знака "<". Для html5lib
 
2778
эти числа представляют позицию конечного знака ">"::
 
2779
   
 
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')
 
2785
 
 
2786
Вы можете отключить эту функцию, передав ``store_line_numbers = False``
 
2787
в конструктор ``BeautifulSoup``::
 
2788
 
 
2789
   markup = "<p\n>Paragraph 1</p>\n    <p>Paragraph 2</p>"
 
2790
   soup = BeautifulSoup(markup, 'html.parser', store_line_numbers=False)
 
2791
   soup.p.sourceline
 
2792
   # None
 
2793
  
 
2794
Эта функция является новой в 4.8.1, и парсеры, основанные на lxml, не
 
2795
поддерживают ее.
 
2796
 
 
2797
Проверка объектов на равенство
 
2798
==============================
 
2799
 
 
2800
Beautiful Soup считает, что два объекта ``NavigableString`` или ``Tag``
 
2801
равны, если они представлены в одинаковой разметке HTML или XML. В этом
 
2802
примере два тега <b> рассматриваются как равные, даже если они находятся
 
2803
в разных частях дерева объекта, потому что они оба выглядят как
 
2804
``<b>pizza</b>``::
 
2805
 
 
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
 
2810
 # True
 
2811
 
 
2812
 print first_b.previous_element == second_b.previous_element
 
2813
 # False
 
2814
 
 
2815
Если вы хотите выяснить, указывают ли две переменные на один и тот же
 
2816
объект, используйте `is`::
 
2817
 
 
2818
 print first_b is second_b
 
2819
 # False
 
2820
 
 
2821
Копирование объектов Beautiful Soup
 
2822
===================================
 
2823
 
 
2824
Вы можете использовать ``copy.copy()`` для создания копии любого ``Tag`` или
 
2825
``NavigableString``::
 
2826
 
 
2827
 import copy
 
2828
 p_copy = copy.copy(soup.p)
 
2829
 print p_copy
 
2830
 # <p>I want <b>pizza</b> and more <b>pizza</b>!</p>
 
2831
 
 
2832
Копия считается равной оригиналу, так как у нее
 
2833
такая же разметка, что и у оригинала, но это другой объект::
 
2834
 
 
2835
 print soup.p == p_copy
 
2836
 # True
 
2837
 
 
2838
 print soup.p is p_copy
 
2839
 # False
 
2840
 
 
2841
Единственная настоящая разница в том, что копия полностью отделена от
 
2842
исходного дерева объекта Beautiful Soup, как если бы в отношении нее вызвали
 
2843
метод  ``extract()``::
 
2844
 
 
2845
 print p_copy.parent
 
2846
 # None
 
2847
 
 
2848
Это потому, что два разных объекта ``Tag`` не могут занимать одно и то же
 
2849
пространство в одно и то же время.
 
2850
 
 
2851
 
 
2852
Разбор части документа
 
2853
======================
 
2854
 
 
2855
Допустим, вы хотите использовать Beautiful Soup, чтобы посмотреть на
 
2856
теги <a> в документе. Было бы бесполезной тратой времени и памяти разобирать весь документ и
 
2857
затем снова проходить по нему в поисках тегов <a>. Намного быстрее
 
2858
изначательно игнорировать все, что не является тегом <a>. Класс
 
2859
``SoupStrainer`` позволяет выбрать, какие части входящего
 
2860
документ разбирать. Вы просто создаете ``SoupStrainer`` и передаете его в
 
2861
конструктор ``BeautifulSoup`` в качестве аргумента ``parse_only``.
 
2862
 
 
2863
(Обратите внимание, что *эта функция не будет работать, если вы используете парсер html5lib*.
 
2864
Если вы используете html5lib, будет разобран весь документ, независимо
 
2865
от обстоятельств. Это потому что html5lib постоянно переставляет части дерева разбора
 
2866
в процессе работы, и если какая-то часть документа не
 
2867
попала в дерево разбора, все рухнет. Чтобы избежать путаницы, в
 
2868
примерах ниже я принудительно использую встроенный в Python
 
2869
парсер HTML.)
 
2870
 
 
2871
``SoupStrainer``
 
2872
----------------
 
2873
 
 
2874
Класс ``SoupStrainer`` принимает те же аргументы, что и типичный
 
2875
метод из раздела `Поиск по дереву`_: :ref:`name <name>`, :ref:`attrs
 
2876
<attrs>`, :ref:`string <string>` и :ref:`**kwargs <kwargs>`. Вот
 
2877
три объекта ``SoupStrainer``::
 
2878
 
 
2879
 from bs4 import SoupStrainer
 
2880
 
 
2881
 only_a_tags = SoupStrainer("a")
 
2882
 
 
2883
 only_tags_with_id_link2 = SoupStrainer(id="link2")
 
2884
 
 
2885
 def is_short_string(string):
 
2886
     return len(string) < 10
 
2887
 
 
2888
 only_short_strings = SoupStrainer(string=is_short_string)
 
2889
 
 
2890
Вернемся к фрагменту из «Алисы в стране чудес»
 
2891
и увидим, как выглядит документ, когда он разобран с этими
 
2892
тремя объектами ``SoupStrainer``::
 
2893
 
 
2894
 html_doc = """
 
2895
 <html><head><title>The Dormouse's story</title></head>
 
2896
 <body>
 
2897
 <p class="title"><b>The Dormouse's story</b></p>
 
2898
 
 
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>
 
2904
 
 
2905
 <p class="story">...</p>
 
2906
 """
 
2907
 
 
2908
 print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
 
2909
 # <a class="sister" href="http://example.com/elsie" id="link1">
 
2910
 #  Elsie
 
2911
 # </a>
 
2912
 # <a class="sister" href="http://example.com/lacie" id="link2">
 
2913
 #  Lacie
 
2914
 # </a>
 
2915
 # <a class="sister" href="http://example.com/tillie" id="link3">
 
2916
 #  Tillie
 
2917
 # </a>
 
2918
 
 
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">
 
2921
 #  Lacie
 
2922
 # </a>
 
2923
 
 
2924
 print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
 
2925
 # Elsie
 
2926
 # ,
 
2927
 # Lacie
 
2928
 # and
 
2929
 # Tillie
 
2930
 # ...
 
2931
 #
 
2932
 
 
2933
Вы также можете передать ``SoupStrainer`` в любой из методов. описанных в разделе
 
2934
`Поиск по дереву`_. Может, это не безумно полезно, но я
 
2935
решил упомянуть::
 
2936
 
 
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']
 
2941
 
 
2942
Устранение неисправностей
 
2943
=========================
 
2944
 
 
2945
.. _diagnose:
 
2946
 
 
2947
``diagnose()``
 
2948
--------------
 
2949
 
 
2950
Если у вас возникли проблемы с пониманием того, что Beautiful Soup делает с
 
2951
документом, передайте документ в функцию ``Diagnose()``. (Новое в
 
2952
Beautiful Soup 4.2.0.)  Beautiful Soup выведет отчет, показывающий,
 
2953
как разные парсеры обрабатывают документ, и сообщит вам, если
 
2954
отсутствует парсер, который Beautiful Soup мог бы использовать::
 
2955
 
 
2956
 from bs4.diagnose import diagnose
 
2957
 with open("bad.html") as fp:
 
2958
     data = fp.read()
 
2959
 diagnose(data)
 
2960
 
 
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
 
2965
 #
 
2966
 # Trying to parse your data with html.parser
 
2967
 # Here's what html.parser did with the document:
 
2968
 # ...
 
2969
 
 
2970
Простой взгляд на вывод diagnose() может показать, как решить
 
2971
проблему. Если это и не поможет, вы можете скопировать вывод ``Diagnose()``, когда
 
2972
обратитесь за помощью.
 
2973
 
 
2974
Ошибки при разборе документа
 
2975
----------------------------
 
2976
 
 
2977
Существует два вида ошибок разбора. Есть сбои,
 
2978
когда вы подаете документ в Beautiful Soup, и это поднимает
 
2979
исключение, обычно ``HTMLParser.HTMLParseError``. И есть
 
2980
неожиданное поведение, когда дерево разбора Beautiful Soup сильно
 
2981
отличается от документа, использованного для создания дерева.
 
2982
 
 
2983
Практически никогда источником этих проблемы не бывает Beautiful
 
2984
Soup. Это не потому, что Beautiful Soup так прекрасно
 
2985
написан. Это потому, что Beautiful Soup не содержит
 
2986
кода, который бы разбирал документ. Beautiful Soup опирается на внешние парсеры. Если один парсер
 
2987
не подходит для разбора документа, лучшим решением будет попробовать
 
2988
другой парсер. В разделе `Установка парсера`_ вы найдете больше информации
 
2989
и таблицу сравнения парсеров.
 
2990
 
 
2991
Наиболее распространенные ошибки разбора — это ``HTMLParser.HTMLParseError:
 
2992
malformed start tag`` и ``HTMLParser.HTMLParseError: bad end
 
2993
tag``. Они оба генерируются встроенным в Python парсером HTML,
 
2994
и решением будет :ref:`установить lxml или
 
2995
html5lib. <parser-installation>`
 
2996
 
 
2997
Наиболее распространенный тип неожиданного поведения — когда вы не можете найти
 
2998
тег, который точно есть в документе. Вы видели его на входе, но
 
2999
``find_all()`` возвращает ``[]``, или ``find()`` возвращает ``None``. Это
 
3000
еще одна распространенная проблема со встроенным в Python парсером HTML, который
 
3001
иногда пропускает теги, которые он не понимает.  Опять же, решение заключается в
 
3002
:ref:`установке lxml или html5lib <parser-installation>`.
 
3003
 
 
3004
Проблемы несоответствия версий
 
3005
------------------------------
 
3006
 
 
3007
* ``SyntaxError: Invalid syntax`` (в строке ``ROOT_TAG_NAME =
 
3008
  u'[document]'``) — вызвано запуском версии Beautiful Soup на Python 2
 
3009
  под Python 3 без конвертации кода.
 
3010
 
 
3011
* ``ImportError: No module named HTMLParser`` — вызвано запуском
 
3012
  версия Beautiful Soup на Python 3 под Python 2.
 
3013
 
 
3014
* ``ImportError: No module named html.parser`` — вызвано запуском
 
3015
  версия Beautiful Soup на Python 2 под Python 3.
 
3016
 
 
3017
* ``ImportError: No module named BeautifulSoup`` — вызвано запуском
 
3018
  кода Beautiful Soup 3 в системе, где BS3
 
3019
  не установлен. Или код писали на Beautiful Soup 4, не зная, что
 
3020
  имя пакета сменилось на ``bs4``.
 
3021
 
 
3022
* ``ImportError: No module named bs4`` — вызвано запуском
 
3023
  кода Beautiful Soup 4 в системе, где BS4 не установлен.
 
3024
 
 
3025
.. _parsing-xml:
 
3026
 
 
3027
Разбор XML
 
3028
----------
 
3029
 
 
3030
По умолчанию Beautiful Soup разбирает документы как HTML. Чтобы разобрать
 
3031
документ в виде XML, передайте "xml" в качестве второго аргумента
 
3032
в конструктор ``BeautifulSoup``::
 
3033
 
 
3034
 soup = BeautifulSoup(markup, "xml")
 
3035
 
 
3036
Вам также нужно будет :ref:`установить lxml <parser-installation>`.
 
3037
 
 
3038
Другие проблемы с парсерами
 
3039
---------------------------
 
3040
 
 
3041
* Если ваш скрипт работает на одном компьютере, но не работает на другом, или работает в одной
 
3042
  виртуальной среде, но не в другой, или работает вне виртуальной
 
3043
  среды, но не внутри нее, это, вероятно, потому что в двух
 
3044
  средах разные библиотеки парсеров. Например,
 
3045
  вы могли разработать скрипт на компьютере с установленным lxml,
 
3046
  а затем попытались запустить его на компьютере, где установлен только
 
3047
  html5lib. Читайте в разделе `Различия между парсерами`_, почему это
 
3048
  важно, и исправляйте проблемы, указывая конкретную библиотеку парсера
 
3049
  в конструкторе ``BeautifulSoup``.
 
3050
 
 
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>`.
 
3057
 
 
3058
.. _misc:
 
3059
 
 
3060
Прочие ошибки
 
3061
-------------
 
3062
 
 
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")``.
 
3074
 
 
3075
* ``KeyError: [attr]`` — вызывается при обращении к ``tag['attr']``, когда
 
3076
  в искомом теге не определен атрибут ``attr``. Наиболее
 
3077
  типичны ошибки ``KeyError: 'href'`` и ``KeyError:
 
3078
  'class'``. Используйте ``tag.get('attr')``, если вы не уверены, что ``attr``
 
3079
  определен — так же, как если бы вы работали со словарем Python.
 
3080
 
 
3081
* ``AttributeError: 'ResultSet' object has no attribute 'foo'`` — это
 
3082
  обычно происходит тогда, когда вы ожидаете, что ``find_all()`` вернет
 
3083
  один тег или строку. Но ``find_all()`` возвращает *список* тегов
 
3084
  и строк в объекте ``ResultSet``. Вам нужно перебрать
 
3085
  список и поискать ``.foo`` в каждом из элементов. Или, если вам действительно
 
3086
  нужен только один результат, используйте ``find()`` вместо
 
3087
  ``find_all()``.
 
3088
 
 
3089
* ``AttributeError: 'NoneType' object has no attribute 'foo'`` — это
 
3090
  обычно происходит, когда вы вызываете ``find()`` и затем пытаетесь
 
3091
  получить доступ к атрибуту ``.foo``. Но в вашем случае
 
3092
  ``find()`` не нашел ничего, поэтому вернул ``None`` вместо
 
3093
  того, чтобы вернуть тег или строку. Вам нужно выяснить, почему
 
3094
  ``find()`` ничего не возвращает.
 
3095
 
 
3096
Повышение производительности
 
3097
----------------------------
 
3098
 
 
3099
Beautiful Soup никогда не будет таким же быстрым, как парсеры, на основе которых он
 
3100
работает. Если время отклика критично, если вы платите за компьютерное время
 
3101
по часам, или если есть какая-то другая причина, почему компьютерное время
 
3102
важнее  программистского, стоит забыть о Beautiful Soup
 
3103
и работать непосредственно с `lxml <http://lxml.de/>`_.
 
3104
 
 
3105
Тем не менее, есть вещи, которые вы можете сделать, чтобы ускорить Beautiful Soup. Если
 
3106
вы не используете lxml в качестве основного парсера, самое время
 
3107
:ref:`начать <parser-installation>`. Beautiful Soup разбирает документы
 
3108
значительно быстрее с lxml, чем с html.parser или html5lib.
 
3109
 
 
3110
Вы можете значительно ускорить распознавание кодировок, установив
 
3111
библиотеку `cchardet <http://pypi.python.org/pypi/cchardet/>`_.
 
3112
 
 
3113
`Разбор части документа`_ не сэкономит много времени в процессе разбора,
 
3114
но может сэкономить много памяти, что сделает
 
3115
`поиск` по документу намного быстрее.
 
3116
 
 
3117
 
 
3118
Beautiful Soup 3
 
3119
================
 
3120
 
 
3121
Beautiful Soup 3 — предыдущая версия, и она больше
 
3122
активно не развивается. На текущий момент Beautiful Soup 3 поставляется со всеми основными
 
3123
дистрибутивами Linux:
 
3124
 
 
3125
:kbd:`$ apt-get install python-beautifulsoup`
 
3126
 
 
3127
Он также публикуется через PyPi как ``BeautifulSoup``:
 
3128
 
 
3129
:kbd:`$ easy_install BeautifulSoup`
 
3130
 
 
3131
:kbd:`$ pip install BeautifulSoup`
 
3132
 
 
3133
Вы можете скачать `tar-архив Beautiful Soup 3.2.0
 
3134
<http://www.crummy.com/software/BeautifulSoup/bs3/download/3.x/BeautifulSoup-3.2.0.tar.gz>`_.
 
3135
 
 
3136
Если вы запустили ``easy_install beautifulsoup`` или ``easy_install
 
3137
BeautifulSoup``, но ваш код не работает, значит, вы ошибочно установили Beautiful
 
3138
Soup 3. Вам нужно запустить ``easy_install beautifulsoup4``.
 
3139
 
 
3140
Архивная документация для Beautiful Soup 3 доступна `онлайн
 
3141
<http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html>`_.
 
3142
 
 
3143
Перенос кода на BS4
 
3144
-------------------
 
3145
 
 
3146
Большая часть кода, написанного для Beautiful Soup 3, будет работать и в Beautiful
 
3147
Soup 4 с одной простой заменой. Все, что вам нужно сделать, это изменить
 
3148
имя пакета c ``BeautifulSoup`` на ``bs4``. Так что это::
 
3149
 
 
3150
  from BeautifulSoup import BeautifulSoup
 
3151
 
 
3152
становится этим::
 
3153
 
 
3154
  from bs4 import BeautifulSoup
 
3155
 
 
3156
* Если выводится сообщение ``ImportError`` "No module named BeautifulSoup", ваша
 
3157
  проблема в том, что вы пытаетесь запустить код Beautiful Soup 3, в то время как
 
3158
  у вас установлен Beautiful Soup 4.
 
3159
 
 
3160
* Если выводится сообщение ``ImportError`` "No module named bs4", ваша проблема
 
3161
  в том, что вы пытаетесь запустить код Beautiful Soup 4, в то время как
 
3162
  у вас установлен Beautiful Soup 3.
 
3163
 
 
3164
Хотя BS4 в основном обратно совместим с BS3, большинство
 
3165
методов BS3 устарели и получили новые имена, чтобы `соответствовать PEP 8
 
3166
<http://www.python.org/dev/peps/pep-0008/>`_. Некоторые
 
3167
из переименований и изменений нарушают обратную совместимость.
 
3168
 
 
3169
Вот что нужно знать, чтобы перейти с BS3 на BS4:
 
3170
 
 
3171
Вам нужен парсер
 
3172
^^^^^^^^^^^^^^^^
 
3173
 
 
3174
Beautiful Soup 3 использовал модуль Python ``SGMLParser``, который теперь
 
3175
устарел и был удален в Python 3.0. Beautiful Soup 4 по умолчанию использует
 
3176
``html.parser``, но вы можете подключить lxml или html5lib
 
3177
вместо него. Вы найдете таблицу сравнения парсеров в разделе `Установка парсера`_.
 
3178
 
 
3179
Поскольку ``html.parser`` — это не то же, что ``SGMLParser``, вы
 
3180
можете обнаружить, что Beautiful Soup 4 дает другое дерево разбора, чем
 
3181
Beautiful Soup 3. Если вы замените html.parser
 
3182
на lxml или html5lib, может оказаться, что дерево разбора опять
 
3183
изменилось. Если такое случится, вам придется обновить код,
 
3184
чтобы разобраться с новым деревом.
 
3185
 
 
3186
Имена методов
 
3187
^^^^^^^^^^^^^
 
3188
 
 
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``
 
3206
 
 
3207
Некоторые аргументы конструктора Beautiful Soup были переименованы по
 
3208
той же причине:
 
3209
 
 
3210
* ``BeautifulSoup(parseOnlyThese=...)`` -> ``BeautifulSoup(parse_only=...)``
 
3211
* ``BeautifulSoup(fromEncoding=...)`` -> ``BeautifulSoup(from_encoding=...)``
 
3212
 
 
3213
Я переименовал один метод для совместимости с Python 3:
 
3214
 
 
3215
* ``Tag.has_key()`` -> ``Tag.has_attr()``
 
3216
 
 
3217
Я переименовал один атрибут, чтобы использовать более точную терминологию:
 
3218
 
 
3219
* ``Tag.isSelfClosing`` -> ``Tag.is_empty_element``
 
3220
 
 
3221
Я переименовал три атрибута, чтобы избежать использования зарезервированных слов
 
3222
в Python. В отличие от других, эти изменения *не являются обратно
 
3223
совместимыми*. Если вы использовали эти атрибуты в BS3, ваш код не сработает
 
3224
на BS4, пока вы их не измените.
 
3225
 
 
3226
* ``UnicodeDammit.unicode`` -> ``UnicodeDammit.unicode_markup``
 
3227
* ``Tag.next`` -> ``Tag.next_element``
 
3228
* ``Tag.previous`` -> ``Tag.previous_element``
 
3229
 
 
3230
Генераторы
 
3231
^^^^^^^^^^
 
3232
 
 
3233
Я дал генераторам PEP 8-совместимые имена и преобразовал их в
 
3234
свойства:
 
3235
 
 
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``
 
3243
 
 
3244
Так что вместо этого::
 
3245
 
 
3246
 for parent in tag.parentGenerator():
 
3247
     ...
 
3248
 
 
3249
Вы можете написать это::
 
3250
 
 
3251
 for parent in tag.parents:
 
3252
     ...
 
3253
 
 
3254
(Хотя старый код тоже будет работать.)
 
3255
 
 
3256
Некоторые генераторы выдавали ``None`` после их завершения и
 
3257
останавливались. Это была ошибка. Теперь генераторы просто останавливаются.
 
3258
 
 
3259
Добавились два генератора: :ref:`.strings и
 
3260
.stripped_strings <string-generators>`. 
 
3261
 
 
3262
``.strings`` выдает
 
3263
объекты NavigableString, а ``.stripped_strings`` выдает строки Python,
 
3264
у которых удалены пробелы.
 
3265
 
 
3266
XML
 
3267
^^^
 
3268
 
 
3269
Больше нет класса ``BeautifulStoneSoup`` для разбора XML. Чтобы
 
3270
разобрать XML, нужно передать "xml" в качестве второго аргумента
 
3271
в конструктор ``BeautifulSoup``. По той же причине
 
3272
конструктор ``BeautifulSoup`` больше не распознает
 
3273
аргумент  ``isHTML``.
 
3274
 
 
3275
Улучшена обработка пустых тегов
 
3276
XML. Ранее при разборе XML нужно было явно указать,
 
3277
какие теги считать пустыми элементами. Аргумент ``SelfClosingTags``
 
3278
больше не распознается. Вместо этого
 
3279
Beautiful Soup считает пустым элементом любой тег без содержимого. Если
 
3280
вы добавляете в тег дочерний элемент, тег больше не считается
 
3281
пустым элементом.
 
3282
 
 
3283
Мнемоники
 
3284
^^^^^^^^^
 
3285
 
 
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
служили для настройки функции, которой больше нет (преобразование отдельных мнемоник в
 
3295
символы Unicode).
 
3296
 
 
3297
Если вы хотите на выходе преобразовать символы Unicode обратно в мнемоники HTML,
 
3298
а не превращать Unicode в символы UTF-8, вам нужно
 
3299
использовать :ref:`средства форматирования вывода <output_formatters>`.
 
3300
 
 
3301
Прочее
 
3302
^^^^^^
 
3303
 
 
3304
:ref:`Tag.string <.string>` теперь работает рекурсивно. Если тег А
 
3305
содержит только тег B и ничего больше, тогда значение A.string будет таким же, как
 
3306
B.string. (Раньше это был None.)
 
3307
 
 
3308
`Многозначные атрибуты`_, такие как ``class``, теперь в качестве значений имеют списки строк,
 
3309
а не строки. Это может повлиять на поиск
 
3310
по классу CSS.
 
3311
 
 
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 игнорировал аргументы, специфичные для тегов, и искал
 
3318
строки.
 
3319
 
 
3320
Конструктор ``BeautifulSoup`` больше не распознает
 
3321
аргумент `markupMassage`. Теперь это задача парсера —
 
3322
обрабатывать разметку правильно.
 
3323
 
 
3324
Редко используемые альтернативные классы парсеров, такие как
 
3325
``ICantBelieveItsBeautifulSoup`` и ``BeautifulSOAP``,
 
3326
удалены. Теперь парсер решает, что делать с неоднозначной
 
3327
разметкой.
 
3328
 
 
3329
Метод ``prettify()`` теперь возвращает строку Unicode, а не байтовую строку.
 
3330
 
 
3331
Перевод документации
 
3332
====================
 
3333
 
 
3334
Переводы документации Beautiful Soup очень
 
3335
приветствуются. Перевод должен быть лицензирован по лицензии MIT,
 
3336
так же, как сам Beautiful Soup и англоязычная документация к нему.
 
3337
 
 
3338
Есть два способа передать ваш перевод:
 
3339
 
 
3340
1. Создайте ветку репозитория Beautiful Soup, добавьте свой
 
3341
   перевод и предложите слияние с основной веткой — так же,
 
3342
   как вы предложили бы изменения исходного кода.
 
3343
2. Отправьте `в дискуссионную группу Beautiful Soup <https://groups.google.com/forum/?fromgroups#!forum/beautifulsoup>`_
 
3344
   сообщение со ссылкой на
 
3345
   ваш перевод, или приложите перевод к сообщению.
 
3346
 
 
3347
Используйте существующие переводы документации на китайский или португальский в качестве образца. В
 
3348
частности, переводите исходный файл ``doc/source/index.rst`` вместо
 
3349
того, чтобы переводить HTML-версию документации. Это позволяет
 
3350
публиковать документацию в разных форматах, не
 
3351
только в HTML.
 
3352
 
 
3353
Об этом переводе
 
3354
----------------
 
3355
 
 
3356
Перевод на русский язык: `authoress <mailto:geekwriter@yandex.ru>`_
 
3357
 
 
3358
Дата перевода: февраль 2020
 
3359
 
 
3360
Перевод выполнен с `оригинала на английском языке <https://www.crummy.com/software/BeautifulSoup/bs4/doc/>`_.