Telegram结合Flask搭建自定义Webhook接收器:从零实现机器人主动推送

本文详细讲解如何使用Python Flask框架搭建Telegram Bot的自定义Webhook接收器,涵盖setWebhook配置、请求验证、消息处理与高级路由,助你摆脱轮询限制,实现低延迟、双向交互的机器人架构。

阅读提示建议先浏览小标题,再根据需要深入阅读具体段落。

引言:为什么需要自定义Webhook接收器?

Telegram Bot API 提供了两种获取更新(Update)的方式:长轮询(Long Polling)和 Webhook。长轮询需要客户端持续请求服务器,不仅浪费资源,而且存在延迟。而 Webhook 允许 Telegram 服务器主动将新消息推送到你的服务器,响应更快、资源占用更低。官方默认的 Webhook 配置虽然简单,但灵活性不足。通过 Flask 构建自定义接收器,你可以完全控制请求处理逻辑,轻松实现消息过滤、多机器人管理、与数据库联动等高级功能。

本文将从零开始,指导你使用 Python 的 Flask 框架搭建一个安全、可扩展的 Webhook 接收器,并与 Telegram Bot 无缝集成。无论你是想开发客服机器人、自动化工具,还是学习 Webhook 原理,这篇文章都能为你打下坚实基础。

准备工作:前置依赖与开发环境

在开始编码之前,请确保你具备以下条件:

  • Python 3.7+ 环境,建议使用虚拟环境隔离项目依赖。
  • Telegram Bot Token:通过 @BotFather 创建机器人并获取 Token。
  • 可公网访问的 HTTPS 地址:Telegram 要求 Webhook URL 必须为 HTTPS。开发阶段可使用 ngroklocaltunnel 将本地端口映射到公网。
  • 基础 Flask 知识:了解路由和请求对象即可。

安装依赖:

pip install flask requests

创建项目目录并编写入口文件 app.py

第一步:创建最小 Flask 应用

我们先构建一个最简单的 Flask 应用,用于接收 Telegram 的 POST 请求。Telegram 会以 JSON 格式发送更新到一个指定的端点(例如 /webhook)。

from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    update = request.get_json()
    # 打印更新内容,便于调试
    print(json.dumps(update, indent=2, ensure_ascii=False))
    return jsonify({'ok': True})

if __name__ == '__main__':
    app.run(port=5000, debug=True)

运行此应用后,访问 http://localhost:5000/webhook 会返回 404,因为只接受 POST 请求。接下来我们需要将 Telegram 的更新转发到此地址。

第二步:设置 Webhook 地址

使用 Telegram Bot API 的 setWebhook 方法,将你的机器人更新发送到 Flask 应用的公网 URL。命令如下:

curl -F "url=https://your-ngrok-url.ngrok.io/webhook" https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook

若返回 true,则设置成功。你可以通过 getWebhookInfo 方法检查当前 Webhook 状态:

curl https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getWebhookInfo

如果出现 SSL 证书错误,请确认你的公网地址确实为有效的 HTTPS。本地开发时,用 ngrok http 5000 获取临时域名即可。

第三步:安全验证:防止伪造请求

任何人都可以向你公开的 Webhook URL 发送 POST 请求,伪造更新。为了安全,必须验证请求确实来自 Telegram。Bot API 支持在 setWebhook 时指定一个 secret_token,Telegram 会在每次请求的 X-Telegram-Bot-Api-Secret-Token 头中携带该 token。我们只需校验这个头即可。

修改 setWebhook 命令:

curl -F "url=https://your-ngrok-url.ngrok.io/webhook" -F "secret_token=your-custom-secret" https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook

然后在 Flask 中校验:

import os
from flask import abort

SECRET_TOKEN = os.getenv('WEBHOOK_SECRET', 'your-custom-secret')

@app.route('/webhook', methods=['POST'])
def webhook():
    received_token = request.headers.get('X-Telegram-Bot-Api-Secret-Token')
    if received_token != SECRET_TOKEN:
        abort(401)
    # 处理更新...
    return jsonify({'ok': True})

这样,只有携带正确 token 的请求才会被处理,大大增强了安全性。

第四步:解析并响应不同类型的更新

Telegram 的更新对象涵盖消息、回调查询、内联查询等。我们在 Flask 中可以根据 update 的字段进行路由。

处理文字消息

@app.route('/webhook', methods=['POST'])
def webhook():
    update = request.get_json()
    if 'message' in update:
        chat_id = update['message']['chat']['id']
        text = update['message'].get('text')
        print(f'收到来自  的消息: ')
        # 可在此处调用 sendMessage API 回复
    elif 'callback_query' in update:
        # 处理按钮回调
        callback = update['callback_query']
        user_id = callback['from']['id']
        data = callback['data']
        print(f'回调:  - ')
    return jsonify({'ok': True})

注意:为了不阻塞 Webhook 响应,建议将处理逻辑放到后台线程或任务队列中。Telegram 要求 Webhook 必须在 60 秒内响应,否则会重试。

一键回复:调用 sendMessage

