Парсинг сайтов с BeautifulSoup в Python

10 сентября 2026
Автор

Антон Ларичев

Что такое BeautifulSoup

BeautifulSoup — библиотека Python для разбора HTML и XML документов. Она строит дерево объектов из исходного кода страницы и предоставляет удобный API для поиска и извлечения данных. Библиотека не скачивает страницы сама — для этого используют requests или httpx. BeautifulSoup занимается только разбором уже полученного HTML.

Парсинг сайтов применяется для сбора данных о ценах, мониторинга изменений на страницах, агрегации новостей, автоматизации работы с веб-контентом.

Курс по теме

Основы Python — курс

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Установка

Для работы нужны две библиотеки: requests для скачивания страниц и beautifulsoup4 для их разбора.

pip install requests beautifulsoup4

BeautifulSoup использует внешние парсеры. Самый быстрый и популярный — lxml:

pip install lxml

Если lxml недоступен, можно использовать встроенный html.parser — он медленнее, но не требует дополнительной установки.

Первый парсер

Разберём базовый пример: скачиваем страницу и создаём объект BeautifulSoup.

import requests
from bs4 import BeautifulSoup

url = "https://example.com"
response = requests.get(url)

soup = BeautifulSoup(response.text, "lxml")
print(soup.title.text)

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

Если парсите локальный HTML без запросов к сети:

html = """
<html>
  <body>
    <h1>Заголовок</h1>
    <p class="intro">Первый абзац</p>
    <p class="content">Второй абзац</p>
  </body>
</html>
"""

soup = BeautifulSoup(html, "lxml")

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

find и find_all

find() возвращает первый найденный элемент, find_all() — список всех совпадений.

html = """
<ul>
  <li class="item">Первый</li>
  <li class="item">Второй</li>
  <li class="item active">Третий</li>
</ul>
"""

soup = BeautifulSoup(html, "lxml")

first_item = soup.find("li")
print(first_item.text)  # Первый

all_items = soup.find_all("li")
for item in all_items:
    print(item.text)

Поиск по атрибуту class:

active = soup.find("li", class_="active")
print(active.text)  # Третий

Обратите внимание: используется class_ с подчёркиванием, потому что class — зарезервированное слово Python.

Поиск по нескольким атрибутам сразу:

result = soup.find("li", {"class": "item active"})

Поиск по CSS-селекторам

Метод select() принимает CSS-селекторы и возвращает список элементов. select_one() возвращает первый.

html = """
<div id="catalog">
  <div class="product">
    <h2 class="product-title">Товар 1</h2>
    <span class="price">1500 руб.</span>
  </div>
  <div class="product">
    <h2 class="product-title">Товар 2</h2>
    <span class="price">2300 руб.</span>
  </div>
</div>
"""

soup = BeautifulSoup(html, "lxml")

titles = soup.select(".product-title")
for title in titles:
    print(title.text)

first_price = soup.select_one("#catalog .price")
print(first_price.text)  # 1500 руб.

CSS-селекторы часто удобнее для сложных запросов, особенно если вы привыкли к JavaScript или jQuery.

Извлечение данных

Текст элемента

html = "<p>Обычный <b>жирный</b> текст</p>"
soup = BeautifulSoup(html, "lxml")

p = soup.find("p")
print(p.text)          # Обычный жирный текст
print(p.get_text())    # то же самое
print(p.string)        # None — есть дочерние теги
print(p.b.string)      # жирный

text и get_text() возвращают весь текст включая дочерние элементы. string работает только если у элемента ровно один дочерний текстовый узел.

Для очистки лишних пробелов используйте параметр strip:

print(p.get_text(strip=True))

Атрибуты

Доступ к атрибутам как к словарю:

html = '<a href="/page" class="link" data-id="42">Ссылка</a>'
soup = BeautifulSoup(html, "lxml")

