如何将Matplotlib生成的PNG发送给Telegram Bot?子线程调用报错如何解决?
报错原因
- Telegram Bot的命令回调函数默认在子线程中执行,而Matplotlib默认启用带GUI交互的后端,所有GUI相关的绘图、窗口操作强制要求只能在主线程执行,你在子线程中调用
plt.subplots()、plt.show()触发了线程限制报错。 - 代码存在冗余逻辑:你只需要生成PNG文件发送,不需要调用
plt.show()弹出GUI预览窗口,这行是多余的触发报错的直接原因之一。 - 存在路径错误:你调用
fig.savefig("test.png")是将文件保存在当前运行目录下,后续open('/test.png', 'rb')尝试读取根目录下的文件,会额外触发文件不存在的错误。
修复步骤
- 第一步:强制指定Matplotlib使用无GUI的Agg后端,这行配置必须写在导入
matplotlib.pyplot之前:
import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt
- 第二步:删除代码中冗余的
plt.show()调用,不需要弹出GUI窗口。 - 第三步:修正文件读写路径,保持保存和读取的路径一致,建议使用
with语句自动管理文件句柄,避免资源泄漏。 - 第四步:删除无用的
bot.send_document(message.chat.id, "FILEID")测试代码。
修正后可用的代码示例
# 后端配置必须放在导入pyplot之前,位置不能错 import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import sqlite3 # 保留你原有导入bot的代码 @bot.message_handler(commands=['show_previous_week']) def send_previous_values(message): # 读取数据库数据 connect = sqlite3.connect('message.db') cursor = connect.cursor() previous_values_p = cursor.execute("SELECT message FROM weight_from ORDER BY id DESC LIMIT 7;") previous_values_p = cursor.fetchall() connect.close() # 仅查询不需要commit,直接关闭连接即可 # 处理数据格式 previous_values_w = [] for i in previous_values_p: previous_values_w.append(float(i[0])) previous_values_w.reverse() t = ["25.10", "26.10", "27.10", "28.10", "29.10", "30.10", "01.11"] s = previous_values_w # 生成图表 fig, ax = plt.subplots() ax.plot(t, s) ax.set(xlabel='time (d)', ylabel='weight (kg)', title='Weight change chart for 7 days') ax.grid() # 保存文件并发送 fig.savefig("test.png") plt.close(fig) # 关闭画布释放内存 with open('test.png', 'rb') as file: bot.send_document(message.chat.id, file)
如果不想生成本地临时文件,也可以直接将图片存在内存中发送,替换上述保存+读文件的代码即可:
import io # 生成图表的代码不变 buf = io.BytesIO() fig.savefig(buf, format='png') buf.seek(0) # 将指针移到内存流开头 plt.close(fig) bot.send_document(message.chat.id, buf, visible_file_name='week_weight.png')
内容的提问来源于stack exchange,提问作者Olga Melnikova
相关产品推荐
相关产品推荐

