终于,又轮到了小土豆我开新系列的日子啦,这也可能是我的为数不多的讲编程的文章。这一系列中我们将详细说说怎么用Python写一个能玩的Telegram bot,顺便学习一下Python中的一些知识点。
前言
假定读者们都知道Telegram是什么,会翻墙并且掌握一定的Python基础。在本系列中我使用Python 2.7和PyCharm,如果要是用Python 3的话最好用Python 3.6
本系列可能会有部分内容翻译自Telegram官方网站,可是官网对于API的说明非常简明易懂,而且有的地方还很幽默。如果英语水平连这都看不懂、理解苦难的话,那说句实在话(不是打击大家),还是尽早转行吧。
所以有的时候我也很纠结,我写这一系列干嘛啊,吃饱了没事干么?
目录
未完待定
什么是bot(机器人)
机器人是一种特殊的不额外需要手机号码的账号。用户可以通过给机器人发消息来进行交互。哎呀反正你们都知道啦。
机器人和普通用户有何不同呢?
机器人没有在线状态、没有上次在线时间;
机器人的存储空间是有限的;
机器人不能主动和用户开启会话,换句话说,要用户先跟它说(或者是添加到群里),他才能回消息;
机器人的用户名结尾永远是bot;
把机器人加到群里的时候,默认情况下(开启隐私模式),机器人是不会收到所有的消息的;
机器人不吃不喝,不睡觉也不抱怨(除非编码太垃圾分分钟抛异常?)。
如何创建一个机器人
很简单,跟BotFather说话,他会指引你创建一个机器人。真的是太简单了,大家去官网自己看吧。
在此系列中,我创建一个名为@benny_demo_bot的机器人,TOKEN为XXX我当然不会告诉你啦。
并且,我使用Edit commands
添加了如下命令(start、help、settings是全局命令):
start - 自我介绍
help - 帮助
talk - 聊天
搭建开发环境
众所周知的是,Python可以使用pip来安装一些非常好用的第三方库。这里我们使用了pyTelegramBotAPI来和Telegram API进行交互。实际上你想用curl也没人拦着你。
为啥不用另一个star更多的库?我嫌弃那个库麻烦不好用。
准备virtual environment与pyTelegramBotAPI
Virtual environment就是一个虚拟出来的纯净的、不包含第三方库的Python环境(想包含本地已安装的,也不是不行)。
为什么要使用virtual environment呢?唔是这样的,pip默认会把库安装到系统里对应的site-packages里,这样无论你在哪打开Python,都是可以用这个库的。这样听起来好像挺好,但实际上还是有一些弊端的。比如说,在一些项目中,我们最后需要整理出来一个这个项目使用了哪些第三方库,然后指引用户去安装这些库。一个两个还好,多了的话,恐怕开发者也容易找不全、漏掉一些库吧。再加上一些库的依赖、版本等问题,所以如果有必要,那么一定要使用virtual Environment的。
所以我们先创建virtual environment吧。有好几种方式,这里我们就说两种:命令行方式和IDE方式
命令行方式
首先我们需要安装virtual environment,用pip
就可以了,网上教程很多,这里我就简述一下。
pip install virtualenv
如果你使用某些Linux发行版,可能会提示pip: command not found
,如果是Debian系的话,那么运行:
sudo apt install python-pip
之后我们创建一个目录,使之成为工程目录:
mkdir bot_demo cd bot_demo #创建virtualenv virtualenv ENV #激活virtualenv(Windows) ENV\Scripts\activate #激活virtualenv(Linux) source ENV/bin/activate
此时命令行前面会变成(ENV),代表着已经进入了。这个时候再运行pip就会安装到虚拟环境中
pip install pyTelegramBotAPI
IDE方式
使用IDE相比来说很方便。我们只需要新建一个项目,File - Settings - Project interpreter,右侧的齿轮里即可选择create VirtualEnv
我建议将VirtualEnv选择到工程目录下,这样方便我们判断哪个VirtualEnv对应哪个项目。当然,如果你打算把代码push到GitHub,记得把这个目录加到.gitignore
里。
之后我们点击加号,安装pyTelegramBotAPI
这个package,PyCharm就会自动为我们安装好这个包了。
开始你的项目
我该怎么处理我的TOKEN
为了方便项目的扩展,咱一般都会把配置信息放在一个Python文件中。但是,咱知道,像TOKEN这类东西都是比较敏感的,如果要打算把代码push到GitHub,那么很容易就会泄露这类敏感信息。也千万别以为删除这些信息了就万事大吉了。要知道时光机可是Git的能力之一。万一不幸发生了这种事情,推荐大家用BFG抹掉历史纪录,详细教程可以参考我。
那从头开始预防这件事,咱该怎么做呢?一个非常愚蠢但是有效的办法是,新建一个名为config.py
的文件,此文件用于保存你的配置信息,并将其加到.gitignore
中,另外复制一个名为config-example.py
,此文件内容与config.py
一致,只是不包含TOKEN敏感信息。
为什么咱不直接把不包含敏感信息的config.py
先push上去,然后加入自己的TOKEN,之后再加入到.gitignore
中呢?其实这样要倍加小心,哦是这样的,gitignore管不到已经在历史记录中的文件的。
另外,如果你使用私有仓库(并且打算以后公开),那就更简单了。可以什么都不管直接push上去,再打算公开之前抹掉TOKEN再push,然后用BFG抹掉历史记录再force push就可以了。
另一个更好的办法是,创建一个config.py
但是不在这里写入真实的配置信息,转而设置环境变量然后用os.environ
加判断获取到对应的KEY,如果使用PyCharm的话,在Run/Debug configuration里就可以添加环境变量,当然直接在命令行里设置也可以(windows要用set xxx='123'
, Linux直接export xxx='123'
设置可以考虑写入.bashrc
里)
项目结构
这里由于只是一个演示,并不打算push到GitHub,所以就不处理TOKEN啦。
很显然,我们需要一个config.py
用于保存配置文件,还需要一个"主程序",这里我就很无耻的叫它main.py
啦。
一些解释:
第一行的#!/usr/bin/python
用于指定当给文件加x权限并执行时(./main.py
)该使用哪个解释器,用法和shell编程中的是一样的;
第二行用于指定编码格式,下面则是注释说明与doc啦。大家不好奇这些是怎么来的吗?实际上是IDE自动生成的啦。我才懒得自己打这些玩意。
在设置中的如下位置即可设定模板,更多信息可以参考JetBrains官网。
config.py
显然我们要在这里放入TOKEN,所以我们直接写一行TOKEN='XXX'
即可。我一般习惯把这类变量全大写,给人“常量”的感觉嘛(实际上Python中是没有常量这个概念的)。如下所示
看这时间戳,我写一篇文章容易嘛我!几个小时了……
main.py
首先我们需要import库和我们的配置文件并创建telebot类的实例
import telebot from config import TOKEN bot = telebot.TeleBot(TOKEN)
定义接受/start和命令的消息处理函数,如果多个命令都用这一个函数处理,就这样['start','help']
@bot.message_handler(commands=['start']) def send_welcome(message): bot.send_message(message.chat.id, '大家好,我是机器人')
定义接受所有消息的处理函数
@bot.message_handler() def echo(message): bot.reply_to(message, message.text)
另一种reply_to
@bot.message_handler(commands=['help']) def send_welcome(message): bot.send_message(reply_to_message_id=message.message_id, chat_id=message.chat.id, text='有什么可以帮您')
定义程序的入口:
if __name__ == '__main__': bot.polling()
我来解释一下:
装饰器
在每个函数前面,我们可以看到有一个@bot.message_handler
,这其实是一个Python装饰器。装饰器其实是一个起初听起来觉得什么用都没有、但是用起来却很方便的高阶Python功能。简单说,装饰器可以在运行时改变或增加函数的功能。此话怎讲?
比如说,我们需要在函数开始运行、结束运行的时候输出消息到控制台,大家可能会想着在函数开始和结尾加两个print
。没错这样是可以的,但是假如我们有10个函数呢?这代码都没能重用,删除的时候还要删除20次。由于函数在Python中是一等公民,我们可以定义一个嵌套函数(也就是装饰器),配合Python的语法糖@来实现这一功能。
在我们说的打印日志这个例子中,装饰器就可以这么写这么用:
当然在这个例子中,这个装饰器无法处理带参数的函数。要处理函数的参数,我们需要在内层的wrapper
的参数中使用*args
和**kwargs
来处理参数(和关键字参数),关于装饰器更多的内容,可以参考奔跑的蜗牛壳《Python 装饰器》
message
Telegram API会返回给我们一组JSON,但是控制台用print
打印出来的JSON都是一行,看起来很不舒服,解决方法是使用pprint
或者新建一个扩展名为json的文件,将一行json放入,Ctrl + Alt + L格式化。
在message这个参数中,有几个是比较重要的,我说几个个人比较常用的:
message.chat.id
类型为int,聊天ID,如果是和机器人私聊,那么这个ID也就是用户的user_id;如果是群组的话,那么就是群组的id
message.text
类型为unicode,用户发给机器人的聊天内容
message.message_id
类型为int,用户发给机器人的消息编号,再使用"回复"功能时会用到这个编号。
send_message与reply_to_message
send_message
用于给用户发送消息,必须要有至少两个参数,一个是chat_id
,一个是text;
reply_to
则是回复某条消息,是send_message(message.chat.id, text, reply_to_message_id=message.message_id, **kwargs)
的方便用法,需要两个参数,一个是message,一个是回复的内容;
如果我们要用send_message
来实现relpy_to_message
,那么就要手动用关键字参数指定回复哪条消息,如代码中的help命令所示。
if __name__ == '__main__':
这一行则定义了这个Python程序的入口,在他下面运行bot.polling()
方法。这个方法可神奇了呢,他会帮你处理各种事件,自动帮你从API里取回消息,基本不用你干预。
如果我们不用__name__
控制的话,那么当在其他文件中import时,也就顺便运行这个文件啦。这很明显不是我们想要的结果。
运行与调试
如果一切顺利的话,当你点击运行时,跟机器人说话,它就会回复你啦。当然啦,运行(run)与调试(debug)其实还是有些区别的,关于这的一些技巧,我们可以以后再谈~
哦对了,我们还需要导出依赖,方便别人也能够用啊!
很简单,进入到virtual env,运行如下命令:
pip freeze > requirements.txt
别人用的时候,pip install -r requirements.txt
就可以安装对应的依赖啦。
我该怎么长期运行我的机器人呢
这个问题问得好!首先你得有个在海外的服务器(或者让程序走代理),可以考虑用用nohup
或者screen
,这样当你关掉终端之后,程序还会继续运行。呃,那些用Windows做服务器的,当我什么都没说好了。
当然了正经的办法,是要用systemd或者supervisor的。具体可以参考这里
我想要任务计划机器人,怎么办
这个问题问得也很好。我们可能需要某种机器人,每十分钟给用户发送一条消息。
此时我的建议是,如果需求很简单,那么使用操作系统自带的任务计划功能来执行脚本(Linux为cron)。
可能有人会有反对意见了。Python里有很多优秀的任务调度、任务计划库,比如说apscheduler。这样也是可以的,只不过需要多写点代码!
当然了,有些时候我们的需求比较灵活,操作系统提供的任务计划用起来会很麻烦,此时就只能用一些任务调度的库啦。
下一篇打算讲点什么呢
如果我们的机器人只能以固定的模式讲话,那么显然是不行的!
那么下一篇,我们就要尝试着接入其他服务(使用pycurl发送POST或者GET请求),操作数据库,给机器人加入"正在输入"的特效,更加深入的理解Telegram Bot API。当然了,我下一篇说不定等到啥时候,所以,别太期待。
requests.exceptions.ConnectTimeout: HTTPSConnectionPool(host='api.telegram.org', port=443): Max retries exceeded with url: /botTOKEN/getUpdates?offset=1&timeout=20 (Caused by ConnectTimeoutError(<requests.packages.urllib3.connection.VerifiedHTTPSConnection object at 0x107696e48>, 'Connection to api.telegram.org timed out. (connect timeout=3.5)'))
是因为我的网络环境有问题吗(:з」∠)挂了全局的VPN 在网上都没有找到答案,希望博主有空的时候能解答一下~