python小项目之微信远程控制

前两天接触了一个有趣的python模块——itchat,这个模块可以非常方便的操作微信,今天就来使用这个模块来实现微信远程控制。

环境准备

itchat模块不是python标准模块(内置模块),是一个第三方模块,需要下载安装,我们可以在命令行中输入如下命令安装:

1
> pip install itchat --user

注意:pip工具是安装python时连带安装的,不清楚的可以回去看前面环境搭建的教程,或者评论提问。

安装完后,尝试导入模块

1
2
>>> import itchat
>>>

没有报错,表明安装成功!

itchat

现在来简单介绍itchat的用法,仅仅是简单了解,为实战做铺垫,如果需要详细学习itchat可以问问度娘。

下面这个简单的例子几乎囊括了我们要用到itchat的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import itchat  # 导入itchat模块

@itchat.msg_register('Text')
def simple(msg):
src = msg['FromUserName'] # 消息的发送方
dst = msg['ToUserName'] # 消息的接受方

text = msg['Text'] # 文本消息

# 字符串可以用+号拼接到一起
send_msg = '从'+src+'到'+dst+':' + text

# 发送消息给文件传输助手,消息内容为字符串send_msg
itchat.send(send_msg, 'filehelper')

if __name__ == '__main__':
itchat.auto_login()
itchat.run()

很多地方看注释就可以懂,但还是有必要解释一下一些我们没接触过的东西。

__name__

首先是,__name__这个变量。

当文件被当作模块导入的时候,也就是被import的时候,在这个文件中,这个__name__变量就是这个文件的名字(不包括后缀.py),而当这个文件被当作程序直接运行的时候,也就是运行python 文件名.py的时候,这个变量的值为__main__,所以if __name__ == '__main__':的意思是,当文件被执行的时候,运行if后的代码块。

为什么会有这样的做法?

python的很多模块很多是可以直接当作程序运行的,一般运行的代码是测试什么的,使用这样的写法可以区分当前模块是作为程序运行,还是作为模块被导入。

而我在这里这么写,只是把这个if代码块当作程序的入口,看起来更清晰罢了。

消息处理

先忽略上面的函数定义,直接从if代码块入手。

运行itchat.auto_login(),不久之后弹出一个二维码,使用手机微信扫描二维码,并确认登录,程序就完成了微信的登录。

然后的itchat.run()里面是一个无限循环(可以在命令行按下Ctrl+C强制结束程序),每当微信有消息的时候,itchat就会调用已注册的消息处理函数来处理对应的消息。

这个消息处理函数就是我们前面定义的simple函数,这个函数带有一个装饰器`@itchat.msg_register(‘Text’),这个装饰器就是把simple函数注册成消息处理函数,参数Text`表示只处理文本消息。

也就是说,每当有文本消息时,itchat.run()里面的代码就会调用simple函数,并传入一个参数msg,这个msg其实是一个字典,包含了这则文本消息的发送方id(FromUserName),接受方id(ToUserName),消息内容(Text)等。

发送消息

我们可以通过itchat.send函数,发送消息,这个函数要用到两个参数,第一个是消息的内容,字符串类型;第二个参数是接受方id(这并不是微信昵称),filehelper是一个特殊的id,它指的是微信的文件传输助手(登录后,手机会话窗会出现),就是下图这个东东:
微信文件传输助手

(⊙ˍ⊙) 我的是英文的,不要介意。不过英文也不是filehelper,这我就不知道了,反正中文显示的应该是“文件传输助手”。

我们的项目就是用手机发送命令给这个文件传输助手,让程序跟据不同的命令来操作电脑。


开始工作

我们创建先一个目录,目录名字就叫做WeChatController,当然,同学们可以起别的名字。这个目录下面存放的就是我们的python代码。

流程

介绍一下程序的基本流程:我们设定一些特定的消息内容做为指令,程序接受到发往消息助手的指令的时候,根据不同的指令做出不同的操作,比如发送消息内容为help,然后我们的程序把帮助信息发回消息助手。

用图可以这样表示:
流程

开始编写代码

现在开始编写我们的代码,在我们创建目录下新建一个文件命名为main.py(其它名字也可以)。

先写好程序的基本框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import itchat

@itchat.msg_register('Text')
def handler(msg):
dst = msg['ToUserName'] # 消息接受方
text = msg['Text']

if dst == 'filehelper':
# 发往消息助手的消息就是我们的命令
pass

if __name__ == "__main__":
itchat.auto_login()
itchat.run()

