aiogram — асинхронный фреймворк на Python для Telegram Bot API. Чтобы собрать на нём бота, поставьте Python 3.10 или новее, выполните pip install -U aiogram, получите токен у @BotFather и запустите скрипт, где Dispatcher вызывает start_polling. Ниже рабочий код третьей версии: команда /start, эхо, inline-кнопки, анкета на машине состояний и запуск на сервере.
Если асинхронный код пока пугает, начните с бота на pyTelegramBotAPI: там те же шаги на синхронной библиотеке. aiogram берут, когда бот обслуживает много людей сразу и не должен ждать, пока Telegram ответит на каждый запрос.
SkillboxПрофессия «Python-разработчик»157 335 ₽10 месяцев
НетологияFullstack-разработчик на Python137 000 ₽21 месяц
GeekBrainsДО Профессия Python-разработчик149 001 ₽10 месяцев
SkillFactoryПрофессия Python-разработчик154 044 ₽10 месяцев
Академия СинергияPython-разработчик + ИИ77 900 ₽6 месяцев
ЭдюсонPython-разработчик119 892 ₽9 месяцев
Что такое aiogram и какая версия актуальна

Библиотека построена на asyncio и aiohttp. Пока один запрос к серверам Telegram в пути, бот уже обрабатывает следующее сообщение. По странице пакета на PyPI, версия 3.31.0 вышла 26 августа 2026 года и работает на Python от 3.10 до 3.14.
Главное, что надо знать до первой строки кода: aiogram 2 и aiogram 3 несовместимы. Примеры с executor и @dp.message_handler, которые ещё встречаются в учебных материалах, написаны под вторую версию и на третьей падают сразу.
| Что | aiogram 2.x | aiogram 3.x |
|---|---|---|
| Запуск опроса | executor.start_polling(dp) |
await dp.start_polling(bot) |
| Обработчик сообщений | @dp.message_handler() |
@router.message() |
| Хранилища состояний | aiogram.contrib.fsm_storage |
aiogram.fsm.storage |
Полный список переименований есть в руководстве по переходу с 2.x на 3.x из официальной документации.
Как установить aiogram и получить токен
- Проверьте версию Python:
python3 --versionна macOS и Linux,python --versionна Windows. Нужна 3.10 или выше. Если Python нет, поставьте его по инструкции для Windows, macOS и Linux. - Создайте папку проекта и виртуальное окружение, чтобы библиотеки бота не смешались с системными.
- Активируйте окружение и поставьте библиотеку из PyPI.
mkdir tgbot
cd tgbot
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -U aiogram
Проверьте, что библиотека встала:
python -c "import aiogram; print(aiogram.__version__)"
3.31.0
Команда pip install -U aiogram взята из раздела Installation документации. Ключ -U обновит библиотеку, если старая версия уже стоит. Для хранения анкет в Redis понадобится дополнение: pip install -U "aiogram[redis]".
Если пишете в редакторе кода, выберите в нём интерпретатор из папки .venv. Иначе запуск из редактора закончится ошибкой ModuleNotFoundError, хотя в терминале всё работает. Как это сделать в одной из популярных сред, показано в настройке VS Code для Python.
Токен у BotFather и где его хранить
- Найдите в Telegram аккаунт @BotFather и отправьте ему
/newbot. - Придумайте имя бота и username. Username должен заканчиваться на bot.
- BotFather пришлёт токен вида
123456789:AAH…. Это ключ от бота: кто его знает, тот и управляет ботом.
Поэтому токен не пишут в код, особенно если код уйдёт на GitHub. Его кладут в переменную окружения, а скрипт читает её при старте:
export BOT_TOKEN="123456789:AAH..." # macOS и Linux
$env:BOT_TOKEN="123456789:AAH..." # Windows, PowerShell
Переменная живёт, пока открыт терминал. Закрыли окно, и перед запуском её придётся задать снова.
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяПервый бот на aiogram 3: команда /start и эхо
Создайте файл bot.py. Этот бот здоровается по имени на /start и повторяет любой текст:
import asyncio
import logging
import os
from aiogram import Bot, Dispatcher, F, Router
from aiogram.filters import CommandStart
from aiogram.types import Message
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message) -> None:
await message.answer(
f"Привет, {message.from_user.first_name}! Пришлите текст, и я его повторю. "
"Меню с кнопками: /menu"
)
@router.message(F.text)
async def echo(message: Message) -> None:
await message.answer(message.text)
async def main() -> None:
logging.basicConfig(level=logging.INFO)
bot = Bot(token=os.environ["BOT_TOKEN"])
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())
Запустите его командой python bot.py. В терминале появится строка INFO:aiogram.dispatcher:Start polling, и бот начнёт отвечать. Остановить его можно сочетанием Ctrl+C.
Что здесь происходит:
Routerсобирает обработчики. В маленьком боте он один, в большом их делят по файлам: анкета отдельно, админка отдельно.CommandStart()ловит /start, аF.textловит любое текстовое сообщение.message.answer()отправляет ответ в тот же чат. Перед ним стоитawait, потому что метод асинхронный. Еслиasyncиawaitпока не складываются в картину, посмотрите курсы по асинхронному программированию.- Имя в приветствии подставляет f-строка: всё, что стоит в фигурных скобках, Python вычисляет и вставляет в текст.
dp.start_polling(bot)раз за разом спрашивает у Telegram новые сообщения. Пока скрипт работает, бот на связи.
Порядок обработчиков важен. Сообщение уходит первому подходящему обработчику, поэтому эхо стоит последним. Поднимите его выше, и бот начнёт повторять даже команды. Если функции и декораторы пока читаются с трудом, загляните в разбор функций в Python.
Кнопки и фото в боте на aiogram
Inline-кнопки под сообщением
Кнопки под сообщением собирают через InlineKeyboardBuilder. Добавьте в bot.py импорты и два обработчика нажатий. Код ставится выше эха:
from datetime import datetime
from aiogram.filters import Command
from aiogram.types import CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
@router.message(Command("menu"))
async def cmd_menu(message: Message) -> None:
builder = InlineKeyboardBuilder()
builder.button(text="Который час", callback_data="time")
builder.button(text="Убрать кнопки", callback_data="hide")
await message.answer("Что сделать?", reply_markup=builder.as_markup())
@router.callback_query(F.data == "time")
async def on_time(callback: CallbackQuery) -> None:
await callback.answer(datetime.now().strftime("%H:%M"), show_alert=True)
@router.callback_query(F.data == "hide")
async def on_hide(callback: CallbackQuery) -> None:
await callback.message.edit_reply_markup(reply_markup=None)
await callback.answer()
Как это работает:
- При нажатии бот получает не сообщение, а
CallbackQueryсо строкой изcallback_data. По описанию InlineKeyboardButton, в ней от 1 до 64 байт, поэтому туда кладут короткий код действия, а не данные. show_alert=Trueпоказывает ответ всплывающим окном. Без этого флага текст всплывёт уведомлением вверху чата.edit_reply_markup(reply_markup=None)убирает клавиатуру из сообщения.- Обе кнопки встанут в один ряд. Чтобы разложить их по одной в строке, вызовите
builder.adjust(1)передas_markup().
callback.answer() вызывайте всегда, даже если отвечать нечего. В описании CallbackQuery сказано: после нажатия клиент Telegram показывает индикатор загрузки, пока бот не вызовет AnswerCallbackQuery.
Клавиатура под полем ввода и отправка фото
Inline-кнопки висят под конкретным сообщением. Есть второй вид: клавиатура вместо обычной, под полем ввода. Нажатие на такую кнопку просто отправляет её текст в чат, поэтому ловят его обычным фильтром по тексту.
from aiogram.types import FSInputFile, ReplyKeyboardRemove
from aiogram.utils.keyboard import ReplyKeyboardBuilder
@router.message(Command("photo"))
async def cmd_photo(message: Message) -> None:
await message.answer_photo(FSInputFile("cat.jpg"), caption="Это кот")
@router.message(Command("ask"))
async def cmd_ask(message: Message) -> None:
builder = ReplyKeyboardBuilder()
builder.button(text="Да")
builder.button(text="Нет")
await message.answer(
"Подписаться на рассылку?",
reply_markup=builder.as_markup(resize_keyboard=True, one_time_keyboard=True),
)
@router.message(F.text.in_({"Да", "Нет"}))
async def on_answer(message: Message) -> None:
await message.answer(f"Ответ принят: {message.text}", reply_markup=ReplyKeyboardRemove())
FSInputFileотправляет файл с диска, путь считается от папки, откуда запущен скрипт, как и при открытии файла через open(). Для документа то же самое делаетanswer_document.resize_keyboard=Trueподгоняет высоту клавиатуры под число рядов, без него она занимает столько же места, сколько обычная.one_time_keyboard=Trueпрячет её после нажатия, вернуть можно значком в поле ввода. Оба флага описаны на странице ReplyKeyboardMarkup.ReplyKeyboardRemove()убирает клавиатуру совсем, когда ответ получен.
Эти обработчики тоже ставятся выше эха, иначе «Да» и «Нет» бот просто повторит.
Анкета на машине состояний (FSM)

