Разработка бота для мониторинга появления новых товаров у конкурентов

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бота для мониторинга появления новых товаров у конкурентов
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

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

Этапы разработки

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

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка бота для мониторинга появления новых товаров у конкурентов

Новинки у конкурентов — сигнал для закупок, ценообразования и SEO. Если конкурент выложил новую линейку, а вы узнали об этом через неделю — упущены позиции в поиске и часть аудитории, которая уже выбрала другой магазин. Бот отслеживает появление новых SKU на страницах конкурентов и немедленно уведомляет команду.

Принцип работы

Мониторинг новых товаров отличается от мониторинга цен: здесь нужно следить не за конкретным URL, а за разделами каталога — категориями, страницами "Новинки", результатами поиска.

Конфигурация (URL каталога + селектор) → Scraper → Snapshot → Diff → Alert

Алгоритм:

  1. Скачать страницу категории/раздела конкурента
  2. Извлечь список товаров (URL + название + SKU)
  3. Сравнить с предыдущим снимком
  4. Новые позиции — отправить в уведомление

Схема данных

CREATE TABLE competitor_catalogs (
    id              BIGSERIAL PRIMARY KEY,
    competitor_id   INT REFERENCES competitors(id),
    url             TEXT NOT NULL,              -- URL страницы категории
    scrape_config   JSONB NOT NULL,             -- CSS-селекторы
    check_interval  INTERVAL DEFAULT '6 hours',
    last_checked_at TIMESTAMP,
    is_active       BOOLEAN DEFAULT TRUE
);

-- Каждый товар, обнаруженный у конкурента
CREATE TABLE competitor_items (
    id              BIGSERIAL PRIMARY KEY,
    catalog_id      BIGINT REFERENCES competitor_catalogs(id),
    external_url    TEXT NOT NULL,
    title           TEXT,
    external_sku    VARCHAR(255),
    price           NUMERIC(12,2),
    image_url       TEXT,
    first_seen_at   TIMESTAMP DEFAULT NOW(),
    last_seen_at    TIMESTAMP DEFAULT NOW(),
    is_new          BOOLEAN DEFAULT TRUE,       -- сброшен после уведомления
    UNIQUE(catalog_id, external_url)
);

CREATE INDEX idx_competitor_items_new
    ON competitor_items(catalog_id, first_seen_at)
    WHERE is_new = TRUE;

Конфигурация через JSON

Каждый конкурент настраивается через JSONB-конфиг:

{
  "pagination": {
    "type": "url_param",
    "param": "page",
    "max_pages": 20
  },
  "item_selector": ".catalog-item",
  "fields": {
    "url":   {"selector": "a.product-link", "attr": "href"},
    "title": {"selector": ".product-name", "text": true},
    "sku":   {"selector": "[data-sku]", "attr": "data-sku"},
    "price": {"selector": ".price", "text": true},
    "image": {"selector": "img.product-image", "attr": "src"}
  }
}

Scraper для каталога

class CatalogScraper
{
    public function scrape(CompetitorCatalog $catalog): array
    {
        $config = $catalog->scrape_config;
        $items  = [];

        $maxPages = $config['pagination']['max_pages'] ?? 1;

        for ($page = 1; $page <= $maxPages; $page++) {
            $url  = $this->buildPageUrl($catalog->url, $config['pagination'], $page);
            $html = $this->fetchWithRetry($url);

            if (!$html) break;

            $pageItems = $this->extractItems($html, $config);

            if (empty($pageItems)) break; // Последняя страница

            $items = array_merge($items, $pageItems);

            // Уважительная задержка между страницами
            usleep(rand(1_500_000, 3_000_000));
        }

        return $items;
    }

    private function extractItems(string $html, array $config): array
    {
        $crawler = new \Symfony\Component\DomCrawler\Crawler($html);
        $items   = [];

        $crawler->filter($config['item_selector'])->each(function ($node) use ($config, &$items) {
            $item = [];

            foreach ($config['fields'] as $field => $fieldConfig) {
                try {
                    $el = $node->filter($fieldConfig['selector'])->first();
                    if ($el->count() === 0) continue;

                    $item[$field] = isset($fieldConfig['attr'])
                        ? $el->attr($fieldConfig['attr'])
                        : $el->text();
                } catch (\Exception $e) {
                    continue;
                }
            }

            if (!empty($item['url'])) {
                $items[] = $item;
            }
        });

        return $items;
    }
}

