← все статьи
~/blog/aiogram-3-tutorial-2025.md
10 апреля 2025 г.·12 мин

Aiogram 3: туториал по созданию Telegram-бота на Python (2025)

Пошаговое руководство по созданию Telegram-бота на Aiogram 3 — хендлеры, FSM, инлайн-клавиатуры, деплой. Без воды.

AiogramPythonТуториалTelegramБоты

Aiogram 3 — это асинхронный Python-фреймворк для Telegram-ботов. Я использовал его в 20+ продакшн-проектах. Вот всё, что нужно чтобы пройти путь от нуля до запущенного бота, не продираясь через устаревшие туториалы.

Почему именно Aiogram 3

Есть несколько Python-библиотек для Telegram-ботов. Aiogram 3 выигрывает для продакшна потому что:

  • Async-first — построен на asyncio, обрабатывает тысячи обновлений эффективно
  • Система роутеров — разделяйте хендлеры по модулям чисто и аккуратно
  • Magic filtersF.text == "привет", F.photo, F.from_user.id == 123456 — выразительно и читаемо
  • FSM — встроенный конечный автомат для многошаговых диалогов
  • Типобезопасность — полная типизация во всём фреймворке

Старый python-telegram-bot тоже работает, но архитектура Aiogram 3 чище для любого проекта сложнее игрушечного.

Установка

pip install aiogram

Получите токен бота у @BotFather — отправьте /newbot, следуйте инструкциям.

Структура проекта, которую я использую:

mybot/
├── bot.py          # точка входа
├── config.py       # токен + настройки
├── handlers/
│   ├── start.py
│   └── orders.py
├── keyboards/
│   └── kb.py
└── states/
    └── states.py

config.py:

import os
BOT_TOKEN = os.getenv("BOT_TOKEN", "your_token_here")

Первый бот: хендлер /start

# bot.py
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message

bot = Bot(token="YOUR_TOKEN")
dp = Dispatcher()

@dp.message(CommandStart())
async def cmd_start(message: Message):
    await message.answer(f"Привет, {message.from_user.first_name}!")

async def main():
    await dp.start_polling(bot)

if __name__ == "__main__":
    asyncio.run(main())

Запустите, отправьте /start своему боту. Это база.

Роутеры: держим код в порядке

В Aiogram 3 Router — ключевая абстракция. Вместо того чтобы регистрировать всё на Dispatcher, создавайте роутеры на каждый модуль и подключайте их:

# handlers/start.py
from aiogram import Router, F
from aiogram.filters import CommandStart
from aiogram.types import Message

router = Router()

@router.message(CommandStart())
async def cmd_start(message: Message):
    await message.answer("Добро пожаловать!")

@router.message(F.text == "помощь")
async def help_text(message: Message):
    await message.answer("Вот как работает этот бот...")
# bot.py
from handlers import start, orders

dp.include_router(start.router)
dp.include_router(orders.router)

Это сохраняет больших ботов поддерживаемыми. Каждый файл хендлеров остаётся сфокусированным.

Magic filters: F.

Объект F — то, где Aiogram 3 особенно хорош:

from aiogram import F

# Точное совпадение текста
@router.message(F.text == "Купить")

# Сообщения с фото
@router.message(F.photo)

# Конкретный пользователь
@router.message(F.from_user.id == 123456789)

# Комбинирование фильтров
@router.message(F.text.startswith("заказ:") & F.from_user.is_premium)

# Отрицание
@router.message(~F.text.in_({"стоп", "отмена"}))

Больше никаких if message.text == "Купить": внутри хендлеров. Фильтры объявлены декларативно — их легко читать и комбинировать.

Инлайн-клавиатуры и колбэки

# keyboards/kb.py
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import InlineKeyboardMarkup

def main_kb() -> InlineKeyboardMarkup:
    b = InlineKeyboardBuilder()
    b.button(text="📦 Товары", callback_data="products")
    b.button(text="📋 Мои заказы", callback_data="my_orders")
    b.button(text="💬 Контакт", url="https://t.me/yourusername")
    b.adjust(2, 1)
    return b.as_markup()
