aiogram Python'да Telegram бот ёзиш учун энг оммабоп кутубхоналардан бири. У асинхрон ишлайди, роутерлар ва фильтрлар орқали кодни тартибли сақлашга ёрдам беради ва FSM ёрдамида кўп босқичли суҳбатларни қулай бошқаради. Бот ноутбукда python bot.py билан аъло ишлайди, лекин компьютер ўчиши билан бот ҳам жавоб беришни тўхтатади. Ушбу мақолада Намангандаги кийим дўкони учун ёзилган буюртма ботини aiogram 3 асосида VPS серверга жойлаб, уни кечаю кундуз ишлайдиган хизматга айлантирамиз.
Нега оддий хостинг эмас, VPS
Поллинг режимидаги бот доимий ишлаб турадиган Python жараёнидир. Оддий shared хостинг асосан PHP сайтлар учун мўлжалланган ва унда узоқ ишлайдиган фон жараёнлари одатда чекланади ёки маълум вақтдан кейин тўхтатилади. Cron орқали ботни қайта-қайта ишга туширишга уриниш ҳам ишончли натижа бермайди. Шунинг учун бот учун энг тўғри жой бу кичик VPS, унда жараённи systemd бошқаради ва бот сервер қайта юкланганда ҳам ўзи кўтарилади. sayt.uz'да VPS кабинет орқали буюртма қилинади ва бир неча дақиқада тайёр бўлади.
Бот кодини тайёрлаш
Production учун биринчи қоида: токен код ичида ёзилмайди. У муҳит ўзгарувчисидан ўқилади, шунда кодни GitHub'га юкласангиз ҳам токен ошкор бўлмайди. Қуйида aiogram 3 учун минимал, лекин тўғри тузилган бот мисоли келтирилган. Логлаш ёқилган, чунки серверда хатоларни фақат логлар орқали кўрасиз.
import asyncio, logging, os
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
dp = Dispatcher()
@dp.message(CommandStart())
async def start(message: Message):
await message.answer("Assalomu alaykum! Kerakli mahsulot nomini yozing.")
async def main():
bot = Bot(token=os.environ["BOT_TOKEN"])
await dp.start_polling(bot)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
Кутубхона версияларини requirements.txt файлида қайд этинг. aiogram'нинг 2 ва 3 версиялари ўртасида катта фарқлар бор, шунинг учун версия чегарасини аниқ ёзиш кейинчалик кутилмаган бузилишларнинг олдини олади: масалан, aiogram>=3.0,<4.0.
Серверда виртуал муҳит яратиш
Тизимдаги Python пакетларига аралашмаслик учун бот алоҳида виртуал муҳитда ишлаши керак. Аввал бот учун алоҳида фойдаланувчи яратамиз, кодни Git орқали юклаймиз ва боғлиқликларни ўрнатамиз. Ubuntu'да venv модули алоҳида пакет сифатида ўрнатилиши мумкин.
sudo apt install -y python3-venv git
sudo adduser --disabled-password bot
sudo -iu bot
git clone https://github.com/misol/shop-bot.git
cd shop-bot
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Токен ва бошқа созламалар учун /home/bot/shop-bot/.env файлини яратинг ва унга BOT_TOKEN=... қаторини ёзинг. Сўнг chmod 600 .env билан файлни фақат эгаси ўқий оладиган қилинг. Бу файл .gitignore рўйхатида бўлиши шарт.
systemd хизмати: бот доимо тирик
systemd жараённи фон режимида ишлатади, у йиқилса бир неча сониядан кейин қайта кўтаради ва сервер ёқилганда автоматик ишга туширади. EnvironmentFile директиваси .env файлидаги қийматларни муҳит ўзгарувчиси сифатида беради, шунинг учун кодга қўшимча кутубхона керак эмас.
# /etc/systemd/system/shop-bot.service
[Unit]
Description=Shop Telegram bot (aiogram)
After=network-online.target
Wants=network-online.target
[Service]
User=bot
WorkingDirectory=/home/bot/shop-bot
EnvironmentFile=/home/bot/shop-bot/.env
ExecStart=/home/bot/shop-bot/.venv/bin/python bot.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now shop-bot
journalctl -u shop-bot -f
Янгиланиш чиқариш жараёни ҳам содда: git pull, керак бўлса .venv/bin/pip install -r requirements.txt ва sudo systemctl restart shop-bot. Ботнинг бир вақтнинг ўзида фақат битта нусхаси ишлашига эътибор беринг. Агар ноутбукдаги эски нусха ҳам ёқиқ қолса, поллинг тўқнашуви сабабли aiogram TelegramConflictError хатосини чиқаради.
FSM ҳолатлари қайта ишга тушганда йўқолмасин
Буюртма боти одатда бир неча босқичдан иборат: маҳсулот, ўлчам, телефон рақами, манзил. aiogram бу босқичларни FSM орқали сақлайди ва стандарт ҳолатда MemoryStorage ишлатилади. Муаммо шундаки, хизмат қайта ишга туширилганда хотирадаги барча ҳолатлар ўчади ва ярим йўлда қолган мижоз бошидан бошлашга мажбур бўлади. Production'да Redis асосидаги омбор ишлатиш тўғри ечим.
from aiogram.fsm.storage.redis import RedisStorage
storage = RedisStorage.from_url("redis://127.0.0.1:6379/0")
dp = Dispatcher(storage=storage)
Буюртмаларнинг ўзини эса доимий базада сақланг. Кичик дўкон учун SQLite ҳам етарли бўлиши мумкин, лекин бир нечта жараён ёки администратор панели бир вақтда ёзадиган бўлса, PostgreSQL ёки MySQL ишончлироқ танлов.
Webhook режимига ўтиш
Фойдаланувчилар кўпайганда ёки бот сайт билан бир серверда турганда webhook қулайроқ бўлиши мумкин. aiogram бунга тайёр aiohttp интеграциясини беради. Бот ички портни тинглайди, Nginx эса HTTPS сўровларини унга узатади. secret_token сохта сўровлардан ҳимоя қилади.
from aiohttp import web
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
SECRET = os.environ["WEBHOOK_SECRET"]
async def on_startup(bot: Bot):
await bot.set_webhook("https://bot.example.uz/tg/webhook", secret_token=SECRET)
def main():
bot = Bot(token=os.environ["BOT_TOKEN"])
dp.startup.register(on_startup)
app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot, secret_token=SECRET).register(app, path="/tg/webhook")
setup_application(app, dp, bot=bot)
web.run_app(app, host="127.0.0.1", port=8081)
Nginx'да location /tg/webhook блоки proxy_pass http://127.0.0.1:8081; билан созланади, субдомен учун эса Let's Encrypt сертификати олинади. systemd хизмати ўзгармайди, фақат ишга тушириладиган файл webhook версиясига алмаштирилади.