logg-out.com

Как написать простой парсер веб-страниц на Python

Как написать простой парсер веб-страниц на Python

Парсер веб-страниц на Python — это скрипт, который автоматически скачивает HTML страницы, находит в нём нужные элементы и вытаскивает структурированные данные: заголовки, цены, ссылки, даты, описания. Для простого старта обычно хватает двух инструментов: библиотека для HTTP-запросов и парсер HTML; стандартный html.parser входит в Python, а для удобной навигации по дереву документа часто используют Beautiful Soup.

На практике такой скрипт становится первым шагом к автоматизации сбора данных с досок объявлений, API застройщиков или риелторских CRM — ровно с этого я когда-то начинал, когда перестал обновлять сайты вручную и написал сборщик, который приводил разрозненные объявления к одному формату.

Зачем вообще нужен простой парсер

На практике парсер нужен не «ради парсинга», а ради экономии времени и снижения рутины. Если вы вручную мониторите объявления, цены, вакансии, каталоги или новости, скрипт может делать это за вас по расписанию и собирать данные в таблицу, JSON или базу.

Для real-world задач это особенно полезно, когда нужно:

  • сравнивать цены на нескольких площадках;
  • отслеживать новые объявления;
  • собирать список ссылок и метаданных;
  • находить изменения на странице;
  • делать первичную аналитику по рынку.

В моём случае автоматизация поиска жилья началась ровно с этого: вместо бесконечного обновления досок объявлений появился небольшой сборщик, который забирал данные с разных страниц и приводил их к одному формату. Позже этот подход перерос в полноценный инструмент аналитики цен и оценки ликвидности, но фундамент остался тем же — простой парсер, который корректно собирает и нормализует данные.

Как устроен парсер в простом виде

У любого простого парсера есть три шага:

  1. Скачать HTML страницы.
  2. Разобрать структуру документа.
  3. Вытащить нужные поля и сохранить результат.

Схема выглядит так: GET-запрос → HTML → разбор дерева → структурированные данные.

Если страница статическая, этого часто достаточно. Если контент рисуется JavaScript’ом, простой запрос может вернуть только «оболочку» страницы без нужных данных, и тогда потребуется другой подход. На досках объявлений это встречается часто: блок с фильтрами и карточками может подгружаться через AJAX, поэтому перед написанием кода всегда проверяю, есть ли нужные цифры в исходном HTML.

Что выбрать: requests, Beautiful Soup или html.parser

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

  • requests — чтобы получить HTML страницы;
  • Beautiful Soup — чтобы удобно искать элементы по тегам, классам и атрибутам;
  • html.parser — стандартный встроенный HTML-парсер Python, который может использоваться как backend для разбора HTML.

Коротко по ролям

Инструмент Задача Когда использовать
requests Скачать HTML по URL Почти всегда на старте
Beautiful Soup Найти элементы на странице Когда нужно быстро и понятно разбирать DOM
html.parser Базовый HTML-парсер из стандартной библиотеки Когда хочется без лишних зависимостей или как backend для обработки HTML

Если нужен именно «простой парсер», чаще всего лучше начать с requests + Beautiful Soup. Это самый понятный путь для учебного и прикладного проекта. В своих первых сборщиках объявлений я использовал именно эту пару, и она до сих пор закрывает 80% задач по статическим страницам.

Подготовка окружения

Для начала установите нужные пакеты:

pip install requests beautifulsoup4

Дальше создайте файл, например parser.py, и проверьте, что Python корректно запускает скрипт.

Первый рабочий пример

Ниже базовый пример: скачиваем страницу, парсим HTML и достаём заголовок.

import requests
from bs4 import BeautifulSoup

url = "https://example.com"
response = requests.get(url, timeout=10)
response.raise_for_status()

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

Что здесь происходит

  • requests.get() отправляет GET-запрос на страницу.
  • timeout=10 защищает от бесконечного ожидания.
  • raise_for_status() сразу покажет ошибку, если сервер вернул неуспешный код.
  • BeautifulSoup(..., "html.parser") создаёт объект дерева документа.
  • soup.title ищет тег <title>.

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

Как находить нужные элементы на странице

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

  • тег (h1, a, div, span);
  • класс CSS;
  • id;
  • атрибуты;
  • вложенность элементов.

