Ви запустили інтернет-магазин, ціни конкурентів змінюються щодня, а ручний моніторинг з'їдає години менеджерів. Без автоматичного збору ви втрачаєте прибуток: не встигаєте зреагувати на зниження ціни у конкурента або пропускаєте новинки в асортименті. Парсер каталогу товарів конкурентів — це інструмент, який щодня збирає актуальні ціни, наявність та характеристики у вашу базу. Більше не потрібно перевіряти сайти вручну: система сама обходить каталог, фіксує зміни та надсилає алерти. Наш досвід — понад 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': '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 разів швидше. Для пошуку ендпоінта — 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







