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

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

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

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

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

Отзывы на Яндекс.Маркете, Ozon, Wildberries, Otzovik, Google Maps и других площадках влияют на покупательское решение задолго до того, как пользователь зайдёт на ваш сайт. Своевременная реакция на негативный отзыв снижает репутационный ущерб; отработанный негатив часто превращается в лояльного клиента.

Задача бота

  • Сканировать страницы товаров/компании на внешних площадках
  • Обнаруживать новые отзывы (положительные и отрицательные)
  • Уведомлять команду о новых отзывах немедленно
  • Хранить историю отзывов для аналитики
  • Вычислять тренды рейтинга по площадкам

Схема данных

CREATE TABLE review_sources (
    id              BIGSERIAL PRIMARY KEY,
    platform        VARCHAR(50) NOT NULL,    -- 'yandex_market', 'ozon', 'google', 'otzovik'
    product_id      BIGINT REFERENCES products(id),
    external_url    TEXT NOT NULL,
    external_id     VARCHAR(255),            -- ID карточки на площадке
    scrape_config   JSONB,
    is_active       BOOLEAN DEFAULT TRUE,
    UNIQUE(platform, external_url)
);

CREATE TABLE reviews (
    id              BIGSERIAL PRIMARY KEY,
    source_id       BIGINT REFERENCES review_sources(id),
    external_id     VARCHAR(255),            -- ID отзыва на площадке
    author          VARCHAR(255),
    rating          SMALLINT,               -- 1–5
    text            TEXT,
    pros            TEXT,
    cons            TEXT,
    published_at    TIMESTAMP,
    discovered_at   TIMESTAMP DEFAULT NOW(),
    sentiment       VARCHAR(20),            -- 'positive', 'negative', 'neutral' (ML)
    is_notified     BOOLEAN DEFAULT FALSE,
    UNIQUE(source_id, external_id)
);

CREATE INDEX idx_reviews_rating ON reviews(source_id, rating);
CREATE INDEX idx_reviews_notified ON reviews(source_id) WHERE is_notified = FALSE;

Адаптеры для платформ

Каждая площадка — отдельный адаптер с реализацией парсинга.

Яндекс.Маркет (API):

class YandexMarketReviewAdapter implements ReviewAdapterInterface
{
    // Яндекс.Маркет предоставляет API для получения отзывов партнёрам
    public function fetchReviews(ReviewSource $source): array
    {
        $modelId = $source->external_id;

        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . config('services.yandex_market.token'),
        ])->get("https://api.partner.market.yandex.ru/v2/models/{$modelId}/reviews", [
            'count'  => 30,
            'page'   => 1,
        ]);

        return collect($response->json('result.reviews', []))
            ->map(fn($r) => new ReviewDTO(
                externalId:  (string) $r['id'],
                author:      $r['author']['name'] ?? 'Аноним',
                rating:      (int) $r['grade'],
                text:        $r['text'] ?? '',
                pros:        $r['pros'] ?? null,
                cons:        $r['cons'] ?? null,
                publishedAt: Carbon::parse($r['date']),
            ))
            ->toArray();
    }
}

Парсинг Ozon (HTML):

class OzonReviewAdapter implements ReviewAdapterInterface
{
    public function fetchReviews(ReviewSource $source): array
    {
        // Ozon загружает отзывы через XHR, поэтому нужен браузер
        $data = $this->playwright->evaluate($source->external_url, <<<JS
            await page.waitForSelector('[data-widget="webReviewProductScore"]', {timeout: 10000});
            const items = document.querySelectorAll('[data-widget="webSingleReview"]');
            return Array.from(items).map(el => ({
                id:        el.dataset.reviewId,
                rating:    parseInt(el.querySelector('[data-rating]')?.dataset.rating) || 0,
                text:      el.querySelector('.review-text')?.textContent?.trim() || '',
                pros:      el.querySelector('.pros')?.textContent?.trim() || null,
                cons:      el.querySelector('.cons')?.textContent?.trim() || null,
                author:    el.querySelector('.author-name')?.textContent?.trim() || 'Аноним',
                date:      el.querySelector('time')?.getAttribute('datetime'),
            }));
        JS);

        return collect($data)->map(fn($r) => new ReviewDTO(
            externalId:  $r['id'],
            author:      $r['author'],
            rating:      $r['rating'],
            text:        $r['text'],
            pros:        $r['pros'],
            cons:        $r['cons'],
            publishedAt: $r['date'] ? Carbon::parse($r['date']) : now(),
        ))->toArray();
    }
}

