Блог AST-SoftPro
aiogram 3.x: создание Telegram-ботов нового поколения
Введение
Разработка Telegram-ботов эволюционировала от простых скриптов, реагирующих на команды, до сложных распределенных систем, интегрированных с CRM, ERP и аналитическими платформами. В экосистеме Python библиотека aiogram долгое время оставалась стандартом де-факто для создания ботов благодаря своей асинхронной природе и богатому функционалу. Однако с выходом версии 3.x произошел значительный концептуальный сдвиг, который требует от разработчиков пересмотра подхода к архитектуре приложений.
В компании AST-SOFT мы регулярно сталкиваемся с задачами модернизации legacy-ботов и создания новых высоконагруженных решений. Наша практика показывает, что переход на aiogram 3.x не просто улучшает производительность, но и делает код более предсказуемым, типизированным и легким для поддержки. В этой статье мы детально разберем ключевые аспекты разработки на aiogram 3, включая работу с фильтрами, middleware, конечными автоматами (FSM) и интеграцию с внешними сервисами.
Почему именно aiogram 3.x?
Главное отличие версии 3 от предыдущей 2.x заключается в полной переписке архитектуры с упором на современные стандарты Python 3.10+.
- Асинхронность на всех уровнях: В aiogram 3 асинхронность стала неотъемлемой частью ядра. Все вызовы API Telegram теперь строго асинхронные, что требует от разработчиков осознанного подхода к управлению потоками и задачами.
- Типизация: Библиотека активно использует type hints, что позволяет IDE и линтерам (например, Pyright или Mypy) эффективно находить ошибки на этапе разработки.
- Гибкая система фильтрации: Старая система декораторов была заменена на более модульную систему фильтров, позволяющую создавать сложные условия обработки сообщений без захламления кода.
- Middleware: Внедрение полноценной системы middleware (похожей на тот, что используется в FastAPI или Django) позволяет реализовывать поперечную логику (логирование, аутентификацию, управление сессиями) централизованно.
Архитектурные изменения и настройка
В aiogram 3 инициализация бота и диспетчера стала более декларативной. Раньше мы часто передавали токен и диспетчер в конструктор Bot, теперь процесс разбит на более четкие этапы.
import asyncio
import logging
from aiogram import Bot, Dispatcher, types
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import Message
# Настройка логирования
logging.basicConfig(level=logging.INFO)
async def main():
# 1. Инициализация хранилища состояний (FSM)
storage = MemoryStorage()
# 2. Инициализация диспетчера
dp = Dispatcher(storage=storage)
# 3. Инициализация бота
bot = Bot(token="YOUR_TOKEN_HERE")
# Регистрация обработчиков будет происходить здесь
# dp.message.register(...)
# 4. Запуск polling или webhook
print("Starting polling...")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Обратите внимание на использование MemoryStorage. Для продакшен-решений, которые разрабатывают в AST-SOFT, мы всегда рекомендуем использовать RedisStorage или DBStorage, чтобы состояние диалога не терялось при перезапуске сервера.
Структура профессионального проекта
Для ботов малого бизнеса достаточно одного файла, но для enterprise-решений необходима четкая структура. В проектах, которые мы сдаем клиентам, мы придерживаемся следующего стандарта:
project_root/
├── bot/
│ ├── __init__.py
│ ├── config.py # Конфигурация (env vars)
│ ├── loader.py # Загрузка роутов
│ ├── dp.py # Инициализация Dispatcher
│ └── middlewares/ # Пользовательские middleware
│ ├── __init__.py
│ └── auth.py
├── handlers/ # Обработчики событий
│ ├── __init__.py
│ ├── start.py
│ └── admin/
├── filters/ # Пользовательские фильтры
│ ├── __init__.py
│ └── is_admin.py
├── fsm/ # Состояния (States)
│ ├── __init__.py
│ └── dialog_states.py
├── services/ # Бизнес-логика и API интеграции
├── tests/ # Тесты
├── main.py # Точка входа
└── requirements.txt
Такая структура позволяет масштабировать проект, добавлять новых разработчиков и поддерживать код в чистоте. Модульность — ключ к успеху сложных ботов.
Фильтры: точечная обработка событий
Система фильтров в aiogram 3 позволяет отсеивать события до того, как они достигнут обработчика. Фильтры могут быть простыми (встроенными) или сложными (пользовательскими).
Встроенные фильтры
Aiogram предоставляет множество готовых фильтров. Например, можно отфильтровать сообщения только из групп, только от администраторов или по тексту.
from aiogram import Dispatcher, F
from aiogram.types import Message
dp = Dispatcher()
# Пример 1: Обработка только текстовых сообщений, содержащих команду /start
dp.message.filter(F.text == "/start")
# Пример 2: Обработка сообщений только в группах
dp.message.filter(F.chat.type.in_("group", "supergroup"))
@dp.message()
async def handle_start(message: Message):
await message.answer("Привет! Я бот нового поколения.")
Использование объекта F (Flag) позволяет создавать сложные логические выражения. Например, F.text.startswith("/cmd") & F.chat.type == "private".
Пользовательские фильтры
Для бизнес-логики часто требуются кастомные фильтры. Представим, что нам нужно обрабатывать сообщения только от пользователей, которые подтвердили email. В AST-SOFT мы часто реализуем такие фильтры для систем верификации.
from aiogram.filters import BaseFilter
from aiogram.types import Message
from aiogram.enums import ChatType
class IsVerifiedUser(BaseFilter):
async def __call__(self, message: Message) -> bool:
# Здесь должна быть асинхронная проверка в базе данных
# user_db = await db.get_user(message.from_user.id)
# return user_db.is_verified
# Для примера имитируем проверку
return message.from_user.id != 123456789
# Использование
@dp.message(IsVerifiedUser())
async def handle_verified(message: Message):
await message.answer("Добро пожаловать, подтвержденный пользователь!")
Класс BaseFilter требует реализации метода __call__, который должен возвращать bool. Важно, чтобы этот метод был асинхронным (async def), если внутри выполняются операции ввода-вывода.
Middleware: поперечная логика
Middleware в aiogram 3 — это функции или классы, которые выполняются перед и после обработки события диспетчером. Это идеальный инструмент для реализации логики, которая нужна во многих местах: логирование, аутентификация, управление транзакциями БД.
Пример: Middleware для логирования
Давайте создадим middleware, который будет логировать время обработки каждого обновления и ID пользователя.
import logging
import time
from aiogram.types import Update
from aiogram.dispatcher.middlewares import BaseMiddleware
logger = logging.getLogger(__name__)
class LoggingMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable,
event: Update,
data: dict
) -> dict:
start_time = time.time()
# Извлекаем ID пользователя, если он есть
user_id = None
if isinstance(event, Update) and event.message:
user_id = event.message.from_user.id
logger.info(f"Processing update for user {user_id}")
# Вызываем следующий обработчик
result = await handler(event, data)
# Логирование после выполнения
duration = time.time() - start_time
logger.info(f"Update processed in {duration:.2f}s for user {user_id}")
return result
# Регистрация middleware
dp.update.middleware(LoggingMiddleware())
Управление контекстом
Middleware могут добавлять данные в контекст data, которые затем будут доступны в обработчиках. Это полезно для передачи объектов сессии базы данных или объектов текущего пользователя.
from aiogram.types import Message
from aiogram.dispatcher.middlewares import BaseMiddleware
from aiogram.types import Update
# Представим, что у нас есть сервис для получения пользователя
class UserContextMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable,
event: Update,
data: dict
) -> dict:
if isinstance(event, Update) and event.message:
message: Message = event.message
# Получаем объект пользователя из БД
# db_user = await db.get_user(message.from_user.id)
# data["db_user"] = db_user
pass
return await handler(event, data)
dp.message.middleware(UserContextMiddleware())
# Теперь в обработчике мы можем получить data["db_user"]
@dp.message()
async def echo(message: Message, db_user: dict = Depends(lambda data: data.get("db_user"))):
# Использование db_user
pass
Примечание: В aiogram 3 для извлечения данных из контекста часто используют функции-зависимости или просто доступ к словарю data, если он передается в обработчик.
FSM: управление диалогами
Конечные автоматы (Finite State Machines) — это сердце интерактивных ботов. Они позволяют вести диалог, запоминать ответы пользователя и переходить между шагами. В aiogram 3 FSM полностью переписана и стала более гибкой.
Определение состояний
Состояния группируются в классы, наследующие StatesGroup.
from aiogram.fsm.state import State, StatesGroup
class DialogStates(StatesGroup):
waiting_for_name = State() # Ждем имя
waiting_for_age = State() # Ждем возраст
waiting_for_city = State() # Ждем город
Реализация диалога
from aiogram import Dispatcher, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
dp = Dispatcher()
@dp.message(F.text == "/start")
async def start_dialog(message: Message, state: FSMContext):
await message.answer("Как вас зовут?")
await state.set_state(DialogStates.waiting_for_name)
@dp.message(DialogStates.waiting_for_name)
async def process_name(message: Message, state: FSMContext):
name = message.text
await state.update_data(name=name)
await message.answer(f"Приятно познакомиться, {name}! Сколько вам лет?")
await state.set_state(DialogStates.waiting_for_age)
@dp.message(DialogStates.waiting_for_age)
async def process_age(message: Message, state: FSMContext):
age = message.text
await state.update_data(age=age)
await message.answer("В каком городе вы живете?")
await state.set_state(DialogStates.waiting_for_city)
@dp.message(DialogStates.waiting_for_city)
async def process_city(message: Message, state: FSMContext):
city = message.text
data = await state.get_data()
# Формируем итоговое сообщение
final_message = (
f"Результаты анкетирования:\n"
f"Имя: {data.get('name')}\n"
f"Возраст: {data.get('age')}\n"
f"Город: {city}"
)
await message.answer(final_message)
await state.finish() # Завершаем диалог и очищаем состояние
Хранение состояний
В примере выше используется MemoryStorage (по умолчанию). Для продакшена критически важно использовать RedisStorage. Это обеспечивает:1. Горизонтальное масштабирование: Несколько экземпляров бота могут работать с одной базой состояний.2. Устойчивость к перезагрузкам: Состояние пользователя сохранится, даже если сервер перезапустится.
from aiogram.fsm.storage.redis import RedisStorage
import aioredis
redis = aioredis.from_url("redis://localhost")
storage = RedisStorage(redis)
dp = Dispatcher(storage=storage)
В AST-SOFT мы всегда настраиваем TTL (Time To Live) для состояний в Redis, чтобы не засорять память диалогами, которые пользователь забросил. Например, если диалог неактивен 24 часа, он удаляется.
Интеграция с внешними API и базами данных
Современный бот редко существует в вакууме. Он должен взаимодействовать с внешними сервисами: отправлять уведомления в Slack, проверять наличие товаров в 1C, сохранять заказы в PostgreSQL.
Асинхронный HTTP
Для работы с HTTP-API в Python стандартом является библиотека httpx или aiohttp. httpx предпочтительнее благодаря совместимости с requests и отличной поддержке типизации.
import httpx
class ExternalAPIService:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.headers = {"Authorization": f"Bearer {api_key}"}
self.client = httpx.AsyncClient(base_url=base_url, headers=self.headers)
async def get_product(self, product_id: int) -> dict:
try:
response = await self.client.get(f"/products/{product_id}")
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
# Логирование ошибки
raise Exception(f"API Error: {e.response.status_code}")
# Использование в обработчике
@dp.message(F.text == "/check_price")
async def check_price(message: Message, api_service: ExternalAPIService):
# Представим, что ID продукта берется из контекста или предыдущего шага
product_id = 101
try:
product = await api_service.get_product(product_id)
await message.answer(f"Цена: {product['price']} руб.")
except Exception as e:
await message.answer("Произошла ошибка при проверке цены.")
Инъекция зависимостей
В aiogram 3 нет встроенного DI-контейнера как в FastAPI, но мы можем использовать паттерн "Фабрика" или просто передавать экземпляры сервисов через middleware, добавляя их в контекст data. Это позволяет легко тестировать обработчики, подменяя реальные сервисы на моки.
Безопасность и производительность
При разработке ботов для крупных проектов в AST-SOFT мы уделяем особое внимание безопасности и производительности.
Защита от флуда и спама
Нужно ограничивать частоту запросов от одного пользователя. Это можно реализовать через middleware, которое хранит timestamp последних запросов в Redis.
import time
import aioredis
class RateLimiterMiddleware(BaseMiddleware):
def __init__(self, redis: aioredis.Redis, limit: int = 5, window: int = 60):
self.redis = redis
self.limit = limit
self.window = window
async def __call__(self, handler: Callable, event: Update, data: dict) -> dict:
user_id = event.message.from_user.id
key = f"rate_limit:{user_id}"
# Увеличиваем счетчик
current = await self.redis.incr(key)
if current == 1:
await self.redis.expire(key, self.window)
if current > self.limit:
# Блокируем обработку, если лимит превышен
return await handler(event, data) # Или возвращаем пустой ответ
return await handler(event, data)
Обработка ошибок
Глобальная обработка ошибок позволяет избежать падения бота при непредвиденных исключениях в обработчиках.
@dp.errors()
async def error_handler(update: Update, exception: Exception):
logger.error(f"Error: {exception}", exc_info=exception)
# Можно отправить уведомление администратору
# await bot.send_message(ADMIN_ID, f"Возникла ошибка: {exception}")
Оптимизация
- Используйте
task_group: Если нужно отправить несколько сообщений параллельно, используйтеasyncio.gather. - Кэширование: Кэшируйте ответы на часто запрашиваемые команды или данные, которые редко меняются.
- Webhook vs Polling: Для высокой нагрузки всегда используйте Webhook, а не Polling. Polling создает лишнюю нагрузку на сервер Telegram и ваш сервер из-за постоянных опросов. Webhook позволяет Telegram отправлять обновления напрямую на ваш сервер, что эффективнее.
Заключение
Разработка Telegram-ботов на aiogram 3.x — это мощный инструмент для создания сложных, масштабируемых и надежных решений. Переход на новую архитектуру требует времени на изучение, но окупается за счет лучшей производительности, типизации и гибкости системы middleware и фильтров.
Команда AST-SOFT обладает глубоким опытом внедрения таких решений для бизнеса. Мы помогаем компаниям автоматизировать рутинные процессы, интегрировать ботов с существующими информационными системами и обеспечивать бесперебойную работу сервисов 24/7. Если ваш проект требует сложной логики, интеграции с API или высокой нагрузки, современные инструменты Python и aiogram 3 станут отличным выбором.
Мы рекомендуем начинающим разработчикам не бояться экспериментировать с FSM и middleware, а опытным — пересматривать структуру старых проектов, внедряя современные паттерны проектирования. Будущее чат-ботов за асинхронностью, типобезопасностью и микросервисной архитектурой.
Эта статья подготовлена техническими специалистами AST-SOFT. Мы специализируемся на разработке высоконагруженных систем и интеграционных решений. Посетите наш сайт ast-softpro.ru, чтобы узнать больше о наших услугах и кейсах.