Представьте: ваш конкурент снижает цену на 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 дней. Стоимость рассчитывается индивидуально, исходя из числа конкурентов, сложности парсинга и требуемых интеграций. Закажите индивидуальную разработку системы мониторинга цен — свяжитесь с нами для оценки вашего проекта.







