Представьте: ваш конкурент снижает цену на 15% на топ-20 позиций, а вы узнаёте об этом через неделю, когда продажи уже упали. Именно это произошло с одним из наших клиентов, потерявшим 30% маржи за месяц. Чтобы исключить такие ситуации, мы разрабатываем автоматические боты-парсеры цен, которые работают по расписанию, хранят историю и могут самостоятельно корректировать ваши цены по гибким правилам. Мы занимаемся мониторингом цен более 7 лет и реализовали 40+ проектов для e-commerce — от одежды до электроники. Гарантируем корректную работу парсера на всех этапах, от сбора до авторепрайсинга. Свяжитесь с нами, чтобы мы разработали систему под ваш каталог.
Почему ручной мониторинг цен неэффективен?
- Устаревшие данные: вы проверяете цены раз в неделю, а конкуренты меняют их ежедневно. К моменту вашей реакции вы уже потеряли часть продаж или маржи.
- Антиблокировочные меры: конкуренты используют Cloudflare, капчи, динамические селекторы. Ручной сбор не справляется, нужен headless-браузер с ротацией прокси.
- Нормализация: цены приходят в разном формате: с налогом, без налога, в разных валютах, с пробелами и символами. Без нормализации сравнивать их бесполезно.
Как мы строим систему мониторинга?
Мы используем Laravel 11 на PHP 8.3, PostgreSQL для хранения, Redis для очередей и Playwright для парсинга динамики. Архитектура микросервисная — каждый конкурент обрабатывается независимо.
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 раз быстрее ручного сбора.
Какие данные сохраняются в истории цен?
Сохраняется цена, цена со скидкой, признак наличия на складе и дата. История ведётся по дням — это позволяет строить графики трендов и анализировать сезонность. Например, вы можете увидеть, что каждую зиму конкурент снижает цены на 10% на определённые категории, и подготовиться заранее.
Стратегии автоматического перейценивания
| Стратегия | Описание | Когда применять |
|---|---|---|
| beat_lowest | Установить цену на N рублей ниже минимальной | Агрессивный захват доли, если есть запас маржи |
| match_lowest | Сравнять с минимальной ценой конкурента | Удержание позиций в высококонкурентных нишах |
| beat_average | Цена на N рублей ниже средней цены конкурентов | Баланс между маржой и конкурентоспособностью |
| percentile | Цена на заданном перцентиле (например, 25-й) | Сохранение позиционирования (премиум или бюджет) |
Каждая стратегия учитывает минимальное изменение в 0.5%, чтобы избежать скачков цен.
Дашборд мониторинга
Ключевые метрики для отображения:
| Метрика | Описание |
|---|---|
| Товары, где мы дороже | Количество и % от каталога |
| Средняя дельта к минимуму | Средний % разницы с lowest price |
| Товары с изменением > 5% | Требуют ручной проверки |
| Товары вне остатков у конкурентов | Потенциал для повышения цены |
Дашборд позволяет быстро выявлять аномалии, например, внезапное изменение цены у крупного конкурента.
Типичные ошибки при внедрении мониторинга цен
- Игнорирование изменения селекторов. Если сайт конкурента меняет вёрстку — парсер падает. Всегда используйте fallback-селекторы и алерты при сбоях.
- Слишком частые запросы. Защита Cloudflare блокирует IP. Выдерживайте задержки и используйте ротацию прокси.
- Отсутствие порогов в автоперейценке. Без минимального процента изменения вы будете менять цену на копейки, что раздражает покупателей и теряет доверие.
Что входит в работу
- Документация: описание архитектуры мониторинга, инструкция по добавлению нового конкурента, настройка правил репрайсинга.
- Доступы: предоставляем доступ к серверу, очередям, дашборду.
- Обучение: проводим демо-сессию дашборда и правил для вашей команды.
- Поддержка: бесплатная поддержка в течение 1 месяца после запуска.
Сроки и стоимость
Разработка базовой системы (1 конкурент + история + алерты) занимает 4–6 рабочих дней. Полная система с автоперейценкой и дашбордом для 5+ конкурентов — 10–14 дней. Стоимость рассчитывается индивидуально, исходя из числа конкурентов, сложности парсинга и требуемых интеграций. Закажите индивидуальную разработку системы мониторинга цен — свяжитесь с нами для оценки вашего проекта.