Когда я разбираю страницы с объявлениями, чаще всего ориентируюсь на структуру карточки: каждая карточка — это родительский блок с повторяющимися внутренними элементами. Стабильный класс родителя позволяет вытащить все карточки одной командой.

Пример поиска ссылок

links = soup.find_all('a')
for link in links:
    print(link.get('href'))

Пример поиска по классу

prices = soup.find_all(class_='price')
for price in prices:
    print(price.get_text(strip=True))

Пример поиска одного элемента

title = soup.find('h1', class_='title')
if title:
    print(title.get_text(strip=True))

Пошагово: как написать простой парсер страницы

Шаг 1. Определите, какие данные вам нужны

Не начинайте с кода. Сначала ответьте на вопрос: что именно нужно собрать?

  • заголовок;
  • цена;
  • адрес;
  • ссылка;
  • дата публикации;
  • описание.

Чем точнее список полей, тем проще потом найти селекторы. Для недвижимости я обычно фиксирую: цена, площадь, этаж, адрес, количество комнат, ссылка на объявление — это база для дальнейшей аналитики.

Шаг 2. Откройте страницу в браузере и посмотрите HTML

Используйте инструменты разработчика:

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

Это критически важный момент: если данные генерируются на клиенте, простой HTTP-запрос их не увидит, и вы потратите время впустую.

Шаг 3. Напишите запрос и проверьте ответ

Сначала просто выведите HTML в консоль или сохраните его в файл.

import requests

url = "https://example.com"
response = requests.get(url, timeout=10)
response.raise_for_status()
print(response.text[:1000])  # первые 1000 символов

Шаг 4. Найдите стабильный селектор

Хрупкая ошибка новичка — парсить по слишком общим или случайным классам. Лучше искать:

  • уникальный класс;
  • стабильный атрибут;
  • структуру карточки;
  • родительский блок с повторяющимися элементами.

Шаг 5. Извлеките данные и приведите к единому формату

Например:

title = soup.find('h1', class_='title')
if title:
    title_text = title.get_text(strip=True)
else:
    title_text = None

Шаг 6. Сохраните результат

Для старта подойдут:

  • CSV;
  • JSON;
  • SQLite;
  • Google Sheets через API.

Я предпочитаю SQLite или CSV для локальной аналитики: их легко открыть в Pandas и сразу строить графики цен или распределение предложений по районам.

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

Представим, что на странице есть карточки, у каждой:

  • заголовок;
  • цена;
  • ссылка.
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

url = "https://example.com/listings"
response = requests.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")

cards = soup.find_all('div', class_='listing-card')
for card in cards:
    title_elem = card.find('h2', class_='listing-title')
    price_elem = card.find('span', class_='listing-price')
    link_elem = card.find('a', class_='listing-link')

    title = title_elem.get_text(strip=True) if title_elem else None
    price = price_elem.get_text(strip=True) if price_elem else None
    link = urljoin(url, link_elem.get('href')) if link_elem else None

    print(title, price, link)

Что важно в этом примере

  • urljoin() помогает собрать абсолютную ссылку из относительной.
  • get_text(strip=True) убирает лишние пробелы.
  • Проверки на None защищают от падения скрипта, если элемент не найден.

Такая структура — типичная для досок объявлений. Когда я писал первый парсер для своей задачи, именно такие карточки и были основным источником данных. Позже пришлось добавить нормализацию цен (убирать «₽», пробелы, переводить в число), чтобы можно было сравнивать предложения между собой.

Типовые ошибки новичков

1. Парсинг без проверки статуса ответа

Если не вызвать raise_for_status(), можно случайно обработать страницу ошибки как обычный HTML. В результате в базу попадут пустые или некорректные записи, а вы этого даже не заметите.

2. Жёсткая привязка к одному классу

Сайт меняет классы, и парсер ломается. Лучше закладывать резервные варианты. Например, сначала пробовать один селектор, потом второй, и логировать, если оба не сработали.

3. Игнорирование JavaScript

Если нужные данные загружаются динамически, requests может не увидеть их в HTML. Тогда простой парсер не поможет без дополнительного инструмента. На многих современных сайтах объявлений карточки рендерятся на клиенте, поэтому приходится либо искать внутренние JSON-эндпоинты, либо использовать headless-браузер.

4. Слишком частые запросы