Сервис обнаружения новинок

class NewProductDetectionService
{
    public function process(CompetitorCatalog $catalog): DetectionResult
    {
        $scraped  = $this->scraper->scrape($catalog);
        $newItems = [];

        foreach ($scraped as $item) {
            $url = $this->normalizeUrl($item['url'], $catalog->url);

            $existing = CompetitorItem::where([
                'catalog_id'   => $catalog->id,
                'external_url' => $url,
            ])->first();

            if (!$existing) {
                // Новый товар!
                $created = CompetitorItem::create([
                    'catalog_id'   => $catalog->id,
                    'external_url' => $url,
                    'title'        => $item['title'] ?? null,
                    'external_sku' => $item['sku'] ?? null,
                    'price'        => $this->parsePrice($item['price'] ?? ''),
                    'image_url'    => $item['image'] ?? null,
                    'is_new'       => true,
                ]);
                $newItems[] = $created;
            } else {
                // Обновить время последнего обнаружения
                $existing->update(['last_seen_at' => now()]);
            }
        }

        // Товары, исчезнувшие из каталога конкурента
        $disappeared = CompetitorItem::where('catalog_id', $catalog->id)
            ->where('last_seen_at', '<', now()->subDays(3))
            ->get();

        $catalog->update(['last_checked_at' => now()]);

        return new DetectionResult(newItems: $newItems, disappeared: $disappeared);
    }
}

Уведомления в Telegram

class NewProductNotifier
{
    public function notify(DetectionResult $result, CompetitorCatalog $catalog): void
    {
        if ($result->newItems->isEmpty()) return;

        $lines = ["🆕 *Новые товары у {$catalog->competitor->name}*\n"];

        foreach ($result->newItems->take(10) as $item) {
            $price = $item->price ? number_format($item->price, 0, '.', ' ') . ' руб.' : 'цена не определена';
            $lines[] = "• [{$item->title}]({$item->external_url}) — {$price}";
        }

        if ($result->newItems->count() > 10) {
            $lines[] = "\n_...и ещё " . ($result->newItems->count() - 10) . " товаров_";
        }

        $this->telegram->sendMessage([
            'chat_id'                  => config('telegram.new_products_chat'),
            'text'                     => implode("\n", $lines),
            'parse_mode'               => 'Markdown',
            'disable_web_page_preview' => true,
        ]);

        // Сбросить флаг is_new
        CompetitorItem::whereIn('id', $result->newItems->pluck('id'))
            ->update(['is_new' => false]);
    }
}

Обход защит от ботов

Большинство крупных магазинов используют защиту. Стратегии обхода:

Защита Метод обхода
Cloudflare Bot Management Playwright + stealth plugin
Rate limiting Случайные задержки 2–5 сек между запросами
IP-блокировки Ротация прокси (residential proxies)
Требование cookies/сессии Headless-браузер с сохранением сессии
JS-рендеринг Playwright/Puppeteer вместо curl

Расписание

// Проверка каждые 6 часов для стандартных каталогов
$schedule->command('competitors:scan-catalogs')->everySixHours();

// Ежедневный сводный отчёт
$schedule->job(new WeeklyNewProductsReportJob)->weekly()->mondays()->at('08:00');

Дополнительные возможности

  • Автоматическое добавление в список закупок — найденные новинки конкурента сразу попадают в задачи байера
  • Уведомление об исчезновении — товар пропал у конкурента (снят с продажи, нет в наличии)
  • Ценовое сравнение — если аналог есть в нашем каталоге, сразу показать разницу цен

Сроки реализации

  • CatalogScraper + конфигурация через JSON: 1–2 дня
  • NewProductDetectionService + схема данных: 1 день
  • Уведомления Telegram: 0.5 дня
  • Playwright-адаптер для JS-сайтов: +1 день
  • Ротация прокси: +0.5 дня

Итого: 3–4 рабочих дня.