Почему именно Telegram-бот для поиска событий?
В мире, перегруженном информацией, найти интересные мероприятия становится настоящей проблемой. Традиционные афиши и агрегаторы событий часто предлагают слишком общие результаты или требуют сложных фильтров. Решение? Умный Telegram-бот, который понимает естественный язык и находит именно то, что вам нужно.
Представьте: вы пишете боту "Хочу сходить на что-то интересное в пятницу вечером, не очень дорогое, в центре города", и он предлагает подборку мероприятий, соответствующих всем критериям. Именно такой бот мы создадим сегодня, используя Python, современные языковые модели и векторный поиск.
Этот проект отлично подходит для портфолио разработчика, демонстрируя навыки работы с LLM, Telegram API и архитектурой RAG (Retrieval-Augmented Generation).
Архитектура умной афиши
Наш бот будет работать по следующей схеме:
- Пользователь отправляет запрос в Telegram
- Бот преобразует запрос в векторное представление
- Система ищет похожие события в векторной базе данных
- LLM анализирует результаты и формирует ответ
- Пользователь получает персонализированные рекомендации
| Компонент | Технология | Назначение |
|---|---|---|
| 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 openai2Создание 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"]Альтернативные подходы и сравнение
| Подход | Плюсы | Минусы | Когда выбирать |
|---|---|---|---|
| Полностью локальный (Ollama + Qdrant) | Конфиденциальность, нет API-лимитов | Требует ресурсов, сложнее настройка | Для приватных проектов или при ограниченном бюджете |
| Облачные API (OpenAI + Pinecone) | Простота, высокая точность | Зависимость от интернета, стоимость | Для быстрого старта и коммерческих проектов |
| Гибридный подход | Баланс стоимости и качества | Сложная архитектура | Для масштабируемых проектов |
Кому подойдет этот проект?
Этот Telegram-бот для поиска событий идеально подойдет:
- Начинающим разработчикам: Отличный проект для портфолио, охватывающий полный цикл разработки
- Стартапам в сфере event-индустрии: Готовое решение для персонализированных рекомендаций
- IT-компаниям: Внутренний инструмент для поиска корпоративных мероприятий
- Образовательным проектам: Как в квесте Google по обнаружению болезней глаз, можно адаптировать для учебных целей
Важно: При использовании LLM для генерации контента всегда проверяйте точность информации, особенно в отношении дат, мест и цен мероприятий. Как и в случае с проверкой AI-видео, критическое отношение к сгенерированному контенту обязательно.
Дальнейшее развитие проекта
Для улучшения бота можно реализовать:
- Персонализацию: Сохранение предпочтений пользователя и история поиска
- Уведомления: Оповещения о новых событиях по интересам
- Мультимедиа: Показ изображений мероприятий и карт локаций
- Интеграции: Подключение календарей для сохранения событий
- Аналитика: Сбор статистики популярных запросов и мероприятий
Создание умной афиши на Python и LLM — это не только практичный инструмент, но и отличный способ освоить современные технологии искусственного интеллекта. Начните с базовой версии, постепенно добавляя новые функции, и вы получите мощный сервис, который действительно полезен пользователям.