Когда бот задаёт вопросы по очереди, ему нужно помнить, на каком шаге человек. Для этого в aiogram есть машина состояний. Пример спрашивает имя и город:
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
class Signup(StatesGroup):
name = State()
city = State()
@router.message(Command("signup"))
async def signup_start(message: Message, state: FSMContext) -> None:
await state.set_state(Signup.name)
await message.answer("Как вас зовут?")
@router.message(Signup.name, F.text)
async def signup_name(message: Message, state: FSMContext) -> None:
await state.update_data(name=message.text)
await state.set_state(Signup.city)
await message.answer("Из какого вы города?")
@router.message(Signup.city, F.text)
async def signup_city(message: Message, state: FSMContext) -> None:
data = await state.get_data()
await state.clear()
await message.answer(f"Записал: {data['name']}, {message.text}")
Signup — обычный класс Python, где каждое поле State() означает один шаг анкеты. Диалог выглядит так: /signup → «Как вас зовут?» → «Аня» → «Из какого вы города?» → «Казань» → «Записал: Аня, Казань». state.clear() в конце сбрасывает анкету, и следующее сообщение снова попадёт в эхо.
По умолчанию состояния лежат в памяти процесса. В разделе Storages про это хранилище прямо написано: для продакшена его не советуют, при перезапуске бота данные теряются. Для настоящего бота подключают Redis:
from aiogram.fsm.storage.redis import RedisStorage
dp = Dispatcher(storage=RedisStorage.from_url("redis://localhost:6379/0"))
Как запустить бота на сервере
Бот на вашем компьютере засыпает вместе с компьютером. Чтобы он отвечал круглосуточно, скрипт переносят на Linux-сервер. Получать сообщения можно двумя способами:
| Способ | Как работает | Что нужно |
|---|---|---|
| Polling | Бот сам спрашивает у Telegram новые сообщения, как в примерах выше | Любой сервер, домен не нужен |
| Webhook | Telegram сам присылает каждое обновление HTTPS-запросом с JSON на ваш адрес (метод SetWebhook) | Домен с HTTPS и веб-сервер; готовый каркас есть в разделе Webhook |
Для первого бота хватит polling. Скрипт оформляют службой systemd, чтобы он сам поднимался после сбоя и перезагрузки сервера. Файл службы и порядок команд разобраны в инструкции по боту на pyTelegramBotAPI, для aiogram меняется только одно. Наш код читает токен из переменной окружения, поэтому добавьте в раздел [Service] строку с файлом, где лежит токен:
[Service]
EnvironmentFile=/home/bot/tgbot/.env
ExecStart=/home/bot/tgbot/.venv/bin/python bot.py
В самом .env одна строка: BOT_TOKEN=123456789:AAH.... Права на файл лучше сразу ограничить: chmod 600 .env.
Частые ошибки и что с ними делать

