Telegram-боты на aiogram

Telegram-бот — это программа, которая отвечает на сообщения пользователей через API Telegram. В этом уроке напишем бота на Python с библиотекой aiogram: от токена до запуска на сервере.

Уровень: начальный 17 разделов 3–4 часа

1. Как работают Telegram-боты

Бот — это обычная программа, у которой есть токен — секретный ключ доступа к вашему боту в Telegram. Через API бот получает события (сообщения, нажатия кнопок) и отправляет ответы. Есть два способа получать события.

СпособКак работаетКогда нужен
Long pollingбот сам опрашивает сервер Telegramучебные и средние проекты
WebhookTelegram сам шлёт события на ваш URLбоевые боты и высокая нагрузка
Для старта достаточно long polling: ничего не нужно покупать и настраивать, бот запускается локально.

2. Токен и BotFather

Бота создаёт официальный аккаунт @BotFather. Напишите ему команду /newbot, задайте имя и username — в ответ придёт токен.

/newbot
Имя бота: Учебный бот
Username: my_first_study_bot

Токен: 1234567890:AAHwLg...-секретная-часть

Если токен потерялся, команда /token в BotFather покажет его снова. Username бота должен заканчиваться на bot.

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

3. Установка aiogram

aiogram — асинхронная библиотека для Telegram Bot API на Python. Работает от Python 3.9 и новее. Установим её в виртуальном окружении проекта.

python -m venv .venv
.venv\Scripts\activate     # Windows
source .venv/bin/activate  # Linux и macOS
pip install aiogram
Виртуальное окружение изолирует зависимости проекта. Подробнее — в уроке Python.

4. Первый бот: команда /start

Самый маленький рабочий бот. Токен удобно хранить в переменной окружения BOT_TOKEN.

from aiogram import Bot, Dispatcher
from aiogram.types import Message
from aiogram.filters import CommandStart

bot = Bot(token="1234567890:AAHwLg...")
dp = Dispatcher()

@dp.message(CommandStart())
async def start(message: Message):
    await message.answer("Привет! Я учебный бот.")

if __name__ == "__main__":
    dp.run_polling(bot)

Запустите файл командой python bot.py и напишите боту /start в Telegram.

Вынесите токен из кода: import os, затем os.environ["BOT_TOKEN"]. Так токен не попадёт в репозиторий.

5. Обработчики сообщений

Обработчик — функция с декоратором @dp.message и фильтром. Фильтры выбирают, какие события попадёт в функцию: текстовые, с фото, определённая команда.

from aiogram import F
from aiogram.filters import Command

@dp.message(Command("help"))
async def help_cmd(message: Message):
    await message.answer("Команды: /start, /help")

@dp.message(F.text)
async def echo(message: Message):
    await message.answer("Вы написали: " + message.text)
ФильтрСрабатывает когда
Command("start") / CommandStart()прислана команда /start
F.textприслан обычный текст
F.text.lower() == "привет"текст строго равен «привет»
F.photoприслана фотография
Обработчики проверяются по порядку объявления. Второй @dp.message(F.text) примет всё, что не попало в более узкие фильтры, поэтому «ловушку» обычно ставят последним.

6. Клавиатуры и inline-кнопки

Клавиатур бывает два вида: обычная (под полем ввода) и inline (кнопки прямо в сообщении). Обычная — ReplyKeyboardMarkup.

from aiogram.types import ReplyKeyboardMarkup, KeyboardButton

kb = ReplyKeyboardMarkup(keyboard=[
    [KeyboardButton(text="Каталог"), KeyboardButton(text="Помощь")]
], resize_keyboard=True)

@dp.message(Command("menu"))
async def menu(message: Message):
    await message.answer("Выберите:", reply_markup=kb)

Inline-кнопки отправляют callback — нажатие обрабатывает @dp.callback_query.

from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram import F

kb = InlineKeyboardMarkup(inline_keyboard=[[
    InlineKeyboardButton(text="Да", callback_data="yes"),
    InlineKeyboardButton(text="Нет", callback_data="no"),
]])

