三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Python Telegram Bot开发实战:从API接入到定时任务与异步优化

Python Telegram Bot开发实战:从API接入到定时任务与异步优化

1. 从零开始的Telegram Bot:不只是“Hello World”

如果你对Python有点基础,想找个有意思的项目练手,或者想给自己的小社群、个人项目加个自动化通知功能,那Telegram Bot绝对是个绝佳的选择。它不像微信生态那么封闭,API文档清晰,功能强大,而且完全免费。网上教程很多,但很多都停留在发个“/start”回个“Hello”就结束了,真正把Bot用起来,让它帮你处理点实际任务,中间有不少细节和“坑”需要趟过去。今天我就以一个实际可用的通知机器人项目为蓝本,带你从零开始,不仅接入API,更会分享几个让Bot真正“活”起来的实用技巧和那个能提升效率的“彩蛋”。

简单说,一个Telegram Bot就是一个运行在你服务器(或者电脑)上的程序,它通过Telegram提供的Bot API,与用户进行交互。你可以让它回复消息、发送图片、处理按钮点击,甚至管理群组。整个过程的核心,就是让你的程序能和Telegram的服务器“对话”。我们将使用Python中最流行的python-telegram-bot库(通常简称ptb)来实现,它封装了底层的HTTP请求,让我们能用更Pythonic的方式编写机器人逻辑。

在开始敲代码之前,你需要准备好两样东西:一个Telegram账号(用来创建和管理Bot),以及一个能运行Python 3.7+的环境。环境配置是老生常谈,但我还是要啰嗦一句:强烈建议使用虚拟环境。无论是venv还是conda,这能避免不同项目间的依赖冲突,是专业开发的起点。你可以用python -m venv telegram-bot-env创建,然后激活它。接下来,我们就从创建你的第一个Bot实体开始。

2. 获取通行证:BotFather与Token的奥秘

所有Telegram Bot的生命都始于与一位名叫@BotFather的官方机器人的对话。这不是比喻,BotFather本身就是Telegram官方用来管理Bot的超级机器人。你需要像添加普通好友一样,在Telegram里搜索@BotFather并开始对话。

2.1 创建Bot与理解Token

向BotFather发送/newbot指令,它会引导你完成创建:

  1. 为你的Bot起一个显示名称(比如My Notification Bot)。
  2. 为你的Bot设置一个唯一的用户名,必须以bot结尾(比如my_awesome_notifier_bot)。

成功后,BotFather会发给你一串至关重要的信息——HTTP API Token。它长得像这样:1234567890:ABCdefGhIJKlmNoPQRsTUVwxyZ-abcdefghijk。这串Token就是你的Bot在整个网络世界的唯一身份证和钥匙。任何拥有这串Token的人,都能完全控制你的Bot。因此,第一条黄金法则诞生了:

绝对不要将Token硬编码在代码中,更不要上传到GitHub等公开仓库。我见过太多因为Token泄露导致Bot被恶意滥用的案例。正确的做法是使用环境变量。

在你的项目根目录创建一个名为.env的文件(记得把它加入.gitignore),内容如下:

TELEGRAM_BOT_TOKEN=你的_真实_Token_放在这里

然后在Python代码中,使用python-dotenv库来读取:

pip install python-dotenv python-telegram-bot
import os from dotenv import load_dotenv load_dotenv() # 加载 .env 文件中的环境变量 TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') if not TOKEN: raise ValueError("请在 .env 文件中设置 TELEGRAM_BOT_TOKEN 环境变量")

这样做,你的敏感信息就与代码分离了,安全又便于在不同环境(开发、生产)中配置。

2.2 Bot的初始配置与隐私模式

