Разработка парсера каталога товаров конкурентов

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка парсера каталога товаров конкурентов
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка парсера каталога товаров конкурентов

Парсер каталога конкурента — это инструмент конкурентной разведки. Задача узкая: регулярно получать актуальный список товаров с ценами, характеристиками и наличием. Не общая система парсинга, а специализированный сборщик под конкретный источник. Результат — актуальная копия каталога конкурента у вас в базе данных.

Анализ сайта перед разработкой

До написания кода — анализ целевого сайта:

  • Структура URL каталога: пагинация через ?page=N, бесконечная прокрутка или tree-навигация по категориям
  • Рендеринг: статический HTML (быстро и просто) или данные подгружаются через XHR/fetch (нужен перехват или headless)
  • Защита: Cloudflare, rate limiting, авторизация
  • Частота обновления данных на сайте — как быстро появляются новые товары и меняются цены

Типичный минимальный набор полей: 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 вкладка при ручном переходе по каталогу.

Пагинация и полный обход

Для пагинации через ?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;

Обработка изменений структуры сайта

Сайты конкурентов меняются — парсер периодически ломается. Признаки поломки: нулевой результат при обходе, резкое падение числа найденных товаров, пустые поля в 80%+ записей. Мониторинг: alert если за последний запуск собрано менее 50% от среднего количества товаров.

Сроки

Парсер статического каталога (1 сайт, до 50k товаров) — 3–5 дней. С XHR-перехватом и Playwright — 5–8 дней. История цен, алерты и дашборд — ещё 3–5 дней. Поддержка: при изменении структуры сайта конкурента — обновление парсера обычно занимает 2–4 часа.