当前位置:   article > 正文

python操纵windows自动化-pywinauto_pywinauto 窗口置顶

pywinauto 窗口置顶

想在Windows系统里实现一些程序的点击操作

如果用类似按键精灵的包是pyautogui,也可以针对一个图片进行点击,但都不是很精准;
通过pywinauto可以智能找到相关程序、按钮和菜单,实现启动、关闭、点击,输入文字等功能。

一、pywinauto

特点:通过进程、菜单、按钮等直接控制

(一)入门

1. 安装

安装的时候,遇到了一些插件需要再装
ModuleNotFoundError: No module named ‘win32api’
提示错误,需要安装pypiwin32这个插件:
pip install pypiwin32
又提示错误
ModuleNotFoundError: No module named ‘six’
装完提示,需要comtypes
完成安装

2. 判断程序类型backend

程序的backend有如下两种:

  • win32:MFC, VB6, VCL, simple WinForms,旧应用
    探测方式:Spy++
  • UIA,WinForms, WPF, Store apps, Qt5, browsers,很多都是这类
    探测方式:系统自带工具inspect,目前已升级为Accessibility Insights
    官网下载https://accessibilityinsights.io/

还有一种py_inspect支持以上两种类型

只有当以上工具都探测不到的时候,再考虑用鼠标和键盘

3. 建立入口

  • Application 可以在一个程序的多个实例间切换,每个实例都是一个进程
  • Desktop组件,利用suprocess.Popen 单个程序实例由多进程组成,例如win10的计算器
from pywinauto.application import Application
app = Application(backend="uia").start('notepad.exe')

# describe the window inside Notepad.exe process
dlg_spec = app.UntitledNotepad
# wait till the window is really open
actionable_dlg = dlg_spec.wait('visible')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
from subprocess import Popen
from pywinauto import Desktop

Popen('calc.exe', shell=True)
dlg = Desktop(backend="uia").Calculator
dlg.wait('visible')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

最终都是timeout,可能我的电脑没有计算器

这两个都是特定的程序类型,后面不用使用具体程序名称。

4. windows窗口规格

>>> dlg_spec = app.window(title='Untitled - Notepad')

>>> dlg_spec
<pywinauto.application.WindowSpecification object at 0x0568B790>

>>> dlg_spec.wrapper_object()
<pywinauto.controls.win32_controls.DialogWrapper object at 0x05639B70>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

窗口规则是给后续高级操作用的,有更细节的操作,或在程序还没开启时用
其中wrapper是包装器,我运行报错

另外以下两个语言是一样的效果,相当于是简化了语言

dlg_spec.wrapper_object().minimize() # while debugging
dlg_spec.minimize() # in production
  • 1
  • 2
# can be multi-level
app.window(title_re='.* - Notepad$').window(class_name='Edit')

# can combine criteria 组合条件
dlg = Desktop(backend="uia").Calculator
dlg.window(auto_id='num8Button', control_type='Button')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5. 属性解析魔术

空格、逗号、大小写,都可以通过最佳匹配来查找

app.UntitledNotepad
# is equivalent to
app.window(best_match='UntitledNotepad')

一些unicode和特色字符,可以像字典一样

app['Untitled - Notepad']
# is the same as
app.window(best_match='Untitled - Notepad')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

6. 如何知道魔术的属性名称

  • By title (window text, name) 按标题
app.Properties.OK.click()
  • 1
  • By title and control type 按标题和控制类型
app.Properties.OKButton.click()
  • 1
  • By control type and number 按控制类型和编号
app.Properties.Button3.click() 
  • 1

(Button0和Button1是同一个按钮,Button2才是下一个)

  • By top-left label and control type 左上标签和控制类型
app.OpenDialog.FileNameEdit.set_text("")
  • 1
  • By control type and item text 控制类型和项目文本
    app.Properties.TabControlSharing.select(“General”)

不是所有同时可用,针对特定的对话框,可使用以下命令检查名称,通过最佳匹配名称,列出所有的子项:

app.UntitledNotepad.print_control_identifiers()
Control Identifiers:
Notepad - '无标题 - 记事本'    (L251, T293, R642, B681)
['无标题 - 记事本Notepad', 'Notepad', '无标题 - 记事本']
child_window(title="无标题 - 记事本", class_name="Notepad")
   | 
   | Edit - ''    (L259, T344, R634, B651)
   | ['Edit', '无标题 - 记事本Edit']
   | child_window(class_name="Edit")
   | 
   | StatusBar - ''    (L259, T651, R634, B673)
   | ['StatusBar 100%', 'StatusBar', 'StatusBar UTF-8', 'StatusBar Windows (CRLF)', '无标题 - 记事本StatusBar', 'StatusBar  第 1 行,第 1 列']
   | child_window(class_name="msctls_statusbar32")
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述
如果还要进一步看细节,可以用以下命令(未测试):

app.Properties.child_window(title="Contains:", auto_id="13087", control_type="Edit")
  • 1

7. 综合应用

  1. 引入包,建实例,打开app记事本(其他应用暂时没试过)
from pywinauto import application
app = application.Application()
app.start("Notepad.exe")
<pywinauto.application.Application object at 0x0000017B785502B0>
app.UntitledNotepad.draw_outline()
  • 1
  • 2
  • 3
  • 4
  • 5

注意这里app就是应用,并不能直接赋值为start,否则后面的属性就不太好调用了

  1. 画框
    draw_outline是对该应用画框
    在这里插入图片描述
  2. 点击菜单,弹出替换框
    当按说明书里写英文时,会提示在字典里没找到,字典有什么内容,然后替换为中文即可
app.UntitledNotepad.menu_select("Edit -> Replace")
app.UntitledNotepad.menu_select("编辑(&E) -> 替换(&R)")
  • 1
  • 2

相当于按了它的菜单-编辑-替换按钮
在这里插入图片描述

在这里插入图片描述

  1. 打印控制信息
    上一步选择的”替换”对话框,用打印控制信息,可以列出所有的控件,原文是replace,在测试错误后提示应该用“替换”,更换后成功得到以下内容:
app.替换.print_control_identifiers()
Control Identifiers:
Dialog - '替换'    (L322, T438, R741, B677)
['替换', 'Dialog', '替换Dialog']
child_window(title="替换", class_name="#32770")
   | 
   | Static - '查找内容(&N):'    (L337, T488, R421, B505)
   | ['Static', '查找内容(&N):', '查找内容(&N):Static', 'Static0', 'Static1']
   | child_window(title="查找内容(&N):", class_name="Static")
   | 
   | Edit - '张'    (L425, T484, R625, B510)
   | ['Edit', '查找内容(&N):Edit', 'Edit0', 'Edit1']
   | child_window(title="张", class_name="Edit")
   | 
   | Static - '替换为(&P):'    (L337, T524, R421, B541)
   | ['Static2', '替换为(&P):', '替换为(&P):Static']
   | child_window(title="替换为(&P):", class_name="Static")
   | 
   | Edit - ''    (L425, T520, R625, B546)
   | ['Edit2', '替换为(&P):Edit']
   | child_window(class_name="Edit")
   | 
   | CheckBox - '全字匹配(&W)'    (L337, T567, R519, B593)
   | ['CheckBox', '全字匹配(&W)CheckBox', '全字匹配(&W)', 'CheckBox0', 'CheckBox1']
   | child_window(title="全字匹配(&W)", class_name="Button")
   | 
   | CheckBox - '区分大小写(&C)'    (L337, T601, R440, B627)
   | ['区分大小写(&C)', 'CheckBox2', '区分大小写(&C)CheckBox']
   | child_window(title="区分大小写(&C)", class_name="Button")
   | 
   | CheckBox - '循环(&R)'    (L337, T635, R449, B661)
   | ['循环(&R)CheckBox', '循环(&R)', 'CheckBox3']
   | child_window(title="循环(&R)", class_name="Button")
   | 
   | Button - '查找下一个(&F)'    (L635, T478, R723, B508)
   | ['查找下一个(&F)', '查找下一个(&F)Button', 'Button', 'Button0', 'Button1']
   | child_window(title="查找下一个(&F)", class_name="Button")
   | 
   | Button - '替换(&R)'    (L635, T514, R723, B544)
   | ['替换(&R)Button', '替换(&R)', 'Button2']
   | child_window(title="替换(&R)", class_name="Button")
   | 
   | Button - '全部替换(&A)'    (L635, T550, R723, B580)
   | ['全部替换(&A)', '全部替换(&A)Button', 'Button3']
   | child_window(title="全部替换(&A)", class_name="Button")
   | 
   | Button - '取消'    (L635, T586, R723, B616)
   | ['Button4', '取消Button', '取消']
   | child_window(title="取消", class_name="Button")
   | 
   | Button - '帮助(&H)'    (L635, T628, R723, B658)
   | ['帮助(&H)', '帮助(&H)Button', 'Button5']
   | child_window(title="帮助(&H)", class_name="Button")
   | 
   | Static - ''    (L337, T631, R339, B633)
   | ['Static3', '区分大小写(&C)Static']
   | child_window(class_name="Static")
  • 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