Если слать много запросов подряд, сайт может начать ограничивать доступ. В продакшене я всегда добавляю задержку между запросами (например, 1–2 секунды) и слежу за кодом ответа: если получаю 429 или 403, делаю паузу.

5. Отсутствие заголовков

Иногда серверы по-разному отвечают на запросы без User-Agent.

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, headers=headers, timeout=10)

Как сделать парсер устойчивее

Чтобы скрипт не разваливался при первой же правке верстки, используйте несколько приёмов.

Чек-лист устойчивости

  • добавляйте timeout;
  • проверяйте status_code;
  • обрабатывайте исключения;
  • делайте проверки на None;
  • сохраняйте «сырой» HTML для отладки;
  • логируйте проблемные страницы;
  • не завязывайтесь на один-единственный селектор.

Пример обработки ошибок

import requests
from bs4 import BeautifulSoup

def fetch_and_parse(url):
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, "html.parser")
        # ... парсинг
    except requests.exceptions.RequestException as e:
        print(f"Ошибка запроса: {e}")
    except Exception as e:
        print(f"Неожиданная ошибка: {e}")

Когда простой парсер не подойдёт

Есть ситуации, где базового подхода недостаточно:

  • данные рендерятся только после выполнения JavaScript;
  • сайт требует авторизацию;
  • есть сложная защита от автоматизации (капча, fingerprinting);
  • страница меняет структуру слишком часто;
  • нужные данные приходят не в HTML, а через API.

В таких случаях лучше искать:

  • внутренние JSON-эндпоинты;
  • официальное API;
  • выгрузки в открытом формате;
  • headless-браузер для рендеринга.

В недвижимости часто есть официальные API у агрегаторов или застройщиков, но они бывают нестабильными или неполными. Поэтому приходится комбинировать: парсить статические куски, а динамические добирать через другие источники.

Мини-архитектура для реального проекта

Если парсер нужен не на один вечер, а как часть инструмента, удобно разделить код на части:

  • модуль загрузки страниц;
  • модуль разбора HTML;
  • модуль нормализации данных;
  • модуль сохранения;
  • модуль логирования.

Такой подход упрощает поддержку. Если сломалась верстка, вы не переписываете весь проект целиком, а меняете только слой парсинга. В своей платформе для анализа недвижимости я придерживаюсь именно такой модульной схемы: каждый источник (доска, API застройщика, CRM) изолирован, а общий слой агрегации приводит всё к общей схеме с полями «цена», «площадь», «адрес», «ссылка».

Что проверить перед запуском парсера в продакшене

Быстрый чек-лист

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

Вывод

Простой парсер веб-страниц на Python — это несложный, но очень практичный инструмент. В базовом варианте достаточно скачать HTML через requests, разобрать его через Beautiful Soup и достать нужные элементы через find() или find_all(). Для статических страниц этого уже хватает, а для более сложных задач можно постепенно наращивать архитектуру: добавить сохранение, логирование, обработку ошибок и работу с несколькими источниками.

Если задача связана с мониторингом объявлений, цен или изменений на сайте, начинать лучше именно с простого парсера: он быстрее всего даёт полезный результат и помогает понять структуру данных на практике. С этого начиналась и моя автоматизация сбора данных о недвижимости — и со временем выросла в систему, которая экономит часы ручного просмотра.

FAQ

Можно ли написать парсер только на стандартной библиотеке Python?

Да, для простых задач можно использовать встроенный html.parser, который предназначен для разбора HTML и XHTML. Но для удобного поиска элементов обычно проще взять Beautiful Soup.

Почему парсер не видит данные на странице?

Чаще всего причина в том, что данные подгружаются JavaScript’ом, а requests получает только исходный HTML, без отрисовки браузером. В таком случае нужно искать API или использовать браузерный рендеринг.

Какой минимальный набор библиотек нужен новичку?

Обычно достаточно requests и beautifulsoup4. Этого хватает, чтобы скачать страницу и разобрать HTML в большинстве простых сценариев.

Что лучше использовать для разбора HTML: `html.parser` или Beautiful Soup?

Если нужна базовая стандартная возможность — подойдёт html.parser. Если нужен удобный и быстрый способ навигации по дереву документа, чаще выбирают Beautiful Soup.

Нужно ли ставить защиту от ошибок сразу?

Да. Даже простой парсер должен проверять статус ответа, использовать таймаут и учитывать пустые элементы, иначе он быстро ломается на реальных страницах.