我们只需要一个消息处理函数,就是这个handler,变量text就是我们的消息内容,也是我们的指令,我们根据不同的指令实现不同的操作,下面来实现帮助指令,收到这个指令,程序就把帮助信息发到文件助手,我们就可以在手机上看到了。

实现帮助指令

我们把帮助指令的名字命名为help,我们只需要判断收到的消息text是否为字符串'help'即可,核心代码如下

1
2
3
4
5
6
7
---略---
if dst == 'filehelper':
# 发往消息助手的消息就是我们的命令
if text == 'help':
help_msg = '现在支持的命令有:\n help 获取帮助信息\n'
itchat.send(help_msg, 'filehelper')
---略---

(。・∀・)ノ 这没什么难度,用if语句判断一下text的内容,如果是help就把帮助信息发给消息助手,同学们可以试着运行下,扫码登录后用手机向文件助手发送消息help,可以看到结果。

实现退出程序

程序的运行是个死循环,虽然可以强制终止,但总有那么一点麻烦,如果把终止程序也做成一个指令就好了 ヾ(≧∇≦*)ゝ

好!现在就来实现这个指令!

然而,程序怎么终止?

可以用到python内置的sys模块有一个函数叫做exit,这个函数可以不带参数直接调用,所以我们只需要在合适的位置调用这个函数就可以了。

我们给这个指令取个名字,就叫做logout,中文是登出的意思,登出了程序不就意味着结束了吗,下面是关键代码(不要忘了导入sys模块):

1
2
3
4
5
6
7
8
9
10
11
---略---
if dst == 'filehelper':
# 发往消息助手的消息就是我们的命令
if text == 'help':
help_msg = '现在支持的命令有:\nhelp 获取帮助信息\n'
help_msg += 'logout 退出程序\n'
itchat.send(help_msg, 'filehelper')
elif text == 'logout':
itchat.send('退出程序...', 'filehelper')
sys.exit()
---略---

补充:字符串可以用+号拼接到一起,help_msg += 'logout 退出程序\n'也就是给help_msg的尾部拼接字符串'logout 退出程序\n'\n是换行符,这些在前面的教程中可能没有提到,关于字符串的操作,下一节详细介绍。

读者们运行程序,发送退出指令后会发现,程序报错,命令行会输出像下面这样的信息:

1
2
3
4
    r = replyFn(msg)
File "main.py", line 18, in handler
sys.exit()
SystemExit

除此之外,程序并没有退出。在这里解释一下,其实sys.exit()只是发送了一个终止的信号,itchat捕抓了这个信号,不给这个信号发送给系统,阻止我们非正常退出。

嗯,非正常。

不必慌张!itchat提供了一个logout函数让我们退出微信登录,itchat退出了微信登录就自然地退出程序了,让我们修改程序如下:

1
2
3
4
5
---略---
elif text == 'logout':
itchat.send('退出程序...', 'filehelper')
itchat.logout()
---略---

实现执行系统命令

说好的远程控制电脑,怎么可以什么都干不了?

下面就来实现真正的控制电脑的操作! ヾ(≧∇≦*)ゝ

我们来实现给定指令运行系统命令!

什么是系统命令?就是命令行里运行的那些咯 (ˉ^ ̄~) 切~~

呃,这个……我们不能像远程桌面那样直接使用鼠标和键盘操作,水平还不足(其实是我不会 ( ̄_, ̄ )),就实现这个吧。

这个功能对于新手来说,也是有一定的难度的,要多加注意!

<( ̄︶ ̄)↗[GO!]

subprocess模块

要执行系统命令,可以用到subprocess模块,这个模块也是python内置的,我们只需要使用其中一个check_output函数就可以了,这个函数有挺多参数的,但是必要的参数不多,这里我给出我们的用法:

1
2
3
4
5
try:
result = subprocess.check_output(cmd, shell=True,timeout=2)
result = result.decode()
except:
itchat.send('发生错误了!','filehelper')

这里接触到后面教程的内容,这里先解释一下,这个try是一个代码块,会捕捉程序中发生的错误(称为异常),如果捕捉到异常就会执行except代码块,同学们先用着先就好了。

上面的函数调用中,cmd就是我们要执行的系统命令的字符串,shell=True不用管,timeout表示超时时间,单位为秒,超时会发生异常,其次,程序的运行输出会做为返回值返回。

这个返回值是字节串类型的,这是python的一种基本数据类型,之前的教程没有提到,不过不重要,我们只要知道使用result.decode()就可以把字节串解码成字符串就够了。

从消息中分离命令

