Telegram бот для поиска событий на Python: пошаговый туториал с LLM | AiManual
AiManual Logo Ai / Manual.
29 Дек 2025 Инструмент

Умная афиша на Python и LLM: как сделать своего Telegram-бота для поиска событий

Создайте умного Telegram-бота для поиска мероприятий на Python с использованием LLM и векторного поиска. Полный гайд по архитектуре и развёртыванию.

Почему именно Telegram-бот для поиска событий?

В мире, перегруженном информацией, найти интересные мероприятия становится настоящей проблемой. Традиционные афиши и агрегаторы событий часто предлагают слишком общие результаты или требуют сложных фильтров. Решение? Умный Telegram-бот, который понимает естественный язык и находит именно то, что вам нужно.

Представьте: вы пишете боту "Хочу сходить на что-то интересное в пятницу вечером, не очень дорогое, в центре города", и он предлагает подборку мероприятий, соответствующих всем критериям. Именно такой бот мы создадим сегодня, используя Python, современные языковые модели и векторный поиск.

Этот проект отлично подходит для портфолио разработчика, демонстрируя навыки работы с LLM, Telegram API и архитектурой RAG (Retrieval-Augmented Generation).

Архитектура умной афиши

Наш бот будет работать по следующей схеме:

  1. Пользователь отправляет запрос в Telegram
  2. Бот преобразует запрос в векторное представление
  3. Система ищет похожие события в векторной базе данных
  4. LLM анализирует результаты и формирует ответ
  5. Пользователь получает персонализированные рекомендации
КомпонентТехнологияНазначение
Telegram-ботpython-telegram-botИнтерфейс общения с пользователем
ВекторизацияSentence TransformersПреобразование текста в векторы
База данныхQdrant / ChromaDBХранение и поиск векторных эмбеддингов
Языковая модельOllama / OpenAI APIАнализ и генерация ответов
Парсинг событийBeautifulSoup / ScrapyСбор данных с сайтов мероприятий

Пошаговая реализация

1Подготовка окружения и установка зависимостей

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

# Создание виртуального окружения
python -m venv venv
source venv/bin/activate  # для Windows: venv\Scripts\activate

# Установка основных зависимостей
pip install python-telegram-bot
pip install sentence-transformers
pip install qdrant-client
pip install beautifulsoup4
pip install requests

# Для работы с локальными LLM через Ollama
pip install ollama

# Или для использования OpenAI API
pip install openai
💡
Если вы предпочитаете работать с локальными моделями, как в статье про Claude Code для локальных LLM, установите Ollama и скачайте подходящую модель, например Mistral или Llama 3.1.

2Создание Telegram-бота

Сначала получите токен бота у @BotFather в Telegram, затем создайте основной файл бота:

import logging
from telegram import Update
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes

# Настройка логирования
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# Токен вашего бота (замените на свой)
TOKEN = "ВАШ_ТОКЕН_БОТА"

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик команды /start"""
    welcome_text = """👋 Привет! Я умная афиша событий.
    \nПросто напишите, какие мероприятия вас интересуют, например:
    • 'Концерты на выходные'
    • 'Выставки современного искусства'
    • 'Бесплатные лекции в Москве'
    \nЯ найду для вас самые подходящие варианты!"""
    await update.message.reply_text(welcome_text)

async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик текстовых сообщений"""
    user_message = update.message.text
    user_id = update.effective_user.id
    
    logger.info(f"Пользователь {user_id}: {user_message}")
    
    # Здесь будет логика поиска событий
    response = await search_events(user_message)
    
    await update.message.reply_text(response)

async def search_events(query: str) -> str:
    """Поиск событий по запросу пользователя"""
    # Временный заглушка
    return f"🔍 Ищу события по запросу: '{query}'...\n\n(Функция поиска в разработке)"

def main():
    """Запуск бота"""
    application = Application.builder().token(TOKEN).build()
    
    # Регистрация обработчиков
    application.add_handler(CommandHandler("start", start))
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
    
    # Запуск бота
    application.run_polling(allowed_updates=Update.ALL_TYPES)

if __name__ == '__main__':
    main()

3Парсинг и подготовка данных о событиях

Создайте модуль для сбора данных с сайтов мероприятий:

import requests
from bs4 import BeautifulSoup
import json
from datetime import datetime
from typing import List, Dict