对显示的控件信息,可以用以下方式去选择,例如查找内容的输入框。
最后一个是中文版的
app.Replace.Edit
app.Replace.Edit0
app.Replace.Edit1
app.FindwhatEdit
app.查找内容Edit

  1. 关闭弹窗
    方法1,点击取消键
    app.Replace.Cancel.click()
    方法2,按关闭按钮,比较稳妥,因为稍微耗时多一点
    close_click()

  2. 输入文字
    app.UntitledNotepad.Edit.type_keys(“Hi from Python interactive prompt %s” % str(dir()), with_spaces = True)
    在这里插入图片描述
    无标题记事本,英文UntitledNotepad可以用,中文也可以,效果一样
    输入%s占位符,后面字符串是dos命令文件夹列表dir
    可以选择空格是否需要,true、0/1都可以表示

  3. 选择菜单:文件-退出;点击:不保存
    app.UntitledNotepad.menu_select(“文件 -> 退出”)
    app.记事本.不保存.click()

在这里插入图片描述

(二)正式实施

  1. 定义

dialog对话框包括window主窗口和顶部的消息框
control控件是GUI图形化界面的任一级别的元素

Win32的API为每个控件提供了标识符,handle整数
UI自动化API,handle句柄可能是隐藏的,需要用inspect.exe来查找

  1. 连接应用实例
    (1)启动应用
start(self, cmd_line, timeout=app_start_timeout)  # instance method:
app = Application().start(r"c:\path\to\your\application -a -n -y --arguments")
  • 1
  • 2

包含等待启动的时间,为可选项

(2)连接应用

连接方式有进程号、句柄号(每次启动随机)、路径、标题和类名,其中.*是部分内容模糊查询

connect(self, **kwargs)  # instance method:
app = Application().connect(process=2341)
app = Application().connect(handle=0x010f0c)
app = Application().connect(path=r"c:\windows\system32\notepad.exe")
app = Application().connect(title_re=".*Notepad", class_name="Notepad")
  • 1
  • 2
  • 3
  • 4
  • 5

如果应用不在这个程序里启动,需要进行连接,这里没有指定等待时间,需要单独写该命令

  1. 确定应用对话框dialog

前两个一样,第三个是置顶窗口,第四个可以指定具体的名称

dlg = app.Notepad
dlg = app['Notepad']
dlg = app.top_window()
dlg = app.window(title_re="Page Setup", class_name="#32770")
  • 1
  • 2
  • 3
  • 4

查看所有窗口,或指定句柄、过长的名称可用.*模糊查询

dialogs = app.windows()
app.window(handle=win)
app.window(title_re=".*Part of Title.*")
  • 1
  • 2
  • 3
  1. 确定对话框上面的控件

第二种适合中文名称

app.dlg.control
app['dlg']['control']
app.YourDialog.print_control_identifiers()
  • 1
  • 2
  • 3

也可以点击右下角系统托盘(通知区域)按钮

  1. 控件的操作方法
    很多,详见
    https://pywinauto.readthedocs.io/en/latest/controls_overview.html

二、打包成独立app

利用pyinstaller
需要安装pywin32-ctypes、altgraph、 pefile>=2017.8.1、 pyinstaller-hooks-contrib>=2020.6,
安装命令是pip install pywin32-ctypes(据说10月后pip会变化)

将刚才做的.py文件,放在同文件夹里,运行下面的语言,生成exe文件
python pyinstaller.py -F auto1.py

三、远程控制和发送指令

pywinauto官网列举了一些可以直接启动的工具,还没有测试
其他地方有socket感觉也不太好

目前想到的是利用自带的winrm程序,执行cmd命令,运行刚才的app,可能会遇到桌面是否在用的情况。