至于要执行的命令怎么获取,又要接触到后面的知识了,字符串、数字、列表等类型的值都是一个 “对象”,对象都有自己的 “方法”,方法其实就是函数,不过这种函数的调用方式不太一样:

1
对象.方法名()

这有点像调用模块内的函数一样,其实上面的result.decode()就是调用了一个方法。

我们要用到字符串的一个方法:split

split用于分割字符串,指定一个分隔符,字符串就会以这个分隔符从左往右分割成多个字符串,然后一列表返回,比如以空格分割'hello world'

1
2
3
>>> 'hello world!'.split(' ')
['hello', 'world!']
>>>

我们还可以指定一个参数表明最大分割的次数。注意!是分割的次数,不是分割成多少个字符串!

1
2
3
>>> '1 2 3 4'.split(' ', 1)
['1', '2 3 4']
>>>

那么,我们只需要在发送消息的时候用以下格式:

1
指令 其它内容

(。・∀・)ノ゙ 然后使用split分割开来就可以了!

改动程序框架

在这里我们顺便改变一下程序的基本框架,下面说下原因。

我们每次添加命令都要改动help指令的输出(help_msg),有点麻烦,不如我们把每个指令的处理过程写成一个函数,然后在函数的开头加上字符串,这个字符串会成为函数对象(没错!函数也是一种对象)的__doc__属性,之后我们的help指令把所有处理函数的这个属性作为帮助信息发给文件助手就可以了!

对此,我干脆来了一个大“整改”,我们把每个指令的处理函数都定义在文件handlers.py中,然后main.py的程序框架就是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import itchat
import handlers # 导入我们写的模块

@itchat.msg_register('Text')
def handler(msg):
dst = msg['ToUserName']
text = msg['Text']

if dst == 'filehelper':
text = text.split(' ', 1) # 以空格分开
cmd = text[0] # 指令的名字
if hasattr(handlers, 'do_'+cmd):
func = getattr(handlers, 'do_'+cmd)
func(text)


if __name__ == '__main__':
itchat.auto_login()
itchat.run()

上面又出现了新的函数,hasattr和getattr。

hasattr用来查询某个对象是否有某个属性,第一个参数就是这个对象,这里是我们自己写的handlers模块,也就是handlers.py,模块也是对象哦!,第二个参数就是属性,这里查找的属性就是我们的处理函数,我们的处理函数统一命名为下面这种格式:

1
2
3
4
do_指令名
例:
do_help
do_logout

getattr用来获取对象的属性,利用这个函数,我们就可以获取相应的函数赋值给变量func,然后调用func(text)

与此同时,我们在handlers.py定义的help指令处理函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def do_help(arg):
'输出帮助信息'

# 这个arg参数占位用

obj = globals() # 获取全局命名空间
help_msg = '支持的指令有:\n'
for name in obj:
# 遍历所有对象
if name.startswith('do_'):
# startswith方法用于判断字符串是否以所给字符串开头

command = name.strip('do_') # 去除字符串中的'do_'
help_msg += command + ' ' + obj[name].__doc__ + '\n'

itchat.send(help_msg, 'filehelper')

上面的代码用上了两个新的字符串对象的方法,看看注释就好了。

读者们自己加上do_logout函数吧,这里就不多罗嗦了(记得导入itchat模块!!!)

最终实现

做了这么多铺垫,可能大家也忘了我们要干什么了。我们要实现执行系统命令!

现在我们只需在handlers.py文件中添加一个函数就好了:

1
2
3
4
5
6
7
8
9
10
def do_exec(arg):
'执行系统命令'
cmd = arg[1] # 命令
try:
result = subprocess.check_output(cmd, shell=True, timeout=2)
result = result.decode()
except:
result = '执行命令出错了!'

itchat.send(result, 'filehelper')

最后不要忘了在handlers.py文件中加上导入相关模块的语句


运行演示

好了,我们的项目就这样完成了,现在给出几张运行时的图片。

这是程序运行的输出画面,此时正在等待扫描二维码
等待扫码

登录成功
登录成功

此时手机发送指令
发送指令


总结

程序的完整代码上传到github上了,地址是:https://github.com/featherL/WeChatController/tree/master

这一节我们不仅把之前几节的内容综合起来,做了一个完整的项目,而且了解了很多后面探讨的内容。对于这些“超纲”内容,某些地方不太懂也没关系,等学到了的时候,再回头看,你会发现是如此的简单。

如有不懂,欢迎评论,或者邮件联系我 1343145150@qq.com