python-桌面软件自动化(一)(实战微信发消息)

什么是pywinauto

pywinauto是一组用于自动化Microsoft Windows GUI的python模块。 最简单的是,它允许您将鼠标和键盘操作发送到窗口对话框和控件。

pywinauto安装和启动

1.安装pywinauto

在 Pycharm 底部的终端(Terminal)窗口中输入

pip install pywinauto

提示success即安装成功了。

2.backend选择

我们安装好Pywinauto之后,首先要确定哪种可访问性技术(backend)可以用于我们的应用程序,在windows上受支持的有两种:
        Win32 API ( backend= "win32" ) -默认的backend
                MFC, VB6, VCL, 简单的WinForms控件和大多数旧的遗留应用程序
        MS UI Automation ( backend="uia" )
                WinForms, WPF, Store apps, Qt5, 浏览器
如果不能确定程序到底适用于那种backend,可以借助于GUI对象检查工具来查看,常用的检查工具有Inspect.exe,Spy++ 。如果使用Inspect的UIA模式,可见的控件和属性更多的话,backend可选uia,反之,backend可选win32。

3.控件查看工具-inspect

        inspect.exe下载链接:https://z4gvregzdz.feishu.cn/file/boxcnoI8GOg5aIWaYTVXL8hyWzc

        spy++下载链接:https://z4gvregzdz.feishu.cn/file/boxcnqlUR6yBhvuJsoFiUVrVGpe
        将inspect左上角的下拉列表中切换到“UI Automation”,然后鼠标点一下你需要测试的程序窗体,inspect就会显示相关信息,如下图所示。说明backend为uia
        程序里面的任意一个部位其实都是控件,在inspect的控件树中都可以找到,是一层一层分级别的,可以一个个点开所有控件

完整操作步骤

通过pywinauto操作控件需要以下几个步骤:
        第一步 :创建实例化对象,得到的app是Application对象
        第二步 :选择窗口 ,得到的窗口是WindowSpecification对象
        第三步:基于WindowSpecification对象使用其方法再往下查找,定位到具体的控件
        第四步:使用控件的方法属性执行我们需要的操作
        接下来我们先看一个实例代码,(确保电脑版微信已经登录且前台显示在桌面上)按照上面的步骤获取微信的搜索文本框并点击,代码如下:

from pywinauto import Application
# 第一步连接已有微信进程创建实例化对象
PID = 12345 #进程PID在 任务管理器-详细信息 可以查看后修改该值
微信主程序 = Application(backend='uia').connect(process=PID)
# 第二步拿到微信主窗口
主窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
# 第三步通过child_window方法查找搜索文本框
搜索 = 主窗口.child_window(control_type='Edit', title='搜索')
# 第四步进行操作点击控件
搜索.click_input()

        执行后就会自动点击微信左上角搜索文本框了(注意:由于PID是变化的,以上代码一定能执行成功后面会详细介绍)。
        接下来,我们按照上面四步逐步讲解,

1.创建实例化对象

以微信为例,这里介绍下常用的两种获取实例化程序的方式:

启动

start()用于还没有启动软件的情况。timeout为超时参数(可选),若软件启动所需的时间较长可选
timeout,默认超时时间为5s。
start(self, cmd_line, timeout=app_start_timeout)
先确定微信是关闭的,我们通过上面方法启动微信并获取对应的实例化对象,如下:

from pywinauto import Application
# 启动微信进程(注意路径中特殊字符的转义,/和\)
app = Application(backend="uia").start(r'"E:\Program Files
(x86)\Tencent\WeChat\WeChat.exe"')
print(app)

运行后将会自动启动微信。

连接

connect()用于连接已经启动的程序。connect()方式有多种:
process:进程id(PID)

app = Application().connect(process=12580)

handle:应用程序的窗口句柄

app = Application().connect(handle=0x234f1b)

path:进程的执行路径(GetModuleFileNameEx 模块会查找进程的每一个路径并与我们传入的
路径去做比较)

app = Application().connect(path=“c:\windows\system32\notepad.exe”)

参数组合(传递给pywinauto.findwindows.find_elements()这个函数)
app = Application().connect(title_re=".*Notepad", class_name=“Notepad”)
下面我们介绍下通过进程PID与窗口句柄两种链接已启动程序的方式:
PID在 任务管理器-详细信息 可以查看。如下图,WeChat.exe的PID就是18364。

from pywinauto.application import Application
# 通过PID连接已有微信进程
微信主程序 = Application(backend='uia').connect(process=18364)
print(微信主程序)

在星球中也有提到运利用第三方库psutil根据进程名获取进程PID的文章,有兴趣可以去看看,这里就不做详细介绍了。
在前面游戏自动化中学习了如何获取窗口的句柄,所以我们本着偷懒的原则还可以通过pywin32模块根据窗口名获取窗口句柄,然后获取根据句柄来获得主程序,代码如下:

import win32gui
from pywinauto.application import Application
句柄 = win32gui.FindWindow(None, '微信')
微信主程序 = Application(backend='uia').connect(handle=句柄)
print(微信主程序)

注意:应用程序必须先准备就绪,才能使用connect0)。当应用程序start()后没有超时和重连的机制,在pywinauto外启动启动应用程序,则需要睡眠或编程等待循环以等待应用程序完全启动。

实例对象app的常用方法

通过查看pywinauto的源码中application.py文件,可以看到app的所有属性方法,下面列举常用方
法:
app.top_window()
返回应用程序当前顶部窗口,是WindowSpecification对象,可以继续使用对象的方法往下继续
查找控件
如:app.top_window().child_window(title='搜索', control_type='Edit')

app.window(kwargs)
根据筛选条件,返回一个窗口, 是WindowSpecification对象,可以继续适用对象的方法往下继
续查找控件
微信主界面: app.window(class_name='WeChatMainWndForPC')

app.windows(kwargs)
根据筛选条件返回一个窗口列表,无条件默认全部,列表项为wrapped对象,可以使用wrapped
对象的方法,注意不是WindowSpecification对象

[<uiawrapper.UIAWrapper - '微信', Dialog, -5995915281609806513>]

app.kill(soft=False)
强制关闭程序

app.cpu_usage()
返回CPU使用率百分比

app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None)
等待进程CPU使用率百分比小于指定的阈值threshold

app.is64bit()
判断操作的进程是否是64-bit

简单演示下。

from pywinauto.application import Application
# 通过PID连接已有微信进程
微信主程序 = Application(backend='uia').connect(process=18364)
print(微信主程序.top_window())
print(微信主程序.window())
print(微信主程序.windows())
print(微信主程序.cpu_usage())
print(微信主程序.is64bit())

2.选择窗口

pywinauto选择窗口有三种方式,获取到的窗口为 WindowSpecification对象。

dlg = app.window(title="your title", classname="your class", ...)
dlg = app.YourDialogTitle
dlg = app['Your Dialog Title']

以微信主界面窗口为例,先通过inspect工具查看微信的窗体信息:

按照上面的方法我们有三种方式来选取窗口,代码如下:

from pywinauto.application import Application
import win32gui
# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
app = Application(backend='uia').connect(handle=句柄)
dlg = app.window(class_name='WeChatMainWndForPC')
# dlg = app.微信
# dlg = app['微信']
dlg.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
dlg.draw_outline(colour='red') # 控件外围画框,便于查看,支持'red', 'green', 'blue'

前面我们一直是手动先将微信窗口放置到前台,上面代码中我们利用restore()方法就可以自动将窗口恢复到正常大小了,利用draw outline可以将控件外围画框,非常便于调试。

窗口的常用方法

下面则是窗口平时常用的一些方法:

