Розробка бота для моніторингу відгуків на товари на зовнішніх майданчиках

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, 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

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

Відзиви на Yandex.Market, 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;

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

Кожна майданчик — окремий адаптер з реалізацією парсингу.

Yandex.Market (API):

class YandexMarketReviewAdapter implements ReviewAdapterInterface
{
    // Yandex.Market надає 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 Yandex.Market: 0.5 дня
  • Playwright-адаптери для Ozon/WB: 1–2 дні
  • SentimentAnalyzer + сповіщення Telegram: 1 день
  • Дашборд аналітики рейтингу в админці: 1 день

Разом: 4–5 робочих днів.