You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Telegram Bot返回Matplotlib图表时遇线程相关警告问题

解决Matplotlib图表通过Telegram Bot发送时的线程警告问题

问题说明

使用pandas_datareader获取股票数据、matplotlib生成图表,通过Telegram Bot的update.message.reply_photo方法发送图表时,触发以下警告:

用户警告:在主线程之外启动Matplotlib GUI很可能会失败。

核心代码如下:

# imports
def generate_chart(update, context):
    stock = f'^GSPC'
    start = datetime.date(2000, 1, 1)
    end = datetime.date.today()
    data = web.DataReader(stock, 'yahoo', start, end)
    plot = data.plot(y='Open')
    img_buf = BytesIO()
    plt.savefig(img_buf, format='png')

    prepared_data = img_buf

    update.message.reply_photo(prepared_data)

def main():
    updater = Updater(keys.API_KEY, use_context=True)
    dp = updater.dispatcher

    dp.add_handler(CommandHandler("stocks", generate_chart))
    dp.add_handler(MessageHandler(Filters.text, handle_message))
    dp.add_error_handler(error)

    updater.start_polling()
    updater.idle()

main()

问题原因

Telegram Bot的updater.start_polling()会启动多线程处理请求,而matplotlib默认的交互式后端(如TkAgg、QtAgg)要求在主线程运行,在Bot的处理线程中调用绘图逻辑时,就会触发线程不匹配的警告。

解决方案

方案1:指定非交互式后端

在导入matplotlib.pyplot前,设置使用无GUI的非交互式后端(如Agg),彻底避免GUI线程冲突:

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

注意:这行代码必须放在import matplotlib.pyplot as plt之前才会生效。

方案2:绘图后清理资源+修正缓冲区指针

每次绘图完成后关闭图表释放资源,同时将BytesIO缓冲区指针移到开头(否则Telegram读取的是空内容):
修改generate_chart函数:

def generate_chart(update, context):
    stock = f'^GSPC'
    start = datetime.date(2000, 1, 1)
    end = datetime.date.today()
    data = web.DataReader(stock, 'yahoo', start, end)
    
    # 创建独立绘图对象,避免依赖全局plt
    fig, ax = plt.subplots()
    data.plot(y='Open', ax=ax)
    
    img_buf = BytesIO()
    plt.savefig(img_buf, format='png')
    # 关闭图表释放资源
    plt.close(fig)
    # 将缓冲区指针移到起始位置
    img_buf.seek(0)
    
    update.message.reply_photo(img_buf)

方案3:主线程执行绘图(可选)

若必须使用交互式后端,可将绘图任务提交到主线程执行,但实现复杂,Bot场景下不推荐使用。

优化后完整代码示例

import datetime
from io import BytesIO
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import pandas_datareader.data as web
from telegram import Update
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackContext

def generate_chart(update: Update, context: CallbackContext):
    stock = '^GSPC'
    start = datetime.date(2000, 1, 1)
    end = datetime.date.today()
    data = web.DataReader(stock, 'yahoo', start, end)
    
    fig, ax = plt.subplots()
    data.plot(y='Open', ax=ax)
    ax.set_title('标普500开盘价走势')
    ax.set_xlabel('日期')
    ax.set_ylabel('价格(美元)')
    
    img_buf = BytesIO()
    plt.savefig(img_buf, format='png', dpi=100, bbox_inches='tight')
    plt.close(fig)
    img_buf.seek(0)
    
    update.message.reply_photo(img_buf)

def handle_message(update: Update, context: CallbackContext):
    update.message.reply_text("发送 /stocks 获取标普500走势图表!")

def error(update: Update, context: CallbackContext):
    print(f'更新 {update} 触发错误 {context.error}')

def main():
    updater = Updater(keys.API_KEY, use_context=True)
    dp = updater.dispatcher
    
    dp.add_handler(CommandHandler("stocks", generate_chart))
    dp.add_handler(MessageHandler(Filters.text, handle_message))
    dp.add_error_handler(error)
    
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

内容的提问来源于stack exchange,提问作者spal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 06:25:21