文章目录
一.简介
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.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)# 发送微信窗口的截图给文件传输助手
注:为保证发送文件稳定性,首次发送文件可能花费时间较长,后续调用会缩短发送时间
四.遇到的问题
**由于部分版本的微信可能由于UI界面不同从而无法使用,截至
2022-06-10
最新版本可用**
- 因此在代码运行的时候,会发现
图片无法发送、消息无法发送
的报错情况LookupError: Find Control Timeout(10s): {Name: '输入', ControlType: EditControl}
,这是因为pip 下载的wxauto第三方库无法匹配微信客户端3.7的版本
。 - 如果有遇到,可以通过Github的方式下载源码,直接修改源码
wxauto库信息:Author: [email protected]: https://github.com/cluic/wxautoVersion:3.3.5.3
微信最新版本需要重新适配,需修改 wxauto.py 代码
1.在 wxauto.py 的文件中找到 WeChat 的类,并添加下述方法
defChangeWindow(self, window_title):
self.EditMsg = self.UiaAPI.EditControl(Name=f'{window_title}')2.之后在 ChatWith 方法中加入如下代码
defChatWith(self, who, RollTimes=None):'''
打开某个聊天框
who : 要打开的聊天框好友名,str; * 最好完整匹配,不完全匹配只会选取搜索框第一个
RollTimes : 默认向下滚动多少次,再进行搜索
'''
self.UiaAPI.SwitchToThisWindow()
self.ChangeWindow(who)# [2] 加入如下方法,在每次更改聊天对象时调用 ChangeWindow 方法......
wxauto.py完整代码
#!python3# -*- coding: utf-8 -*-"""
Author: [email protected]
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 ='[email protected]'
UPDATE ='2023-02-25'
VERSION ='3.9.0.28'classWxParam:
SYS_TEXT_HEIGHT =33
TIME_TEXT_HEIGHT =34
RECALL_TEXT_HEIGHT =45
CHAT_TEXT_HEIGHT =52
CHAT_IMG_HEIGHT =117
SpecialTypes =['[文件]','[图片]','[视频]','[音乐]','[链接]']classWxUtils:defGetMessageInfos(Item, msglist=None):
msglist = msglist if msglist isnotNoneelselist()iflen(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]defSplitMessage(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:whileTrue: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
defSetClipboard(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()defScreenshot(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
defSavePic(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)ifnot savepath:
savepath = os.getcwd()ifnot filename:
filename = PicName
FilePath = os.path.realpath(os.path.join(savepath, filename + Ex))
SaveAsEdit.SendKeys(FilePath)
SaveButton.Click()
Pic.SendKeys('{Esc}')defControlSize(control):
locate = control.BoundingRectangle
size =(locate.width(), locate.height())return size
defClipboardFormats(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
defCopyDict():
Dict ={}for i in WxUtils.ClipboardFormats():if i ==0:continue
wc.OpenClipboard()try:
content = wc.GetClipboardData(i)
wc.CloseClipboard()except:
wc.CloseClipboard()raise ValueError
iflen(str(i))>=4:
Dict[str(i)]= content
return Dict
classWeChat: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 =[]defChangeWindow(self, window_title):
self.EditMsg = self.UiaAPI.EditControl(Name=f'{window_title}')defGetSessionList(self, reset=False):'''获取当前会话列表,更新会话列表'''
self.SessionItem = self.SessionList.ListItemControl()
SessionList =[]if reset:
self.SessionItemList =[]for i inrange(100):try:
name = self.SessionItem.Name
except:breakif name notin self.SessionItemList:
self.SessionItemList.append(name)if name notin SessionList:
SessionList.append(name)
self.SessionItem = self.SessionItem.GetNextSiblingControl()return SessionList
defSearch(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}')defChatWith(self, who, RollTimes=None):'''
打开某个聊天框
who : 要打开的聊天框好友名,str; * 最好完整匹配,不完全匹配只会选取搜索框第一个
RollTimes : 默认向下滚动多少次,再进行搜索
'''
self.UiaAPI.SwitchToThisWindow()
self.ChangeWindow(who)# [2] 加入如下方法,在每次更改聊天对象时调用 ChangeWindow 方法
RollTimes =10ifnot RollTimes else RollTimes
defroll_to(who=who, RollTimes=RollTimes):for i inrange(RollTimes):if who notin 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)return1return0
rollresult = roll_to()if rollresult:return1else:
self.Search(who)return roll_to(RollTimes=1)defSendMsg(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)defSendFiles(self,*filepath, not_exists='ignore'):"""向当前聊天窗口发送文件
not_exists: 如果未找到指定文件,继续或终止程序
filepath (list): 要复制文件的绝对路径"""
key =''forfilein filepath:file= os.path.realpath(file)ifnot 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 +='<EditElement type="3" filepath="%s" shortcut="" />'%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)whileTrue:try:
data = WxUtils.CopyDict()breakexcept:passfor i in data:
data[i]= data[i].replace(b'<EditElement type="0"><![CDATA[ ]]></EditElement>', 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()return1defSendClipboard(self):'''向当前聊天页面发送剪贴板复制的内容'''
self.SendMsg('{Ctrl}v')@propertydefGetAllMessage(self):'''获取当前窗口中加载的所有聊天记录'''
MsgDocker =[]
MsgItems = self.MsgList.GetChildren()for MsgItem in MsgItems:
MsgDocker.append(WxUtils.SplitMessage(MsgItem))return MsgDocker
@propertydefGetLastMessage(self):'''获取当前窗口中最后一条聊天记录'''
uia.SetGlobalSearchTimeout(1.0)
MsgItem = self.MsgList.GetChildren()[-1]
Msg = WxUtils.SplitMessage(MsgItem)
uia.SetGlobalSearchTimeout(10.0)return Msg
defLoadMoreMessage(self, n=0.1):'''定位到当前聊天页面,并往上滚动鼠标滚轮,加载更多聊天记录到内存'''
n =0.1if n <0.1else1if n >1else n
self.MsgList.WheelUp(wheelTimes=int(500* n), waitTime=0.1)defSendScreenshot(self, name=None, classname=None):'''发送某个桌面程序的截图,如:微信、记事本...
name : 要发送的桌面程序名字,如:微信
classname : 要发送的桌面程序类别名,一般配合 spy 小工具使用,以获取类名,如:微信的类名为 WeChatMainWndForPC'''if name and classname:return0else:
hwnd = win32gui.FindWindow(classname, name)if hwnd:
WxUtils.Screenshot(hwnd)
self.SendClipboard()return1else:return0
wxautoapi
版权归原作者 墩墩分墩 所有, 如有侵权,请联系我们删除。