Большинство проблем с первым ботом укладывается в семь симптомов. Ищите в таблице текст из своего терминала:
| Что видите | Почему | Что сделать |
|---|---|---|
ModuleNotFoundError: No module named 'aiogram' |
Библиотека стоит в другом окружении, чем то, которым запускаете скрипт | Активируйте .venv и выполните python -m pip install -U aiogram |
ImportError: cannot import name 'executor' from 'aiogram' |
Код написан под aiogram 2 | Запускайте через asyncio.run(main()) и await dp.start_polling(bot) |
AttributeError: 'Dispatcher' object has no attribute 'message_handler' |
Тоже код второй версии | Замените декоратор на @router.message() |
TokenValidationError: Token is invalid! |
В переменной не токен: пусто, лишний пробел, обрезана часть | Скопируйте токен из чата с BotFather заново |
TelegramUnauthorizedError: Telegram server says - Unauthorized |
Токен правильного вида, но Telegram его не принимает: опечатка или токен отозван | Возьмите действующий токен у @BotFather |
TelegramConflictError |
Тот же токен уже опрашивает другая копия бота | Оставьте одну запущенную копию: на сервере или на ноутбуке |
| Кнопка нажата, а на ней крутится загрузка | Обработчик не вызвал callback.answer() или нет обработчика под эту callback_data |
Добавьте await callback.answer() и сверьте строки в кнопке и в фильтре F.data |
С ModuleNotFoundError разбираются одинаково для любой библиотеки. Как выбрать правильное окружение, подробно рассказано в разборе той же ошибки в инструкции по установке PyGame.
Если бот молчит без всяких ошибок, проверьте две вещи. Отправляете ли вы текст, а не стикер: эхо ловит только F.text. И не стоит ли обработчик ниже эха.
Где освоить Python, чтобы бот вырос
Эхо, кнопки и анкета закрывают первый вечер. Дальше идёт то, без чего не обходится настоящий бот: база данных, оплата, очереди, тесты, деплой без ручных шагов. Это обычная работа Python-разработчика. Про то, как выглядит профессия целиком, есть отдельный материал: кто такой разработчик чат-ботов.
Если хотите учиться с проверкой заданий и наставником, ниже курсы из каталога по Python:
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 137 000 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| ДО Профессия Python-разработчик Перейти на сайт курса | 149 001 ₽ | 3167 ₽/мес. | 10 месяцев | Обзор курса | |
| Автоматизированное тестирование на Python Перейти на сайт курса | 122 226 ₽ | 4108 ₽/мес. | 9 месяцев | Обзор курса | |
| Профессия Python-разработчик Перейти на сайт курса | 154 044 ₽ | 4279 ₽/мес. | 10 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по Python