创建完成后,别急着关掉和BotFather的对话窗口。它还有很多有用的指令:

  • /setdescription: 设置Bot的描述,用户会在开始对话时看到。
  • /setabouttext: 设置Bot的简介信息。
  • /setuserpic: 给Bot设置一个头像。
  • /setcommands这个非常重要!它可以设置你的Bot支持的命令菜单。例如,你可以设置一个命令列表,让用户在输入/时看到提示。格式如:
    help - 显示帮助信息 start - 开始使用 subscribe - 订阅通知
    这能极大提升用户体验。

还有一个关键设置是群组隐私模式。默认情况下,新Bot处于“隐私模式”开启状态。这意味着,当你的Bot被加入到群组时,它无法看到普通的群消息,只能看到以/开头的命令消息或直接@它的消息。如果你需要Bot监控群内所有聊天内容(例如,用于关键词提醒或聊天统计),你需要向BotFather发送/setprivacy,然后选择你的Bot,将其设置为Disable请注意,关闭隐私模式后,Bot将能接收到群内所有消息,请确保你的Bot处理逻辑符合群规和用户隐私预期。

3. 搭建机器人骨架:Handler、Dispatcher与异步编程

拿到了Token,我们开始用代码赋予Bot灵魂。python-telegram-bot库的核心架构围绕ApplicationDispatcherHandler展开。理解这三者的关系,是写出清晰、可维护Bot代码的关键。

3.1 Application:机器人的大脑与心脏

Application类(在v20版本后,取代了旧的Updater)是机器人的总控中心。它持有你的Bot实例(Bot)和调度器(Dispatcher),并负责组织所有的事件处理程序(Handler)。我们这样创建它:

from telegram.ext import Application application = Application.builder().token(TOKEN).build()

.build()方法会基于Token构建一个Bot对象,并创建好Dispatcher

3.2 Dispatcher与Handler:事件路由系统

你可以把Dispatcher想象成公司的前台或路由器,而Handler就是各个部门的专员。当用户发送一条消息到Telegram服务器,服务器会通过Webhook或轮询(我们稍后讲)将更新(Update)推送给你的程序。Dispatcher收到这个Update后,会询问所有注册的Handler:“你们谁负责处理这个?”每个Handler根据自己的过滤条件(比如消息类型、命令、回调查询等)判断是否接手。第一个表示“我接手”的Handler就会执行其关联的回调函数。

常见的Handler有:

  • CommandHandler: 处理以/开头的命令,如/start,/help
  • MessageHandler: 处理特定类型的普通消息,如文本、图片、文档。
  • CallbackQueryHandler: 处理内联键盘按钮的回调。
  • ConversationHandler: 处理多轮对话(一个复杂但强大的功能)。

3.3 编写你的第一个命令处理器

让我们注册一个最简单的/start命令处理器。当用户首次启动或发送/start时,Bot会打招呼。

from telegram.ext import CommandHandler async def start_command(update, context): """处理 /start 命令.""" # update.message 包含了消息的所有信息 user = update.effective_user # 使用 await 进行异步回复 await update.message.reply_html( fr"Hi {user.mention_html()}! 我是你的通知小助手。", reply_markup=None # 这里可以添加一个键盘 ) # 将处理函数与命令关联,并添加到Application中 application.add_handler(CommandHandler("start", start_command))

注意函数定义前的async和函数内的await。从python-telegram-botv20开始,库全面转向了异步(asyncio)。这意味着你的Bot可以更高效地处理并发请求,不会因为一个耗时操作(如网络请求)而阻塞整个程序。对于新手,记住一个原则:所有与Telegram API交互的方法(如reply_text,send_message)都需要用await调用;所有处理函数都必须定义为async函数。

3.4 启动机器人:Polling vs Webhook

如何让我们的程序持续接收用户消息?有两种主流模式:轮询(Polling)Webhook

轮询(Polling)是最简单、最适合开发和调试的方式。你的程序会主动、不断地向Telegram服务器询问:“有给我的新消息吗?”。

# 在添加完所有Handler之后 application.run_polling(allowed_updates=Update.ALL_TYPES)