a = soup.find("a")
print(a["href"])              # /page
print(a.get("data-id"))      # 42
print(a.get("missing", ""))  # — безопасно, без KeyError
print(a.attrs)               # {'href': '/page', 'class': ['link'], 'data-id': '42'}

Атрибут class всегда возвращается как список, даже если класс один.

Навигация по дереву

BeautifulSoup строит дерево тегов. Можно перемещаться по нему вверх, вниз и в стороны.

html = """
<div class="card">
  <h2>Заголовок</h2>
  <p>Первый абзац</p>
  <p>Второй абзац</p>
  <footer>Подвал</footer>
</div>
"""

soup = BeautifulSoup(html, "lxml")
card = soup.find("div", class_="card")

# Дочерние элементы
print(card.h2.text)               # Заголовок
print(list(card.children))       # все дочерние узлы включая пробелы

# Только теги, без текстовых узлов
for child in card.children:
    if child.name:
        print(child.name, child.text)

# Родитель
p = soup.find("p")
print(p.parent["class"])  # ['card']

# Следующий сосед
first_p = soup.find("p")
next_p = first_p.find_next_sibling("p")
print(next_p.text)  # Второй абзац

# Предыдущий сосед
print(next_p.find_previous_sibling("p").text)  # Первый абзац

Практический пример: сбор новостей

Соберём заголовки и ссылки с гипотетической новостной страницы.

import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass
from typing import List

@dataclass
class Article:
    title: str
    url: str
    summary: str


def parse_articles(html: str, base_url: str) -> List[Article]:
    soup = BeautifulSoup(html, "lxml")
    articles = []

    for card in soup.select(".news-card"):
        title_tag = card.select_one(".news-title a")
        summary_tag = card.select_one(".news-summary")

        if not title_tag:
            continue

        href = title_tag.get("href", "")
        url = href if href.startswith("http") else base_url + href

        articles.append(Article(
            title=title_tag.get_text(strip=True),
            url=url,
            summary=summary_tag.get_text(strip=True) if summary_tag else "",
        ))

    return articles


def fetch_articles(url: str) -> List[Article]:
    headers = {"User-Agent": "Mozilla/5.0"}
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
    return parse_articles(response.text, url)

Обратите внимание на несколько деталей:

  • Заголовок User-Agent помогает избежать блокировки, когда сервер фильтрует запросы без него
  • timeout защищает от зависания при недоступном сервере
  • raise_for_status() бросает исключение при HTTP-ошибках (4xx, 5xx)
  • Проверяем абсолютность ссылки перед добавлением базового URL

Практический пример: парсинг таблицы

Таблицы встречаются на сайтах с финансовыми данными, расписаниями, каталогами.

from bs4 import BeautifulSoup

html = """
<table class="prices">
  <thead>
    <tr>
      <th>Товар</th>
      <th>Цена</th>
      <th>Наличие</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Ноутбук</td>
      <td>89000</td>
      <td>Да</td>
    </tr>
    <tr>
      <td>Монитор</td>
      <td>32000</td>
      <td>Нет</td>
    </tr>
  </tbody>
</table>
"""

soup = BeautifulSoup(html, "lxml")
table = soup.find("table", class_="prices")

# Читаем заголовки
headers = [th.get_text(strip=True) for th in table.select("thead th")]
print(headers)  # ['Товар', 'Цена', 'Наличие']

# Читаем строки
rows = []
for tr in table.select("tbody tr"):
    cells = [td.get_text(strip=True) for td in tr.find_all("td")]
    rows.append(dict(zip(headers, cells)))

for row in rows:
    print(row)
# {'Товар': 'Ноутбук', 'Цена': '89000', 'Наличие': 'Да'}
# {'Товар': 'Монитор', 'Цена': '32000', 'Наличие': 'Нет'}

Обработка ошибок

При реальном парсинге структура страницы непредсказуема. Всегда проверяйте, что элемент найден.

from bs4 import BeautifulSoup