@dp.callback_query(F.data.in_({"yes", "no"}))
async def on_click(cb: CallbackQuery):
    await cb.message.answer("Выбрано: " + cb.data)
    await cb.answer()
Обязательно вызывайте await cb.answer() — иначе Telegram покажет «часики» у кнопки. Поле callback_data ограничено 64 байтами.

7. Состояния FSM

FSM (Finite State Machine) нужна для многошаговых диалогов: бот запоминает, на каком шаге находится пользователь, и спрашивает данные по очереди.

from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup

class Order(StatesGroup):
    name = State()
    address = State()

@dp.message(Command("order"))
async def start_order(message: Message, state: FSMContext):
    await state.set_state(Order.name)
    await message.answer("Как вас зовут?")

@dp.message(Order.name)
async def get_name(message: Message, state: FSMContext):
    await state.update_data(name=message.text)
    await state.set_state(Order.address)
    await message.answer("Укажите адрес доставки.")

Пока пользователь в состоянии Order.name, срабатывает обработчик этого состояния, а не обычные фильтры. После сбора данных состояние снимается командой await state.clear().

Данные из всех состояний копятся в хранилище и доступны через await state.get_data().

8. Отправка файлов

Бот умеет отправлять фото, документы, аудио и видео. Файл можно передать по file_id (после первого получения) либо отправить из локального файла через BufferedInputFile.

from aiogram.types import BufferedInputFile

with open("report.txt", "rb") as f:
    data = f.read()

await message.answer_document(
    BufferedInputFile(data, filename="report.txt")
)

Аналогично работают answer_photo, answer_audio и answer_video.

Если вы уже отправили файл, сохраните его file_id и переиспользуйте: повторная передача по file_id почти бесплатна и мгновенна.
У Telegram есть лимиты на размер файлов (около 50 МБ для ботов) и на частоту сообщений. Массовые рассылки блокируют — сначала изучите политику использования.

9. Логирование и обработка ошибок

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

import logging

logging.basicConfig(level=logging.INFO)

@dp.message(Order.address)
async def finish_order(message: Message, state: FSMContext):
    data = await state.get_data()
    if len(message.text) < 5:
        await message.answer("Адрес слишком короткий, повторите.")
        return
    await state.clear()
    await message.answer("Заказ принят! Ждите курьера.")

Ошибки в обработчиках можно оборачивать в try / except и возвращать пользователю короткое сообщение вместо тишины. Для отлова всех исключений aiogram поддерживает специальный обработчик @dp.errors.

Логи не должны содержать токен и личные данные пользователей.

10. Деплой на VPS

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

# /etc/systemd/system/mybots.service
[Unit]
Description=Telegram bot
After=network.target

[Service]
User=myuser
WorkingDirectory=/home/myuser/bot
EnvironmentFile=/home/myuser/bot/.env
ExecStart=/home/myuser/bot/.venv/bin/python bot.py
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable mybots
sudo systemctl start mybots
sudo systemctl status mybots

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

Для webhook нужен HTTPS-домен и сертификат. Пока бот маленький — используйте long polling: это проще и деплоится одной командой.

11. Платежи и подписки

Telegram умеет принимать платежи прямо в чате через Payments API: бот отправляет инвойс, пользователь оплачивает банковской картой или через Telegram Stars, а бот получает статус оплаты.

from aiogram.types import LabeledPrice
from aiogram.methods import SendInvoice

await bot(SendInvoice(
    chat_id=user_id,
    title="Подписка на месяц",
    description="Доступ к контенту на 30 дней",
    payload="sub_30",
    provider_token=prov_token,
    currency="rub",
    prices=[LabeledPrice(label="Подписка", amount=29900)]
))

Обработчик pre_checkout_query должен подтвердить платёж, а после подтверждения вы даёте доступ: выдаёте роль, ключ или продлеваете срок.

Держите статус подписки в базе данных (см. курс SQL), а не в памяти: перезапуск бота не должен отменять оплаченный доступ.

Цены задаются в минимальных единицах валюты: 29900 означает 299 ₽. Для подписок проверяйте статус на своей стороне: при отмене доступа вовремя отзывайте. Telegram Stars упрощают процесс — эквайринг работает на стороне Telegram.