在 Flask 中集成 requests 库发送回复消息:

import requests

TELEGRAM_API = f"https://api.telegram.org/bot"

def send_message(chat_id, text):
    requests.post(f"/sendMessage", json={
        'chat_id': chat_id,
        'text': text
    })

# 在 webhook 函数中调用 send_message(chat_id, '已收到!')

第五步:高级路由与多机器人支持

如果你的服务器需要同时处理多个机器人,可以在 URL 中加入机器人的标识符。例如,将 Webhook 设置为 https://your-domain.com/webhook/bot1/webhook/bot2,然后在 Flask 中动态获取 token 和 secret。

BOTS = {
    'bot1': {'token': '111:token1', 'secret': 'secret1'},
    'bot2': {'token': '222:token2', 'secret': 'secret2'}
}

@app.route('/webhook/<bot_name>', methods=['POST'])
def webhook(bot_name):
    if bot_name not in BOTS:
        return jsonify({'ok': False, 'error': 'Bot not found'}), 404
    bot = BOTS[bot_name]
    received_token = request.headers.get('X-Telegram-Bot-Api-Secret-Token')
    if received_token != bot['secret']:
        abort(401)
    update = request.get_json()
    # 使用 bot['token'] 进行 API 调用
    return jsonify({'ok': True})

同时设置每个机器人的 Webhook 时,将 URL 指向对应的子路径即可。

第六步:实用技巧与调试建议

  • 使用环境变量:防止 Token 和 Secret 硬编码,利用 os.getenv 管理。
  • 日志记录:使用 Flask 的 app.logger 记录收到的更新和错误,便于排查问题。
  • 快速重试机制:如果处理逻辑可能失败,建议先返回成功避免 Telegram 重复推送,再在后台处理。
  • 压测与性能:Webhook 接收器应快速响应,避免在请求线程中执行耗时操作。可使用 Celery 或 Redis 队列解耦。
  • 更新丢失问题:如果收到 409 错误,说明有另一个 Webhook 或轮询实例在运行,确保只有一个消费者。

第七步:完整示例:回显机器人

让我们将所有内容整合,构建一个简单的回显机器人。用户发送任何消息,机器人都原样返回。

import os
from flask import Flask, request, jsonify, abort
import requests

app = Flask(__name__)

TOKEN = os.getenv('BOT_TOKEN')
SECRET = os.getenv('WEBHOOK_SECRET')
API_URL = f"https://api.telegram.org/bot"

def send_message(chat_id, text):
    try:
        requests.post(f"/sendMessage", json={
            'chat_id': chat_id,
            'text': text
        }, timeout=10)
    except Exception as e:
        app.logger.error(f"Failed to send message: ")

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.headers.get('X-Telegram-Bot-Api-Secret-Token') != SECRET:
        abort(401)
    update = request.get_json()
    if 'message' in update:
        msg = update['message']
        chat_id = msg['chat']['id']
        text = msg.get('text')
        if text:
            send_message(chat_id, f"你说了:")
    return jsonify({'ok': True})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

部署到公网后,设置 Webhook 和 secret_token,即可让你的机器人自动回显消息。

总结

本文详细介绍了如何利用 Flask 搭建 Telegram 自定义 Webhook 接收器,从环境准备、安全验证到高级路由,每一步都提供了可落地的代码示例。通过这种方式,你可以将 Telegram 机器人无缝集成到任何 Python 工作流中,实现自动化推送、智能客服、IoT 控制等无限可能。Webhook 不仅仅是接收更新,更是打通 Telegram 与外部世界的桥梁。掌握它,你的机器人开发能力将迈上一个新台阶。

FAQ

安装与配置指南

常见问题

为什么我在设置 Webhook 时返回错误 404?

通常是因为你的 Webhook URL 路径与 Flask 路由不匹配。请确认 setWebhook 中的 URL 完整路径与你 Flask 中的路由路径完全一致,且方法为 POST。例如,Flask 路由为 /webhook,则 URL 应为 https://域名/webhook。

Webhook 和长轮询可以同时使用吗?

不可以。Telegram 只允许一种获取更新模式。设置 Webhook 后,getUpdates 将返回 409 错误。如果你想切换回轮询,必须先调用 deleteWebhook。

为什么我访问 Webhook 地址时出现 ‘Connection refused’?

这通常发生在本地测试时,Flask 默认监听 127.0.0.1,而 ngrok 可能无法转发。请将 Flask 的 host 设置为 0.0.0.0,即 app.run(host='0.0.0.0')。

如何处理大量并发消息?

Webhook 接收器应快速响应,避免在请求线程中直接调用 Telegram API。建议使用消息队列(如 Celery、RQ)将处理任务异步化,或将 sendMessage 放到后台线程。同时,可以设置 Flask 的 threaded=True 参数。

secret_token 忘记了怎么办?

你可以在 BotFather 或代码中随时重新调用 setWebhook 并设置新的 secret_token。Telegram 不会保存已经设置的 token,但你可以通过 getWebhookInfo 查看是否设置过。如果丢失,只需重新设置即可。