Разработка бота для автоматического управления ценами на маркетплейсах (Repricing)

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бота для автоматического управления ценами на маркетплейсах (Repricing)
Сложная
~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

Разработка бота для автоматического управления ценами на маркетплейсах (Repricing)

Repricing — автоматическое изменение цен на маркетплейсах в ответ на действия конкурентов или рыночную ситуацию. Цель: удерживать позицию в топе поиска и Buy Box без ручного мониторинга. Неправильно настроенный repricing — это ценовая война до нуля или продажи ниже себестоимости. Правильно настроенный — стабильный рост конверсии при сохранении маржинальности.

Модели репрайсинга

Стратегия Описание Когда применять
Min Price Держать цену на уровне лучшего конкурента Конкурентный рынок без уникального предложения
Buy Box Оптимизировать под выигрыш в Buy Box на Ozon/WB Мультиселлерные позиции
Margin Floor Не опускаться ниже заданной маржи Всегда, как ограничитель
Rule-Based Набор условий (если конкурент < нашей цены — снизить на X%) Гибкие сценарии
Demand-Based Поднять цену при высоком спросе / остатках Товары с непостоянным спросом

Схема данных

CREATE TABLE repricing_rules (
    id              BIGSERIAL PRIMARY KEY,
    name            VARCHAR(255) NOT NULL,
    marketplace     VARCHAR(50) NOT NULL,      -- 'ozon', 'wildberries', 'yandex_market'
    scope_type      VARCHAR(20) NOT NULL,       -- 'global', 'category', 'product'
    scope_id        BIGINT,

    strategy        VARCHAR(30) NOT NULL,       -- 'min_price', 'buy_box', 'rule_based'
    min_price_mode  VARCHAR(20) DEFAULT 'margin_floor', -- 'fixed' | 'margin_floor'
    min_price_value NUMERIC(12,2),             -- фиксированная минимальная цена
    min_margin_pct  NUMERIC(5,2) DEFAULT 10,   -- минимальная маржа в %
    max_price       NUMERIC(12,2),             -- потолок цены
    step_pct        NUMERIC(5,2) DEFAULT 1.0,  -- шаг изменения в %
    step_abs        NUMERIC(10,2),             -- или шаг в рублях
    cooldown_minutes INT DEFAULT 60,           -- минимальный интервал между изменениями

    is_active       BOOLEAN DEFAULT TRUE
);

CREATE TABLE repricing_log (
    id              BIGSERIAL PRIMARY KEY,
    product_id      BIGINT REFERENCES products(id),
    marketplace     VARCHAR(50),
    old_price       NUMERIC(12,2),
    new_price       NUMERIC(12,2),
    reason          TEXT,
    rule_id         BIGINT REFERENCES repricing_rules(id),
    triggered_at    TIMESTAMP DEFAULT NOW()
);

Получение конкурентных цен

Ozon API — цены конкурентов в Buy Box:

class OzonCompetitorPriceClient
{
    public function getCompetitorPrices(string $offerId): array
    {
        $response = Http::withHeaders([
            'Client-Id' => $this->clientId,
            'Api-Key'   => $this->apiKey,
        ])->post('https://api-seller.ozon.ru/v1/product/info/competitor-price', [
            'offer_id' => $offerId,
        ]);

        return $response->json('result', []);
    }
}

Wildberries — анализ через карточку товара:

class WildberriesPriceClient
{
    public function getSellerPrices(int $nmId): array
    {
        // WB API v2 для получения прайслиста конкурентов на товар
        $response = Http::get("https://card.wb.ru/cards/detail", [
            'nm'    => $nmId,
            'spp'   => 27,
            'curr'  => 'rub',
        ]);

        $products = $response->json('data.products', []);
        $product  = collect($products)->firstWhere('id', $nmId);

        return $product ? $product['sizes'] ?? [] : [];
    }
}

Движок репрайсинга

class RepricingEngine
{
    public function calculateNewPrice(
        Product       $product,
        string        $marketplace,
        RepricingRule $rule,
    ): ?PriceDecision {
        $competitorData = $this->getCompetitorData($product, $marketplace);
        $costPrice      = $product->cost_price ?? 0;
        $currentPrice   = $this->getCurrentMarketplacePrice($product, $marketplace);

        $decision = match ($rule->strategy) {
            'min_price'  => $this->strategyMinPrice($currentPrice, $competitorData, $rule, $costPrice),
            'buy_box'    => $this->strategyBuyBox($currentPrice, $competitorData, $rule, $costPrice),
            'rule_based' => $this->strategyRuleBased($currentPrice, $competitorData, $rule, $costPrice),
            default      => null,
        };

        if (!$decision) return null;

        // Проверка cooldown — не менять цену слишком часто
        $lastChange = RepricingLog::where('product_id', $product->id)
            ->where('marketplace', $marketplace)
            ->where('triggered_at', '>=', now()->subMinutes($rule->cooldown_minutes))
            ->exists();

        if ($lastChange) return null;

        return $decision;
    }