def safe_parse(html: str) -> dict:
    soup = BeautifulSoup(html, "lxml")

    title_tag = soup.find("h1", class_="product-title")
    price_tag = soup.find("span", class_="price")
    img_tag = soup.find("img", class_="product-image")

    return {
        "title": title_tag.get_text(strip=True) if title_tag else None,
        "price": price_tag.get_text(strip=True) if price_tag else None,
        "image": img_tag.get("src") if img_tag else None,
    }

Используйте паттерн tag.get_text() if tag else None вместо tag.get_text() — это защищает от AttributeError когда элемент отсутствует на странице.

Регулярные выражения в поиске

BeautifulSoup принимает скомпилированные регулярные выражения в аргументах поиска.

import re
from bs4 import BeautifulSoup

html = """
<div class="price-usd">100$</div>
<div class="price-rub">9000 руб.</div>
<div class="description">Описание</div>
"""

soup = BeautifulSoup(html, "lxml")

# Найти все div, у которых class начинается с "price"
prices = soup.find_all("div", class_=re.compile(r"^price"))
for p in prices:
    print(p.text)

# Найти теги, в тексте которых есть число
tags_with_numbers = soup.find_all(string=re.compile(r"\d+"))
print(tags_with_numbers)  # ['100$', '9000 руб.']

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

Параметр recursive=False ищет только среди прямых дочерних элементов, не уходя вглубь дерева.

html = """
<div class="outer">
  <div class="inner">
    <p>Вложенный</p>
  </div>
  <p>Прямой потомок</p>
</div>
"""

soup = BeautifulSoup(html, "lxml")
outer = soup.find("div", class_="outer")

# Найдёт оба абзаца
all_p = outer.find_all("p")
print(len(all_p))  # 2

# Найдёт только прямого потомка
direct_p = outer.find_all("p", recursive=False)
print(len(direct_p))  # 1
print(direct_p[0].text)  # Прямой потомок

Типичные проблемы и их решения

Сайт не отдаёт данные при запросе через requests. Многие сайты проверяют заголовки запроса. Добавьте реалистичный User-Agent и при необходимости Accept, Accept-Language.

Данные загружаются через JavaScript. BeautifulSoup разбирает только статический HTML. Если нужные данные подгружаются через JS, изучите сетевые запросы во вкладке Network в браузере — часто есть JSON API, который проще запросить напрямую. Для полноценного рендеринга JS используют Playwright или Selenium.

Кодировка ломается. Укажите кодировку явно:

response = requests.get(url)
response.encoding = "utf-8"
soup = BeautifulSoup(response.text, "lxml")

Парсинг работает медленно на большом количестве страниц. Используйте lxml вместо html.parser, делайте конкурентные запросы через asyncio + httpx, добавляйте паузы между запросами чтобы не перегружать сервер.

Этика и легальность парсинга

Перед парсингом изучите файл robots.txt сайта — он описывает, какие разделы нельзя сканировать. Не отправляйте запросы слишком часто: добавляйте паузы в 1–2 секунды между запросами. Уточните условия использования сайта — некоторые прямо запрещают автоматический сбор данных.

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


Чтобы освоить Python с нуля и научиться работать с внешними библиотеками, парсингом и автоматизацией — записывайтесь на курс: Python-разработчик на PurpleSchool

Стрелочка влевоPython click: создание CLI-приложенийPython argparse: разбор аргументов командной строкиСтрелочка вправо

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачРабота с JSON в Python на примерахPython get — методы получения данныхКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетЧто нового в Python 3Поддерживается ли Python 2 и стоит ли его использоватьPython 1 — с чего начиналась история языкаПравила именования переменных в PythonКоманда python print - полное руководство по выводу данныхПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Загрузка данных PythonУправление проектами на GitHub с PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак использовать функцию isinstance в PythonКак передать функцию как аргумент в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со строками и символами в PythonРабота со столбцами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий