Перейти к основному содержимому

BeautifulSoup — парсинг HTML

Разработчику

BeautifulSoup (пакет beautifulsoup4, импорт from bs4 import BeautifulSoup) превращает HTML-страницу в дерево объектов, по которому удобно искать теги, классы и атрибуты. Это не загрузчик сайтов: сначала текст страницы получают через requests, httpx или файл на диске.

Практически это инструмент "достать нужные поля со страницы и привести их к структуре". Он особенно полезен, когда API нет, а данные публичны и доступны в HTML.

Путеводитель по инструментам: Инструменты и среды. Раздел про скрапинг в контексте файлов и сети: глава 31.


Установка

pip install beautifulsoup4 requests
# опционально — быстрый парсер —
pip install lxml

Команды ставят набор для HTML-парсинга:

  • beautifulsoup4 даёт API для обхода DOM-дерева;
  • requests загружает HTML по HTTP;
  • lxml подключает более быстрый движок разбора для крупных страниц.
ПакетРоль
beautifulsoup4API для обхода DOM
lxmlПарсер lxml (быстрее на больших страницах)
html5libТолерантный разбор "грязного" HTML
requestsHTTP GET/POST

Минимальный пример


import requests

from bs4 import BeautifulSoup

url = "https://httpbin.org/html"
html = requests.get(url, timeout=10).text

soup = BeautifulSoup(html, "html.parser")
title = soup.find("h1")
print(title.get_text(strip=True) if title else "Заголовок не найден")

Это базовый поток "получить страницу и извлечь поле":

  • requests.get(...).text скачивает HTML как строку;
  • BeautifulSoup(html, "html.parser") строит дерево элементов;
  • find("h1") ищет первый заголовок уровня h1;
  • get_text(strip=True) очищает пробелы и возвращает текст узла.

Цепочка: запрос → текст → soup → find → данные.


Выбор парсера

ПарсерПлюсыМинусы
html.parserВстроен в PythonМедленнее на огромных документах
lxmlСкоростьОтдельная зависимость
html5libИсправляет кривую вёрсткуМедленный
soup = BeautifulSoup(html, "lxml")

Здесь меняется только парсер:

  • BeautifulSoup API остаётся прежним;
  • "lxml" обычно даёт более высокую скорость обработки на больших документах.

Поиск элементов

# один элемент
first_link = soup.find("a", href=True)

# все совпадения
all_p = soup.find_all("p", class_="news-item")

# CSS-селекторы (удобно для сложной вёрстки)
items = soup.select("div.catalog > article h2 a")

Фрагмент показывает три способа поиска элементов:

  • find(...) возвращает одно первое совпадение;
  • find_all(...) собирает список всех найденных узлов;
  • select(...) применяет CSS-селектор, что удобно для сложной вложенности.
МетодКогда использовать
find / find_allТег + class_, атрибуты
select / select_oneCSS как в браузере
.parent, .next_siblingОбход дерева

Извлечение текста и атрибутов:

text = element.get_text(strip=True, separator=" ")
href = element.get("href") # None, если атрибута нет

Этот код извлекает данные из уже найденного тега:

  • get_text(...) собирает текст дочерних узлов в одну строку;
  • strip=True убирает лишние пробелы по краям;
  • element.get("href") безопасно читает атрибут и возвращает None, если его нет.

Типовой сценарий — каталог

Код ITЗагрузка примера кода…

В примере собран полный мини-пайплайн скрапинга:

  • requests.get(...) получает HTML и raise_for_status() прерывает сценарий при HTTP-ошибке;
  • BeautifulSoup(resp.text, "lxml") парсит страницу;
  • цикл по soup.select(".product-card") извлекает карточки товаров;
  • проверки if name and price отбрасывают неполные записи;
  • csv.DictWriter сохраняет результат в табличный файл для дальнейшей обработки.

Селекторы .product-card нужно подставить под реальную вёрстку (инструменты разработчика в браузере).


Ограничения и этика

  • Уважайте robots.txt и условия сайта; учебные задания — на httpbin.org, своих страницах или API.
  • Статический HTML не содержит контент, подгружаемый только JavaScript; тогда нужен браузерный движок (Playwright, Selenium) — см. главу 31.
  • Частые запросы без паузы нагружают сервер — добавляйте time.sleep и кэш.

Для больших краулеров смотрите Scrapy в экосистеме Python.


Частые ошибки новичков

  1. Нет timeout в requests.get, и скрипт может зависнуть на неопределённое время.
  2. Жёсткие CSS-селекторы без проверки None, из-за чего парсер падает при изменении верстки.
  3. Игнорирование кодировок и "грязного" текста (переносы, лишние пробелы, спецсимволы).
  4. Отсутствие пауз между запросами и адекватного User-Agent.

Мини-шаблон безопасного извлечения:

node = soup.select_one(".title")
title = node.get_text(strip=True) if node else ""

Это шаблон безопасного чтения поля:

  • select_one(...) может вернуть None, если элемент отсутствует;
  • тернарное выражение защищает от AttributeError и даёт пустую строку как fallback;
  • подход полезен для "хрупкой" вёрстки, которая часто меняется.

Что делать после парсинга

Типичный следующий шаг — нормализация и сохранение в таблицу:

  • собрать список словарей;
  • загрузить в pandas.DataFrame;
  • очистить дубликаты и пустые значения;
  • сохранить в CSV/Parquet.

Для этого удобно перейти к Pandas — объединение таблиц, своды и временные ряды.


См. также


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.