Создание Telegram-ботов на aiogram 3.x: структура, хендлеры, клавиатуры и код

Курс охватывает разработку Telegram-ботов на aiogram 3.x: от настройки проекта и архитектуры до написания хендлеров, состояний и кнопок. Вы научитесь строить понятную структуру кода, подключать конфигурацию и логирование, а также готовить бота к запуску и поддержке.

1. Введение в aiogram 3.x и настройка окружения

Введение в aiogram 3.x и настройка окружения

Что такое aiogram и почему версия 3.x

aiogram — это асинхронный фреймворк на Python для создания Telegram-ботов, работающий поверх Telegram Bot API.

В версии aiogram 3.x библиотека получила более строгую и предсказуемую архитектуру, удобную маршрутизацию хендлеров через Router, а также более явные зависимости и типизацию. В этом курсе мы будем строить бота так, чтобы код был:

  • понятным и расширяемым
  • удобным для поддержки
  • готовым к добавлению клавиатур, состояний, middleware и т.д.
  • Как работает Telegram-бот на высоком уровне

    Когда пользователь пишет боту, Telegram отправляет вашему приложению обновление (update). Вариантов доставки два:

  • Long polling — бот сам регулярно запрашивает обновления у Telegram
  • Webhook — Telegram отправляет обновления на ваш публичный HTTPS-адрес
  • В начале курса мы используем long polling, потому что это проще для локальной разработки.

    !Схема показывает путь сообщения от пользователя до хендлера aiogram

    Требования к окружению

    Перед началом убедитесь, что у вас есть:

  • Python версии 3.10+
  • установленный pip
  • любой редактор кода (например, VS Code или PyCharm)
  • доступ к Telegram
  • Официальные источники:

  • Python — скачивание и установка
  • Документация aiogram — справочник по фреймворку
  • Telegram Bot API — документация протокола
  • Создаём бота в Telegram и получаем токен

    Чтобы управлять ботом, вам нужен токен.

  • Откройте чат с BotFather.
  • Отправьте команду /newbot .
  • Задайте имя и username (username должен оканчиваться на bot).
  • BotFather выдаст токен вида 123456:ABC-DEF....
  • Важно: токен — это секрет. Не публикуйте его в репозитории и не вставляйте в скриншоты.

    Настраиваем проект: виртуальное окружение и зависимости

    Создаём папку проекта

  • Создайте директорию проекта и перейдите в неё.
  • Откройте терминал в этой папке.
  • Пример (имена можно менять):

    Создаём виртуальное окружение

    Виртуальное окружение изолирует зависимости проекта.

  • Создайте окружение.
  • Активируйте окружение.
  • Для Windows (PowerShell):

    Для macOS/Linux:

    Устанавливаем aiogram

  • Обновите pip.
  • Установите aiogram версии 3.x.
  • Проверьте, что пакет установился.
  • Храним токен безопасно: переменные окружения и .env

    Хорошая практика — не хранить токен в коде. Вместо этого используем файл .env и переменные окружения.

  • Установите библиотеку для загрузки .env.
  • Создайте файл .env в корне проекта.
  • Если команда touch недоступна (Windows), просто создайте файл вручную.

  • Добавьте в .env строку (подставьте свой токен):
  • Добавьте .env в .gitignore, если используете Git.
  • Пример содержимого .gitignore:

    Минимальная структура проекта

    Сразу договоримся о базовой структуре, чтобы дальше расширять её без хаоса.

    Рекомендуемый старт:

  • bot/ — пакет приложения
  • bot/__init__.py — делает папку пакетом
  • bot/config.py — загрузка настроек
  • bot/main.py — точка входа
  • Создайте файлы:

    Пишем первый минимальный бот на aiogram 3.x

    Файл bot/config.py

    Здесь загружаем токен из .env.

    Что здесь происходит:

  • load_dotenv() читает файл .env и добавляет значения в переменные окружения процесса
  • os.getenv("BOT_TOKEN") получает токен
  • если токена нет, мы явно падаем с понятной ошибкой
  • Файл bot/main.py

    Запустим бота в режиме long polling и добавим один хендлер на команду /start.

    Обратите внимание на ключевые элементы:

  • Bot(token=...) — объект для вызовов Telegram Bot API
  • Dispatcher() — диспетчер, который получает обновления и передаёт их роутерам/хендлерам
  • @dp.message(...) — регистрация хендлера сообщений по фильтру
  • CommandStart() — фильтр на команду /start
  • dp.start_polling(bot) — запуск long polling
  • Запуск и проверка

  • Убедитесь, что виртуальное окружение активировано.
  • Запустите:
  • Откройте вашего бота в Telegram и отправьте /start.
  • Если всё настроено верно, бот ответит приветственным сообщением.

    Типичные ошибки и как их быстро исправить

  • BOT_TOKEN не задан
  • Проверьте файл .env и его расположение (в корне проекта).
  • Убедитесь, что запускаете код из корня проекта.
  • Unauthorized / бот не отвечает
  • Токен неверный или вы случайно вставили лишние пробелы.
  • Перевыпустите токен через BotFather, если он мог утечь.
  • ModuleNotFoundError: aiogram
  • Вы установили пакет не в то окружение.
  • Активируйте .venv и повторите pip install aiogram.
  • Конфликты Python-версий
  • Убедитесь, что команды python и pip относятся к одному интерпретатору.
  • Удобная проверка: python -c "import sys; print(sys.executable)".
  • Что будет дальше в курсе

    В следующей части мы начнём строить правильную структуру проекта: вынесем хендлеры в отдельные модули, познакомимся с Router, научимся подключать несколько роутеров и подготовим основу для клавиатур и дальнейшей логики бота.

    2. Структура проекта: роутеры, модули, конфигурация

    Структура проекта: роутеры, модули, конфигурация

    Зачем нужна структура уже на старте

    В предыдущей статье мы запустили минимального бота на aiogram 3.x в одном файле и научились безопасно хранить токен в .env. Такой подход помогает быстро проверить, что окружение настроено правильно, но он плохо масштабируется.

    Как только в боте появляются:

  • несколько команд
  • разные сценарии общения
  • клавиатуры
  • работа с базой данных
  • логирование, админка, роли пользователей
  • один файл превращается в неудобный и хрупкий монолит.

    Цель этой статьи — перейти к понятной структуре проекта: разделить код на модули, научиться собирать хендлеры через Router, а конфигурацию вынести в отдельный слой.

    > Основная идея: точка входа должна только запускать приложение, а вся логика бота должна жить в отдельных модулях.

    Ключевые сущности в aiogram 3.x

    В нашем курсе важны три понятия:

  • Dispatcher — центральный объект, который получает обновления и передаёт их дальше.
  • Router — модульная коробка для хендлеров: сюда мы складываем обработчики сообщений, коллбеков, команд.
  • Handler — конкретная функция, которая срабатывает по фильтру.
  • Документация проекта: Документация aiogram

    !Диаграмма показывает, как Dispatcher распределяет обновления по подключённым Router и хендлерам

    Рекомендуемая структура папок

    Ниже — базовый шаблон, от которого удобно развиваться в следующих уроках (клавиатуры, состояния, middleware).

    Роли модулей:

  • bot/main.py — точка входа: создаёт Bot, Dispatcher, подключает роутеры, запускает polling.
  • bot/config.py — загрузка настроек (например, токен, режим логирования).
  • bot/routers.py — место, где мы собираем общий роутер из роутеров разных модулей.
  • bot/handlers/*.py — отдельные файлы с хендлерами, сгруппированные по смыслу.
  • Конфигурация: один источник правды

    Мы уже хранили токен в .env. Сохраняем этот подход и оформляем конфигурацию как dataclass, чтобы:

  • явно видеть, какие параметры нужны приложению
  • получать понятные ошибки, если переменная не задана
  • избежать магических строк в коде
  • Полезные источники:

  • python-dotenv на PyPI
  • Документация dataclasses
  • Файл bot/config.py

    Что важно:

  • load_dotenv() подтягивает переменные из .env
  • если BOT_TOKEN не найден, мы завершаем работу с ясным сообщением, а не получаем загадочную ошибку позже
  • Роутеры и хендлеры: разнос логики по модулям

    Главная привычка в aiogram 3.x: каждый модуль логики — отдельный Router.

    Плюсы:

  • проще читать код
  • проще подключать или отключать части функционала
  • легче тестировать и расширять
  • Файл bot/handlers/start.py

    Хендлер команды /start вынесем в отдельный модуль.

    Здесь:

  • создаём router = Router()
  • регистрируем хендлер через @router.message(...)
  • используем фильтр CommandStart()
  • Файл bot/handlers/echo.py

    Добавим простой эхо-хендлер, который срабатывает на любое текстовое сообщение (кроме команд, если вы их обработали отдельно).

    Почему проверяем message.text:

  • пользователи могут отправлять стикеры, фото, файлы
  • у таких сообщений text может быть None
  • Сборка роутеров в одном месте

    Чтобы main.py не превращался в список импортов всех хендлеров, удобно сделать модуль-сборщик.

    Файл bot/routers.py

    Идея простая:

  • каждый модуль bot/handlers/*.py отдаёт наружу свой router
  • в setup_routers() мы собираем общий Router, который подключим к Dispatcher
  • Точка входа: только сборка и запуск

    Теперь main.py — это аккуратный файл, который:

  • читает конфиг
  • создаёт Bot и Dispatcher
  • подключает роутеры
  • запускает polling
  • Файл bot/main.py

    Обратите внимание:

  • dp.include_router(...) подключает весь набор хендлеров
  • добавлять новые команды теперь можно, не трогая main.py: достаточно создать новый модуль в handlers/ и подключить его роутер в routers.py
  • Частые ошибки при модульной структуре

  • Импортируете dp внутрь хендлеров
  • - Правильно: хендлеры регистрируются через router = Router() в своём модуле.
  • Забыли подключить роутер
  • - Если хендлеры не срабатывают, проверьте, что модульный роутер реально включён через include_router.
  • Дублируете одинаковые хендлеры в разных роутерах
  • - Тогда порядок подключения роутеров может влиять на поведение. Лучше держать правила маршрутизации явными.

    Что дальше

    Теперь у нас есть каркас проекта, который не развалится при росте кода. В следующих частях курса на эту структуру будут накладываться:

  • фильтры и более сложные хендлеры
  • клавиатуры (Reply и Inline)
  • состояния (FSM) и разветвлённые сценарии
  • middleware и зависимости
  • С текущей структурой каждую новую тему можно добавлять отдельными модулями, не превращая проект в один огромный файл.

    3. Основы обработки апдейтов: сообщения, команды, фильтры

    Основы обработки апдейтов: сообщения, команды, фильтры

    В прошлых статьях мы:

  • настроили окружение и запустили минимального бота на aiogram 3.x
  • привели проект к модульной структуре с Router, чтобы код было легко расширять
  • Теперь разберём фундамент того, как бот понимает, что именно прислал пользователь, и как запускать нужный кусок логики с помощью хендлеров и фильтров.

    Что такое апдейт и как он попадает в хендлер

    Апдейт (update) — это входящее событие от Telegram: сообщение, нажатие кнопки, изменение статуса участника в группе и многое другое.

    На практике в начале курса чаще всего встречаются такие типы апдейтов:

  • Message — пользователь отправил сообщение (текст, фото, стикер, документ)
  • CallbackQuery — пользователь нажал inline-кнопку (к этому вернёмся в теме про inline-клавиатуры)
  • В режиме long polling Dispatcher постоянно запрашивает апдейты у Telegram и передаёт их дальше в роутеры, а роутеры ищут подходящий хендлер по фильтрам.

    !Поток обработки входящего события от Telegram до выполнения хендлера

    Полезные ссылки:

  • Документация aiogram 3.x
  • Update в Telegram Bot API
  • Хендлер и фильтр: что есть что

    Хендлер — это асинхронная функция, которая выполняется, когда событие подходит под условия.

    Фильтр — это условие, которое проверяется на событии, чтобы понять, подходит ли оно данному хендлеру.

    В aiogram 3.x чаще всего вы будете писать хендлеры так:

    Здесь:

  • @router.message(...) означает: обрабатываем события Message
  • внутри скобок можно указывать фильтры: команда, текст, тип чата, пользователь и т.д.
  • Сообщения: как отличать текст, фото, стикеры

    Объект Message может содержать разные типы контента. Важный момент: message.text есть только у текстовых сообщений и команд. Если пришло фото или стикер, message.text будет None.

    Пример безопасного эхо, который отвечает только на текст:

    Почему здесь удобен F.text:

  • это фильтр, который гарантирует, что message.text не None
  • код хендлера становится проще и безопаснее
  • Если вы хотите обработать не текст:

    Команды: /start, /help и свои команды

    Команда — это текстовое сообщение, которое начинается с /. В Telegram команда может выглядеть так:

  • /start
  • /start@YourBotName (часто в группах)
  • В aiogram 3.x для команд используются фильтры из aiogram.filters.

    /start

    CommandStart() удобен тем, что он предназначен именно для /start и корректно обрабатывает варианты команды.

    /help и другие команды

    Команды с аргументами

    Частая практика — принимать аргументы после команды:

  • /setname Alex
  • В aiogram можно получить разобранную команду через CommandObject.

    Здесь:

  • command.args содержит всё, что пользователь написал после команды
  • если аргументов нет, command.args будет None
  • Фильтры на текст: точное совпадение и проверки

    В aiogram 3.x есть удобный механизм magic filters через объект F.

    Импорт:

    Примеры полезных фильтров:

  • F.text == "Привет" — точное совпадение текста
  • F.text.contains("курс") — текст содержит подстроку
  • F.text.startswith("!") — начинается с символа
  • Пример:

    Важно:

  • если фильтр использует F.text, он автоматически отсечёт сообщения без текста
  • если вы напишете фильтр, который не проверяет text, не забывайте, что message.text может быть None
  • Фильтры по типу чата и пользователю

    Иногда один и тот же бот должен вести себя по-разному:

  • в личных сообщениях
  • в группах
  • для администраторов
  • Только личные сообщения

    Только для конкретного пользователя

    Замечание: жёстко вшитый ADMIN_ID допустим для примера, но в реальном проекте лучше хранить такие значения в конфиге (мы это будем развивать дальше).

    Как объединять фильтры

    Если передать несколько фильтров в @router.message(...), то хендлер сработает только когда выполняются все условия.

    Пример: команда /help только в личных сообщениях:

    Порядок хендлеров и почему это важно

    Внутри одного роутера хендлеры проверяются последовательно. На практике это означает:

  • более специфичные хендлеры (команды, точные совпадения) лучше объявлять выше
  • более общие хендлеры (например, просто @router.message(F.text)) — ниже
  • Также влияет порядок подключения роутеров через include_router: роутеры, подключённые раньше, получают шанс обработать событие раньше.

    Практическое следствие: если у вас есть общий «эхо-хендлер на любой текст», его нужно ставить в конце, иначе он перехватит всё.

    Практический шаблон: команды, неизвестные команды, общий текст

    Ниже пример модуля bot/handlers/common.py, который хорошо ложится на структуру из прошлой статьи.

    Что здесь важно:

  • F.text.startswith("/") ловит команды, которые вы не описали отдельными хендлерами
  • @router.message(F.text) стоит ниже команд, чтобы не перехватывать их
  • самый последний @router.message() — это «страховка» на любые нетекстовые сообщения
  • Чтобы модуль заработал, не забудьте подключить его роутер в вашем bot/routers.py (как мы делали в прошлой статье).

    Типичные ошибки при работе с фильтрами

  • Хендлер не срабатывает
  • - Проверьте, что роутер подключён через include_router.

  • Команда обрабатывается как обычный текст
  • - Значит, общий текстовый хендлер стоит выше командного.

  • Падает на message.text
  • - Пришёл не текст (фото/стикер). Используйте F.text или проверку if message.text is None.

    Что дальше

    Теперь у вас есть базовая модель:

  • любой вход от Telegram — это апдейт
  • апдейт попадает в хендлер только если фильтры совпали
  • порядок хендлеров и роутеров влияет на маршрутизацию
  • В следующей теме на эту основу мы будем накладывать клавиатуры: ReplyKeyboardMarkup и InlineKeyboardMarkup, а также обработку нажатий на кнопки через отдельные типы апдейтов.

    4. Клавиатуры: Reply, Inline, CallbackData и обработка нажатий

    Клавиатуры: Reply, Inline, CallbackData и обработка нажатий

    К этому моменту у нас уже есть:

  • рабочий проект на aiogram 3.x с long polling
  • модульная структура через Router
  • понимание хендлеров и фильтров (Command, CommandStart, F.text и т.д.)
  • Теперь добавим следующий важный слой интерфейса Telegram-бота: кнопки.

    В Telegram есть два основных типа клавиатур:

  • Reply-клавиатура (ReplyKeyboardMarkup) появляется вместо системной клавиатуры пользователя и отправляет в чат обычные сообщения
  • Inline-клавиатура (InlineKeyboardMarkup) прикрепляется к конкретному сообщению и при нажатии чаще всего отправляет боту callback-запрос (без нового сообщения в чат)
  • Как выбрать тип клавиатуры

    | Ситуация | Что выбрать | Почему | |---|---|---| | Нужно дать пользователю быстрые варианты текста: "Да", "Нет", "Меню" | Reply | Нажатие отправляет текст, можно обрабатывать как Message | | Нужны кнопки прямо под сообщением: "Купить", "Подробнее", переключение страниц | Inline | Нажатие приходит как CallbackQuery, можно менять текст/клавиатуру у сообщения | | Нужны ссылки на сайты | Inline | InlineKeyboardButton(url=...) открывает ссылку |

    Где хранить клавиатуры в структуре проекта

    Чтобы код оставался чистым, клавиатуры обычно выносят в отдельный модуль.

    Рекомендуемое расширение структуры:

  • bot/keyboards/common.py содержит функции, которые создают клавиатуры
  • bot/callbacks.py содержит описания структур CallbackData
  • bot/handlers/keyboards_demo.py содержит хендлеры, которые показывают клавиатуры и обрабатывают нажатия
  • > Идея та же, что и с хендлерами: точка входа не должна разрастаться.

    Reply-клавиатура

    Что происходит при нажатии

    Когда пользователь нажимает кнопку Reply-клавиатуры, Telegram отправляет боту обычное сообщение (Message) с текстом этой кнопки.

    То есть обрабатывается это так же, как и любой текст: через @router.message(...).

    Пример: простое меню на Reply

    Создадим клавиатуру.

    Файл bot/keyboards/common.py:

    Пояснения:

  • keyboard это список строк, где каждая строка это список кнопок
  • resize_keyboard=True делает клавиатуру компактнее
  • input_field_placeholder показывает подсказку в поле ввода
  • Теперь покажем её по команде.

    Файл bot/handlers/keyboards_demo.py:

    Важно: команда /menu здесь обработана как точный текст (F.text == "/menu"). При желании можно сделать через Command("menu"), это будет более канонично.

    Как убрать Reply-клавиатуру

    Чтобы скрыть Reply-клавиатуру, нужно отправить специальную разметку ReplyKeyboardRemove.

    Добавим обработчик:

    Inline-клавиатура

    Что происходит при нажатии

    Когда пользователь нажимает inline-кнопку с callback_data, Telegram не отправляет новое сообщение.

    Вместо этого бот получает апдейт типа CallbackQuery. Внутри него есть:

  • callback_query.data строка, которую вы положили в callback_data
  • callback_query.message сообщение, к которому была прикреплена клавиатура
  • callback_query.from_user пользователь, который нажал кнопку
  • !Поток событий при нажатии inline-кнопки

    Создаём inline-клавиатуру

    Файл bot/keyboards/common.py:

    Замечания:

  • кнопка с url не вызывает CallbackQuery, она просто открывает ссылку
  • кнопка с callback_data вызывает CallbackQuery, и это нужно обработать
  • Показываем inline-клавиатуру

    Файл bot/handlers/keyboards_demo.py:

    Обработка нажатий: CallbackQuery

    Почему нужно подтверждать нажатие

    Telegram ждёт, что бот подтвердит нажатие, иначе у пользователя будет крутиться индикатор загрузки.

    Подтверждение делается через await callback_query.answer().

    Простой обработчик по F.data

    Файл bot/handlers/keyboards_demo.py:

    Плюс такого подхода: просто.

    Минус: если у вас много кнопок и параметров, строки callback_data быстро превращаются в хаос. Для этого и существует CallbackData.

    CallbackData: безопасная упаковка данных в callback_data

    Что такое CallbackData

    callback_data в Telegram это строка, которую бот получает обратно при нажатии. В реальных ботах часто нужно передавать параметры:

  • действие: buy
  • id товара: 42
  • страница: 3
  • Можно собирать строку вручную, но легче и безопаснее использовать CallbackData из aiogram.

    Официальная документация aiogram (разделы про клавиатуры и callback):

  • Документация aiogram
  • Описываем структуру callback

    Файл bot/callbacks.py:

    Что это даёт:

  • prefix="demo" помогает отделять ваши callback-и по смыслу
  • поля action и value будут автоматически упакованы в строку и распакованы обратно
  • Генерируем callback_data для кнопок

    Обновим inline-клавиатуру.

    Файл bot/keyboards/common.py:

    Здесь:

  • DemoCb(...).pack() превращает структурированные данные в строку
  • aiogram сможет распарсить её обратно в объект DemoCb
  • Фильтрация и получение данных в хендлере

    Файл bot/handlers/keyboards_demo.py:

    Ключевой момент:

  • DemoCb.filter() отфильтровывает только те callback-и, которые соответствуют этой схеме
  • второй аргумент callback_data: DemoCb будет автоматически заполнен распарсенными данными
  • Редактирование сообщения вместо отправки нового

    Inline-клавиатуры часто используют для интерфейса вида "меню в одном сообщении": вы нажимаете кнопку, а бот меняет текст и клавиатуру у исходного сообщения.

    Пример: по нажатию меняем текст сообщения.

    Здесь используется сочетание:

  • DemoCb.filter(F.action == "hello") это более точный фильтр по полю структуры
  • callback.message.edit_text(...) редактирует то самое сообщение, под которым кнопки
  • Частые ошибки с клавиатурами и callback

  • Кнопка нажимается, но ничего не происходит
  • - Проверьте, что у вас есть @router.callback_query(...) хендлер, и что роутер подключён через include_router.

  • У пользователя долго крутится индикатор загрузки после нажатия
  • - Вы забыли await callback.answer().

  • Reply-кнопка не вызывает callback-хендлер
  • - Reply-кнопки не создают CallbackQuery. Они отправляют обычный Message.

  • Слишком длинный callback_data
  • - Telegram ограничивает callback_data (исторически 64 байта). Не кладите туда большие тексты, используйте короткие идентификаторы.

    Подключаем новый роутер

    Не забудьте включить роутер модуля keyboards_demo.py в сборщик.

    Файл bot/routers.py:

    Обратите внимание на порядок:

  • лучше подключать роутеры с командами и меню раньше
  • общий echo_router лучше оставлять ближе к концу, чтобы он не перехватывал сообщения, которые вы хотите обрабатывать отдельно
  • Что дальше

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

  • Reply-кнопки как быстрый ввод текста
  • Inline-кнопки как управление сообщением без спама в чате
  • CallbackQuery хендлеры и обязательное подтверждение через answer()
  • CallbackData как способ хранить структуру данных в callback_data без ручной склейки строк
  • Следующий логичный шаг после клавиатур: построение сценариев с состояниями (FSM), где кнопки и сообщения ведут пользователя по цепочке шагов.

    5. FSM и сценарии: состояния, переходы, хранение данных

    FSM и сценарии: состояния, переходы, хранение данных

    К этому моменту у нас уже есть каркас проекта на aiogram 3.x: роутеры, хендлеры, фильтры и клавиатуры. Следующий шаг, который превращает набор разрозненных команд в сценарий, это FSM (Finite State Machine, конечный автомат состояний).

    FSM помогает отвечать на вопрос: на каком шаге диалога сейчас находится пользователь и какое сообщение мы ожидаем следующим.

    Когда нужен FSM

    FSM полезен в любых многошаговых сценариях:

  • регистрация пользователя
  • оформление заказа
  • анкета с несколькими вопросами
  • настройка уведомлений
  • редактирование профиля
  • Без FSM вы обычно храните «текущий шаг» вручную (в памяти, в БД, в глобальных переменных), что быстро приводит к ошибкам и путанице.

    Что такое состояние и контекст

    В FSM есть две ключевые сущности:

  • Состояние — метка шага сценария, например waiting_name или waiting_age
  • Контекст FSM — хранилище данных, связанных с текущим состоянием пользователя, например введённое имя, возраст, выбранный тариф
  • В aiogram 3.x с FSM вы чаще всего взаимодействуете через:

  • StatesGroup и State — описание набора состояний
  • FSMContext — объект, через который вы:
  • - ставите состояние set_state - очищаете состояние clear - сохраняете данные update_data - читаете данные get_data

    Официальный раздел документации:

  • FSM в документации aiogram
  • Как aiogram понимает, чьё это состояние

    FSM привязывает состояние к ключу вида чат + пользователь.

    Практический смысл:

  • один и тот же пользователь может проходить сценарий в разных чатах независимо
  • разные пользователи могут одновременно проходить сценарий в одном и том же боте
  • Где хранится состояние: storage

    Чтобы FSM работал, нужен storage — слой хранения состояния и данных.

    Основные варианты:

  • MemoryStorage — хранение в памяти процесса
  • - подходит для локальной разработки - при перезапуске бота состояния сбросятся
  • Redis storage — хранение во внешнем Redis
  • - подходит для продакшена - переживает перезапуски

    Полезные ссылки:

  • Redis
  • redis-py
  • Обновляем структуру проекта под FSM

    Добавим модуль со состояниями и отдельный роутер под сценарий.

    Описываем состояния сценария

    Сделаем пример сценария "заполнить профиль":

  • команда /profile запускает анкету
  • бот спрашивает имя
  • затем возраст
  • затем подтверждение
  • Файл bot/states.py:

    Смысл:

  • ProfileForm — группа состояний одного сценария
  • name, age, confirm — шаги, на которые мы будем переключать пользователя
  • Клавиатуры для сценария

    Сделаем inline-кнопки "Подтвердить", "Отменить".

    Файл bot/keyboards/common.py (добавляем новые функции):

    Пишем сценарий с переходами и хранением данных

    Файл bot/handlers/profile_wizard.py:

    Что здесь важно:

  • @router.message(ProfileForm.name, F.text) означает: этот хендлер сработает только если пользователь находится в состоянии ProfileForm.name и прислал текст
  • state.update_data(...) сохраняет данные внутри FSM (в storage)
  • state.get_data() возвращает словарь всех сохранённых данных сценария
  • state.set_state(...) переключает пользователя на следующий шаг
  • state.clear() завершает сценарий и очищает данные
  • Визуальная модель сценария

    !Схема переходов между состояниями в сценарии заполнения профиля

    Подключаем storage и роутер

    Подключение FSM storage

    Если вы не зададите storage, FSM работать не будет так, как ожидается. Для старта используем MemoryStorage.

    Файл bot/main.py (покажем только важные изменения):

    Подключение роутера сценария

    Файл bot/routers.py (добавляем новый роутер и следим за порядком):

    Смысл порядка:

  • сценарные хендлеры должны иметь шанс отработать раньше
  • общий echo_router лучше держать ближе к концу
  • Частые ошибки при работе с FSM

  • забыли передать storage в Dispatcher
  • не очистили состояние clear() при отмене или завершении сценария
  • сделали слишком общий хендлер (например, на любой текст) и подключили его раньше FSM-хендлеров
  • не обрабатываете нетекстовые сообщения на шагах, где ожидаете текст
  • Что дальше

    FSM даёт основу для сложных пользовательских сценариев, но в реальных проектах почти сразу появляются следующие задачи:

  • хранить состояние в Redis, чтобы переживать перезапуски
  • добавлять роли, зависимости и проверки через middleware
  • выносить бизнес-логику и доступ к БД из хендлеров в отдельные сервисы
  • На текущем этапе у вас уже есть полный минимальный набор: структура проекта, клавиатуры и FSM-сценарии с хранением данных и переходами.

    6. Middleware, зависимости и общие сервисы (БД, API, кэш)

    Middleware, зависимости и общие сервисы (БД, API, кэш)

    В предыдущих частях курса мы построили основу бота на aiogram 3.x: структуру проекта с Router, научились писать хендлеры с фильтрами, подключили клавиатуры и сделали многошаговые сценарии через FSM.

    На практике следующий шаг почти всегда один и тот же: вам нужно подключить общие ресурсы, которые используются во многих хендлерах.

  • база данных (чтобы хранить пользователей, настройки, заявки)
  • внешний API (погода, платежи, CRM)
  • кэш (чтобы не дергать API слишком часто)
  • И важный вопрос: как передавать эти зависимости в хендлеры правильно, не превращая код в набор глобальных переменных и импорта всего отовсюду.

    В aiogram 3.x для этого есть связка:

  • middleware: код, который выполняется до и после хендлера
  • зависимости (dependency injection): возможность получать нужные объекты прямо в параметрах хендлера
  • !Поток апдейта через middleware и внедрение зависимостей в хендлер

    Что такое middleware в aiogram

    Middleware — это компонент, который оборачивает выполнение хендлеров.

    Типичные задачи middleware:

  • подключить общие сервисы и сделать их доступными в хендлерах
  • логировать входящие апдейты и время обработки
  • проверить права пользователя (роль, подписка)
  • ограничить частоту запросов (rate limit)
  • создать контекст запроса (например, загрузить пользователя из БД один раз и использовать дальше)
  • В aiogram 3.x middleware обычно реализуется как класс, наследующийся от BaseMiddleware.

    Как зависимости попадают в хендлер

    Внутри aiogram при обработке события есть общий словарь data (контекст выполнения). Middleware может положить туда любые объекты, например:

  • data["config"] = config
  • data["db"] = db
  • data["api"] = api_client
  • После этого aiogram может передать эти объекты в хендлер по имени параметра.

    Пример идеи:

  • вы положили db в data
  • вы написали хендлер async def handler(message: Message, db: Database): ...
  • aiogram подставит db автоматически
  • Это и есть практическая dependency injection в рамках фреймворка.

    Рекомендуемая структура проекта для middleware и сервисов

    Продолжим структуру из прошлых уроков и добавим новые слои.

    Смысл разделения:

  • services/ содержит чистые сервисы без привязки к Telegram
  • middlewares/ соединяет сервисы и слой Telegram-хендлеров
  • handlers/ остаётся тонким слоем логики обработки сообщений и сценариев
  • Пример сервисов: БД, API, кэш

    Ниже мы сделаем учебный набор:

  • БД: aiosqlite (просто и асинхронно)
  • API: клиент на aiohttp
  • кэш: простой in-memory кэш (для продакшена чаще берут Redis)
  • Ссылки на источники:

  • Документация aiogram
  • aiohttp documentation
  • aiosqlite на PyPI
  • Redis
  • Сервис кэша

    Файл bot/services/cache.py:

    Этот кэш:

  • живёт в памяти процесса
  • сбрасывается при перезапуске бота
  • подходит для разработки и простых задач
  • Сервис API-клиента

    Файл bot/services/api.py:

    Здесь ExampleApi демонстрирует важный принцип:

  • не создавайте ClientSession внутри каждого запроса
  • создайте одну сессию на приложение и переиспользуйте
  • Сервис БД

    Файл bot/services/db.py:

    Замечания:

  • это учебный репозиторий, чтобы показать идею сервисного слоя
  • в реальных проектах часто используют PostgreSQL и ORM (например SQLAlchemy), но принцип внедрения зависимости тот же
  • Middleware для внедрения сервисов

    Теперь создадим middleware, которое положит в data объекты:

  • db, users (репозиторий)
  • api
  • cache
  • при желании config
  • Файл bot/middlewares/services.py:

    Ключевая идея:

  • middleware не должен делать бизнес-логику
  • middleware должен подготовить окружение для хендлера
  • Логирующий middleware (пример полезной обвязки)

    Файл bot/middlewares/logging.py:

    Этот middleware:

  • измеряет время обработки каждого события
  • показывает, где бот начинает тормозить
  • Подключение middleware и жизненный цикл сервисов

    У сервисов часто есть жизненный цикл:

  • создать подключение к БД
  • создать aiohttp.ClientSession
  • корректно закрыть ресурсы при завершении
  • В aiogram удобно использовать startup и shutdown.

    Инициализация в main.py

    Ниже пример bot/main.py, который:

  • создаёт сервисы
  • регистрирует middleware
  • инициализирует схему БД на старте
  • закрывает ресурсы на остановке
  • Практические правила:

  • сервисы создаются один раз при запуске
  • сервисы закрываются в finally, чтобы закрытие происходило даже при ошибке
  • middleware подключается до start_polling
  • > Важно: точный способ регистрации middleware зависит от того, на каком уровне вы хотите его применять. В примере показан глобальный вариант через dp.update.outer_middleware(...), чтобы он срабатывал для всех типов апдейтов.

    Использование зависимостей в хендлерах

    Теперь хендлеры могут получать сервисы как параметры.

    Файл bot/handlers/services_demo.py:

    Что важно в этом примере:

  • хендлеры не знают, как именно создаются UserRepo, ExampleApi, SimpleTTLCache
  • хендлеры получают уже готовые зависимости
  • кэш снижает количество запросов во внешний API
  • Не забудьте подключить этот роутер в bot/routers.py.

    Middleware как место для общих проверок

    Частая задача: сделать проверку до входа в хендлер.

    Примеры:

  • блокировать пользователя
  • проверять, что пользователь зарегистрирован
  • разрешать команды только админам
  • Подход:

  • middleware читает входное событие
  • при необходимости завершает обработку (не вызывает handler)
  • Простейший пример идеи (псевдо-логика):

    Так вы не дублируете проверки в каждом хендлере.

    Типичные ошибки и как их избежать

  • Создаёте ClientSession внутри каждого хендлера
  • - Правильно: одна сессия на приложение, закрытие в конце.

  • Делаете глобальные переменные db, api, cache
  • - Правильно: создаёте сервисы в main.py, передаёте в middleware.

  • Забываете закрывать ресурсы
  • - Правильно: закрывать соединения в finally.

  • Смешиваете бизнес-логику и Telegram-слой
  • - Правильно: хендлеры тонкие, основная логика в services/.

    Что дальше

    Теперь у вас есть архитектурный слой, который нужен почти любому реальному боту:

  • middleware как точка расширения
  • зависимости, которые инжектятся в хендлеры
  • сервисы для БД, API и кэша
  • Следующий логичный шаг в развитии проекта (за рамками базового плана курса) обычно такой:

  • заменить MemoryStorage FSM на Redis storage
  • вынести конфиги окружений (dev/prod)
  • добавить роли, авторизацию и полноценное логирование
  • 7. Запуск, деплой и поддержка: вебхуки, логи, обработка ошибок

    Запуск, деплой и поддержка: вебхуки, логи, обработка ошибок

    Мы уже умеем строить бота на aiogram 3.x с понятной структурой, роутерами, клавиатурами, FSM и зависимостями через middleware. Теперь разберём, как перевести бота из режима разработки в режим работающего сервиса: настроить webhook, организовать логи, и сделать устойчивую обработку ошибок.

    Polling и webhook: что выбрать для продакшена

    В разработке мы использовали long polling через dp.start_polling(bot): бот сам забирает апдейты у Telegram.

    Для продакшена чаще выбирают webhook: Telegram сам отправляет апдейты на ваш HTTPS-адрес.

    | Критерий | Long polling | Webhook | |---|---|---| | Нужен публичный HTTPS | Нет | Да | | Запуск локально | Проще | Сложнее | | Экономия ресурсов | Обычно хуже | Обычно лучше | | Подходит для VPS/контейнеров | Да | Да | | Подходит для serverless-подходов | Редко | Часто |

    Официальная спецификация вебхуков находится в документации Telegram Bot API: Webhook в Telegram Bot API

    !Общая архитектура webhook-бота

    Что нужно для webhook

    Чтобы webhook работал стабильно, нужны базовые условия:

  • Публичный домен или статический публичный IP.
  • HTTPS с валидным сертификатом.
  • HTTP-сервер в вашем приложении, который принимает запросы от Telegram.
  • Настроенный setWebhook.
  • Дополнительно рекомендуется:

  • Использовать secret token для проверки, что запрос пришёл именно от Telegram.
  • Иметь reverse proxy (например, Nginx), который завершает TLS и проксирует на ваше приложение.
  • Конфигурация для двух режимов: polling и webhook

    Чтобы не заводить отдельные проекты под разные режимы запуска, удобно переключать режим переменной окружения.

    Расширяем конфиг

    Файл bot/config.py:

    Рекомендуемые переменные окружения для webhook-режима:

  • BOT_MODE=webhook
  • WEBHOOK_URL=https://your-domain.example
  • WEBHOOK_PATH=/tg/webhook
  • WEBHOOK_SECRET=случайная_секретная_строка
  • WEBAPP_HOST=0.0.0.0
  • WEBAPP_PORT=8080
  • > WEBHOOK_URL это публичный адрес, который видит Telegram. WEBAPP_HOST и WEBAPP_PORT это то, где слушает ваше приложение внутри сервера или контейнера.

    Реализация webhook на aiohttp + aiogram 3.x

    Для webhook вам нужен HTTP-сервер. В экосистеме aiogram стандартный вариант для webhook в Python это aiohttp.

    Установите зависимости:

    Основная идея

  • Мы создаём Bot и Dispatcher как раньше.
  • Подключаем роутеры через dp.include_router(setup_routers()).
  • Создаём aiohttp.web.Application().
  • Регистрируем обработчик webhook-запросов.
  • На startup выставляем webhook через bot.set_webhook(...).
  • На shutdown удаляем webhook и закрываем ресурсы.
  • Пример файла запуска с двумя режимами

    Файл bot/main.py:

    Что важно в этом коде:

  • set_webhook(..., secret_token=...) включает проверку секретного токена.
  • SimpleRequestHandler(..., secret_token=...) заставляет сервер принимать запросы только с корректным токеном.
  • drop_pending_updates=True полезен при деплое, чтобы не разгребать очередь старых апдейтов.
  • Подробности параметров webhook в Telegram: Webhook в Telegram Bot API

    Reverse proxy и HTTPS: типичная продакшен-схема

    Обычно приложение слушает HTTP на локальном порту, а HTTPS и сертификаты обслуживает отдельный слой:

  • VPS: Nginx
  • Kubernetes: Ingress Controller
  • PaaS: встроенный прокси платформы
  • !Зачем нужен reverse proxy при webhook

    Практическое правило:

  • Telegram должен видеть публичный HTTPS.
  • Ваше приложение может быть обычным HTTP-сервером за прокси.
  • Логи: как сделать бота наблюдаемым

    Логи нужны не для красоты, а чтобы отвечать на вопросы:

  • бот жив?
  • почему конкретный сценарий упал?
  • где тормозит обработка?
  • что изменилось после деплоя?
  • Базовая документация по модулю логирования Python: logging в Python

    Уровни логирования

  • DEBUG подробности для разработки.
  • INFO нормальные события работы.
  • WARNING странные, но не критичные ситуации.
  • ERROR ошибка, запрос обработан некорректно.
  • CRITICAL состояние, при котором сервис не может работать.
  • Практика для структуры проекта

  • В каждом модуле используйте logger = logging.getLogger(__name__).
  • Логируйте контекст: user_id, команда, состояние FSM.
  • Не логируйте секреты: токены, пароли, полные персональные данные.
  • Пример в хендлере:

    Ротация логов в файл

    Если вы пишете логи в файл на сервере, добавьте ротацию.

    Смысл параметров:

  • maxBytes ограничивает размер одного файла.
  • backupCount хранит несколько предыдущих файлов.
  • Обработка ошибок: как не превращать баг в падение сервиса

    Ошибки в боте неизбежны: сеть, внешние API, неожиданные входные данные, изменения в сценариях FSM. Цель продакшен-бота не в том, чтобы ошибок не было, а в том, чтобы:

  • бот не падал целиком из-за одной ошибки
  • ошибка попадала в логи с контекстом
  • пользователь получал понятный ответ
  • Локальная обработка ошибок в хендлере

    Если ошибка ожидаемая, обрабатывайте её там же.

    logger.exception(...) важен тем, что добавляет traceback в лог.

    Глобальная обработка ошибок через error handler

    Чтобы не дублировать одинаковый try/except в каждом месте, используют глобальный обработчик ошибок на уровне роутера.

    Файл bot/handlers/errors.py:

    Что здесь происходит:

  • ErrorEvent содержит исходное событие и исключение.
  • Мы логируем исключение так, чтобы оно попадало в логи с traceback.
  • Подключите этот роутер как обычный модуль в bot/routers.py, чтобы он был включён в приложение.

    Ошибки Telegram API и сетевые проблемы

    Даже если ваш код идеальный, Telegram API может отвечать ошибками, или сеть может быть нестабильной.

    Практики, которые помогают:

  • Разносите внешний API и Telegram-слой, как мы делали в разделе про сервисы.
  • Ставьте таймауты во внешних запросах.
  • Кэшируйте результаты, если это уместно.
  • Поддержка в продакшене: минимальный чеклист

    Перед деплоем проверьте:

  • Переменные окружения заданы и не коммитятся в репозиторий.
  • В webhook-режиме задан WEBHOOK_URL, используется HTTPS.
  • Установлен WEBHOOK_SECRET, и он совпадает между set_webhook и SimpleRequestHandler.
  • Включены логи как минимум в stdout, чтобы их собирала платформа.
  • У вас есть глобальная фиксация неожиданных ошибок в логах.
  • После деплоя проверьте:

  • Команда /start отвечает.
  • Сценарии FSM проходят полный цикл.
  • Inline-кнопки подтверждаются answer() и не «крутятся».
  • Логи реально пишутся и доступны для просмотра.
  • Что дальше можно улучшать

    Если бот становится серьёзным сервисом, обычно добавляют:

  • Redis для FSM storage вместо MemoryStorage.
  • Централизованный сбор логов.
  • Уведомления об ошибках в отдельный чат админа.
  • Метрики и healthcheck-эндпоинты.
  • На текущем уровне курса вам достаточно уверенно владеть тремя вещами:

  • запуском в polling и webhook режимах
  • базовой наблюдаемостью через logging
  • устойчивой обработкой ошибок через локальные try/except и глобальный error handler