需要安装pywimrm
安装过程出现了一点问题,补了一个插件后才装好,pip install chardet
pip install pywinrm2-0.0.0-py2.py3-none-any.whl
安装这种whl文件,需要cd到目录,且不能改变文件名

如果在没有pycharm的电脑里装,会提示缺少xmltodic、requests、urllib等

1. 配置被控端,开启winrm

  • 检查winrm service开启状态,默认未启用,无返回信息:winrm enumerate winrm/config/listener
  • 针对winrm service 进行基础配置:winrm quickconfig
  • 查看winrm service listener:winrm e winrm/config/listener
  • 为winrm service 配置auth:winrm set winrm/config/service/auth @{Basic=“true”}
  • 为winrm service 配置加密方式为允许非加密:winrm set winrm/config/service @{AllowUnencrypted=“true”}

2.运行远程命令

import winrm
# r = s.run_cmd('cd /d d: & dir')
# 链接服务器
s = winrm.Session('http://xx.xx.xx.xx:5985/wsman', auth=('username', 'password')) 
# 查看备份是否OK
def shg_erp():
    r = s.run_cmd('if exist "D:\ERPbeifen\BSERP3_backup_'
                  '%date:~0,4%_%date:~5,2%_%date:~8,2%*.bak"'
                  ' (echo 1) else (echo 0)')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

————————————————————————————————————————————————————————————————————————————————
以下是春节写的比较老了,暂时不用看


一、pyautogui

特点:图片识别,鼠标和键盘按钮

工作中有很多重复的操作,有些可以固化成程序来完成:
有个软件因为没有激活,会定期(5分钟左右)触发关闭按钮,导致程序无法使用。如果靠人工一直去点击也是不现实的。

1:点击屏幕某个位置的点

之前看过王者荣耀模拟器点击金币教学,固定位置的做法,虽然简单,但需要保证这个点位置在某个时间是确定的。暂时不考虑。

2:点击屏幕里与截图相同的地方

如果用原生语言,就连左右键、键盘输入转换码,这些基本功能都非常复杂,后来发现已融合到这个pyautogui库里:

pip install pyautogui
  • 1

自动安装这个坑很多,还是去PyPI里下载好安装,有多个文件格式,安装在英文目录里,cmd运行即可;安的时候提示缺文件,还是再下载装了。

想要点击的东西,例如chrome图标,将其截图,保存在py文件目录。一定要用小写的png,今天被这个代码原文章坑了,它居然用大写

pyautogui的常用函数:

  1. locateCenterOnScreen,第一个参数是所需的图标截图,;第二个参数confidence是相似率,默认是1没有结果,太低的话返回会太多;
    输出结果传给单个变量的话,是Point格式,可用Click和doubleClick进行单击和双击。
import pyautogui
x = pyautogui.locateCenterOnScreen('chrome.png', confidence=0.9)
print(x)
pyautogui.doubleClick(x)
  • 1
  • 2
  • 3
  • 4

输出结果

Point(x=262, y=465)
  • 1
  1. locateAllOnScreen,上面那个只返回一个结果的中心值,如果用All会返回所有,利用confidence控制所需的数量,for循环来获取每个结果,左上宽高四个值,通过.center获得中间值,即x和y坐标,除了像上面那种给一个值,还可以给x,y两个值
    click点击鼠标里传递这两个值,并有点击次数和左右键的选项
for pos in pyautogui.locateAllOnScreen('chrome.png', confidence=0.95):
    print(pos)
    x, y = pyautogui.center(pos)
    print('center()', x, y)
    pyautogui.click(x=x,y=y,clicks=1,button='left')
  • 1
  • 2
  • 3
  • 4
  • 5

输出结果

Point(x=262, y=465)
Box(left=237, top=438, width=50, height=54)
center() 262 465
Box(left=237, top=439, width=50, height=54)
center() 262 466
Box(left=237, top=440, width=50, height=54)
center() 262 467
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

小例子:实现自动按计算器

x = [0]*4
x[0] = pyautogui.locateCenterOnScreen('3.png')
x[1] = pyautogui.locateCenterOnScreen('+.png')
x[2] = pyautogui.locateCenterOnScreen('4.png')
x[3] = pyautogui.locateCenterOnScreen('=.png')
print(x)
for i in range(4):
    pyautogui.click(x[i])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