run_polling()会启动一个循环,直到你按下Ctrl+C。它的优点是设置简单,无需公网IP,在本地电脑就能跑。缺点是效率相对较低,且有获取消息的延迟。

Webhook则是生产环境的推荐方式。你需要一个具有公网IP和SSL证书(HTTPS)的服务器。你告诉Telegram服务器一个URL(你的服务器地址),当有新消息时,Telegram会主动以HTTP POST请求的形式将Update推送到这个URL。这种方式实时性更高,更节省资源。

from telegram import Update from telegram.ext import Application, CommandHandler, CallbackContext import asyncio async def start(update: Update, context: CallbackContext): await update.message.reply_text('Hello!') async def main(): application = Application.builder().token(TOKEN).build() application.add_handler(CommandHandler("start", start)) # 假设你的服务器域名为 https://yourdomain.com # 你需要先设置Webhook地址 await application.bot.set_webhook(url="https://yourdomain.com/your-webhook-path") # 然后你需要一个web框架(如aiohttp, FastAPI)来接收POST请求 # 并将请求体传递给 application.update_queue # 这里省略了web框架的搭建部分 if __name__ == '__main__': asyncio.run(main())

对于初学者和大多数中小型项目,Polling开始是完全没问题的。当你的Bot用户量增长,需要部署到云服务器时,再考虑迁移到Webhook。

4. 让机器人“能干”:消息处理、键盘与状态管理

一个只会说Hi的Bot显然不够看。我们来给它添加一些实用功能,比如让用户订阅通知,并发送一条自定义消息。

4.1 处理文本消息与实现订阅逻辑

假设我们想让用户发送“订阅”来订阅通知。我们需要一个MessageHandler来过滤文本消息。

from telegram.ext import MessageHandler, filters # 一个简单的内存存储,用于记录订阅用户。生产环境请用数据库! subscribed_users = set() async def handle_text(update, context): """处理用户发送的文本消息.""" user_text = update.message.text user_id = update.effective_user.id if user_text == '订阅': if user_id not in subscribed_users: subscribed_users.add(user_id) await update.message.reply_text(f"✅ 订阅成功!你的用户ID是:{user_id}") else: await update.message.reply_text("⚠️ 你已经订阅过了。") elif user_text == '取消订阅': subscribed_users.discard(user_id) # 使用discard避免KeyError await update.message.reply_text("🗑️ 已取消订阅。") else: # 对于其他非命令文本,可以不做回复,或者给一个默认回复 # await update.message.reply_text(“你说:‘{user_text}’?”) pass # 添加处理器,filters.TEXT 只捕获文本消息 application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))

这里用filters.TEXT & ~filters.COMMAND确保只捕获非命令的纯文本消息。subscribed_users是一个Python集合,用于在内存中存储订阅者ID。重要警告:程序重启后,这个集合会被清空!对于任何需要持久化的数据(用户状态、订阅关系、配置),你必须使用外部存储,如SQLite、PostgreSQL、Redis等。

4.2 内联键盘:提升交互体验

让用户打字“订阅”不够友好。我们可以提供一个漂亮的按钮。Telegram支持两种键盘:回复键盘(ReplyKeyboardMarkup)和内联键盘(InlineKeyboardMarkup)。内联键盘更灵活,按钮点击后会触发一个回调(CallbackQuery),而不会在聊天中发送消息。

from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import CallbackQueryHandler async def start_with_keyboard(update, context): """带内联键盘的 /start 命令.""" keyboard = [ [InlineKeyboardButton("📩 订阅通知", callback_data='subscribe')], [InlineKeyboardButton("ℹ️ 帮助", callback_data='help')] ] reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text('请选择操作:', reply_markup=reply_markup) async def button_callback(update, context): """处理内联键盘按钮的回调.""" query = update.callback_query await query.answer() # 必须调用,以关闭客户端上的加载状态 user_id = query.from_user.id data = query.data if data == 'subscribe': if user_id not in subscribed_users: subscribed_users.add(user_id) # 编辑原始消息,更新文本和移除键盘 await query.edit_message_text(text=f"✅ 用户 {user_id} 订阅成功!") else: await query.answer(text="你已经订阅过了!", show_alert=True) # 弹窗提示 elif data == 'help': await query.edit_message_text(text="这是一个帮助信息...") # 更新start命令的处理器 application.add_handler(CommandHandler("start", start_with_keyboard)) # 添加回调查询处理器 application.add_handler(CallbackQueryHandler(button_callback))

