← Назад к вопросам
Как реализуешь очередность вопросов в Telegram боте на Python?
2.0 Middle🔥 181 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Реализация очередности вопросов в Telegram боте на Python
Это реальная задача, которая часто встречается при разработке интерактивных ботов. Есть несколько подходов — от простых (линейные вопросы) до сложных (с ветвлением и условиями).
1. Простой подход: FSM (Finite State Machine) в aiogram
FSM (конечный автомат) — это стандартный способ управления состояниями в диалоге.
from aiogram import F, Router, types
from aiogram.filters.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.memory import MemoryStorage
router = Router()
# Определить состояния (очередь вопросов)
class QuestionStates(StatesGroup):
waiting_for_name = State() # Вопрос 1: Имя
waiting_for_age = State() # Вопрос 2: Возраст
waiting_for_email = State() # Вопрос 3: Email
waiting_for_confirmation = State() # Вопрос 4: Подтверждение
# Команда начать
@router.message(commands=['start'])
async def start_command(message: types.Message, state: FSMContext):
await state.set_state(QuestionStates.waiting_for_name)
await message.answer("Привет! Как тебя зовут?")
# Обработка ответа на первый вопрос
@router.message(QuestionStates.waiting_for_name)
async def process_name(message: types.Message, state: FSMContext):
await state.update_data(name=message.text) # Сохранить ответ
await state.set_state(QuestionStates.waiting_for_age) # Перейти к следующему
await message.answer("Сколько тебе лет?")
# Обработка ответа на второй вопрос
@router.message(QuestionStates.waiting_for_age)
async def process_age(message: types.Message, state: FSMContext):
try:
age = int(message.text)
if age < 0 or age > 120:
await message.answer("Пожалуйста, введи корректный возраст")
return
await state.update_data(age=age)
await state.set_state(QuestionStates.waiting_for_email)
await message.answer("Какой твой email?")
except ValueError:
await message.answer("Пожалуйста, введи число")
# Обработка ответа на третий вопрос
@router.message(QuestionStates.waiting_for_email)
async def process_email(message: types.Message, state: FSMContext):
if '@' not in message.text:
await message.answer("Пожалуйста, введи корректный email")
return
await state.update_data(email=message.text)
# Показать все собранные данные
data = await state.get_data()
summary = f"""
Спасибо за информацию!
Имя: {data['name']}
Возраст: {data['age']}
Email: {data['email']}
Это правильно?
"""
await state.set_state(QuestionStates.waiting_for_confirmation)
await message.answer(summary, reply_markup=get_confirmation_keyboard())
# Подтверждение
@router.message(QuestionStates.waiting_for_confirmation)
async def process_confirmation(message: types.Message, state: FSMContext):
if message.text.lower() == 'да':
await message.answer("Спасибо! Данные сохранены.")
await state.clear() # Завершить диалог
else:
# Вернуться к первому вопросу
await state.set_state(QuestionStates.waiting_for_name)
await message.answer("Давай начнём заново. Как тебя зовут?")
def get_confirmation_keyboard():
"""Клавиатура для подтверждения"""
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
return ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="Да"), KeyboardButton(text="Нет")]
],
resize_keyboard=True
)
2. Продвинутый подход: Контроллер для управления очередью
Для более сложных сценариев создай специальный класс:
from typing import Callable, Optional
from dataclasses import dataclass
from aiogram import F, types
from aiogram.fsm.context import FSMContext
@dataclass
class Question:
"""Один вопрос в очереди"""
key: str # Ключ для сохранения ответа
text: str # Текст вопроса
validator: Optional[Callable] = None # Функция валидации
on_answer: Optional[Callable] = None # Обработчик ответа
class QuestionnaireController:
"""Контроллер для управления очередью вопросов"""
def __init__(self):
self.questions: list[Question] = []
self.current_index: dict[int, int] = {} # user_id -> index
def add_question(self, key: str, text: str, validator=None, on_answer=None):
"""Добавить вопрос в очередь"""
self.questions.append(Question(key, text, validator, on_answer))
return self
async def start(self, user_id: int, state: FSMContext):
"""Начать опрос"""
self.current_index[user_id] = 0
await state.update_data(questionnaire_data={})
await self._ask_current_question(user_id, state)
async def _ask_current_question(self, user_id: int, state: FSMContext, message: types.Message = None):
"""Задать текущий вопрос"""
index = self.current_index[user_id]
if index >= len(self.questions):
# Все вопросы закончились
data = await state.get_data()
await message.answer(f"Спасибо! Ваши ответы сохранены: {data['questionnaire_data']}")
await state.clear()
return
question = self.questions[index]
await message.answer(question.text)
await state.update_data(current_question_key=question.key)
async def process_answer(self, user_id: int, answer: str, state: FSMContext, message: types.Message):
"""Обработать ответ и перейти к следующему вопросу"""
index = self.current_index[user_id]
question = self.questions[index]
# Валидация
if question.validator and not question.validator(answer):
await message.answer(f"Некорректный ответ. Пожалуйста, попробуй снова.")
return
# Сохранить ответ
data = await state.get_data()
data['questionnaire_data'][question.key] = answer
await state.update_data(questionnaire_data=data['questionnaire_data'])
# Пользовательский обработчик
if question.on_answer:
await question.on_answer(answer, state)
# Перейти к следующему вопросу
self.current_index[user_id] += 1
await self._ask_current_question(user_id, state, message)
# Использование
questionnaire = QuestionnaireController()
# Добавить вопросы
questionnaire\
.add_question(
key='name',
text='Как тебя зовут?',
validator=lambda x: len(x) > 0
)\
.add_question(
key='age',
text='Сколько тебе лет?',
validator=lambda x: x.isdigit() and 0 < int(x) < 120
)\
.add_question(
key='city',
text='Из какого города?',
)
@router.message(commands=['start'])
async def start(message: types.Message, state: FSMContext):
await questionnaire.start(message.from_user.id, state)
@router.message()
async def handle_answer(message: types.Message, state: FSMContext):
await questionnaire.process_answer(
message.from_user.id,
message.text,
state,
message
)
3. Вопросы с ветвлением (условные ветки)
Иногда следующий вопрос зависит от предыдущего ответа:
class ConditionalQuestionnaire:
"""Опрос с условными ветками"""
async def ask_next_question(self, user_id: int, state: FSMContext, message: types.Message):
"""Выбрать следующий вопрос на основе данных"""
data = await state.get_data()
answers = data.get('answers', {})
# Ветвление на основе предыдущего ответа
if answers.get('age') and int(answers['age']) < 18:
# Для несовершеннолетних
await message.answer("Нужно согласие родителя. Введи имя родителя:")
else:
# Для взрослых
await message.answer("Перейдём к следующему вопросу...")
await state.set_state(QuestionStates.waiting_for_next_answer)
# Использование с инлайн клавиатурой
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
async def ask_with_choice(message: types.Message, state: FSMContext):
"""Вопрос с выбором (сложнее чем текстовый ввод)"""
keyboard = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Да", callback_data="choice_yes")],
[InlineKeyboardButton(text="Нет", callback_data="choice_no")],
]
)
await message.answer("Ты согласен с условиями?", reply_markup=keyboard)
await state.set_state(QuestionStates.waiting_for_choice)
@router.callback_query(F.data == "choice_yes")
async def process_choice_yes(callback: types.CallbackQuery, state: FSMContext):
await state.update_data(agreed=True)
await callback.message.edit_text("Спасибо!")
# Перейти к следующему вопросу
4. Сохранение и восстановление прогресса
import json
from datetime import datetime
class PersistentQuestionnaire:
"""Опрос с сохранением прогресса в БД"""
async def save_progress(self, user_id: int, data: dict, db):
"""Сохранить прогресс пользователя"""
await db.execute("""
INSERT INTO questionnaire_progress (user_id, data, updated_at)
VALUES (:user_id, :data, :updated_at)
ON CONFLICT (user_id) DO UPDATE SET data = :data, updated_at = :updated_at
""", {
'user_id': user_id,
'data': json.dumps(data),
'updated_at': datetime.now()
})
async def load_progress(self, user_id: int, db):
"""Загрузить прогресс пользователя"""
result = await db.fetch_one(
"SELECT data FROM questionnaire_progress WHERE user_id = :user_id",
{'user_id': user_id}
)
return json.loads(result['data']) if result else {}
async def resume(self, user_id: int, state: FSMContext, db, message: types.Message):
"""Возобновить опрос с того же места"""
data = await self.load_progress(user_id, db)
if data:
await state.update_data(questionnaire_data=data)
await message.answer(f"Приветствую снова! Продолжим опрос. Следующий вопрос...")
# Показать следующий вопрос
else:
await message.answer("Начнём с начала...")
Ключевые моменты
- FSM (State Machine) — основной инструмент для управления очередностью
- StatesGroup — объявить все состояния (вопросы)
- state.set_state() — переход к следующему состоянию (вопросу)
- state.update_data() — сохранение ответов
- Валидация — проверка корректности ответа перед сохранением
- Ветвления — разные вопросы на основе предыдущих ответов
- Сохранение прогресса — восстановление при перезагрузке бота
- Инлайн клавиатуры — для выбора вместо текстового ввода
Данный подход масштабируется от простых линейных опросов до сложных многоуровневых анкет с условной логикой.