Ключи провайдера платежей живут только в переменных окружения. Утёкший provider_token — это деньги и блокировка аккаунта бота.

12. Webhook вместо long polling

При большом потоке сообщений long polling неэффективен: бот постоянно «спрашивает» сервер Telegram. Webhook выгоднее — Telegram сам отправляет обновления на ваш HTTPS-адрес.

from aiogram.webhook.aiohttp_server import SimpleRequestHandler

async def main():
    await bot.delete_webhook()
    await bot.set_webhook(url="https://mybot.example.com/bot")
    # запуск aiohttp-сервера с точкой /bot

Требования: публичный домен с HTTPS-сертификатом. Классическая связка — Nginx как прокси и сертификат Let's Encrypt, см. уроки Nginx и Хостинг и деплой.

Дополнительная защита: поставьте секретный токен в адрес webhook — Telegram подписывает им каждый запрос, и злоумышленник не сможет имитировать апдейты. Здоровье бота проверяется методом getWebhookInfo со списком последних обращений.

Для разработки удобнее long polling, для продакшена под нагрузкой — webhook. Переключаться между ними можно простым кодом запуска.

13. Нагрузка и устойчивость

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

Перед запуском под нагрузкой прогоните нагрузочный тест: десятки тысяч сообщений за минуту. Смотрите на время ответа и ошибки, а не только на «а упал ли». Предельную скорость запишите в документацию, чтобы знать, когда пора масштабировать.

import asyncio

async def heavy_processing(message):
    await asyncio.sleep(2)  # имитация работы
    await message.answer("Готово!")

@dp.message()
async def incoming(message: Message):
    asyncio.create_task(heavy_processing(message))
    await message.answer("Принято, считаю...")
Не создавайте бесконечные очереди задач: при постоянном наплыве бот «уйдёт» в каскад фоновых задач. Ставьте лимит параллельных операций и очередь с отбрасыванием лишнего.

14. Безопасность бота

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

import os
TOKEN = os.environ["BOT_TOKEN"]   # не в коде, не в .env в git

Проверяйте источник апдейта: у long polling это делает библиотека, у webhook — секретный токен. Если бот вдруг начал рассылать спам — быстрый доступ к логам укажет, кто и как запустил команду.

Утёкший токен немедленно отзывайте через @BotFather и меняйте: «восстановить» скомпрометированного бота нельзя.
Держите обработчики команд тонкими, а логику — в сервис-слое, покрытом тестами: меньше мест, где может прятаться уязвимость.

15. Хранилища состояний и данных

Бот без памяти отвечает только на одиночные команды. Появляется сценарий из нескольких шагов (заказ, опрос) — нужно состояние: его хранят FSM-хранилище и база данных.

from aiogram.fsm.storage.redis import RedisStorage
storage = RedisStorage.from_url("redis://localhost:6379/0")
Память процесса — худшее хранилище состояний: после рестарта пользователи должны продолжить там, где остановились.
Схему из многих шагов описывайте отдельным классом состояния, а не «пятиэтажным» словарём — так легче дебажить и тестировать.

16. Интеграция с внешними сервисами

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

import httpx

async def fetch_weather(city):
    async with httpx.AsyncClient(timeout=10) as client:
        data = (await client.get(f"/v1/weather?city={city}")).json()
    return data
Не лезьте во внешний API из каждого хендлера: выносите вызовы в сервис-слой с общим таймаутом, ретраями и логом ошибок.
Заведите health-endpoint: оператор видит, что бот живой, даже когда внешний сервис погоды недоступен.

17. Аналитика, A/B-тесты и монетизация

Бот, который не измеряется, не развивается. Минимальный набор — события ключевых действий: через две недели станет ясно, какие сценарии живые, а какие «мёртвые».

МетрикаЧто показывает
DAU / MAUактивность и возвращаемость
Retentionсколько пользователей остаётся после недели
Воронка командыгде теряется путь «старт → целевое действие»
# A/B: две версии приветствия
pool = ["hi_v1", "hi_v2"]
variant = random.choice(pool)
await message.answer(hi_texts[variant])

