Мониторинг цен и наличия на внешних сайтах: реализация

Ручная проверка цен и наличия на сайтах дистрибьюторов и конкурентов отнимает часы и приводит к потерям из-за несвоевременной реакции. Мы реализуем автоматический мониторинг, который регулярно снимает показания с нужных URL и оповещает о любых изменениях. Наша команда берёт на себя весь цикл — от настройки до поддержки, обеспечивая надёжное решение, которое масштабируется вместе с вашим бизнесом.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Мониторинг цен и наличия на внешних сайтах: реализация
Средний
~3-5 дней
Часто задаваемые вопросы

Реализация мониторинга изменений цен/наличия на внешних сайтах

Мониторинг внешних сайтов — задача, с которой сталкиваются владельцы интернет-магазинов и менеджеры по закупкам. Первый сценарий: отслеживание дистрибьюторов, чтобы не нарушали рекомендованную розничную цену. Второй — наблюдение за конкурентами по конкретным SKU для оперативного изменения собственной цены. Мы реализуем систему, которая регулярно снимает показания с указанных URL, сравнивает с предыдущими значениями и сигнализирует при отклонениях, превышающих заданный порог.

Наш опыт показывает: ручная проверка 50 SKU отнимает до 10 часов в неделю. Автоматизация окупается за 2–3 месяца при 100+ товарах, а при 500 SKU экономия превышает 150 часов в месяц. Мы гарантируем точность данных на уровне 99.5% — это подтверждено на 200+ проектах.

Почему мониторинг цен критичен для вашего бизнеса?

Конкуренты меняют цены каждый день. Если вы не успеваете реагировать — теряете продажи. Мониторинг даёт преимущество: вы видите любое снижение цены у конкурента в течение часа. Для дистрибьюторов — контроль соблюдения MAP (Minimum Advertised Price). Нарушения фиксируются автоматически, и вы можете выставлять штрафы.

Архитектура системы и модель данных

URL List → Scheduler → Fetcher → Parser → Comparator → Alert Engine ↓ Snapshot Store 

Ключевая особенность: система хранит историю значений, а не только текущее. Это позволяет строить графики изменений и видеть паттерны.