callback_data是一个字符串,用于标识是哪个按钮被点击了。你可以传递更复杂的数据(如action_subscribe_123),但注意有长度限制(目前是64字节)。query.answer()是必须的,它告诉Telegram客户端回调已收到。edit_message_text可以让你动态更新之前发送的消息,实现无刷新交互,体验非常好。

4.3 定时任务与主动推送:让Bot“动”起来

Bot不仅能被动响应,还能主动给用户发消息。这就是我们“通知”功能的精髓。python-telegram-bot提供了JobQueue来执行定时任务。 假设我们想每天下午5点向所有订阅者发送一条通知:

from telegram.ext import ApplicationBuilder, CommandHandler, CallbackContext import datetime async def callback_daily_notification(context: CallbackContext): """JobQueue回调函数,用于发送每日通知.""" job = context.job if not subscribed_users: print("没有订阅用户,跳过发送。") return message = "🕔 下午5点啦!这是今天的每日通知。" for user_id in subscribed_users.copy(): # 遍历副本以防在迭代中修改集合 try: await context.bot.send_message(chat_id=user_id, text=message) print(f"消息已发送给用户 {user_id}") except Exception as e: print(f"发送给用户 {user_id} 失败: {e}") # 可选:如果用户已阻止Bot,将其从订阅列表移除 # subscribed_users.discard(user_id) async def set_daily_job(update, context): """一个命令,用于设置每日任务(通常由管理员调用)。""" chat_id = update.effective_chat.id # 移除可能存在的同名旧任务 current_jobs = context.job_queue.get_jobs_by_name("daily_notification") for job in current_jobs: job.schedule_removal() # 设置新任务,每天17:00执行 # 注意:时间默认是UTC。中国是UTC+8,所以要传17-8=9点。 # 更健壮的做法是使用pytz库处理时区。 target_time = datetime.time(hour=9, minute=0, second=0) # UTC时间9点,即北京时间17点 context.job_queue.run_daily(callback_daily_notification, target_time, chat_id=chat_id, name="daily_notification") await update.message.reply_text(f"✅ 已设置每日通知任务,将于UTC时间 {target_time}(北京时间17:00)执行。") # 添加设置任务的命令处理器(可加权限判断,仅管理员可用) application.add_handler(CommandHandler("setdaily", set_daily_job))

JobQueue非常强大,除了run_daily,还有run_once,run_repeating等。关键点在于:JobQueue需要与Application一起运行(通过run_polling或Webhook)才能正常工作。如果你重启了Bot,所有存储在内存中的定时任务都会丢失。对于需要持久化的复杂定时任务,你可能需要结合数据库来记录任务状态,并在Bot启动时重新调度。

5. 部署实战与效率“彩蛋”:日志、错误处理与开发工具

当你的Bot功能越来越复杂,代码超过几百行时,良好的工程实践就变得至关重要。这里分享几个提升开发效率和稳定性的“彩蛋”。

5.1 结构化日志记录:让问题无处遁形

使用Python标准的logging模块,而不是到处用print()

import logging # 配置日志 logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) async def start_command(update, context): user = update.effective_user logger.info(f"用户 {user.id} ({user.first_name}) 启动了Bot。") try: # ... 你的业务逻辑 ... await update.message.reply_text("Hello!") except Exception as e: logger.error(f"处理 /start 命令时发生错误: {e}", exc_info=True) await update.message.reply_text("抱歉,处理您的请求时出了点问题。")