Монетизация: подписки через Payments API (раздел 11), разовые продажи доступа, спонсорская реклама и партнёрские программы. Правило — сначала метрики и удержание, потом платное.

Начинайте с DAU и retention, а не с «красивой панели»: две понятные цифры полезнее дашборда из десяти похожих.
Платные сценарии тестируйте только в песочнице платежей, а логи покупок храните пристально — споры решаются данными, а не скриншотами.

18. Форматирование сообщений: HTML и Markdown

Telegram поддерживает разметку сообщений двумя способами: HTML-тегами и Markdown. От выбора зависит, как красиво и безопасно рендерится ваш текст в чатах.

# HTML-вариант
Визит: <b>завтра</b>
Цена: <a href="https://site.ru/pay">оплатить</a>
<pre>2026-09-19 10:00</pre>

# MarkdownV2 (здесь нужны экранирования)
Визит: <b>завтра</b>

Практическое правило: в aiogram используйте parse_mode="HTML", а текст стройте через класс HTMLBuilder или f-строки с экранированием пользовательского ввода.

Никогда не вставляйте текст от пользователя в неэкранированном виде: HTML-инъекции в сообщениях — хакерский трюк.
Всегда указывайте link_preview_options и контролируйте превью ссылок — оно портит вид сообщения.

19. Тестирование бота: pytest и моки

Бот без тестов растёт до момента, когда новая фича ломает старую. Основной инструмент — pytest: вы подменяете сетевой слой моком и проверяете логику, не дергая Telegram API.

import pytest
from unittest.mock import AsyncMock

@pytest.mark.asyncio
async def test_help_handler(bot):
    message = AsyncMock(text="/start")
    await start_handler(message)
    message.answer.assert_called_once()

Не смешивайте «интеграционные» тесты с реальным API в CI: Telegram может лимитировать и «упасть» по сети. Держите быстрый набор моков локально.

Разделите хендлеры и чистую бизнес-логику: логику тестируйте без моков, хендлеры — через подмену зависимостей.
aiogram поддерживает запуск «без сети» через объекты типов: проще всего — тест-response, а не сети.
Не храните настоящий токен в тестах/репо: только переменные окружения и mock-токен.

20. Фоновые задачи и планировщики

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

# APScheduler + aiogram
from apscheduler.schedulers.asyncio import AsyncIOScheduler

scheduler = AsyncIOScheduler()
scheduler.add_job(send_digest, "cron", hour=9, minute=0)
scheduler.start()

Осторожно с состоянием: каждая задача — отдельный контекст; храните зависимости вне, получая их по одиночке. Логирование фоновых задач — обязательно: падение «в тишине» незаметно.

Для рассылок используйте очередь (Redis/брокер), если подписчиков тысячи — спам-лимиты Telegram защитят от блокировки.
Ночной перезапуск сервера может «съесть» запущенные задачи: регистрируйте jobs при старте приложения и обрабатывайте долгие процессы идемпотентно.
Планировщик и хендлеры — разные «миры»: не зовите хендлеры из задачи, делите общую логику на функции.

21. Обновление aiogram: миграция версий

Допустим, бот написан на aiogram 2.x, а вышел 3.x с другим API. Переход затрагивает почти все импорты и логику хендлеров, поэтому миграцию делают проект за проект, а не «в лоб».

# aiogram 2 → 3
# было: from aiogram import Bot, types
# стало: from aiogram import Bot, Dispatcher, types
# было: dp.register_message_handler(...)
# стало: @router.message(Command("start"))

Стратегия: сначала поднять версию, потом пройтись по явным изменениям (changelog aiogram), потом — чинить тесты. Каждый блок — отдельный коммит с рабочей сборкой.

Ведите кодовую базу «мягко расширяемой»: обертки над API бота упрощают будущие миграции.
Основная ставка — тесты: именно они дадут понять, что миграция завершена, и не «разорвут» продакшн молча.

Что дальше

Закрепите язык в уроке Python. Храните данные бота в SQL, подключайте внешние HTTP-запросы из FastAPI и REST API. А чтобы бот научился отвечать умнее — подтяните Промпт-инжиниринг.

← Вернуться к курсам