class EventParser:
    def __init__(self):
        self.events = []
    
    def parse_kudago(self, city: str = "msk") -> List[Dict]:
        """Парсинг событий с KudaGo"""
        url = f"https://kudago.com/{city}/events/"
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        
        try:
            response = requests.get(url, headers=headers, timeout=10)
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # Пример парсинга (структура сайта может меняться)
            event_cards = soup.find_all('div', class_='post-card')
            
            for card in event_cards[:20]:  # Ограничим количество
                event = {
                    'title': self._get_text(card, 'h3'),
                    'description': self._get_text(card, 'p', class_='description'),
                    'date': self._get_text(card, 'time'),
                    'price': self._get_text(card, 'span', class_='price'),
                    'venue': self._get_text(card, 'span', class_='venue'),
                    'category': self._extract_category(card),
                    'url': self._extract_url(card),
                    'source': 'KudaGo',
                    'timestamp': datetime.now().isoformat()
                }
                
                if event['title']:
                    self.events.append(event)
                    
        except Exception as e:
            print(f"Ошибка парсинга KudaGo: {e}")
        
        return self.events
    
    def _get_text(self, element, tag: str, **kwargs):
        """Вспомогательный метод для извлечения текста"""
        found = element.find(tag, **kwargs)
        return found.text.strip() if found else ""
    
    def _extract_category(self, element):
        """Извлечение категории события"""
        # Логика определения категории
        return "Разное"
    
    def _extract_url(self, element):
        """Извлечение URL события"""
        link = element.find('a', href=True)
        return f"https://kudago.com{link['href']}" if link else ""
    
    def save_to_json(self, filename: str = "events.json"):
        """Сохранение событий в JSON файл"""
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(self.events, f, ensure_ascii=False, indent=2)
        print(f"Сохранено {len(self.events)} событий в {filename}")

# Использование
if __name__ == "__main__":
    parser = EventParser()
    events = parser.parse_kudago()
    parser.save_to_json()

Важно: При парсинге сайтов соблюдайте правила robots.txt и не перегружайте серверы запросами. Рассмотрите использование официальных API, если они доступны.

4Векторизация и поиск событий

Создайте систему векторного поиска с использованием Sentence Transformers и Qdrant:

from sentence_transformers import SentenceTransformer
import json
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import numpy as np

class VectorSearch:
    def __init__(self, collection_name="events"):
        # Используем легкую модель для эмбеддингов
        self.model = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2')
        
        # Инициализация Qdrant (можно использовать in-memory для тестов)
        self.client = QdrantClient(":memory:")  # Для продакшена используйте Qdrant Cloud или Docker
        
        self.collection_name = collection_name
        self._init_collection()
    
    def _init_collection(self):
        """Инициализация коллекции в Qdrant"""
        # Размерность векторов нашей модели
        vector_size = self.model.get_sentence_embedding_dimension()
        
        # Создаем коллекцию, если её нет
        collections = self.client.get_collections().collections
        collection_names = [c.name for c in collections]
        
        if self.collection_name not in collection_names:
            self.client.create_collection(
                collection_name=self.collection_name,
                vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE)
            )
    
    def add_events(self, events_file: str = "events.json"):
        """Добавление событий в векторную базу"""
        with open(events_file, 'r', encoding='utf-8') as f:
            events = json.load(f)
        
        points = []
        for idx, event in enumerate(events):
            # Создаем текстовое представление события для векторизации
            event_text = f"{event['title']}. {event['description']}. Категория: {event['category']}. " \
                        f"Место: {event['venue']}. Цена: {event['price']}. Дата: {event['date']}."
            
            # Генерируем векторное представление
            vector = self.model.encode(event_text).tolist()
            
            # Создаем точку для Qdrant
            point = PointStruct(
                id=idx,
                vector=vector,
                payload=event
            )
            points.append(point)
        
        # Загружаем точки в коллекцию
        self.client.upsert(
            collection_name=self.collection_name,
            points=points
        )
        
        print(f"Добавлено {len(points)} событий в коллекцию {self.collection_name}")
    
    def search(self, query: str, limit: int = 5):
        """Поиск событий по текстовому запросу"""
        # Векторизуем запрос
        query_vector = self.model.encode(query).tolist()
        
        # Ищем похожие события
        search_result = self.client.search(
            collection_name=self.collection_name,
            query_vector=query_vector,
            limit=limit
        )
        
        return [hit.payload for hit in search_result]

# Использование
if __name__ == "__main__":
    vs = VectorSearch()
    vs.add_events("events.json")
    
    # Пример поиска
    results = vs.search("бесплатные концерты джаз", limit=3)
    for event in results:
        print(f"{event['title']} - {event['date']}")

5Интеграция с языковой моделью

Добавим LLM для анализа результатов поиска и генерации ответов:

import ollama  # Для локальных моделей
# или
# import openai  # Для OpenAI API

