Beautiful Soup: Поиск всех тегов и атрибутов (полное руководство)

Что такое BeautifulSoup и зачем он нужен

BeautifulSoup — это библиотека Python, предназначенная для парсинга HTML и XML документов. Она превращает сложный HTML-код в дерево объектов Python, по которому легко перемещаться и извлекать нужную информацию. В контексте интернет-маркетинга и веб-разработки BeautifulSoup незаменим для скрейпинга данных с веб-сайтов, анализа структуры страниц, автоматизации тестирования и многого другого. Например, можно автоматически извлекать цены на товары у конкурентов, мониторить изменения в выдаче поисковой системы или собирать информацию для анализа тональности отзывов.

Установка и настройка BeautifulSoup

Установить BeautifulSoup можно с помощью pip:

pip install beautifulsoup4

Кроме BeautifulSoup, потребуется парсер, например, lxml или html.parser. lxml обычно быстрее и предоставляет больше возможностей, но html.parser входит в стандартную библиотеку Python и не требует дополнительной установки. Установим lxml:

pip install lxml

Разбор HTML и XML документов

Для начала работы необходимо импортировать библиотеку и создать объект BeautifulSoup:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml') # Или 'html.parser'

print(soup.prettify())

Поиск тегов в BeautifulSoup

Основные методы поиска тегов: find() и find_all()

BeautifulSoup предоставляет два основных метода для поиска тегов: find() и find_all(). find() возвращает первый найденный тег, соответствующий критериям, а find_all() возвращает список всех таких тегов.

Поиск по имени тега

Самый простой способ поиска — указать имя тега:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

h1_tag = soup.find('h1')
print(h1_tag)

a_tags = soup.find_all('a')
print(a_tags)

Поиск нескольких тегов одновременно

Можно передать список тегов для поиска:

from bs4 import BeautifulSoup
from typing import List

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

tags: List = soup.find_all(['h1', 'a'])
print(tags)

Ограничение количества результатов поиска

Аргумент limit в find_all() позволяет ограничить количество возвращаемых результатов:

from bs4 import BeautifulSoup
from typing import List

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tags: List = soup.find_all('a', limit=1)
print(a_tags)

Навигация по дереву DOM: parent, children, nextsibling, previoussibling

После нахождения тега можно перемещаться по дереву DOM:

  • parent: родительский тег
  • children: дочерние теги (возвращает генератор)
  • next_sibling: следующий соседний тег
  • previous_sibling: предыдущий соседний тег
from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tag = soup.find('a')

if a_tag:
    print(f"Родитель: {a_tag.parent.name}")

Поиск тегов по атрибутам

Поиск по одному атрибуту

Можно искать теги по значению атрибута:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

h1_tag = soup.find('h1', id='main-title')
print(h1_tag)

Поиск по нескольким атрибутам (использование словаря)

Для поиска по нескольким атрибутам передается словарь:

from bs4 import BeautifulSoup
from typing import Dict

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

attributes: Dict[str, str] = {'id': 'main-title', 'class': 'title'}

h1_tag = soup.find('h1', attrs=attributes)
print(h1_tag)

Поиск по частичному совпадению атрибута (использование регулярных выражений)

Для поиска по частичному совпадению атрибута используется модуль re:

import re
from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com" data-track="link-1">Ссылка</a>
        <a href="https://example.org" data-track="link-2">Другая ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

links = soup.find_all('a', attrs={'data-track': re.compile(r'link-\d+')})
print(links)

Поиск по значению атрибута, содержащему пробелы

Если значение атрибута содержит пробелы (например, class), то поиск осуществляется так же, как и по одному атрибуту:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p class="paragraph main">Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

p_tag = soup.find('p', class_='paragraph main')
print(p_tag)

Работа с атрибутами тегов

Получение значения атрибута тега

Значение атрибута можно получить как элемент словаря или используя метод get():

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tag = soup.find('a')

if a_tag:
    print(a_tag['href'])
    print(a_tag.get('href'))
Реклама

Получение всех атрибутов тега в виде словаря

Атрибуты тега доступны через свойство attrs:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tag = soup.find('a')

if a_tag:
    print(a_tag.attrs)

Проверка наличия атрибута у тега

Проверить наличие атрибута можно с помощью оператора in:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tag = soup.find('a')

if a_tag:
    if 'href' in a_tag.attrs:
        print("Атрибут href существует")

Изменение и удаление атрибутов тега

Атрибуты можно изменять и удалять:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

a_tag = soup.find('a')

if a_tag:
    a_tag['href'] = 'https://new-example.com'
    del a_tag['href']
    print(a_tag)

Фильтры поиска: string, limit, recursive

Поиск по текстовому содержимому тега (string)

Аргумент string позволяет искать теги, содержащие определенный текст:

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

