【Python】通过第三方库wxauto自动化操作微信电脑客户端
短信预约 -IT技能 免费直播动态提醒
文章目录
一.简介
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 = 33 TIME_TEXT_HEIGHT = 34 RECALL_TEXT_HEIGHT = 45 CHAT_TEXT_HEIGHT = 52 CHAT_IMG_HEIGHT = 117 SpecialTypes = ['[文件]', '[图片]', '[视频]', '[音乐]', '[链接]']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.Name if 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 = 1 User = MsgItem.ButtonControl(foundIndex=Index) try: while True: if User.Name == '': Index += 1 User = MsgItem.ButtonControl(foundIndex=Index) else: break Msg = (User.Name, MsgItemName, MessageInfos) except: Msg = ('SYS', MsgItemName, MessageInfos) uia.SetGlobalSearchTimeout(10.0) return Msg def SetClipboard(data, dtype='text'): '''复制文本信息或图片到剪贴板 data : 要复制的内容,str 或 Image 图像''' if dtype.upper() == 'TEXT': type_data = win32con.CF_UNICODETEXT elif dtype.upper() == 'IMAGE': from io import BytesIO type_data = win32con.CF_DIB output = 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 shot bbox = 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 im def 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 = PicName FilePath = os.path.realpath(os.path.join(savepath, filename + Ex)) SaveAsEdit.SendKeys(FilePath) SaveButton.Click() Pic.SendKeys('{Esc}') def ControlSize(control): locate = control.BoundingRectangle size = (locate.width(), locate.height()) return size def 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 units def CopyDict(): Dict = {} for i in WxUtils.ClipboardFormats(): if i == 0: continue wc.OpenClipboard() try: content = wc.GetClipboardData(i) wc.CloseClipboard() except: wc.CloseClipboard() raise ValueError if len(str(i)) >= 4: Dict[str(i)] = content return 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.Name except: break if name not in self.SessionItemList: self.SessionItemList.append(name) if name not in SessionList: SessionList.append(name) self.SessionItem = self.SessionItem.GetNextSiblingControl() return SessionList def 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 RollTimes def 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 1 return 0 rollresult = roll_to() if rollresult: return 1 else: 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) continue elif not_exists.upper() == 'RAISE': raise FileExistsError('File Not Exists: %s' % file) else: raise ValueError('param not_exists only "ignore" or "raise" supported') key += ' ' % file self.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() break except: pass for 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 1 def SendClipboard(self): '''向当前聊天页面发送剪贴板复制的内容''' self.SendMsg('{Ctrl}v') @property def GetAllMessage(self): '''获取当前窗口中加载的所有聊天记录''' MsgDocker = [] MsgItems = self.MsgList.GetChildren() for MsgItem in MsgItems: MsgDocker.append(WxUtils.SplitMessage(MsgItem)) return MsgDocker @property def GetLastMessage(self): '''获取当前窗口中最后一条聊天记录''' uia.SetGlobalSearchTimeout(1.0) MsgItem = self.MsgList.GetChildren()[-1] Msg = WxUtils.SplitMessage(MsgItem) uia.SetGlobalSearchTimeout(10.0) return Msg def LoadMoreMessage(self, n=0.1): '''定位到当前聊天页面,并往上滚动鼠标滚轮,加载更多聊天记录到内存''' n = 0.1 if n < 0.1 else 1 if n > 1 else n self.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 0 else: hwnd = win32gui.FindWindow(classname, name) if hwnd: WxUtils.Screenshot(hwnd) self.SendClipboard() return 1 else: return 0
来源地址:https://blog.csdn.net/qq877728715/article/details/131792307
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341