CREATE TABLE watch_targets (
    id BIGSERIAL PRIMARY KEY,
    url TEXT NOT NULL UNIQUE,
    label VARCHAR(255),
    our_product_id BIGINT REFERENCES products(id),
    site_id INT REFERENCES external_sites(id),
    check_interval INTERVAL DEFAULT '4 hours',
    price_selector VARCHAR(500),
    stock_selector VARCHAR(500),
    price_type VARCHAR(20) DEFAULT 'text',
    price_attr VARCHAR(100),
    price_regex VARCHAR(255),
    alert_threshold_pct NUMERIC(5,2) DEFAULT 5.0,
    is_active BOOLEAN DEFAULT TRUE,
    last_checked_at TIMESTAMP,
    last_price NUMERIC(12,2),
    last_in_stock BOOLEAN
);
CREATE TABLE watch_snapshots (
    id BIGSERIAL PRIMARY KEY,
    target_id BIGINT REFERENCES watch_targets(id) ON DELETE CASCADE,
    price NUMERIC(12,2),
    in_stock BOOLEAN,
    raw_price_text VARCHAR(200),
    http_status SMALLINT,
    error TEXT,
    captured_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_snapshots_target_time ON watch_snapshots(target_id, captured_at DESC);

Гибкий парсер цены: методы и сравнение

Разные сайты хранят цену по-разному. Парсер поддерживает несколько режимов:

class FlexiblePriceExtractor {
    public function extract(string $html, WatchTarget $target): ?ExtractedValue {
        return match ($target->price_type) {
            'text' => $this->extractText($html, $target),
            'attr' => $this->extractAttr($html, $target),
            'meta' => $this->extractMeta($html, $target),
            'json' => $this->extractJson($html, $target),
            'ld' => $this->extractLdJson($html),
            default => null,
        };
    }

    private function extractLdJson(string $html): ?ExtractedValue {
        // <cite>Schema.org Product markup</cite> — универсальный для многих магазинов
        // Подробнее: [Schema.org](https://ru.wikipedia.org/wiki/Schema.org)
        $crawler = new Crawler($html);
        $nodes = $crawler->filter('script[type="application/ld+json"]');
        foreach ($nodes as $node) {
            $data = json_decode($node->textContent, true);
            if (!$data) continue;
            $type = $data['@type'] ?? $data[0]['@type'] ?? null;
            if (!in_array($type, ['Product', 'Offer'])) continue;
            $offer = $data['offers'] ?? $data;
            if (is_array($offer) && isset($offer[0])) $offer = $offer[0];
            $price = $offer['price'] ?? null;
            $inStock = ($offer['availability'] ?? '') === 'https://schema.org/InStock';
            if ($price !== null) {
                return new ExtractedValue(
                    price: (float) $price,
                    inStock: $inStock,
                    rawText: (string) $price,
                    method: 'ld_json',
                );
            }
        }
        return null;
    }

    private function extractMeta(string $html, WatchTarget $target): ?ExtractedValue {
        // Open Graph / meta теги: <meta property="product:price:amount" content="29990">
        $crawler = new Crawler($html);
        $selector = "meta[property='{$target->price_attr}'], meta[name='{$target->price_attr}']";
        try {
            $content = $crawler->filter($selector)->attr('content');
            return $this->parseNumeric($content);
        } catch (\Exception $e) {
            return null;
        }
    }

    private function extractText(string $html, WatchTarget $target): ?ExtractedValue {
        if (!$target->price_selector) return null;
        $crawler = new Crawler($html);
        try {
            $text = $crawler->filter($target->price_selector)->first()->text();
            if ($target->price_regex) {
                preg_match($target->price_regex, $text, $m);
                $text = $m[1] ?? $text;
            }
            return $this->parseNumeric($text);
        } catch (\Exception $e) {
            return null;
        }
    }

    private function parseNumeric(string $raw): ?ExtractedValue {
        $clean = preg_replace('/[^\d.,]/', '', $raw);
        $clean = str_replace(',', '.', $clean);
        // "29.990" (разделитель тысяч точкой) → "29990"
        if (preg_match('/^\d{1,3}\.\d{3}$/', $clean)) {
            $clean = str_replace('.', '', $clean);
        }
        if (!is_numeric($clean) || (float) $clean <= 0) return null;
        return new ExtractedValue(price: (float) $clean, rawText: $raw);
    }
}
Метод Скорость Надёжность Универсальность
text (CSS-селектор) высокая средняя (зависит от вёрстки) низкая
attr (атрибут) высокая высокая (если атрибут статичен) средняя
meta (метатеги) высокая высокая (стандартизированы) высокая
json (JSON-блок) средняя высокая средняя
ld (JSON-LD Schema) средняя очень высокая (структурированные данные) очень высокая

Не знаете, какой метод подходит для ваших сайтов? Наши инженеры проанализируют структуру страниц и подберут оптимальный парсер.

Когда нужен Playwright для мониторинга?

Для сайтов, которые динамически загружают контент через JavaScript (SPA, React, Vue), стандартный HTTP-запрос не даст корректных данных. Playwright эмулирует полноценный браузер, выполняя JS-код и дожидаясь рендеринга. Это увеличивает время проверки, но гарантирует точные цены и остатки. В нашей системе Playwright-адаптер подключается как опция для выбранных URL.

Механизм проверок: Job и диспетчер

class CheckWatchTargetJob implements ShouldQueue {
    public int $timeout = 30;
    public int $tries = 2;

    public function handle(FlexiblePriceExtractor $extractor, WatchAlertService $alerts): void
    {
        $target = WatchTarget::findOrFail($this->targetId);

        // Fetch
        $response = $this->fetch($target->url);
        if (!$response) {
            WatchSnapshot::create([
                'target_id' => $target->id,
                'http_status' => 0,
                'error' => 'Fetch failed',
            ]);
            return;
        }

        // Parse
        $extracted = $extractor->extract($response->body(), $target);
        $httpStatus = $response->status();
        WatchSnapshot::create([
            'target_id' => $target->id,
            'price' => $extracted?->price,
            'in_stock' => $extracted?->inStock,
            'raw_price_text' => $extracted?->rawText,
            'http_status' => $httpStatus,
        ]);

        // Compare and alert
        if ($extracted && $target->last_price) {
            $changePct = abs($extracted->price - $target->last_price) / $target->last_price * 100;
            if ($changePct >= $target->alert_threshold_pct) {
                $alerts->priceChanged($target, $target->last_price, $extracted->price);
            }
        }

        if ($extracted && $target->last_in_stock !== null && $extracted->inStock !== $target->last_in_stock) {
            $alerts->stockStatusChanged($target, $target->last_in_stock, $extracted->inStock);
        }

        $target->update([
            'last_checked_at' => now(),
            'last_price' => $extracted?->price ?? $target->last_price,
            'last_in_stock' => $extracted?->inStock ?? $target->last_in_stock,
        ]);
    }
}

Крон-задача каждую минуту запускает диспетчер, который выбирает цели, у которых last_checked_at + check_interval < NOW(), и диспатчит CheckWatchTargetJob в очередь monitoring. Это обеспечивает равномерную нагрузку и соблюдение интервалов.

Управление и уведомления

В административной панели доступны: список URL с текущей ценой и временем последней проверки, кнопка «Проверить сейчас», график изменений цены за 30 дней, настройка порога уведомления для каждого URL, массовое добавление URL из CSV.

class WatchAlertService {
    public function priceChanged(WatchTarget $target, float $oldPrice, float $newPrice): void
    {
        $direction = $newPrice < $oldPrice ? '▼' : '▲';
        $pctChange = round(abs($newPrice - $oldPrice) / $oldPrice * 100, 1);
        $ourPrice = $target->ourProduct?->price;

        $text = "{$direction} *Изменение цены* на {$target->site->name}\n"
            . "{$target->label}\n"
            . "Было: " . number_format($oldPrice, 0, '.', ' ') . " руб.\n"
            . "Стало: " . number_format($newPrice, 0, '.', ' ') . " руб. ({$pctChange}%)\n";

        if ($ourPrice) {
            $diff = round(($newPrice - $ourPrice) / $ourPrice * 100, 1);
            $text .= "Наша цена: " . number_format($ourPrice, 0, '.', ' ') . " руб. "
                . ($diff > 0 ? "(мы дешевле на {$diff}%)" : "(они дешевле на " . abs($diff) . "%)") . "\n";
        }

        $text .= "\n[Открыть страницу]({$target->url})";

        $this->telegram->sendMessage([
            'chat_id' => config('telegram.price_watch_chat'),
            'text' => $text,
            'parse_mode' => 'Markdown',
        ]);
    }
}

Как настроить мониторинг за 5 шагов?

  1. Подготовьте список URL отслеживаемых товаров.
  2. Определите селекторы цены и наличия для каждого сайта.
  3. Настройте интервалы проверки (от 10 минут до 24 часов).
  4. Укажите каналы уведомлений (Telegram, Slack, email).
  5. Запустите систему и контролируйте через дашборд.

Сроки реализации и точность

Этап Время
Схема данных + FlexiblePriceExtractor + LD-JSON 1–2 дня
CheckWatchTargetJob + диспетчер 0.5 дня
Telegram-уведомления 0.5 дня
Интерфейс управления + графики 1 день
Playwright-адаптер для JS-сайтов (при необходимости) +1 день
Итого 3–4 рабочих дня

Сравнение с ручной проверкой: автоматический мониторинг в 100 раз быстрее. На 1000 SKU ручная проверка занимает 40 часов в неделю, автоматическая — 10 минут. Погрешность данных — менее 0.5%.

Для сайтов на JavaScript мы применяем Playwright — он эмулирует браузер и дожидается рендеринга. Это замедляет проверку, но гарантирует точность. Для SPA и динамических страниц — обязательный компонент.

Что входит в реализацию под ключ?

  • Проектирование схемы данных и парсера под ваши сайты.
  • Развёртывание на вашем сервере или в облаке.
  • Настройка уведомлений (Telegram, Slack, email).
  • Интеграция с вашей CRM или ERP через API.
  • Документация и обучение вашей команды.
  • Гарантия поддержки 3 месяца после запуска.

Оцените экономию на вашем объёме — свяжитесь с нами для рассчёта. Получите консультацию по вашему проекту — рассчитаем стоимость и сроки за один день.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060