# 以下几个只支持窗口模式的控件
dlg.close() # 关闭界面
dlg.minimize() # 最小化界面
dlg.maximize() # 最大化界面
dlg.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
dlg.get_show_state() # 正常0,最大化1,最小化2
dlg.exists(timeout=None, retry_interval=None) # 判断是否存在
#timeout:等待时间,一般默认5s
#retry_interval:timeout内重试时间
dlg.wait(wait_for, timeout=None, retry_interval=None) # 等待窗口处于特定状态
dlg.wait_not(wait_for_not, timeout=None, retry_interval=None) # 等待窗口不处于特定状
态,即等待消失
# wait_for/wait_for_not:
# * 'exists' means that the window is a valid handle
# * 'visible' means that the window is not hidden
# * 'enabled' means that the window is not disabled
# * 'ready' means that the window is visible and enabled
# * 'active' means that the window is active
# timeout:等待多久
# retry_interval:timeout内重试时间# eg: dlg.wait('ready')

代码如下:

import time
from pywinauto.application import Application
import win32gui
# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
微信主程序 = Application(backend='uia').connect(handle=句柄)
微信窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
微信窗口.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
微信窗口.draw_outline(colour='red') # 控件外围画框,便于查看,支持'red', 'green', 'blue'
time.sleep(2)
微信窗口.maximize() # 最小化界面
print(微信窗口.get_show_state())
time.sleep(2)
微信窗口.minimize() # 最大化界面
print(微信窗口.get_show_state())
time.sleep(2)
微信窗口.restore() # 将窗口恢复为正常大小,比如最小化的让他正常显示在桌面
print(微信窗口.get_show_state())

3.定位控件

最常用查找控件方法

找到窗口后,我们就需要查找我们所需要的控件了。在pywinauto中查找控件都是通过层级查找的,我们通过inspect工具也可以看到一个窗口中所有控件都是成树状的层级结构的。
最常用的查找控件的方法就是child_window(**kwargs)

child_window(**kwargs) # 可以不管层级的找后代中某个符合条件的元素,最常用

kwargs为筛选条件,最常用的就是class_name、title和control_type。

class_name=None, # 类名,对应ClassName
title=None, # 控件的标题文字,对应inspect中Name字段
control_type=None, # 控件类型,inspect界面LocalizedControlType字段的英文名

kwargs筛选条件可以设置一个或多个。

这里有个小技巧,inspect界面LocalizedControlType字段的英文名对应其实就是ControlType字段去除 UIA_和 ControlTypeId 。
class_name在选择微信窗口时已经使用过了,这里就不再介绍了。下面介绍下另外两种筛选参数。

title参数的使用如下:

from pywinauto.application import Application
import win32gui
# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
微信主程序 = Application(backend='uia').connect(handle=句柄)
# 拿到微信主窗口
微信窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
微信窗口.restore()
搜索 = 微信窗口.child_window(title='搜索')
print(搜索.window_text())
搜索.draw_outline(colour='red')

draw outline不仅对窗口有用,对窗口中的控件也是可用的。

但是这里会有一个问题,当你的窗口中出现多个标题为 搜索 的控件时,我们上面的代码就会报错
ElementAmbiguousError,所以我们还需要添加更多的筛选条件,比如上面所说的control_type。

