Что такое 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 и попрактиковаться в решении реальных задач парсинга веб-страниц. Также полезно изучить регулярные выражения для более гибкого поиска по атрибутам и текстовому содержимому.