要将计算器的各个按钮截图保存下,重点看下数组的使用;

pyautogui截图对比,除了上面的OnScreen与屏幕比,还可以跟自定义图片比,由于用不到,这里就不展开了。

在上面打开chrome程序后,可以实现其他实现的功能:延时、快捷键ctrl和t新建页面,ctrl+l定位在地址栏,输入网址,按下两次回车键,截屏保存在程序文件夹

pyautogui.PAUSE = 2
pyautogui.hotkey('ctrl', 't')
pyautogui.hotkey('ctrl', 'l')
pyautogui.typewrite("www.dangdang.com")
pyautogui.keyDown('enter')
pyautogui.keyDown('enter')
pyautogui.PAUSE = 2
im2 = pyautogui.screenshot('my_screenshot.png')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3:将程序切换到前台

前面的问题是,如果程序在后台怎么切到前台,程序被挡了,图片对比无法正常使用…
有人推荐pywinauto,但是写的时候发现并没有这个函数…
from pywinauto.win32functions import SetForegroundWindow
这个暂时不研究了…

二、pywinauto

点击相应模块、做成可运行的小程序

如果操作电脑的步骤可以很明确的写出来,而且前提是这个程序就在桌面能看到的地方在运行,那之前的pyautogui还是很方便的,类似按键精灵吧。

我想到的例子是之前登陆网页版IP摄像机进行自动对焦,如果写出来什么时候干什么事,然后再进行循环,的确可以批量完成。但是这个最好还是可以通过调用硬件接口直接去进行控制,以后研究下。

当然如果还是想通过点击来实现,上次的图片对比面临两个问题:

  1. 想点击的东西被挡了,解决方法是输入程序路径,重开一个新的,或调用相应进程,直接操作;
  2. 当面临客户端而不是网页,客户端上面的按钮和输入框有好多重复的,图片很难区分,也很难进行定位;这时候如果能获取每个按钮和输入框的名字就可以直接操作啦。
    ————————————————————————————

我测的类型都是UIA,即ControlType: UIA_WindowControlTypeId,原版教程有个测不出来类型的是win32型。
Inspect工具

通常的单进程用Application(),罕见的跨进程Desktop()暂不考虑,比如计算器

1:通过路径打开新的程序

其实这个原生的os就可以实现~u是unique的意思

import os
os.system(u"C:\\Windows\\System32\\notepad.exe")
  • 1
  • 2

os其他功能就不深究了,试了试都出不来,感觉是Windows不支持

pywinauto的实现方式

from pywinauto import Application
app = Application().start(r"C:\Program Files (x86)\Thunder Network\Thunder\Program\Thunder.exe")
  • 1
  • 2

成功打开迅雷

2:通过进程号连接已有程序

from pywinauto import Application
app = Application().connect(process=10276)
  • 1
  • 2

弊端是进程号每次重启都不固定
WIN7还是用inspect看,WIN10在任务管理器里有
进程号

3:通过句柄连接已有程序

app = Application().connect(handle=0x00250512)
  • 1

模板里中间多了两个0,先按这样补00

弊端同上不稳定,查看只能用inspect
在这里插入图片描述

4:通过路径连接已有程序

app = Application().connect(path=r"C:\Program Files (x86)\Thunder Network\Thunder\Program\Thunder.exe")
  • 1

跟第一个打开程序很像

5:通过标题和类型连接已有程序

app = Application().connect(title_re="截屏程序.*", class_name="TMainForm")
  • 1

inspect上面都有,但是不是每个控件都有这些信息,要看具体看情况~
在这里插入图片描述
在这里插入图片描述

对菜单进行控制(待测试)

我们以截屏程序为例,在程序连接后(app),通过window选择窗体
便可按菜单进行操作,或通过快捷键进行操作

prt_sc = app.window(title='截屏程序’)
prt_sc.menu_select(r"文件->另存为")
或
prt_sc.type_keys('%TPR')
  • 1
  • 2
  • 3
  • 4

其中%代表ALT,一般像文字带括号,都是用ALT触发,例如:工具(T)笔(P)红笔(R);还可以用^代表快捷键CTRL,另存为是CTRL+S

对按钮\文本框\复选框进行控制

看上去很复杂

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/236673
推荐阅读
相关标签
  

闽ICP备14008679号