这样,你可以在控制台清晰地看到谁在什么时候做了什么,出错时也有完整的堆栈跟踪,极大方便了调试和运维。

5.2 集中化错误处理:避免Bot静默崩溃

即使单个处理器出错,也不应该导致整个Bot崩溃。Application提供了错误处理器。

from telegram import Update from telegram.ext import ContextTypes async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE): """集中处理所有未被处理器捕获的异常.""" logger.error("在处理更新时发生异常:", exc_info=context.error) # 可以在这里将错误信息发送给开发者 # if context.bot_data.get('admin_chat_id'): # tb_list = traceback.format_exception(None, context.error, context.error.__traceback__) # tb_string = ''.join(tb_list) # message = f'处理更新时发生异常:\n<pre>{html.escape(tb_string)}</pre>' # await context.bot.send_message(chat_id=context.bot_data['admin_chat_id'], text=message, parse_mode=ParseMode.HTML) # 可选:尝试通知用户 if isinstance(update, Update) and update.effective_message: await update.effective_message.reply_text('哎呀,机器人内部出了点小故障,工程师正在排查!') # 在创建Application后,添加错误处理器 application.add_error_handler(error_handler)

5.3 “彩蛋”环节:使用PTB的“Extensions”和第三方库加速开发

这才是真正的效率提升点。python-telegram-bot社区提供了一些“扩展”(Extensions),它们不是核心库的一部分,但解决了常见痛点。

彩蛋一:python-telegram-bot[job-queue]如果你使用JobQueue并计划部署(比如用gunicorn运行Webhook),官方推荐安装python-telegram-bot[job-queue]。这个可选依赖包含了apscheduler的特定版本,能确保JobQueue在类似生产环境的多进程模式下稳定工作。安装命令:pip install "python-telegram-bot[job-queue]"

彩蛋二:使用cachetools优化频繁数据访问如果你的Bot需要频繁查询数据库或外部API来获取一些不常变的数据(如用户配置、静态内容),使用内存缓存可以大幅降低延迟和负载。

from cachetools import TTLCache # 创建一个生存时间为300秒(5分钟)的缓存 user_info_cache = TTLCache(maxsize=1024, ttl=300) async def get_user_profile(user_id): """获取用户信息,带缓存.""" if user_id in user_info_cache: logger.debug(f"从缓存获取用户 {user_id} 信息") return user_info_cache[user_id] # 模拟一个耗时的数据库或API查询 logger.info(f"查询数据库获取用户 {user_id} 信息...") # user_info = await database.fetch_user(user_id) # 假设的异步查询 user_info = {"name": f"User{user_id}", "level": "VIP"} # 模拟数据 user_info_cache[user_id] = user_info return user_info

彩蛋三:利用aiohttphttpx进行高效的并发外部请求当你的Bot需要调用其他REST API(比如获取天气、翻译文本、调用AI模型)时,使用异步HTTP客户端可以避免阻塞Bot的主循环。

import httpx async def fetch_external_data(api_url): """异步获取外部API数据.""" async with httpx.AsyncClient(timeout=10.0) as client: try: response = await client.get(api_url) response.raise_for_status() # 如果状态码不是2xx,抛出异常 return response.json() except httpx.RequestError as exc: logger.error(f"请求 {api_url} 时发生错误: {exc}") return None # 在处理器中调用 async def weather_command(update, context): data = await fetch_external_data("https://api.weather.com/...") if data: await update.message.reply_text(f"当前天气:{data['temp']}°C")

将这些工具和模式组合起来,你的Bot代码将变得健壮、高效且易于维护。从获取Token到实现交互,再到部署优化,每一步的细节都决定了最终用户体验的流畅度。记住,安全地保管Token,合理地使用异步,妥善地处理错误,并善用社区工具,你的Telegram Bot就能从一个小玩具,成长为一个真正有用的自动化助手。

← 返回列表