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

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, 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': 'uk-UA,uk;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 разів швидше. Для пошуку еndpoint'а—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 з webhook у Slack/Telegram. Приклад запиту:

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 години.