您现在的位置是:首页 > 技术教程 正文

【Python】通过第三方库wxauto自动化操作微信电脑客户端

admin 阅读: 2024-03-21
后台-插件-广告管理-内容页头部广告(手机)

文章目录

  • 一.简介
  • 二.wxauto提供的函数
    • 1.WxUtils类功能函数:
    • 2.WeChat类主要函数:
  • 三.使用
  • 四.遇到的问题

一.简介

wxauto是一个Python第三方库,用于自动化操作微信电脑客户端通过wxauto,我们可以使用Python编写脚本,实现以下功能

  • 获取微信好友列表、群组列表、聊天记录等信息。
  • 在微信中发送文本、图片、语音等信息给好友或群组。
  • 自动回复好友或群组的消息。
  • 自动加入或退出群组。
  • 自动发送文件给好友或群组。
  • 自动发送红包给好友或群组。
  • 其他自定义的自动化操作。

使用wxauto需要先安装其库文件,可以使用pip命令进行安装

pip install wxauto
  • 1

二.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.GetAllMessage for msg in msgs: print('%s : %s'%(msg[0], msg[1])) ## 获取更多聊天记录 wx.LoadMoreMessage() msgs = wx.GetAllMessage for msg in msgs: print('%s : %s'%(msg[0], msg[1])) # 向某人发送消息(以`文件传输助手`为例) msg = '你好~' who = '文件传输助手' wx.ChatWith(who) # 打开`文件传输助手`聊天窗口 wx.SendMsg(msg) # 向`文件传输助手`发送消息:你好~ ## 发送换行消息(最近很多人问换行消息如何发送,新增说明一下) msg = '''你好 这是第二行 这是第三行 这是第四行''' who = '文件传输助手' WxUtils.SetClipboard(msg) # 将内容复制到剪贴板,类似于Ctrl + C wx.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) # 发送微信窗口的截图给文件传输助手 注:为保证发送文件稳定性,首次发送文件可能花费时间较长,后续调用会缩短发送时间
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

四.遇到的问题

由于部分版本的微信可能由于UI界面不同从而无法使用,截至2022-06-10最新版本可用

  • 因此在代码运行的时候,会发现图片无法发送、消息无法发送的报错情况 LookupError: Find Control Timeout(10s): {Name: '输入', ControlType: EditControl},这是因为pip 下载的wxauto第三方库无法匹配微信客户端3.7的版本。

  • 如果有遇到,可以通过Github的方式下载源码,直接修改源码
    在这里插入图片描述

    wxauto库信息: Author: tikic@qq.com Source: https://github.com/cluic/wxauto Version: 3.3.5.3
    • 1
    • 2
    • 3
    • 4

微信最新版本需要重新适配,需修改 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 方法 ... ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

wxauto.py完整代码

#!python3 # -*- coding: utf-8 -*- """ Author: tikic@qq.com Source: https://github.com/cluic/wxauto License: MIT License Version: 3.9.0.28 """ import uiautomation as uia import win32gui, win32con import win32clipboard as wc import time import os AUTHOR_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 Dict class 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317

wxautoapi

标签:
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

在线投稿:投稿 站长QQ:1888636

后台-插件-广告管理-内容页尾部广告(手机)
关注我们

扫一扫关注我们,了解最新精彩内容

搜索
排行榜