from pywinauto.application import Application
import win32gui
# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
微信主程序 = Application(backend='uia').connect(handle=句柄)
# 拿到微信主窗口
微信窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
微信窗口.restore()
搜索 = 微信窗口.child_window(control_type='Edit', title='搜索') # 多个筛选条件确定唯一的
控件
print(搜索.window_text())
搜索.draw_outline(colour='red)

当然也可以通过添加筛选参数found index来取多个筛选结果中的一个,使用方法如下:
found index=0,取值从0开始

from pywinauto.application import Application
import win32gui

# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
微信主程序 = Application(backend='uia').connect(handle=句柄)
# 拿到微信主窗口
微信窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
微信窗口.restore()
搜索 = 微信窗口.child_window(title='搜索', found_index=0)
print(搜索.window_text())
搜索.draw_outline(colour='red')

这里还需要注意一点,当要查找的控件不存在时,程序会报错ElementNotFoundError(kwargs)

4.操作控件

定位到控件后就是对控件进行操作了,下面介绍几种最常用的方法与属性。

控件常用的方法属性

ctrl.click_input() # 最常用的点击方法,一切点击操作的基本方法(底层调用只是参数不同),左键单
击,使用时一般都使用默认不需要带参数
# 键盘输入
ctrl.type_keys(keys, pause = None, with_spaces = False,)
# keys:要输入的文字内容
# pause:每输入一个字符后等待时间,默认0.01就行
# with_spaces:是否保留keys中的所有空格,默认去除0
# 调试经常用的属性与方法
ctrl.window_text() # 控件的标题文字,对应inspect中Name字段
ctrl.draw_outline(colour='green') # 控件外围画框,便于查看,支持'red', 'green', 'blue'

上面几种方法属性我们基本上就用到了,我们来看看输入的使用。

from pywinauto import Application
import win32gui
# 根据应用程序窗口名获得句柄
句柄 = win32gui.FindWindow(None, '微信')
# 通过句柄连接已有微信进程
微信主程序 = Application(backend='uia').connect(handle=句柄)
# 第二步拿到微信主窗口
主窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
# 第三步通过child_window方法查找搜索文本框
搜索 = 主窗口.child_window(control_type='Edit', title='搜索')
# 第四步进行操作点击控件
搜索.click_input()
搜索.type_keys("有霸夫") # 输入 有霸夫

实战 给微信好友发送消息

前面我们已经学会了pywinauto操作的基本步骤,那接下来我们进入实战。
实战要求如下:在微信搜索框中搜索 文件传输助手, 发一条 学Python,要努力
步骤拆解如下:
1.连接已有微信进程创建实例化对象
2.拿到微信主窗口
3.查找搜索文本框
4.搜索框输中入文件传输助手
5.查找到联系人中文件传输助手
6.点击有文件传输助手
7.查找消息输入框
8.点击发送按钮。
我们通过inspect可以定位到这些控件的,实现代码如下:

from pywinauto import Application, ElementNotFoundError
import win32gui
import time

联系人 = '文件传输助手'  # 可以改成联系人名称
消息 = '学Python,要努力'

try:
    句柄 = win32gui.FindWindow(None, '微信')
    if 句柄 == 0:
        raise RuntimeError("无法找到微信窗口")

    微信主程序 = Application(backend='uia').connect(handle=句柄)
    微信窗口 = 微信主程序.window(class_name='WeChatMainWndForPC')
    微信窗口.restore()

    搜索 = 微信窗口.child_window(control_type='Edit', title='搜索')
    搜索.click_input()
    搜索.type_keys(联系人)

    time.sleep(2)  # 等待搜索结果加载

    搜索结果 = 微信窗口.child_window(control_type='List', title='@str:IDS_FAV_SEARCH_RESULT:3780')
    联系人项列表 = 搜索结果.children(control_type='ListItem', title=联系人)

    if not 联系人项列表:
        raise RuntimeError("没有找到匹配的联系人项")

    # 选择第一个匹配的项
    联系人项 = 联系人项列表[0]
    联系人项.click_input()

    消息输入框 = 微信窗口.child_window(control_type='Edit', title='文件传输助手')
    消息输入框.click_input()
    消息输入框.type_keys(消息)

    发送 = 微信窗口.child_window(control_type='Button', title='发送(S)')
    发送.click_input()

except ElementNotFoundError as e:
    print(f"未找到指定的控件: {e}")
except RuntimeError as e:
    print(f"运行时错误: {e}")
except Exception as e:
    print(f"发生了一个错误: {e}")

课程总结

本节课程我们学习了inspect工具的使用,pywinauto操作控件的完整步骤。只要熟练掌握实例对象,选择窗口,定位控件,以及操作控件的相关方法,我们就已经可以开始编写一些软件自动化功能了。任何复杂的操作其实都可以化繁为简的,掌握基础,多写多练,相信同学们都能写出自己想要的功能的。


课后习题

模仿实战案例,搜索 有一个自己所在群聊 ,在群聊里发送一句 你好 的文本和一个得意 的表情。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值