class LLMAnalyzer:
    def __init__(self, model_name="mistral"):
        self.model_name = model_name
    
    def format_events_for_llm(self, events, user_query):
        """Форматирование событий для контекста LLM"""
        events_text = ""
        for i, event in enumerate(events, 1):
            events_text += f"""
            Событие {i}:
            Название: {event.get('title', 'Нет названия')}
            Описание: {event.get('description', 'Нет описания')[:200]}...
            Дата: {event.get('date', 'Дата не указана')}
            Место: {event.get('venue', 'Место не указано')}
            Цена: {event.get('price', 'Цена не указана')}
            Ссылка: {event.get('url', 'Нет ссылки')}
            Категория: {event.get('category', 'Неизвестно')}
            ---
            """
        
        prompt = f"""Ты помощник для поиска событий. Пользователь ищет: "{user_query}"
        
        Вот найденные события:
        {events_text}
        
        Проанализируй эти события и составь ответ пользователю:
        1. Если есть подходящие события - кратко опиши 3 самых релевантных
        2. Укажи ключевую информацию: дату, место, цену
        3. Если событий нет или они не очень подходят - вежливо извинись и предложи изменить запрос
        4. Будь дружелюбным и полезным
        5. Отвечай на русском языке
        
        Ответ:"""
        
        return prompt
    
    def generate_response(self, events, user_query):
        """Генерация ответа с помощью LLM"""
        if not events:
            return "К сожалению, по вашему запросу ничего не найдено. Попробуйте изменить формулировку."
        
        prompt = self.format_events_for_llm(events, user_query)
        
        try:
            # Использование Ollama для локальных моделей
            response = ollama.generate(
                model=self.model_name,
                prompt=prompt,
                options={"temperature": 0.7}
            )
            return response['response']
            
            # Альтернатива: OpenAI API
            # response = openai.ChatCompletion.create(
            #     model="gpt-3.5-turbo",
            #     messages=[{"role": "user", "content": prompt}]
            # )
            # return response.choices[0].message.content
            
        except Exception as e:
            print(f"Ошибка генерации ответа: {e}")
            # Fallback: простой формат, если LLM не работает
            return self._simple_format(events)
    
    def _simple_format(self, events):
        """Простое форматирование без LLM"""
        result = "Найдены следующие события:\n\n"
        for event in events[:3]:
            result += f"• {event['title']}\n"
            result += f"  📅 {event.get('date', 'Дата не указана')}\n"
            result += f"  📍 {event.get('venue', 'Место не указано')}\n"
            result += f"  💰 {event.get('price', 'Цена не указана')}\n\n"
        return result

# Интеграция с поиском
if __name__ == "__main__":
    vs = VectorSearch()
    analyzer = LLMAnalyzer()
    
    query = "куда сходить с детьми в выходные"
    events = vs.search(query, limit=5)
    response = analyzer.generate_response(events, query)
    print(response)

Развёртывание и масштабирование

Для развёртывания бота в продакшене рассмотрите следующие варианты:

  • Docker-контейнеризация: Создайте Dockerfile для упаковки приложения
  • Облачные платформы: Heroku, Railway, или российские аналоги
  • База данных: Используйте Qdrant Cloud или разверните свой экземпляр
  • Планировщик задач: Celery или APScheduler для периодического обновления событий

Пример Dockerfile для развёртывания:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Установка Ollama для локальных моделей (опционально)
RUN curl -fsSL https://ollama.com/install.sh | sh

CMD ["python", "bot.py"]
💡
Для обработки изображений афиш или создания визуальных рекомендаций можно интегрировать инструменты вроде FlaxeoUI для генерации изображений, хотя это потребует дополнительных вычислительных ресурсов.

Альтернативные подходы и сравнение

ПодходПлюсыМинусыКогда выбирать
Полностью локальный (Ollama + Qdrant)Конфиденциальность, нет API-лимитовТребует ресурсов, сложнее настройкаДля приватных проектов или при ограниченном бюджете
Облачные API (OpenAI + Pinecone)Простота, высокая точностьЗависимость от интернета, стоимостьДля быстрого старта и коммерческих проектов
Гибридный подходБаланс стоимости и качестваСложная архитектураДля масштабируемых проектов

Кому подойдет этот проект?

Этот Telegram-бот для поиска событий идеально подойдет:

  • Начинающим разработчикам: Отличный проект для портфолио, охватывающий полный цикл разработки
  • Стартапам в сфере event-индустрии: Готовое решение для персонализированных рекомендаций
  • IT-компаниям: Внутренний инструмент для поиска корпоративных мероприятий
  • Образовательным проектам: Как в квесте Google по обнаружению болезней глаз, можно адаптировать для учебных целей

Важно: При использовании LLM для генерации контента всегда проверяйте точность информации, особенно в отношении дат, мест и цен мероприятий. Как и в случае с проверкой AI-видео, критическое отношение к сгенерированному контенту обязательно.

Дальнейшее развитие проекта

Для улучшения бота можно реализовать:

  1. Персонализацию: Сохранение предпочтений пользователя и история поиска
  2. Уведомления: Оповещения о новых событиях по интересам
  3. Мультимедиа: Показ изображений мероприятий и карт локаций
  4. Интеграции: Подключение календарей для сохранения событий
  5. Аналитика: Сбор статистики популярных запросов и мероприятий

Создание умной афиши на Python и LLM — это не только практичный инструмент, но и отличный способ освоить современные технологии искусственного интеллекта. Начните с базовой версии, постепенно добавляя новые функции, и вы получите мощный сервис, который действительно полезен пользователям.