Определение тональности отзыва

class SentimentAnalyzer
{
    private array $negativeKeywords = [
        'брак', 'сломан', 'не работает', 'возврат', 'обман',
        'разочарован', 'ужас', 'кошмар', 'мусор', 'дрянь',
    ];

    private array $positiveKeywords = [
        'отлично', 'супер', 'доволен', 'рекомендую', 'превзошёл',
        'быстро', 'качественно', 'спасибо',
    ];

    public function analyze(ReviewDTO $review): string
    {
        if ($review->rating <= 2) return 'negative';
        if ($review->rating >= 4) return 'positive';

        // Для рейтинга 3 — анализ текста
        $text = mb_strtolower($review->text . ' ' . $review->cons);

        foreach ($this->negativeKeywords as $kw) {
            if (str_contains($text, $kw)) return 'negative';
        }

        return 'neutral';
    }
}

Для точного анализа тональности — интеграция с OpenAI:

public function analyzeWithAI(string $text): string
{
    $response = $this->openai->chat()->create([
        'model'    => 'gpt-4o-mini',
        'messages' => [
            ['role' => 'system', 'content' => 'Определи тональность отзыва. Ответь одним словом: positive, negative или neutral.'],
            ['role' => 'user',   'content' => $text],
        ],
        'max_tokens' => 10,
    ]);

    return in_array($response->choices[0]->message->content, ['positive', 'negative', 'neutral'])
        ? $response->choices[0]->message->content
        : 'neutral';
}

Уведомления

class ReviewNotifier
{
    public function notifyNew(Review $review): void
    {
        $emoji   = match ($review->sentiment) {
            'positive' => '⭐',
            'negative' => '🚨',
            default    => '💬',
        };

        $stars = str_repeat('★', $review->rating) . str_repeat('☆', 5 - $review->rating);

        $text = "{$emoji} *Новый отзыв* — {$review->source->platform}\n"
            . "{$stars} {$review->rating}/5\n"
            . "*{$review->author}*\n\n"
            . mb_substr($review->text, 0, 300)
            . (mb_strlen($review->text) > 300 ? '...' : '') . "\n\n"
            . "[Открыть отзыв]({$review->source->external_url})";

        $chatId = $review->sentiment === 'negative'
            ? config('telegram.urgent_reviews_chat')
            : config('telegram.reviews_chat');

        $this->telegram->sendMessage([
            'chat_id'    => $chatId,
            'text'       => $text,
            'parse_mode' => 'Markdown',
        ]);

        $review->update(['is_notified' => true]);
    }
}

Аналитика рейтинга

// Агрегат рейтинга по платформам за последние 30 дней
SELECT
    rs.platform,
    COUNT(*) AS total_reviews,
    ROUND(AVG(r.rating), 2) AS avg_rating,
    COUNT(*) FILTER (WHERE r.rating <= 2) AS negative_count,
    COUNT(*) FILTER (WHERE r.rating >= 4) AS positive_count
FROM reviews r
JOIN review_sources rs ON r.source_id = rs.id
WHERE r.published_at >= NOW() - INTERVAL '30 days'
GROUP BY rs.platform
ORDER BY avg_rating;

Расписание проверки

// Быстрые платформы с API — каждый час
$schedule->command('reviews:check --platform=yandex_market')->hourly();

// Парсинг через браузер — каждые 4 часа (ресурсоёмко)
$schedule->command('reviews:check --platform=ozon')->everyFourHours();
$schedule->command('reviews:check --platform=wildberries')->everyFourHours();

// Еженедельный сводный отчёт с трендом рейтинга
$schedule->job(new WeeklyReviewsReportJob)->weekly()->mondays()->at('09:00');

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

  • Схема данных + базовый адаптер (HTML-парсинг): 1–2 дня
  • Адаптер для API Яндекс.Маркета: 0.5 дня
  • Playwright-адаптеры для Ozon/WB: 1–2 дня
  • SentimentAnalyzer + уведомления Telegram: 1 день
  • Дашборд аналитики рейтинга в админке: 1 день

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