Разработка бота-парсера цен конкурентов по расписанию

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

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

Информационные сайты или веб-приложения
Сайты визитки, 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

Разработка бота-парсера цен конкурентов по расписанию

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

Архитектура системы мониторинга

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);
            }
        }
    }
}

Движок автоматического перейценивания

// app/Services/PriceMonitor/RepricingEngine.php
class RepricingEngine
{
    public function recalculate(int $productId): void
    {
        $product = Product::with('competitorPrices', 'repricingRule')->findOrFail($productId);
        $rule = $product->repricingRule;

        if (!$rule || !$rule->is_active) return;

        $competitorPrices = $product->competitorPrices
            ->where('in_stock', true)
            ->pluck('price');

        if ($competitorPrices->isEmpty()) return;

        $newPrice = match ($rule->strategy) {
            'beat_lowest'  => $competitorPrices->min() - $rule->delta,
            'match_lowest' => $competitorPrices->min(),
            'beat_average' => $competitorPrices->avg() - $rule->delta,
            'percentile'   => $this->percentile($competitorPrices, $rule->percentile),
            default        => null,
        };

        if (!$newPrice) return;

        // Применяем ограничения
        $newPrice = max($newPrice, $rule->min_price ?? 0);
        $newPrice = min($newPrice, $rule->max_price ?? PHP_FLOAT_MAX);

        // Не перейцениваем, если изменение < 0.5%
        $currentPrice = $product->price;
        if (abs($newPrice - $currentPrice) / $currentPrice < 0.005) return;

        $product->update(['price' => round($newPrice, 2)]);

        Log::info("Repriced product #{$productId}", [
            'old_price' => $currentPrice,
            'new_price' => $newPrice,
            'strategy'  => $rule->strategy,
        ]);
    }
}

Расписание мониторинга

// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
    // Высокоприоритетные товары — каждые 2 часа
    $schedule->command('monitor:prices --priority=high')
        ->everyTwoHours()->withoutOverlapping();

    // Все товары — раз в день, ночью
    $schedule->command('monitor:prices --all')
        ->dailyAt('02:00')->withoutOverlapping();

    // Агрегация истории цен
    $schedule->command('prices:aggregate-history')
        ->dailyAt('23:50');
}

Дашборд мониторинга

Ключевые метрики для отображения:

Метрика Описание
Товары, где мы дороже Количество и % от каталога
Средняя дельта к минимуму Средний % разницы с lowest price
Товары с изменением > 5% Требуют ручной проверки
Товары вне остатков у конкурентов Потенциал для повышения цены

Срок разработки: мониторинг 1 конкурента + история + алерты — 4-6 рабочих дней. Система с автоперейценкой и дашбордом для 5+ конкурентов — 10-14 дней.