Уявіть: ваш конкурент знижує ціну на 15% на топ-20 позицій, а ви дізнаєтеся про це через тиждень, коли продажі вже впали. Саме це сталося з одним із наших клієнтів, який втратив 30% маржі за місяць. Щоб виключити такі ситуації, ми розробляємо автоматичних ботів-парсерів цін, які працюють за розкладом, зберігають історію та можуть самостійно коригувати ваші ціни за гнучкими правилами. Ми займаємося моніторингом цін понад 7 років і реалізували 40+ проєктів для e-commerce — від одягу до електроніки. Гарантуємо коректну роботу парсера на всіх етапах, від збору до авторепрайсингу. Зв'яжіться з нами, щоб ми розробили систему під ваш каталог.
Чому ручний моніторинг цін неефективний?
- Застарілі дані: ви перевіряєте ціни раз на тиждень, а конкуренти змінюють їх щодня. На момент вашої реакції ви вже втратили частину продажів або маржі.
- Антиблокувальні заходи: конкуренти використовують Cloudflare, капчі, динамічні селектори. Ручний збір не справляється, потрібен headless-браузер з ротацією проксі.
- Нормалізація: ціни надходять у різному форматі: з податком, без податку, в різних валютах, з пробілами та символами. Без нормалізації порівнювати їх марно.
Як ми будуємо систему моніторингу?
Ми використовуємо Laravel 11 на PHP 8.3, PostgreSQL для зберігання, Redis для черг та Playwright для парсингу динаміки. Архітектура мікросервісна — кожен конкурент обробляється незалежно.
- Планувальник запускає Job для кожного конкурента.
- Скрапер (HTTP або Playwright) отримує HTML.
- Нормалізатор перетворює ціну на єдиний формат.
- Зберігаємо поточну ціну та історію.
- Детектор змін визначає, чи змінилася ціна значно (більше ніж на 2%).
- Якщо так, надсилаємо сповіщення та запускаємо автопереоцінку.
Scheduler
→ ScrapeCompetitorPrices Job (per competitor)
→ CompetitorScraper (HTTP/Playwright)
→ PriceNormalizer
→ PriceHistoryRepository (INSERT)
→ PriceChangeDetector
→ AlertDispatcher (якщо зміна > порогу)
→ RepricingEngine (якщо увімкнено autoprice)
Модель даних
// database/migrations/create_competitor_prices_table.php
Schema::create('competitor_prices', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained();
$table->foreignId('competitor_id')->constrained();
$table->string('competitor_sku')->nullable();
$table->string('competitor_url');
$table->decimal('price', 12, 2);
$table->decimal('sale_price', 12, 2)->nullable();
$table->boolean('in_stock')->default(true);
$table->timestamp('scraped_at');
$table->timestamps();
$table->index(['product_id', 'competitor_id', 'scraped_at']);
});
// Історія для графіків та аналітики
Schema::create('competitor_price_history', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained();
$table->foreignId('competitor_id')->constrained();
$table->decimal('price', 12, 2);
$table->boolean('in_stock');
$table->date('recorded_date');
$table->timestamps();
$table->unique(['product_id', 'competitor_id', 'recorded_date']);
});
Конфігурація конкурентів
// config/competitors.php
return [
'competitor_a' => [
'name' => 'Shop A',
'base_url' => 'https://shop-a.ru',
'selectors' => [
'price' => '.current-price',
'in_stock' => '.in-stock-badge',
],
'price_regex' => '/[\d\s]+/',
'url_pattern' => 'https://shop-a.ru/product/{sku}',
'requires_js' => false,
'request_delay' => [1000, 3000], // ms
],
'wildberries' => [
'name' => 'Wildberries',
'type' => 'api',
'scraper' => WildberriesPriceScraper::class,
'request_delay' => [500, 1500],
],
];
Базовий скрапер цін
// app/Services/PriceMonitor/CompetitorPriceScraper.php
class CompetitorPriceScraper
{
public function __construct(
private Client $httpClient,
private array $config
) {}
public function scrapePrice(string $url): ?PriceData
{
try {
$html = $this->fetch($url);
$crawler = new Crawler($html);
$priceText = $crawler->filter($this->config['selectors']['price'])
->first()
->text('');
$price = $this->extractPrice($priceText);
if ($price === null) return null;
$salePrice = null;
if (!empty($this->config['selectors']['sale_price'])) {
$salePriceText = $crawler->filter($this->config['selectors']['sale_price'])
->first()->text('');
$salePrice = $this->extractPrice($salePriceText);
}
$inStock = true;
if (!empty($this->config['selectors']['in_stock'])) {
$inStock = $crawler->filter($this->config['selectors']['in_stock'])->count() > 0;
}
return new PriceData(
price: $price,
salePrice: $salePrice,
inStock: $inStock,
);
} catch (\Exception $e) {
Log::warning("Price scrape failed for {$url}: {$e->getMessage()}");
return null;
}
}
private function extractPrice(string $text): ?float
{
$cleaned = preg_replace('/[^\d,.]/', '', str_replace(' ', '', $text));
$cleaned = str_replace(',', '.', $cleaned);
return is_numeric($cleaned) ? (float) $cleaned : null;
}
}
Job з детектором змін
// app/Jobs/MonitorCompetitorPrice.php
class MonitorCompetitorPrice implements ShouldQueue
{
public int $tries = 3;
public array $backoff = [60, 120, 300];
public function __construct(
private int $productId,
private int $competitorId,
private string $competitorUrl
) {}
public function handle(
CompetitorPriceScraper $scraper,
PriceChangeDetector $detector,
RepricingEngine $repricer
): void {
$config = Competitor::find($this->competitorId)->config;
$priceData = $scraper->scrapePrice($this->competitorUrl);
if ($priceData === null) return;
// Збереження поточної ціни
$record = CompetitorPrice::updateOrCreate(
[
'product_id' => $this->productId,
'competitor_id' => $this->competitorId,
],
[
'price' => $priceData->price,
'sale_price' => $priceData->salePrice,
'in_stock' => $priceData->inStock,
'scraped_at' => now(),
]
);
// Збереження в історію (один раз на день)
CompetitorPriceHistory::firstOrCreate(
[
'product_id' => $this->productId,
'competitor_id' => $this->competitorId,
'recorded_date' => today(),
],
[
'price' => $priceData->price,
'in_stock' => $priceData->inStock,
]
);
// Детект змін
if ($record->wasChanged('price')) {
$change = $detector->analyze($record);
if ($change->isSignificant()) {
Notification::route('mail', config('monitoring.alert_email'))
->notify(new CompetitorPriceChangedNotification($record, $change));
}
// Автопереоцінка
if (config('repricing.enabled')) {
$repricer->recalculate($this->productId);
}
}
}
}
Налаштування розкладу моніторингу
Розклад налаштовується під ваші завдання. Високопріоритетні товари — кожні 2 години, масові — раз на день вночі. Планувальник Laravel запобігає накладанню завдань.
Для агрегації історії цін ми запускаємо щоденний артефакт, який зводить дані за день в один запис — це економить місце та прискорює запити графіків.
Автоматичний парсер обробляє 1000 товарів за 10 хвилин, що в 40 разів швидше за ручний збір, а вартість впровадження в 3 рази нижча за найм окремого співробітника.
Які дані зберігаються в історії цін?
Зберігається ціна, ціна зі знижкою, ознака наявності на складі та дата. Історія ведеться по днях — це дозволяє будувати графіки трендів та аналізувати сезонність. Наприклад, ви можете побачити, що щозими конкурент знижує ціни на 10% на певні категорії, і підготуватися заздалегідь.
Стратегії автоматичного переоцінювання
| Стратегія | Опис | Коли застосовувати |
|---|---|---|
| beat_lowest | Встановити ціну на N рублів нижче мінімальної | Агресивне захоплення частки, якщо є запас маржі |
| match_lowest | Зрівняти з мінімальною ціною конкурента | Утримання позицій у висококонкурентних нішах |
| beat_average | Ціна на N рублів нижче середньої ціни конкурентів | Баланс між маржею та конкурентоспроможністю |
| percentile | Ціна на заданому перцентилі (наприклад, 25-му) | Збереження позиціонування (преміум або бюджет) |
Кожна стратегія враховує мінімальну зміну в 0.5%, щоб уникнути стрибків цін.
Дашборд моніторингу
Ключові метрики для відображення:
| Метрика | Опис |
|---|---|
| Товари, де ми дорожчі | Кількість та % від каталогу |
| Середня дельта до мінімуму | Середній % різниці з lowest price |
| Товари зі зміною > 5% | Потребують ручної перевірки |
| Товари поза залишками у конкурентів | Потенціал для підвищення ціни |
Дашборд дозволяє швидко виявляти аномалії, наприклад, раптову зміну ціни у великого конкурента.
Типові помилки при впровадженні моніторингу цін
- Ігнорування зміни селекторів. Якщо сайт конкурента змінює верстку — парсер падає. Завжди використовуйте fallback-селектори та алерти при збоях.
- Занадто часті запити. Захист Cloudflare блокує IP. Витримуйте затримки та використовуйте ротацію проксі.
- Відсутність порогів в автопереоцінці. Без мінімального відсотка зміни ви будете змінювати ціну на копійки, що дратує покупців і втрачає довіру.
Часті запитання
Як часто бот сканує ціни?
Частота налаштовується індивідуально. Високопріоритетні товари можна перевіряти кожні 2 години, всі інші — раз на день вночі. Система підтримує будь-який розклад через планувальник Laravel.Чи можна парсити сайти із захистом Cloudflare?
Так. Для обходу захисту використовуємо Playwright з ротацією User-Agent і проксі. При необхідності підключаємо API конкурента (наприклад, Wildberries). Для складних випадків застосовуємо headless-браузер і обробку капч.Які дані зберігаються в історію?
Зберігається ціна, ціна зі знижкою, ознака наявності на складі та дата. Історія ведеться по днях — це дозволяє будувати графіки трендів та аналізувати сезонність.Як працює автоматична зміна цін?
Ви задаєте стратегію: бити мінімальну ціну конкурента, відповідати середній або утримувати заданий перцентиль. Двигун автоматично перераховує ціну при кожному оновленні даних, з урахуванням мінімального порогу зміни в 0.5%.Скільки часу займає впровадження?
Базова версія з моніторингом одного конкурента і алертами — 4–6 робочих днів. Повна система з автопереоцінкою та дашбордом для 5+ конкурентів — 10–14 днів. Терміни уточнюються після аналізу вашого каталогу та цілей.Що входить в роботу під ключ
- Документація: опис архітектури моніторингу, інструкція з додавання нового конкурента, налаштування правил репрайсингу.
- Доступи: надаємо доступ до сервера, черг, дашборду.
- Навчання: проводимо демо-сесію дашборду та правил для вашої команди.
- Підтримка: безкоштовна підтримка протягом 1 місяця після запуску.
- Гарантія: 1 рік безкоштовних оновлень.
Терміни та вартість
Розробка базової системи (1 конкурент + історія + алерти) займає 4–6 робочих днів, вартість розраховується індивідуально. Повна система з автопереоцінкою та дашбордом для 5+ конкурентів — 10–14 днів, ціна уточнюється після аналізу вашого каталогу. Ми також пропонуємо безкоштовну оцінку вашого проєкту — напишіть нам, і ми розрахуємо точну вартість та терміни. Наш парсер товарів забезпечує безперервний моніторинг e-commerce, а система динамічного ціноутворення допомагає швидко реагувати на ринок.







