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可以将控件外围画框,非常便于调试。

窗口的常用方法

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

python 复制代码
# 以下几个只支持窗口模式的控件
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')

代码如下:

python 复制代码
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可以定位到这些控件的,实现代码如下:

python 复制代码
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操作控件的完整步骤。只要熟练掌握实例对象,选择窗口,定位控件,以及操作控件的相关方法,我们就已经可以开始编写一些软件自动化功能了。任何复杂的操作其实都可以化繁为简的,掌握基础,多写多练,相信同学们都能写出自己想要的功能的。

课后习题

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

相关推荐
_.Switch3 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_850410833 小时前
文件系统和日志管理
linux·运维·服务器
JokerSZ.3 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
芯盾时代4 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
心灵彼岸-诗和远方5 小时前
DevOps业务价值流:架构设计最佳实践
运维·产品经理·devops
一只哒布刘5 小时前
NFS服务器
运维·服务器
苹果醋36 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
二十雨辰6 小时前
[linux]docker基础
linux·运维·docker
Jason-河山7 小时前
【自动化更新,让商品信息跳舞】——利用API返回值的幽默编程之旅
运维·自动化
lihuhelihu7 小时前
第3章 CentOS系统管理
linux·运维·服务器·计算机网络·ubuntu·centos·云计算