文章目录
- 一.简介
- 二.wxauto提供的函数
- 1.WxUtils类功能函数:
- 2.WeChat类主要函数:
- 三.使用
- 四.遇到的问题
一.简介
wxauto是一个Python第三方库,用于自动化操作微信电脑客户端
通过wxauto,我们可以使用Python编写脚本,实现以下功能
- 获取微信好友列表、群组列表、聊天记录等信息。
- 在微信中发送文本、图片、语音等信息给好友或群组。
- 自动回复好友或群组的消息。
- 自动加入或退出群组。
- 自动发送文件给好友或群组。
- 自动发送红包给好友或群组。
- 其他自定义的自动化操作。
使用wxauto需要先安装其库文件,可以使用pip命令进行安装
pip install wxauto
二.wxauto提供的函数
wxauto目前有WxParam、WxUtils、WeChat三个类:
- 其中WxParam设置基本参数设置。
1.WxUtils类功能函数:
- SetClipboard(data, dtype=‘text’) 复制文本信息或图片到剪贴板data : 要复制的内容,str 或 Image 图像;
- Screenshot(hwnd, to_clipboard=True) 为句柄为hwnd的窗口程序截图;hwnd : 句柄;to_clipboard : 是否复制到剪贴板;
- SavePic(savepath=None, filename=None) 保存截图;savepath:文件保存位置;filename:文件名字;
- ControlSize(control) 获取控制窗口大小;
- ClipboardFormats(unit=0, *units) 获取剪切板格式 ;
- CopyDict()
2.WeChat类主要函数:
- GetSessionList(self, reset=False) 获取当前会话列表,更新会话列表
- Search(self, keyword) 查找微信好友或关键词;keywords: 要查找的关键词,最好完整匹配,不完全匹配只会选取搜索框第一个;
- ChatWith(self, who, RollTimes=None) 打开某个聊天框;who : 要打开聊天框的好友名,最好完整匹配,不完全匹配只会选取搜索框第一个;RollTimes : 默认向下滚动次数,再进行搜索;
- SendMsg(self, msg, clear=True) 向当前窗口发送消息;msg : 要发送的消息;
- SendFiles(self, *filepath, not_exists=‘ignore’) 向当前聊天窗口发送文件;not_exists: 如果未找到指定文件,继续或终止程序;*filepath: 要复制文件的绝对路径;
- SendClipboard(self) 向当前聊天页面发送剪贴板复制的内容;
- GetAllMessage(self) 获取当前窗口中加载的所有聊天记录;
- GetLastMessage(self) 获取当前窗口中最后一条聊天记录
- LoadMoreMessage(self, n=0.1) 定位到当前聊天页面,并往上滚动鼠标滚轮,加载更多聊天记录到内存发送某个桌面程序的截图,如:微信、记事本;name : 要发送的桌面程序名字;classname : 要发送的桌面程序类别名;
- SendScreenshot(self, name=None, classname=None) 发送某个桌面程序的截图,如:微信、记事本;name : 要发送的桌面程序名字;classname : 要发送的桌面程序类别名;
三.使用
from wxauto import *# 获取当前微信客户端wx = WeChat()# 获取会话列表wx.GetSessionList()# 输出当前聊天窗口聊天消息msgs = wx.GetAllMessagefor msg in msgs:print('%s : %s'%(msg[0], msg[1]))## 获取更多聊天记录wx.LoadMoreMessage()msgs = wx.GetAllMessagefor msg in msgs:print('%s : %s'%(msg[0], msg[1]))# 向某人发送消息(以`文件传输助手`为例)msg = '你好~'who = '文件传输助手'wx.ChatWith(who)# 打开`文件传输助手`聊天窗口wx.SendMsg(msg)# 向`文件传输助手`发送消息:你好~## 发送换行消息(最近很多人问换行消息如何发送,新增说明一下)msg = '''你好这是第二行这是第三行这是第四行'''who = '文件传输助手'WxUtils.SetClipboard(msg)# 将内容复制到剪贴板,类似于Ctrl + Cwx.ChatWith(who)# 打开`文件传输助手`聊天窗口wx.SendClipboard() # 发送剪贴板的内容,类似于Ctrl + V# 向某人发送文件(以`文件传输助手`为例,发送三个不同类型文件)file1 = 'D:/test/wxauto.py'file2 = 'D:/test/pic.png'file3 = 'D:/test/files.rar'who = '文件传输助手'wx.ChatWith(who)# 打开`文件传输助手`聊天窗口wx.SendFiles(file1, file2, file3)# 向`文件传输助手`发送上述三个文件# 注:为保证发送文件稳定性,首次发送文件可能花费时间较长,后续调用会缩短发送时间# 向某人发送程序截图(以`文件传输助手`为例,发送微信截图)name = '微信'classname = 'WeChatMainWndForPC'wx.ChatWith(who)# 打开`文件传输助手`聊天窗口wx.SendScreenshot(name, classname)# 发送微信窗口的截图给文件传输助手注:为保证发送文件稳定性,首次发送文件可能花费时间较长,后续调用会缩短发送时间
四.遇到的问题
由于部分版本的微信可能由于UI界面不同从而无法使用,截至2022-06-10
最新版本可用
因此在代码运行的时候,会发现
图片无法发送、消息无法发送
的报错情况LookupError: Find Control Timeout(10s): {Name: '输入', ControlType: EditControl}
,这是因为pip 下载的wxauto第三方库无法匹配微信客户端3.7的版本
。如果有遇到,可以通过Github的方式下载源码,直接修改源码
wxauto库信息:Author: tikic@qq.comSource: https://github.com/cluic/wxautoVersion: 3.3.5.3
微信最新版本需要重新适配,需修改 wxauto.py 代码
1.在 wxauto.py 的文件中找到 WeChat 的类,并添加下述方法def ChangeWindow(self, window_title):self.EditMsg = self.UiaAPI.EditControl(Name=f'{window_title}')2.之后在 ChatWith 方法中加入如下代码def ChatWith(self, who, RollTimes=None):'''打开某个聊天框who : 要打开的聊天框好友名,str;* 最好完整匹配,不完全匹配只会选取搜索框第一个RollTimes : 默认向下滚动多少次,再进行搜索'''self.UiaAPI.SwitchToThisWindow()self.ChangeWindow(who)# [2] 加入如下方法,在每次更改聊天对象时调用 ChangeWindow 方法... ...
wxauto.py完整代码
#!python3# -*- coding: utf-8 -*-"""Author: tikic@qq.comSource: https://github.com/cluic/wxautoLicense: MIT LicenseVersion: 3.9.0.28"""import uiautomation as uiaimport win32gui, win32conimport win32clipboard as wcimport timeimport osAUTHOR_EMAIL = 'tikic@qq.com'UPDATE = '2023-02-25'VERSION = '3.9.0.28'class WxParam:SYS_TEXT_HEIGHT = 33TIME_TEXT_HEIGHT = 34RECALL_TEXT_HEIGHT = 45CHAT_TEXT_HEIGHT = 52CHAT_IMG_HEIGHT = 117SpecialTypes = ['[文件]', '[图片]', '[视频]', '[音乐]', '[链接]']class WxUtils:def GetMessageInfos(Item, msglist=None):msglist = msglist if msglist is not None else list()if len(Item.GetChildren()) == 0:msglist.append(Item.Name)else:for i in Item.GetChildren():WxUtils.GetMessageInfos(i, msglist)return [i for i in msglist if i]def SplitMessage(MsgItem):uia.SetGlobalSearchTimeout(0)MessageInfos = WxUtils.GetMessageInfos(MsgItem)MsgItemName = MsgItem.Nameif MsgItem.BoundingRectangle.height() == WxParam.SYS_TEXT_HEIGHT:Msg = ('SYS', MsgItemName, MessageInfos)elif MsgItem.BoundingRectangle.height() == WxParam.TIME_TEXT_HEIGHT:Msg = ('Time', MsgItemName, MessageInfos)elif MsgItem.BoundingRectangle.height() == WxParam.RECALL_TEXT_HEIGHT:if '撤回' in MsgItemName:Msg = ('Recall', MsgItemName, MessageInfos)else:Msg = ('SYS', MsgItemName, MessageInfos)else:Index = 1User = MsgItem.ButtonControl(foundIndex=Index)try:while True:if User.Name == '':Index += 1User = MsgItem.ButtonControl(foundIndex=Index)else:breakMsg = (User.Name, MsgItemName, MessageInfos)except:Msg = ('SYS', MsgItemName, MessageInfos)uia.SetGlobalSearchTimeout(10.0)return Msgdef SetClipboard(data, dtype='text'):'''复制文本信息或图片到剪贴板data : 要复制的内容,str 或 Image 图像'''if dtype.upper() == 'TEXT':type_data = win32con.CF_UNICODETEXTelif dtype.upper() == 'IMAGE':from io import BytesIOtype_data = win32con.CF_DIBoutput = BytesIO()data.save(output, 'BMP')data = output.getvalue()[14:]else:raise ValueError('param (dtype) only "text" or "image" supported')wc.OpenClipboard()wc.EmptyClipboard()wc.SetClipboardData(type_data, data)wc.CloseClipboard()def Screenshot(hwnd, to_clipboard=True):'''为句柄为hwnd的窗口程序截图hwnd : 句柄to_clipboard : 是否复制到剪贴板'''import pyscreenshot as shotbbox = win32gui.GetWindowRect(hwnd)win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0, \win32con.SWP_SHOWWINDOW | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)win32gui.SetWindowPos(hwnd, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, \win32con.SWP_SHOWWINDOW | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)win32gui.BringWindowToTop(hwnd)im = shot.grab(bbox)if to_clipboard:WxUtils.SetClipboard(im, 'image')return imdef SavePic(savepath=None, filename=None):Pic = uia.WindowControl(ClassName='ImagePreviewWnd', Name='图片查看')Pic.SendKeys('{Ctrl}s')SaveAs = Pic.WindowControl(ClassName='#32770', Name='另存为...')SaveAsEdit = SaveAs.EditControl(ClassName='Edit', Name='文件名:')SaveButton = Pic.ButtonControl(ClassName='Button', Name='保存(S)')PicName, Ex = os.path.splitext(SaveAsEdit.GetValuePattern().Value)if not savepath:savepath = os.getcwd()if not filename:filename = PicNameFilePath = os.path.realpath(os.path.join(savepath, filename + Ex))SaveAsEdit.SendKeys(FilePath)SaveButton.Click()Pic.SendKeys('{Esc}')def ControlSize(control):locate = control.BoundingRectanglesize = (locate.width(), locate.height())return sizedef ClipboardFormats(unit=0, *units):units = list(units)wc.OpenClipboard()u = wc.EnumClipboardFormats(unit)wc.CloseClipboard()units.append(u)if u:units = WxUtils.ClipboardFormats(u, *units)return unitsdef CopyDict():Dict = {}for i in WxUtils.ClipboardFormats():if i == 0:continuewc.OpenClipboard()try:content = wc.GetClipboardData(i)wc.CloseClipboard()except:wc.CloseClipboard()raise ValueErrorif len(str(i)) >= 4:Dict[str(i)] = contentreturn Dictclass WeChat:def __init__(self):self.UiaAPI = uia.WindowControl(ClassName='WeChatMainWndForPC')self.SessionList = self.UiaAPI.ListControl(Name='会话')self.EditMsg = self.UiaAPI.EditControl(Name='输入')self.SearchBox = self.UiaAPI.EditControl(Name='搜索')self.MsgList = self.UiaAPI.ListControl(Name='消息')self.SessionItemList = []def ChangeWindow(self, window_title):self.EditMsg = self.UiaAPI.EditControl(Name=f'{window_title}')def GetSessionList(self, reset=False):'''获取当前会话列表,更新会话列表'''self.SessionItem = self.SessionList.ListItemControl()SessionList = []if reset:self.SessionItemList = []for i in range(100):try:name = self.SessionItem.Nameexcept:breakif name not in self.SessionItemList:self.SessionItemList.append(name)if name not in SessionList:SessionList.append(name)self.SessionItem = self.SessionItem.GetNextSiblingControl()return SessionListdef Search(self, keyword):'''查找微信好友或关键词keywords: 要查找的关键词,str * 最好完整匹配,不完全匹配只会选取搜索框第一个'''self.UiaAPI.SetFocus()time.sleep(0.2)self.UiaAPI.SendKeys('{Ctrl}f', waitTime=1)self.SearchBox.SendKeys(keyword, waitTime=1.5)self.SearchBox.SendKeys('{Enter}')def ChatWith(self, who, RollTimes=None):'''打开某个聊天框who : 要打开的聊天框好友名,str;* 最好完整匹配,不完全匹配只会选取搜索框第一个RollTimes : 默认向下滚动多少次,再进行搜索'''self.UiaAPI.SwitchToThisWindow()self.ChangeWindow(who)# [2] 加入如下方法,在每次更改聊天对象时调用 ChangeWindow 方法RollTimes = 10 if not RollTimes else RollTimesdef roll_to(who=who, RollTimes=RollTimes):for i in range(RollTimes):if who not in self.GetSessionList()[:-1]:self.SessionList.WheelDown(wheelTimes=3, waitTime=0.1 * i)else:time.sleep(0.5)self.SessionList.ListItemControl(Name=who).Click(simulateMove=False)return 1return 0rollresult = roll_to()if rollresult:return 1else:self.Search(who)return roll_to(RollTimes=1)def SendMsg(self, msg, clear=True):'''向当前窗口发送消息msg : 要发送的消息clear : 是否清除当前已编辑内容'''self.UiaAPI.SwitchToThisWindow()if clear:self.EditMsg.SendKeys('{Ctrl}a', waitTime=0)self.EditMsg.SendKeys(msg, waitTime=0)self.EditMsg.SendKeys('{Enter}', waitTime=0)def SendFiles(self, *filepath, not_exists='ignore'):"""向当前聊天窗口发送文件not_exists: 如果未找到指定文件,继续或终止程序filepath (list): 要复制文件的绝对路径"""key = ''for file in filepath:file = os.path.realpath(file)if not os.path.exists(file):if not_exists.upper() == 'IGNORE':print('File not exists:', file)continueelif not_exists.upper() == 'RAISE':raise FileExistsError('File Not Exists: %s' % file)else:raise ValueError('param not_exists only "ignore" or "raise" supported')key += '' % fileself.EditMsg.SendKeys(' ', waitTime=0)self.EditMsg.SendKeys('{Ctrl}a', waitTime=0)self.EditMsg.SendKeys('{Ctrl}c', waitTime=0)self.EditMsg.SendKeys('{Delete}', waitTime=0)while True:try:data = WxUtils.CopyDict()breakexcept:passfor i in data:data[i] = data[i].replace(b'', key.encode())data1 = {'13': '','16': b'\x04\x08\x00\x00','1': b'','7': b''}data.update(data1)wc.OpenClipboard()wc.EmptyClipboard()for k, v in data.items():wc.SetClipboardData(int(k), v)wc.CloseClipboard()self.SendClipboard()return 1def SendClipboard(self):'''向当前聊天页面发送剪贴板复制的内容'''self.SendMsg('{Ctrl}v')@propertydef GetAllMessage(self):'''获取当前窗口中加载的所有聊天记录'''MsgDocker = []MsgItems = self.MsgList.GetChildren()for MsgItem in MsgItems:MsgDocker.append(WxUtils.SplitMessage(MsgItem))return MsgDocker@propertydef GetLastMessage(self):'''获取当前窗口中最后一条聊天记录'''uia.SetGlobalSearchTimeout(1.0)MsgItem = self.MsgList.GetChildren()[-1]Msg = WxUtils.SplitMessage(MsgItem)uia.SetGlobalSearchTimeout(10.0)return Msgdef LoadMoreMessage(self, n=0.1):'''定位到当前聊天页面,并往上滚动鼠标滚轮,加载更多聊天记录到内存'''n = 0.1 if n < 0.1 else 1 if n > 1 else nself.MsgList.WheelUp(wheelTimes=int(500 * n), waitTime=0.1)def SendScreenshot(self, name=None, classname=None):'''发送某个桌面程序的截图,如:微信、记事本...name : 要发送的桌面程序名字,如:微信classname : 要发送的桌面程序类别名,一般配合 spy 小工具使用,以获取类名,如:微信的类名为 WeChatMainWndForPC'''if name and classname:return 0else:hwnd = win32gui.FindWindow(classname, name)if hwnd:WxUtils.Screenshot(hwnd)self.SendClipboard()return 1else:return 0
wxautoapi