
1. 开工前先想清楚你要的 Bot 到底解决什么问题很多人第一次接触 Telegram Bot是被机器人这三个字勾起了兴趣以为是什么高大上的 AI 黑科技。实际上Bot 本质上就是一个跑在你自己服务器上的普通 Python 脚本通过 Telegram 开放的 HTTP 接口收发消息。你把脚本部署在一台长期运行的机器上Telegram 收到用户消息后转发给你的脚本你的脚本处理完再调用接口把回复发回去用户那边看到的就是一个聊天对象在秒回。这个机制决定了它在实际使用中的定位适合做轻量级通知、群管辅助、定时任务提醒、简单查询服务这类事情。比如我写过最实用的一个 bot就是挂在群里谁发一条/rank 关键词它就去查一下历史消息里这个词的出现次数把排行贴回来。比写网页快得多而且用户零学习成本直接在输入框敲命令就行。所以动手之前我建议你先花两分钟明确三件事机器人的服务对象是谁自己一个人用还是给群里几十上百人用这决定了你要不要做用户权限控制。机器人需要处理几种消息纯文本命令图片文件消息类型直接影响你后面要调用哪些 API。机器人跑在哪里你的电脑只调试、一台云服务器长期运行还是直接跑在树莓派上这决定了后续部署章节你该看哪部分。明白这三点再动手写代码几乎不会走弯路。当前市面上主流的 Python 方案无非就是直接调 HTTP API、用python-telegram-bot、用aiogram这三种我在下一节把它们的选型逻辑一次说透。2. 环境准备与三方库选型别一上来就抄装饰器2.1 三种实现方式按需选择不要急着装库先搞清楚你面前有三条路。方案依赖上手难度适合场景直接调 HTTP APIrequests / httpx只需要 requests中等需自己处理 offset、重试、类型解析想彻底理解 Bot 原理的人极简脚本python-telegram-bot自带上手封装低官方风格、文档全、社区例子多多数人的首选尤其是初学者和中小型 botaiogram无原生 asyncio中高异步风格强高并发、复杂状态机、追求用纯异步处理大量用户请求我自己的建议非常明确第一次写用python-telegram-bot。它的 API 设计在代码简洁和可控性之间平衡得最好尤其是 v20 之后全面转向异步写起来比以前更舒服跑并发也游刃有余。等你在它之上把功能越堆越多感觉 Handler 机制已经不够灵活了再去看 aiogram 的思想那时你已经有足够的场景来判断它还差在哪而不是一开始就淹没在 asyncio 的细节里。至于直接调 HTTP API我强烈建议至少跑通一次最原始的 getUpdates 轮询这个动作能帮你破除Bot 很神秘的心理障碍好处在后面排查问题上会体现得非常明显。2.2 Python 环境与虚拟环境动手前先确认你的 Python 版本python3 --version只要大于等于 3.8 就没问题。python-telegram-bot 最新版要求 3.8我自己用的是 3.10跑得很稳。接下来老规矩给项目建一个独立虚拟环境mkdir my-tg-bot cd my-tg-bot python3 -m venv venv source venv/bin/activate然后再装三方库pip install python-telegram-bot21.6为什么我指定版本号因为 python-telegram-bot 的 API 在 v20 从同步切异步时变化极大网上很多老教程用的是 v13 的写法直接pip install python-telegram-bot装上新的照着老教程抄必然报错。固定一个已知好用的版本后续行为可以预期。安装完验证一下版本python -c import telegram; print(telegram.__version__)2.3 申请 Token找 BotFather 拿钥匙去 Telegram 里搜索BotFather这个官方机器人负责制造机器人。对话里输入/newbot它会依次问你两个问题展示名称随便取比如My Demo Bot、用户名必须以bot结尾比如my_demo_bot。完成后你会收到一串类似123456789:AAF...的 token这就是你机器人的唯一钥匙。这里有两个实操细节值得记住。第一token 一旦泄露别人可以直接控制你的机器人所以千万别硬编码在代码里也不要提交到公开仓库建议存成环境变量或者.env文件。第二以后想改机器人头像、简介、命令列表都可以回来找 BotFather 操作比如/setcommands可以给机器人设置输入框旁边的快捷命令菜单这个对用户体验提升非常明显。3. 消息轮询原理与第一个能回话的机器人3.1 先手写一轮 getUpdates弄懂 Bot 的原始数据流在写框架代码之前我先带你用最原始的方式看一眼 Telegram 到底给你发了什么。这段代码不依赖任何 bot 框架只有最朴素的 HTTP 调用import requests import time TOKEN 你的token def get_updates(offsetNone): url fhttps://api.telegram.org/bot{TOKEN}/getUpdates params {timeout: 30} if offset: params[offset] offset resp requests.get(url, paramsparams, timeout35) return resp.json() if __name__ __main__: offset None while True: data get_updates(offset) if data.get(ok): for update in data[result]: print(update) # 每处理完一条就把 offset 更新为这条 update_id 1 offset update[update_id] 1 time.sleep(1)运行这个脚本然后在 Telegram 里给你的机器人随便发一句 hello终端会刷出一大坨 JSON。你会发现关键的结构是message.chat.id谁发来的、message.from.id发送者 ID、message.text消息内容。这就是 Bot 的全部秘密Bot 不主动接收消息它只是定期找 Telegram 服务器要新消息拿到手再决定怎么回复。这个动作叫轮询offset的作用是告诉服务器我已经处理到哪一条了避免重复处理。很多后来在并发场景翻车的人都是因为没理解 offset 与多条消息的关系。3.2 第一个 echobot最少代码能跑起来跑通上面的轮询脚本你对原理已经有底了。现在换成python-telegram-bot的写法同样功能只需要十行import asyncio from telegram import Update from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes BOT_TOKEN 你的token async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text(update.message.text) async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text(你好我是机器人你发什么我回什么。) def main(): app Application.builder().token(BOT_TOKEN).build() app.add_handler(CommandHandler(start, start)) app.add_handler(MessageHandler(filters.TEXT ~filters.COMMAND, echo)) print(Bot 已启动按 CtrlC 停止...) app.run_polling(allowed_updatesUpdate.ALL_TYPES) if __name__ __main__: main()这就是一个完整的能跑起来的 Bot给它的私聊对话框里发任何文本它原样回给你发/start它回一句欢迎语。我逐行解释几个关键点这些是你后面写任何 Bot 都要反复用到的基础概念Application.builder().token(BOT_TOKEN).build()构建整个应用对象它内部维护了与 Telegram 服务器的长轮询连接。app.add_handler(...)往应用里注册处理器。CommandHandler监听斜杠命令MessageHandler监听普通消息。filters.TEXT ~filters.COMMAND表示只要文本消息但排除以 / 开头的命令。这个过滤器体系非常强大后面我们还会用它来筛选图片、文件。app.run_polling(allowed_updatesUpdate.ALL_TYPES)启动轮询allowed_updates决定了你愿意接收哪些类型的更新。如果你只需要文本消息可以只传allowed_updates[message]能省一点网络流量。3.3 async/await 是 Python-Telegram-Bot 的灵魂上面代码里每个处理函数都是async def里面用了await。在 v20 之后python-telegram-bot 全面拥抱 asyncio。这意味着你的 bot 在等待网络响应的时候可以同时处理其他用户的消息不会因为某个请求慢而卡住所有人。这里给初学者提一个最常见的坑不要在 async 处理函数里写time.sleep()。time.sleep是同步阻塞的会把整个事件循环卡死其他所有人的消息都进不来。正确做法是await asyncio.sleep()。同理如果你要在处理函数里请求第三方 HTTP API建议用httpx.AsyncClient这样的异步客户端否则请求期间同样会阻塞整个 bot。4. 给 Bot 加上实用功能命令、按钮与消息类型4.1 命令处理器/start、/help 与带参数的命令上一节已经见过了/start的写法。实际项目中命令是用户和 Bot 交互的主要入口我建议一开始就规划好完整的命令体系而不是随用随加。最常用的一种模式是带参数的命令。例如做一个/echo 内容命令用户带上内容Bot 原样返回from telegram.ext import CommandHandler async def echo_command(update: Update, context: ContextTypes.DEFAULT_TYPE): if not context.args: await update.message.reply_text(用法/echo 你想说的话) return text .join(context.args) await update.message.reply_text(text) app.add_handler(CommandHandler(echo, echo_command))注意context.args里拿到的是用户在命令后用空格分隔的参数列表如果命令和参数之间用了多个空格args会包含空字符串建议先去除再拼接。我这里做了最简单的容错没有参数就提示用法。实际线上项目里这种参数校验 用法提示的模板你用一百次都不亏。另一个常用命令管理技巧限制命令只有管理员或特定人能用。比如你想让 Bot 只服务自己可以在处理函数里检查update.effective_user.idALLOWED_USER_ID 123456789 # 换成你的 ID async def secret(update: Update, context: ContextTypes.DEFAULT_TYPE): if update.effective_user.id ! ALLOWED_USER_ID: await update.message.reply_text(无权限) return await update.message.reply_text(这是只有你能看到的秘密)关于如何拿到自己的用户 ID你可以在给机器人发消息后用前面那节手写的 getUpdates 脚本查看message.from.id或者直接在群里 一个看 ID 的机器人。4.2 ReplyKeyboardMarkup 与 InlineKeyboardMarkup两种按钮怎么选纯文本命令虽然能用但用户体验比较硬。加按钮能显著降低用户的使用门槛尤其是给非技术朋友用的时候。python-telegram-bot 里有两类按钮很多人会混淆我放在一起对比ReplyKeyboardMarkup出现在输入框位置替代键盘。用户点击后那个按钮的文案会直接作为消息发出来。InlineKeyboardMarkup出现在消息正文下方是行内按钮。用户点击后会触发一次callback_query回调Bot 可以根据回调内容即时修改这条消息的内容不需要用户再输入任何东西。举一个具体例子。如果你想让用户一键查天气两种实现方式差异非常明显from telegram import ReplyKeyboardMarkup, InlineKeyboardButton, InlineKeyboardMarkup, Update from telegram.ext import CallbackQueryHandler # 方式一ReplyKeyboardMarkup reply_keyboard [[查天气, 查汇率]] reply_markup ReplyKeyboardMarkup(reply_keyboard, resize_keyboardTrue) await update.message.reply_text(请选择功能, reply_markupreply_markup) # 方式二InlineKeyboardMarkup inline_keyboard [ [InlineKeyboardButton(查看北京天气, callback_dataweather:beijing)], [InlineKeyboardButton(查看上海天气, callback_dataweather:shanghai)], ] inline_markup InlineKeyboardMarkup(inline_keyboard) await update.message.reply_text(点击按钮查询天气, reply_markupinline_markup)ReplyKeyboard适合做输入引导——让用户从预设选项里挑一个省去敲键盘。InlineKeyboard适合做就地交互——按钮和消息绑定点击后可以直接更新这条消息比如翻页、确认操作、切换选项不需要刷出满屏新消息。CallbackQueryHandler的写法是async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE): query update.callback_query await query.answer() # 告诉 Telegram 按钮已被点击 data query.data # 拿到 callback_data比如 weather:beijing if data.startswith(weather:): city data.split(:)[1] await query.edit_message_text(f正在查询 {city} 的天气...)这里有三件事必须记住。第一callback_data字符串长度上限是 64 字节所以不要在按钮里塞长文本。第二必须调用await query.answer()否则用户在客户端会看到按钮一直转圈体验很差。第三如果要把编辑过的消息改成查询已完成用query.edit_message_text它可以直接换掉消息正文。实际开发中我会尽量多用 InlineKeyboard因为它的交互闭环完整用户点击 - 回调处理 - 即时更新这条消息整个流程不需要新增聊天记录群里面也不会被刷屏。4.3 接收图片、文件和文档并下载到本地Bot 不只是能处理文字。处理图片和文件是很多自动收集资料场景的基础。例如让用户发一张图片给机器人机器人自动保存下来。python-telegram-bot 的处理逻辑是先用MessageHandler配合filters.PHOTO捕捉图片消息然后调用get_file拿到文件信息最后download到本地async def save_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): # 取最大尺寸的图片Telegram 会发多个尺寸 photo update.message.photo[-1] file await context.bot.get_file(photo.file_id) await file.download_to_drive(fdownloads/{photo.file_unique_id}.jpg) await update.message.reply_text(图片已保存) async def save_document(update: Update, context: ContextTypes.DEFAULT_TYPE): doc update.message.document file await context.bot.get_file(doc.file_id) # 文件名为空时用 file_id 兜底 filename doc.file_name or f{doc.file_unique_id}.bin await file.download_to_drive(fdownloads/{filename}) await update.message.reply_text(文件已保存) app.add_handler(MessageHandler(filters.PHOTO, save_photo)) app.add_handler(MessageHandler(filters.Document.ALL, save_document))两个细节值得提醒。第一update.message.photo是一个数组因为 Telegram 会给你同一个图片的不同压缩尺寸我惯例取最后一个是清晰度最高的。第二download_to_drive在 v20 是推荐写法老教程里的file.download()在 v20 之后已经废弃了如果照着抄老代码会报错。下载前记得先建目录否则文件写不进去。这个好习惯能让你后面处理类似任务时少很多莫名其妙的问题。4.4 群聊场景下的 Bot 行为差异如果你要把 Bot 拉进群会发现行为模式和私聊不太一样。群里所有消息你的 Bot 默认都能看到但默认不会对你的消息产生回复动作需要靠过滤器控制。一个非常容易踩的坑Bot 在群里收到普通消息时update.message依然存在但群里可能有人在回复别人的消息构成reply_to_message结构。比如你想做一个收到 /ping 就回复 pong的机器人在私聊里很简单在群里如果你的 Bot 也响应所有人的所有消息群会很快被刷屏。解决办法是显式限制触发条件async def group_ping(update: Update, context: ContextTypes.DEFAULT_TYPE): # 仅当消息是直接 机器人 时响应 bot_mention f{context.bot.username} if bot_mention not in update.message.text: return await update.message.reply_text(pong) app.add_handler(MessageHandler(filters.TEXT filters.ChatType.GROUPS, group_ping))还可以用filters.ChatType把群聊和私聊的处理逻辑彻底分开。这样同一个机器人既能在私聊里当私人助手也能在群里当被 才响应的群管。这种一个 Bot 多种身份的架构在真实项目中非常常见。5. 部署上线让 Bot 稳定地 7×24 运行5.1 为什么不能关掉终端就跑路大多数新手写完 bot 之后直接在终端里python bot.py跑起来然后随手一关终端bot 立刻死掉。这个阶段用于本地调试完全没问题但正式提供服务你必须让你的 Python 进程脱离终端独立在后台运行。常用的方案有几种按推荐程度排序方案优点缺点适用场景nohup 简单直接进程重启需要手动管理日志要自己重定向快速临时启动systemd 服务开机自启、崩溃自动拉起、日志统一管理需要系统权限配置稍繁琐长期运行的首选我强烈推荐Docker restartalways环境隔离、部署一致性好需要掌握 Docker 基础多机器部署、复杂依赖对于只跑一个 Bot 的个人项目systemd足够且最省心。我下面给出一个能直接用的配置。5.2 用 systemd 把 Bot 变成系统服务假设你的项目放在/opt/my-tg-bot/虚拟环境在/opt/my-tg-bot/venv/。创建一个服务配置文件sudo nano /etc/systemd/system/my-tg-bot.service内容如下[Unit] DescriptionMy Telegram Bot Afternetwork.target [Service] Typesimple WorkingDirectory/opt/my-tg-bot ExecStart/opt/my-tg-bot/venv/bin/python /opt/my-tg-bot/bot.py Restartalways RestartSec5 EnvironmentBOT_TOKEN你的token # 可选按用户运行更安全 Userwww-data [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl enable my-tg-bot sudo systemctl start my-tg-bot之后你只需要用这三个命令维护它systemctl status my-tg-bot查看运行状态systemctl restart my-tg-bot重启journalctl -u my-tg-bot -f实时看日志这里我特意在Environment里配置了BOT_TOKEN环境变量而不是直接写在代码里。这样就算别人看到了你的代码也拿不到 token安全性和可迁移性都好很多。5.3 日志、异常与自动重连上一个 systemd 配置里的Restartalways保证了进程挂了会自动拉起。但 Bot 本身的业务层错误比如发送消息失败、网络短暂抖动不会导致进程退出只是某条消息处理失败。如果不做任何处理用户那边就会消息已读但不回非常影响体验。我的做法是在主流程里统一包一层异常处理并记录详细日志import logging import traceback logging.basicConfig( format%(asctime)s - %(name)s - %(levelname)s - %(message)s, levellogging.INFO ) logger logging.getLogger(__name__) async def safe_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): try: await update.message.reply_text(我收到你的消息了正在处理...) # 你的业务逻辑 except Exception: logger.error(traceback.format_exc()) try: await update.message.reply_text(处理出错请稍后再试) except Exception: pass app.add_handler(MessageHandler(filters.TEXT ~filters.COMMAND, safe_handler))另外所有await context.bot.xxx的调用都有可能在 Telegram API 返回 5xx 错误时抛出异常我建议设置一个全局错误处理器async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): logger.error(fUpdate {update.update_id} caused error: {context.error}) app.add_error_handler(error_handler)这样就算某个回调炸了也只是打日志不会拖垮整个进程。5.4 长轮询和 Webhook 如何选我之前几节全部用的是run_polling()也就是长轮询模式。Bot 主动向 Telegram 服务器发起连接保持挂起有新消息就返回。这种方式对个人项目完全足够不需要公网 IP也不需要配置 HTTPS跑起来最简单。另一种方式是 Webhook 模式Telegram 服务器主动往你的公网 HTTPS 地址推送更新。这种方案响应延迟更低、更省资源但前提是你的服务器必须有公网 IP 和合法 HTTPS 证书配置门槛明显高很多。我的明确建议个人项目、群管小工具、内部服务通知一律用轮询模式。只有当你明确遇到需要极低延迟或同一台服务器上要跑大量 Bot这类需求时再认真考虑 Webhook。轮询模式最直观的坑就是如果你的进程意外开了两个实例比如 systemd 没停就手动启动了都会因为没有正确维护长轮询连接触发 409 Conflic 冲突错误所以你上线后要养成操作前先停掉旧进程的习惯。6. 实际项目中的高频坑与排查路径6.1 Token 泄漏与权限边界写这么久 Bot我见过翻车翻得最狠的一次是有人把 token 硬编码进代码然后代码随手推到公开仓库。两个小时之后机器人就开始不受控制地加群发广告了。因为只要拿到 token任何人就可以直接调用sendMessage接口让你的 Bot 变成他们的肉鸡。好在 BotFather 提供了补救措施找到你的 bot发送/revoke它会立刻吊销旧 token生成一个新 token。然后你改掉代码里的 token 重启服务就行。但这属于亡羊补牢最关键的是从源头管好 token 的分发和存储。推荐的做法代码里一律通过os.environ[BOT_TOKEN]读取环境变量。.env文件使用python-dotenv加载并确保它被.gitignore忽略。永远不要把 token 截图发到群聊或粘贴到在线代码分享平台。6.2 发送格式化的坑HTML 与 Markdown 转义很多时候你不只想发纯文本还想发加粗、斜体、行内代码。Telegram 的 Bot API 支持两种解析模式HTML和MarkdownV2。HTML模式相对亲和一些支持b加粗/b、i斜体/i、code代码/code。但这里有一个极其隐蔽的坑如果消息里同时包含用户输入的、带有或的文本直接拼进 HTML 再 parse_modeHTML 发送会导致整条消息解析失败并抛出异常。正确做法是对用户文本做转义from html import escape user_text 我这里有 bug 和 符号 safe_text escape(user_text) await update.message.reply_text( f你输入的是code{safe_text}/code, parse_modeHTML )同样的道理适用于MarkdownV2模式它要求对_ * [ ] ( ) ~ \ # - | { } . ! 这 17 个字符全部转义比 HTML 麻烦得多。所以我个人建议能用 HTML 就不要用 MarkdownV2除非你真的需要它独有的某种语法特性。实测下来HTML 模式的转义规则最简单、出错率最低。6.3 4096 字符长度限制与消息分片Telegram 单条消息最多 4096 字符。一旦超出reply_text会直接报错。如果你的 Bot 需要输出长文本比如日志列表、排行榜务必备好分片逻辑。我常用的分片函数非常简单def split_message(text: str, chunk_size: int 4000): for i in range(0, len(text), chunk_size): yield text[i:i chunk_size] async def send_long_message(update: Update, text: str): for chunk in split_message(text): await update.message.reply_text(chunk)注意切片时尽量在换行处切否则会显示得比较丑。更严谨一点的做法是先按\nsplit再累积拼接成不超过 4000 字符的段。对于纯文本通知来说第一版用上面这种朴素的等长切片完全够用。6.4 清理旧状态与并发冲突有时候你在本地调试一个交互流程卡到一半换了思路重新启动了 Bot。旧的 update 可能还没来得及 ack新进程又去 getUpdates然后就报Conflict: terminated by other getUpdates request。这个报错出现的核心原因是同一个 token 同时有多个 getUpdates 请求在跑。Telegram 只允许一个轮询连接存在。我排查这种问题的固定路径是查进程列表ps aux | grep bot.py确认没有多个实例在跑。查 systemd 状态确认服务没有被自动拉起了旧进程。如果确认只有一个进程等几分钟 Telegram 服务器会自动释放旧的轮询连接再重启即可。类似的状态类问题还有一个常见于开发环境你给某个用户发过的 InlineKeyboard 回调因为调试期按钮的callback_data过期了点击后什么都发生不了。这不是 bug而是没有使用唯一且时效性的 callback_data。我通常会在callback_data里带上时间戳或随机 ID并在回调处理里判断其是否仍然有效。6.5 测试技巧Build 一个和线上环境一致的行为最后一个实践建议在你把 Bot 拉进任何正式群之前一定要单独建一个只有你自己和 Bot 的测试群并且在这个群里演练所有命令和按钮流程。原因很简单——正式群里的消息是真实用户的出了 bug 影响的是别人对项目的信任。而测试群里你可以随意发消息、随意触发命令甚至创造一个有人发错文件的环境来验证容错逻辑。我在测试群里试过的最有实用价值的一件事是模拟同时多个用户操作的场景开两个不同账号一个连续发消息一个在过程中点按钮看 Bot 是否都能正确处理。很多只在并发下才暴露的问题在这个阶段就会被逮住。7. 最后给你的一份可直接抄的完整骨架前面零散讲了很多知识点这里我把一个私聊 群聊 收文件 日志 systemd 部署的最小完整项目骨架串起来你完全可以照着抄然后在此基础上加自己的业务逻辑。目录结构my-tg-bot/ ├── bot.py ├── requirements.txt └── downloads/bot.py完整示例import asyncio import logging import os import traceback from html import escape from telegram import Update from telegram.ext import ( Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes, ) logging.basicConfig( format%(asctime)s - %(name)s - %(levelname)s - %(message)s, levellogging.INFO, ) logger logging.getLogger(__name__) BOT_TOKEN os.environ[BOT_TOKEN] async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): await update.message.reply_text( 你好我是示例机器人。\n 可用命令\n /start - 开始\n /echo 内容 - 回显\n 也可以直接给我发图片我会保存下来。 ) async def echo_command(update: Update, context: ContextTypes.DEFAULT_TYPE): if not context.args: await update.message.reply_text(用法/echo 你说的内容) return text .join(context.args) await update.message.reply_text(f你说的是b{escape(text)}/b, parse_modeHTML) async def save_photo(update: Update, context: ContextTypes.DEFAULT_TYPE): photo update.message.photo[-1] file await context.bot.get_file(photo.file_id) await file.download_to_drive(fdownloads/{photo.file_unique_id}.jpg) await update.message.reply_text(图片已收到已保存。) async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): logger.error(traceback.format_exc()) def main(): app Application.builder().token(BOT_TOKEN).build() app.add_handler(CommandHandler(start, start)) app.add_handler(CommandHandler(echo, echo_command)) app.add_handler(MessageHandler(filters.PHOTO, save_photo)) app.add_error_handler(error_handler) print(Bot 已启动按 CtrlC 停止...) app.run_polling(allowed_updatesUpdate.ALL_TYPES) if __name__ __main__: main()requirements.txtpython-telegram-bot21.6运行方式export BOT_TOKEN你的token python bot.py到这一步你已经从零写出了自己的第一个 Telegram Bot并且知道怎么让它稳定运行、怎么排常见的坑。最后再分享一个我自己的体会机器人这种项目最忌讳一上来就想着把所有功能做全。先用最薄的一个功能跑通整条链路再逐步往上加按钮、加文件处理、加定时任务每加一层都重新测试一遍。迭代个三五轮下来你会发现原来那些觉得复杂的异步回调状态管理其实都只是同一个朴素逻辑的不同表现形式收到消息做处理把结果发回去。就这么简单。