Реализация AI-рекомендаций контента на сайте

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация AI-рекомендаций контента на сайте
Сложная
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация AI-рекомендаций контента на сайте

Рекомендации контента удерживают пользователей и увеличивают глубину просмотра. Подход зависит от наличия данных о поведении: для нового сайта без истории — content-based фильтрация на эмбеддингах; для сайта с тысячами пользователей и событиями — коллаборативная фильтрация или двухуровневые системы.

Выбор подхода

Подход Данные Сложность Когда
Content-based (эмбеддинги) Только контент Низкая Новый сайт, малая аудитория
Коллаборативная фильтрация История взаимодействий Средняя 10K+ пользователей
Гибридная Контент + поведение Высокая Медиа, блоги, новостные сайты
LLM-based Контент + профиль Средняя Персонализованные подборки

Content-Based: похожий контент через эмбеддинги

Самый быстрый старт — похожие статьи на основе векторного расстояния:

import OpenAI from 'openai';
import { sql } from '@vercel/postgres';

const openai = new OpenAI();

// Индексирование при публикации статьи
async function indexArticle(article) {
  const textToEmbed = [
    article.title,
    article.excerpt,
    article.tags.join(', '),
    article.body.slice(0, 2000), // первые 2000 символов
  ].join('\n\n');

  const { data: [{ embedding }] } = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: textToEmbed,
  });

  await sql`
    UPDATE articles
    SET embedding = ${JSON.stringify(embedding)}::vector
    WHERE id = ${article.id}
  `;
}

// Получение похожих статей
async function getSimilarArticles(articleId, limit = 6) {
  const result = await sql`
    WITH source AS (
      SELECT embedding FROM articles WHERE id = ${articleId}
    )
    SELECT
      a.id, a.title, a.slug, a.excerpt, a.published_at,
      a.category, a.read_time,
      1 - (a.embedding <=> source.embedding) AS similarity
    FROM articles a, source
    WHERE a.id != ${articleId}
      AND a.published = true
      AND a.embedding IS NOT NULL
    ORDER BY a.embedding <=> source.embedding
    LIMIT ${limit}
  `;

  return result.rows;
}

Коллаборативная фильтрация: "пользователи похожие на тебя читали"

Матричная факторизация через implicit feedback (просмотры, время на странице):

# Python-скрипт для периодического обучения (cron)
import implicit
import numpy as np
from scipy.sparse import csr_matrix
import pickle

def train_collaborative_model():
    # Загружаем события: user_id, article_id, weight
    # weight = 1 (просмотр) + 2 (scroll 50%) + 5 (прочитал до конца) + 10 (поделился)
    events = fetch_events_from_db()

    users = {u: i for i, u in enumerate(events['user_id'].unique())}
    items = {a: i for i, a in enumerate(events['article_id'].unique())}

    rows = events['user_id'].map(users)
    cols = events['article_id'].map(items)
    data = events['weight']

    matrix = csr_matrix((data, (rows, cols)))

    model = implicit.als.AlternatingLeastSquares(
        factors=128,
        regularization=0.01,
        iterations=50,
        use_gpu=False,
    )
    model.fit(matrix)

    # Сохраняем модель и маппинги
    with open('/models/collab_model.pkl', 'wb') as f:
        pickle.dump({ 'model': model, 'users': users, 'items': items }, f)
// Node.js: получение рекомендаций через Python-сервис
async function getCollaborativeRecs(userId, limit = 10) {
  const response = await fetch('http://ml-service:5000/recommend', {
    method: 'POST',
    body: JSON.stringify({ user_id: userId, limit }),
  });
  return response.json();
}

Гибридная система с персонализацией

Объединяем content-based и коллаборативные сигналы:

async function getPersonalizedRecommendations(userId, currentArticleId) {
  const [contentBased, collaborative, trending] = await Promise.all([
    getSimilarArticles(currentArticleId, 10),
    getCollaborativeRecs(userId, 10),
    getTrendingArticles(10), // по просмотрам за последние 24 часа
  ]);

  // Объединяем с весами
  const scores = new Map();

  contentBased.forEach((article, i) => {
    scores.set(article.id, (scores.get(article.id) || 0) + (10 - i) * 0.4);
  });

  collaborative.forEach((article, i) => {
    scores.set(article.id, (scores.get(article.id) || 0) + (10 - i) * 0.5);
  });

  trending.forEach((article, i) => {
    scores.set(article.id, (scores.get(article.id) || 0) + (10 - i) * 0.1);
  });

  // Сортируем по итоговому скору
  const allArticleIds = [...scores.keys()];
  const articles = await fetchArticlesByIds(allArticleIds);

  return articles
    .map(a => ({ ...a, score: scores.get(a.id) }))
    .sort((a, b) => b.score - a.score)
    .slice(0, 6);
}

LLM-рекомендации с объяснением

Для более умного подбора и персонализированного объяснения:

async function getLLMRecommendations(user, readHistory, availableArticles) {
  const userProfile = `
    Прочитал: ${readHistory.map(a => a.title).join(', ')}
    Категории интересов: ${getTopCategories(readHistory).join(', ')}
    Среднее время чтения: ${user.avgReadTime} мин
  `;

  const articlesList = availableArticles.slice(0, 20).map(a =>
    `ID:${a.id} | ${a.title} | ${a.category} | ${a.tags.join(',')}`
  ).join('\n');

  const response = await openai.chat.completions.create({
    model: 'gpt-4o-mini',
    response_format: { type: 'json_object' },
    messages: [
      {
        role: 'system',
        content: 'Ты рекомендательная система. Отвечай JSON: { recommendations: [{id, reason}] }',
      },
      {
        role: 'user',
        content: `Профиль: ${userProfile}\n\nДоступные статьи:\n${articlesList}\n\nВыбери 4 наиболее релевантных для этого пользователя.`,
      },
    ],
    max_tokens: 400,
  });

  const { recommendations } = JSON.parse(response.choices[0].message.content);

  // Обогащаем данными из БД
  return Promise.all(recommendations.map(async rec => ({
    ...await fetchArticle(rec.id),
    reason: rec.reason, // "Вы читали похожие материалы о React"
  })));
}

Трекинг событий

Данные о поведении — основа для улучшения рекомендаций:

// Клиентский трекер
class ReadingTracker {
  constructor(articleId) {
    this.articleId = articleId;
    this.startTime = Date.now();
    this.maxScroll = 0;
    this.trackScroll();
  }

  trackScroll() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const progress = entry.target.dataset.progress;
          if (progress > this.maxScroll) {
            this.maxScroll = progress;
            this.sendEvent('scroll', { progress });
          }
        }
      });
    });

    document.querySelectorAll('[data-progress]').forEach(el => observer.observe(el));
  }

  async sendEvent(type, data = {}) {
    navigator.sendBeacon('/api/track', JSON.stringify({
      type,
      articleId: this.articleId,
      timeOnPage: Date.now() - this.startTime,
      ...data,
    }));
  }
}

Кэширование рекомендаций

Рекомендации — дорогая операция, кэшируем:

async function getCachedRecommendations(userId, articleId) {
  const cacheKey = `recs:${userId}:${articleId}`;
  const cached = await redis.get(cacheKey);

  if (cached) return JSON.parse(cached);

  const recs = await getPersonalizedRecommendations(userId, articleId);
  await redis.setex(cacheKey, 3600, JSON.stringify(recs)); // 1 час

  return recs;
}

Сроки

  • Content-based рекомендации через pgvector — 3–4 дня
  • Трекинг событий + аналитика поведения — плюс 2 дня
  • Коллаборативная фильтрация (implicit ALS) — плюс 3–4 дня
  • Гибридная система с LLM-объяснениями — 2–3 недели полного цикла
  • A/B тестирование алгоритмов — плюс 2–3 дня