# handler
from aiogram.types import CallbackQuery

@router.message(CommandStart())
async def cmd_start(message: Message):
    await message.answer("Выберите:", reply_markup=main_kb())

@router.callback_query(F.data == "products")
async def show_products(callback: CallbackQuery):
    await callback.answer()
    await callback.message.edit_text("Вот наши товары...", reply_markup=products_kb())

Всегда вызывайте callback.answer() первой — без неё кнопка остаётся в состоянии загрузки 30 секунд.

FSM: многошаговые диалоги

FSM (конечный автомат) — это то, как обрабатывать сценарии, которые длятся несколько сообщений: формы заказа, регистрация, опросы.

# states/states.py
from aiogram.fsm.state import State, StatesGroup

class OrderForm(StatesGroup):
    waiting_name = State()
    waiting_phone = State()
    waiting_description = State()
# handlers/orders.py
from aiogram.fsm.context import FSMContext
from states.states import OrderForm

@router.callback_query(F.data == "new_order")
async def start_order(callback: CallbackQuery, state: FSMContext):
    await state.set_state(OrderForm.waiting_name)
    await callback.answer()
    await callback.message.answer("Как вас зовут?")

@router.message(OrderForm.waiting_name)
async def got_name(message: Message, state: FSMContext):
    await state.update_data(name=message.text)
    await state.set_state(OrderForm.waiting_phone)
    await message.answer("Ваш номер телефона:")

@router.message(OrderForm.waiting_phone)
async def got_phone(message: Message, state: FSMContext):
    await state.update_data(phone=message.text)
    await state.set_state(OrderForm.waiting_description)
    await message.answer("Опишите задачу:")

@router.message(OrderForm.waiting_description)
async def got_description(message: Message, state: FSMContext):
    data = await state.get_data()
    await state.clear()
    await message.answer(
        f"✅ Заказ принят!\n"
        f"Имя: {data['name']}\n"
        f"Телефон: {data['phone']}\n"
        f"Задача: {message.text}"
    )

FSMContext автоматически внедряется Aiogram. Состояние хранится между сообщениями. По умолчанию используется MemoryStorage (теряется при перезапуске) — для продакшна переключайтесь на RedisStorage.

Middleware: код на каждое обновление

from aiogram import BaseMiddleware
from aiogram.types import TelegramObject

class LoggingMiddleware(BaseMiddleware):
    async def __call__(self, handler, event: TelegramObject, data: dict):
        print(f"Обновление от пользователя {event.from_user.id}")
        return await handler(event, data)

dp.update.middleware(LoggingMiddleware())

Я использую middleware чтобы автоматом сохранять пользователей в базу при первом контакте — это выполняется до любого хендлера, никогда не забудешь добавить.

Деплой: systemd на VPS

Простейшая продакшн-установка: Ubuntu VPS, systemd сервис. Docker не нужен для простых ботов.

# /etc/systemd/system/mybot.service
[Unit]
Description=My Telegram Bot
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/mybot
Environment="BOT_TOKEN=your_token"
ExecStart=/home/ubuntu/mybot/.venv/bin/python bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl enable mybot
sudo systemctl start mybot
sudo systemctl status mybot
journalctl -u mybot -f

Бот теперь переживает перезагрузки VPS и автоматически перезапускается после падений.

Что изучать дальше

Когда освоите базу:

  • PostgreSQL + asyncpg — большинству ботов рано или поздно нужна база данных
  • Telegram Stars платежиsend_invoice(), pre_checkout_query, successful_payment
  • Webhooks — быстрее чем polling для высоконагруженных ботов, нужен HTTPS домен
  • Telegram Mini App — полноценный веб-интерфейс внутри Telegram
  • Система рассылок — отправка сообщений всем пользователям без превышения лимитов

Документация на docs.aiogram.dev отличная. Telegram-чат Aiogram (@aiogram) активный и полезный.

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

// нужна помощь с проектом?

Разрабатываю бэкенды, боты, автоматизации и AI-инструменты.

написать →