    private function strategyMinPrice(
        float $current, array $competitors, RepricingRule $rule, float $costPrice
    ): ?PriceDecision {
        $competitorMin = collect($competitors)->min('price');
        if (!$competitorMin) return null;

        $floor = $this->calculateFloor($rule, $costPrice);

        // Конкурент дешевле — снизить до его цены (не ниже floor)
        if ($competitorMin < $current) {
            $newPrice = max($competitorMin, $floor);
            if ($newPrice >= $current) return null; // нет смысла

            return new PriceDecision(
                newPrice: $newPrice,
                reason:   "Конкурент снизил цену до {$competitorMin}",
            );
        }

        // Конкурент дороже — можно поднять (не выше max_price)
        if ($competitorMin > $current && $rule->max_price && $current < $rule->max_price) {
            $newPrice = min($competitorMin - 1, $rule->max_price);
            return new PriceDecision(
                newPrice: $newPrice,
                reason:   "Конкурент поднял цену до {$competitorMin}",
            );
        }

        return null;
    }

    private function calculateFloor(RepricingRule $rule, float $costPrice): float
    {
        if ($rule->min_price_mode === 'fixed' && $rule->min_price_value) {
            return $rule->min_price_value;
        }

        if ($rule->min_margin_pct && $costPrice > 0) {
            return $costPrice * (1 + $rule->min_margin_pct / 100);
        }

        return 0;
    }
}

Публикация цены через API

class OzonPricePublisher
{
    public function setPrice(string $offerId, float $newPrice): bool
    {
        $response = Http::withHeaders([
            'Client-Id' => $this->clientId,
            'Api-Key'   => $this->apiKey,
        ])->post('https://api-seller.ozon.ru/v1/product/import/prices', [
            'prices' => [[
                'offer_id'       => $offerId,
                'price'          => (string) $newPrice,
                'old_price'      => '0',
                'premium_price'  => '0',
                'price_strategy_enabled' => false,
            ]],
        ]);

        return $response->successful()
            && collect($response->json('result', []))->first()['updated'] === true;
    }
}

Защита от ценовых войн

Ценовая война — ситуация, когда два конкурента бесконечно снижают цены друг за другом. Механизмы защиты:

class PriceWarDetector
{
    public function isWarring(int $productId, string $marketplace): bool
    {
        // Если цена изменялась более 5 раз за 24 часа — признак войны
        $changes = RepricingLog::where('product_id', $productId)
            ->where('marketplace', $marketplace)
            ->where('triggered_at', '>=', now()->subDay())
            ->count();

        if ($changes >= 5) {
            // Остановить repricing на 6 часов и уведомить
            Cache::put("repricing.paused.{$productId}.{$marketplace}", true, now()->addHours(6));
            Notification::send($this->admins, new PriceWarAlert($productId, $marketplace));
            return true;
        }

        return false;
    }
}

Мониторинг и отчёты

-- Статистика изменений цен за день
SELECT
    p.name,
    rl.marketplace,
    COUNT(*) AS changes_count,
    MIN(rl.new_price) AS min_price_today,
    MAX(rl.new_price) AS max_price_today,
    ROUND(AVG(rl.new_price), 2) AS avg_price_today
FROM repricing_log rl
JOIN products p ON p.id = rl.product_id
WHERE rl.triggered_at >= NOW() - INTERVAL '24 hours'
GROUP BY p.name, rl.marketplace
ORDER BY changes_count DESC;

Расписание

// Запуск репрайсинга каждые 30 минут
$schedule->job(new RunRepricingJob)->everyThirtyMinutes();

// Ночью — сброс счётчиков и пересчёт стратегий
$schedule->job(new ResetRepricingCountersJob)->dailyAt('03:00');

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

  • Схема данных + движок базовых стратегий: 2 дня
  • Ozon API интеграция (цены конкурентов + публикация): 1–2 дня
  • Wildberries API: +1 день
  • PriceWarDetector + cooldown + алерты: 1 день
  • Интерфейс управления правилами + лог изменений: 1–2 дня

Итого: 6–8 рабочих дней.