Вы запустили интернет-магазин, цены конкурентов меняются каждый день, а ручной мониторинг съедает часы менеджеров. Без автоматического сбора вы теряете прибыль: не успеваете среагировать на снижение цены у конкурента или пропускаете новинки в ассортименте. Парсер каталога товаров конкурентов — это инструмент, который ежедневно собирает актуальные цены, наличие и характеристики в вашу базу. Больше не нужно проверять сайты вручную: система сама обходит каталог, фиксирует изменения и присылает алерты. Наш опыт — более 10 лет в разработке таких решений, десятки успешных проектов под ключ.
Почему ручной сбор неэффективен?
Ручной мониторинг трёх конкурентов по 500 товаров занимает 2–3 часа в день. Ошибки, пропуски, устаревшие данные. Автоматический парсер решает эти проблемы: собирает данные за минуты, работает 24/7, не устаёт. Экономия времени — до 90% по сравнению с ручным сбором. Окупается за 2–3 месяца.
Анализ сайта перед разработкой
До написания кода — анализ целевого сайта:
- Структура URL каталога: пагинация через
?page=N, бесконечная прокрутка или tree-навигация по категориям - Рендеринг: статический HTML (быстро и просто) или данные подгружаются через XHR/fetch (нужен перехват или headless)
- Защита: Cloudflare, rate limiting, авторизация
- Частота обновления данных на сайте — как быстро появляются новые товары и меняются цены
| Тип сайта | Сложность парсинга | Скорость сбора (1000 товаров) | Надёжность |
|---|---|---|---|
| Статический HTML | Низкая | 1–2 минуты | Высокая |
| SPA с XHR (API) | Средняя | 3–5 минут | Очень высокая |
| SPA без API (Client-side render) | Высокая | 5–10 минут | Высокая (при правильных паузах) |
Типичный минимальный набор полей: SKU / артикул, название, цена (обычная + акционная), наличие, категория, URL страницы товара, дата сбора. Для некоторых ниш важны: рейтинг, количество отзывов, вес/габариты, бренд.
Техническая реализация
Для статических сайтов — httpx + parsel (или Cheerio для Node.js). Async-запросы, пул соединений 10–20 воркеров, задержка 1–3 секунды между запросами к одному домену.
import httpx
import asyncio
import random
from parsel import Selector
UA_POOL = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
]
async def fetch_page(session: httpx.AsyncClient, url: str) -> str:
headers = {
'User-Agent': random.choice(UA_POOL),
'Accept-Language': 'ru-RU,ru;q=0.9',
}
resp = await session.get(url, headers=headers, timeout=15)
resp.raise_for_status()
return resp.text
async def parse_catalog_page(html: str, base_url: str) -> list[dict]:
sel = Selector(html)
products = []
for item in sel.css('.product-card'):
price_raw = item.css('.price::text').get('').strip()
price = int(''.join(c for c in price_raw if c.isdigit())) if price_raw else None
products.append({
'title': item.css('.product-title::text').get('').strip(),
'price': price,
'sku': item.attrib.get('data-sku'),
'url': base_url + item.css('a::attr(href)').get(''),
'in_stock': bool(item.css('.in-stock')),
'image_url': item.css('img::attr(src)').get(),
})
return products
Для SPA с XHR — перехват API-запросов через Playwright. Многие современные интернет-магазины при открытии страницы делают fetch-запрос к собственному API, возвращающему JSON с данными о товарах:
from playwright.async_api import async_playwright
import json
async def intercept_catalog_api(catalog_url: str) -> list[dict]:
products = []
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
async def handle_response(response):
if '/api/catalog' in response.url and response.status == 200:
try:
data = await response.json()
if 'products' in data:
products.extend(data['products'])
except Exception:
pass
page.on('response', handle_response)
await page.goto(catalog_url, wait_until='networkidle')
await browser.close()
return products
Если API возвращает JSON напрямую — можно обращаться к нему минуя браузер, что в 10–20 раз быстрее. Для поиска эндпоинта — DevTools Network вкладка при ручном переходе по каталогу.
Как работает парсинг SPA с XHR?
В SPA основная сложность — не HTML, а API-запросы, которые подгружают данные. Мы перехватываем эти запросы через Playwright и получаем чистый JSON. Это надёжнее, чем разбирать динамически сгенерированный DOM. Если API открытый — обращаемся к нему напрямую, экономя ресурсы.
Пагинация и полный обход
Для пагинации через ?page=N — последовательный обход до пустой страницы:
async def scrape_full_catalog(base_url: str) -> list[dict]:
all_products = []
page_num = 1
async with httpx.AsyncClient() as session:
while True:
url = f'{base_url}?page={page_num}'
html = await fetch_page(session, url)
products = await parse_catalog_page(html, base_url)
if not products:
break
all_products.extend(products)
page_num += 1
await asyncio.sleep(random.uniform(1.5, 3.0)) # вежливая задержка
return all_products
Для категорийного дерева — сначала рекурсивный сбор всех URL категорий, затем обход каждой категории с пагинацией.
Хранение и инкрементальное обновление
CREATE TABLE competitor_products (
id SERIAL PRIMARY KEY,
source VARCHAR(100) NOT NULL, -- 'competitor_a', 'competitor_b'
external_id VARCHAR(255) NOT NULL,
title TEXT NOT NULL,
price DECIMAL(10,2),
price_sale DECIMAL(10,2),
in_stock BOOLEAN DEFAULT TRUE,
category VARCHAR(500),
url TEXT NOT NULL,
image_url TEXT,
attributes JSONB DEFAULT '{}',
first_seen TIMESTAMPTZ DEFAULT NOW(),
last_seen TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(source, external_id)
);
CREATE TABLE competitor_price_history (
id BIGSERIAL PRIMARY KEY,
product_id INT REFERENCES competitor_products(id),
price DECIMAL(10,2),
price_sale DECIMAL(10,2),
in_stock BOOLEAN,
scraped_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON competitor_price_history(product_id, scraped_at DESC);
При повторном обходе — INSERT ... ON CONFLICT (source, external_id) DO UPDATE SET last_seen = NOW(), price = EXCLUDED.price, .... Запись в историю делается только если цена или наличие изменились (сравнение с последней записью через LAG() или хранение price в основной таблице).
Расписание и оповещения
Celery Beat или Node.js cron. Рекомендуемая частота для каталога конкурента — раз в 4–12 часов, в зависимости от динамики цен в нише. Для маркетплейсов с быстро меняющимися ценами — раз в час для топ-позиций.
Оповещение при снижении цены конкурента ниже вашей — SQL-запрос или триггер PostgreSQL с уведомлением в Slack/Telegram через webhook. Пример запроса:
SELECT cp.title, cp.price AS competitor_price, mp.price AS my_price
FROM competitor_products cp
JOIN my_products mp ON mp.sku = cp.external_id
WHERE cp.source = 'competitor_a'
AND cp.price < mp.price
AND cp.in_stock = TRUE
ORDER BY (mp.price - cp.price) DESC;
Как настроить алерты о снижении цен конкурента?
- Установите сигнатуру:
SELECT ... WHERE cp.price < mp.price * 0.95— алерт при снижении на 5%. - Настройте webhook в Telegram/Slack.
- Запустите SQL-запрос после каждого обхода и отправляйте результат.
Мы реализуем эту логику в составе парсера: вы получаете уведомление в мессенджер с таблицей товаров, где конкурент стал дешевле.
Как обеспечить бесперебойную работу парсера?
Сайты конкурентов меняются — парсер периодически ломается. Мы настраиваем мониторинг: alert если за последний запуск собрано менее 50% от среднего количества товаров. При изменении структуры — обновление обычно занимает 2–4 часа. Гарантируем поддержку и адаптацию под новые версии сайтов.
Что входит в работу
- Исчерпывающий анализ целевого сайта (структура, защита, API)
- Разработка парсера с учётом пагинации, категорий, инкрементального обновления
- Настройка базы данных для хранения истории цен и ассортимента
- Организация расписания (cron) и алертов (Telegram/Slack)
- Документация по эксплуатации и доступам
- Обучение вашего сотрудника работе с системой
- Гарантийная поддержка 1 месяц и реагирование на сбои 2–4 часа
Оценим ваш проект — пишите, мы предложим оптимальное решение под ключ. Закажите разработку парсера и получите инструмент, который принесёт реальную пользу в конкурентной борьбе. Wikipedia: Web scraping