p_tag = soup.find('p', string='Это параграф.')
print(p_tag)

Ограничение глубины поиска (recursive)

Аргумент recursive указывает, нужно ли искать во всех дочерних тегах (по умолчанию True). Если установить False, поиск будет производиться только среди прямых потомков.

Оптимизация поиска с помощью limit

Как упоминалось ранее, limit позволяет ограничить количество возвращаемых результатов, что может повысить производительность при работе с большими документами.

Использование CSS-селекторов для поиска

Метод select() для поиска по CSS-селекторам

BeautifulSoup поддерживает поиск элементов с использованием CSS-селекторов с помощью метода select().

Примеры использования различных CSS-селекторов (id, class, атрибуты)

  • #id: поиск по id
  • .class: поиск по class
  • [attribute]: поиск по наличию атрибута
  • [attribute=value]: поиск по значению атрибута
from bs4 import BeautifulSoup
from typing import List

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p class="paragraph main">Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

h1_tags: List = soup.select('#main-title')
p_tags: List = soup.select('.paragraph')
a_tags: List = soup.select('a[href]')

print(h1_tags)
print(p_tags)
print(a_tags)

Комбинирование CSS-селекторов

Можно комбинировать CSS-селекторы для более точного поиска, используя конструкции вроде div > p (прямые потомки) или div p (все потомки):

from bs4 import BeautifulSoup
from typing import List

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1 id="main-title">Заголовок</h1>
    <div class="content">
        <p class="paragraph main">Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

p_tags: List = soup.select('div > p')
print(p_tags)

Практические примеры

Извлечение всех ссылок с веб-страницы

import requests
from bs4 import BeautifulSoup
from typing import List

url: str = 'https://example.com'
response = requests.get(url)
soup: BeautifulSoup = BeautifulSoup(response.text, 'lxml')

links: List = [a['href'] for a in soup.find_all('a', href=True)]
print(links)

Получение всех изображений с определенным атрибутом alt

import requests
from bs4 import BeautifulSoup
from typing import List

url: str = 'https://example.com'
response = requests.get(url)
soup: BeautifulSoup = BeautifulSoup(response.text, 'lxml')

images: List = [img['src'] for img in soup.find_all('img', alt='example image', src=True)]
print(images)

Парсинг таблицы и извлечение данных

import requests
from bs4 import BeautifulSoup
from typing import List, Dict

url: str = 'https://example.com/table'
response = requests.get(url)
soup: BeautifulSoup = BeautifulSoup(response.text, 'lxml')

# Предположим, что таблица имеет структуру <table><thead><tr><th>...</th></tr></thead><tbody><tr><td>...</td></tr></tbody></table>

table = soup.find('table')
if table:
    headers: List = [th.text.strip() for th in table.find_all('th')]
    data: List[Dict] = []
    for row in table.find_all('tr')[1:]:
        values: List = [td.text.strip() for td in row.find_all('td')]
        row_data: Dict = dict(zip(headers, values))
        data.append(row_data)
    print(data)

Обработка ошибок и исключений

Обработка случая, когда тег или атрибут не найден

Важно обрабатывать ситуации, когда тег или атрибут не найден. Метод find() вернет None в таком случае. Необходимо проверять результат на None перед дальнейшей работой.

from bs4 import BeautifulSoup

html_doc: str = '''
<!DOCTYPE html>
<html>
<head>
    <title>Пример страницы</title>
</head>
<body>
    <h1>Заголовок</h1>
    <div class="content">
        <p>Это параграф.</p>
        <a href="https://example.com">Ссылка</a>
    </div>
</body>
</html>
'''

soup: BeautifulSoup = BeautifulSoup(html_doc, 'lxml')

h1_tag = soup.find('h1', id='non-existent-id')

if h1_tag is None:
    print("Тег h1 с id 'non-existent-id' не найден.")
else:
    print(h1_tag.text)

Работа с некорректным HTML

BeautifulSoup довольно устойчив к некорректному HTML, но в некоторых случаях может потребоваться предварительная очистка документа с помощью других библиотек.

Заключение

Краткое повторение основных моментов

В этой статье мы рассмотрели основные методы поиска тегов и атрибутов в BeautifulSoup, включая find(), find_all(), использование CSS-селекторов, работу с атрибутами тегов и обработку ошибок. BeautifulSoup — мощный инструмент для парсинга HTML и XML, который значительно упрощает извлечение данных из веб-страниц.

Рекомендации по дальнейшему изучению BeautifulSoup

Для дальнейшего изучения рекомендуется ознакомиться с официальной документацией BeautifulSoup и попрактиковаться в решении реальных задач парсинга веб-страниц. Также полезно изучить регулярные выражения для более гибкого поиска по атрибутам и текстовому содержимому.


Добавить комментарий