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

这篇具有很好参考价值的文章主要介绍了【Python】通过第三方库wxauto自动化操作微信电脑客户端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.简介

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的方式下载源码,直接修改源码
    python 微信库,Python,python,开发语言,自动化

    wxauto库信息:
    Author: tikic@qq.com
    Source: https://github.com/cluic/wxauto
    Version: 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.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 += '<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)
        while True:
            try:
                data = WxUtils.CopyDict()
                break
            except:
                pass

        for 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()
        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

wxautoapi文章来源地址https://www.toymoban.com/news/detail-650070.html

到了这里,关于【Python】通过第三方库wxauto自动化操作微信电脑客户端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Xcode通过Add package自动集成第三方SDK问题汇总

    问题1:  解决方法:这个问题可能是因为 Adjust 或者 Facebook 的库当中依赖的某些类库的仓库地址是 git:// 协议,通过这种协议与 GitHub 通讯时会使用到你的 SSH 配置,你电脑上相关的 ssh key 使用了 GitHub 不再支持的格式,请参考提示中的网址重新生成相关的 SSH key:https://githu

    2024年02月13日
    浏览(52)
  • Argis通过Python的Arcpy第三方库进行字段计算、批量将mxd导出为jpg图片、合并数据库

    前言   近来公司有开发Arcgis脚本工具的需求,我就去学了一下用Arcpy来操作Arcgis的数据,今天学习了字段计算,将学习成果记录如下。   arcpy帮助文档传送门:https://resources.arcgis.com/zh-cn/help/main/10.2/ 一、字段计算 1、以python函数的形式进行字段计算   我们打开字段计算

    2024年02月11日
    浏览(41)
  • 通过第三方软件修改 MacOS 的键盘映射

    由于文本编辑时大量使用word level的左移、右移,其中: OSX的单词级左右移为option + Left/Right Arrow,整行级左右移为command + Left/Right Arrow 单词级移动与进行编辑常用的command不同键位,因此尝试交换二者功能 由于 command 键在OSX下的其他重要作用,不方便直接将 option 与 command 进行

    2024年02月09日
    浏览(64)
  • Android开发:通过Tesseract第三方库实现OCR

    一、引言         什么是 OCR ?OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程。简单地说,OCR是一种技术,该项技术采用光学的

    2024年02月16日
    浏览(68)
  • Python第三方库安装教程、什么是第三方库

    Python有一个全球社区:https://pypi.org/,在这里我们可以搜索任何主题的Python第三方库。PyPI全称是Python Package Index,指的是Python包的索引,它由PSF(Python Software Foundation)来维护,并且展示全球Python计算生态。 我们需要学会利用PyPI的主站检索,找到我们使用和关心的Python第三方

    2024年02月03日
    浏览(108)
  • SAP PO 接口配置1:连通WebService-通过PO调用第三方接口

    SAP 通过 PO 中间件进行接口调用,调用外部接口。 外部接口可以用任意方式生成,常见的REST类型接口即可,关于如何使用python生成接口,其他章节另述。 本教程的前置条件,PO中已配置Business Systems,并与SAP环境连通。 这里以常见的post接口做示例,如有其他类型接口,需要每

    2024年02月05日
    浏览(104)
  • Python第三方库安装——使用vscode、pycharm安装Python第三方库

    在这里介绍vscode、Pycharm安装python第三方库的方法。 操作系统:windows10 专业版 环境如下: Pycharm Comunity 2022.3 Visual Studio Code 2019 Python 3.8 pip:23.0.1 pycharm是一款很强大的、专用于写python的ide。 小白式安装第三方库往往能给初学者一种 “高级感” ,而对于使用惯了Linux的人而言

    2024年02月03日
    浏览(78)
  • Python第三方库批量下载到本地,并离线批量安装第三方库

    鉴于公司内网安装的python版本为python3.6.5,而此时又需要安装第三方库pytest,本来是想直接在Python官网PyPI直接搜对应可匹配跑python3.6.5版本的pytest进行下载然后传到内网安装即可,但是发现pytest依赖别的第三方库,根据报错装了几个依赖的第三方库之后,发现还是一堆的问题

    2024年02月07日
    浏览(94)
  • 138. 第三方系统或者工具通过 HTTP 请求发送给 ABAP 系统的数据,应该如何解析

    本教程第 37 篇文章,我们介绍了如何在 SAP ABAP 系统 SICF 事务码 里,开发一段 ABAP 代码,用来响应通过浏览器或者第三方工具,比如 curl,Postman 发起的 HTTP 请求。 31. 如何让 ABAP 服务器能够响应通过浏览器发起的自定义 HTTP 请求 在实际的 ABAP 集成项目中,这种方式非常使用。

    2024年03月21日
    浏览(58)
  • SpringBoot 自动扫描第三方包及spring.factories失效的问题

    Spring 依赖注入 就是要让spring找到要注入的类 并且识别到了 @Component、@Service 等注解。 1. 当在开发的第三方包里写明了 @Component、@Service 等等 2. 引入了包,不论第三方库的引入,还是本地jar。总之是要引入到工程的 这时候还加入不到 IOC 容器,那就说明SpringBoot工程没有扫描到

    2024年02月03日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包