pywinauto 自动化入门
# 第一步 实例化要操作的进程:得到的 Application 对象
# 第二步 选择窗口 :app.window(筛选条件) 得到的窗口是 WindowSpecification对象
# 第三步:基于 WindowSpecification 对象使用其方法再往下查找,定位到具体的控件
# 第四步:使用控件执行我们需要的控制操作
# 获取应用对象两种方式
# 以直接打开方式
# app = Application(backend='uia').start('D:\Program Files (x86)\Tencent\WeChat\WeChat.exe')
# 以连接方式
# app = Application(backend='uia').connect(path = r"D:\Program Files (x86)\Tencent\WeChat\WeChat.exe")
# app = Application(backend='uia').connect(process = pid)
# app = Application(backend='uia').connect(handle = 0x010f0c) # 句柄
# app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", control_type="Window") # 参数组合
# Application 对象常用方法属性
# app.process 进程 id 属性
# app.kill(soft=False) # 强制关闭
# app.cpu_usage() # 返回指定秒数期间的CPU使用率百分比
# app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None) # 等待进程CPU使用率百分比小于指定的阈值threshold
# app.is_process_running() 当前进程是否运行中
# app.is_process_running() 当前进程是否运行中
# app.top_window() 返回顶层窗口对象, app 特有
# pywinauto.application.process_module(app.process) 打印进程模块名
# 父级控件对象查找子控件的方法
# parent.window(**keyword)
# parent.windows(**keyword)
# parent.children(**keyword) #直系
# parent.child_window(**keyword) #能隔代
# parent.parent()
# parent.iter_children(**keyword)
# parent.descendants(**keyword) #查找所有后代中满足的
# 查找条件(keyword)关键字
# title_re 标题正则匹配
# class_name_re 类名正则匹配
# best_match 标题与此类似的元素
# top_level_only=True 仅限顶级元素(默认值=True)
# handle 要返回的元素的句柄
# ctrl_index 要返回的子元素的索引
# found_index 要返回的已过滤子元素的索引
# predicate_func 用户为自定义元素验证提供了钩子
# active_only=False 仅限活动元素
# control_id 具有此控件ID的元素
# control_type 具有此控件类型的元素(字符串;用于UIAutomation元素)
# auto_id 具有此自动化ID的元素(用于UIAutomation元素)
# framework_id 具有此框架ID的元素(用于UIAutomation元素)
# backend 搜索时使用的后端名称(默认=None表示当前活动后端)
# 关键字对应 inspect.exe(尽量找最新版本) 工具查看到的属性
# title_re = Name
# class_name_re = ClassName
# process = ProcessId
# auto_id = AutomationId
# handle = NativeWindowHandle 例如:handle=0x1038C
# framework_id = FrameworkId
# control_type = LocalizedControlType 不是中文的,control_type 值有 Window Pane MenuBar Button Image Edit MenuItem
# 支持窗口模式的控件对象方法
# control.close()
# control.minimize()
# control.maximize()
# control.restore() # 恢复
# control.get_show_state() # 正常0,最大化1,最小化2
# control.exists(timeout=None, retry_interval=None) # retry_interval:timeout内重试时间
# control.wait(state, timeout=None, retry_interval=None) #状态:'exists' 'visible' 'enabled' 'ready' 'active'
# control.wait_not(state, timeout=None, retry_interval=None)
# 控件的常用属性和操作
# control.window_text()
# control.class_name()
# control.children_texts()
# control.element_info.name
# control.element_info.class_name
# control.element_info.control_type
# control.is_child(parent)
# control.legacy_properties().get('Value') # 可以获取inspect界面LegacyIAccessible开头的一系列字段
# control.is_dialog()
# control.draw_outline(colour='red') # 给控件画轮廓线
# control.dump_tree(depth=None, filename=None) # 打印窗体内所有元素控件
# control.scroll(direction, amount, count=1,) # direction :"up", "down", "left", "right" 方向 # amount:"line" or "page" 行或页 # count:int 滚动次数
# control.capture_as_image().save(path) # 获取控件的 PIL image对象并保存
# control.rectangle() # 控件上下左右坐标
# control.exists()
# control.wrapper_object()
# 控件里的鼠标键盘操作
# control.click_input()
# control.right_click_input()
# control.type_keys(keys, pause = None, with_spaces = False,) # pause:输入间隔 with_spaces:是否保留空格
# control.double_click_input(button ="left", coords = (None, None)) # 左键双击
# control.press_mouse_input(coords = (None, None)) # 指定坐标按下左键,不传坐标默认左上角
# control.release_mouse_input(coords = (None, None)) # 指定坐标释放左键,不传坐标默认左上角
# control.move_mouse_input(coords=(0, 0)) # 将鼠标移动到指定坐标,不传坐标默认左上角
# control.drag_mouse_input(dst=(0, 0)) # 拖拽到指定坐标位置
# 自由鼠标键盘操作
# mouse.move(coords=(x, y))
# mouse.click(button='left', coords=(40, 40))
# mouse.double_click(button='left', coords=(140, 40))
# mouse.press(button='left', coords=(140, 40))
# mouse.release(button='left', coords=(300, 40))
# mouse.right_click(coords=(400, 400))
# mouse.wheel_click(coords=(400, 400)) # 中键单击
# mouse.scroll(coords=(1200,300),wheel_dist=-3) #滚轮滑动,正数往上,负数往下
# 按键名称
# {ESC} {F1} {SPACE} {BKSP} {DELETE} {HELP} {NUMLOCK} {TAB} {END} {BACK} {SCROLLLOCK} {VK_SCROLL} {VK_MULTIPLY} {VK_HOME} {VK_PAUSE} {VK_LSHIFT}
# {UP} {DOWN} {LEFT} {RIGHT} {VK_LSHIFT}, {VK_PAUSE}, {VK_MODECHANGE},
# {BACK}, {VK_HOME}, {VK_HANGEUL}, {VK_KANJI}, {VK_RIGHT}, {BS}, {HOME}, {VK_F4}, {VK_ACCEPT}, {VK_F18}, {VK_SNAPSHOT},
# {VK_PA1}, {VK_NONAME}, {VK_LCONTROL}, {ZOOM}, {VK_ATTN}, {VK_UP}, {NUMLOCK}, {VK_APPS}, {PGUP}, {VK_CONTROL},
# {VK_LEFT}, {PRTSC}, {VK_NUMPAD4}, {CAPSLOCK}, {VK_CONVERT}, {VK_PROCESSKEY},
# {ENTER}, {VK_SEPARATOR}, {VK_RWIN}, {VK_LMENU}, {VK_NEXT}, {VK_ADD}, {VK_RCONTROL},
# {VK_RETURN}, {BREAK}, {VK_NUMPAD9}, {VK_NUMPAD8}, {RWIN}, {VK_KANA},
# {PGDN}, {VK_NUMPAD3}, {VK_NUMPAD1}, {VK_NUMPAD0}, {VK_NUMPAD7},
# {VK_NUMPAD6}, {VK_NUMPAD5}, {VK_PRIOR}, {VK_SUBTRACT}, {HELP},
# {VK_PRINT}, {VK_BACK}, {CAP}, {VK_RBUTTON}, {VK_RSHIFT}, {VK_LWIN},
# {VK_HELP}, {VK_NONCONVERT}, {BACKSPACE}, {VK_SELECT}, {VK_TAB}, {VK_HANJA},
# {VK_NUMPAD2}, {INSERT}, {VK_DECIMAL}, {VK_FINAL}, {VK_EXSEL},
# {RMENU}, {VK_F3}, {VK_F2}, {VK_F1}, {VK_F7}, {VK_F6}, {VK_F5}, {VK_CRSEL},
# {VK_SHIFT}, {VK_EREOF}, {VK_CANCEL}, {VK_DELETE}, {VK_HANGUL}, {VK_MBUTTON},
# {VK_NUMLOCK}, {VK_CLEAR}, , {VK_MENU}, {VK_INSERT},
# {VK_CAPITAL}, {VK_LBUTTON}, {VK_OEM_CLEAR}, {VK_ESCAPE}, {VK_DIVIDE}, {INS}, {VK_JUNJA},
# {VK_EXECUTE}, {VK_PLAY}, {VK_RMENU}, {LWIN},
# 控制键别名:
# {VK_CONTROL}: ^
# {VK_SHIFT} : +
# {VK_MENU} (Alt键) : %
# keyboard.send_keys('^a^c') # Ctrl + A 和 Ctrl + C
# keyboard.send_keys('+{INS}') # Shift + Ins 从剪贴板插入
# keyboard.send_keys('%{F4}') # Alt + F4
# keyboard.send_keys(' {ENTER 2}') # 表示按两次Enter
# keyboard.send_keys("{VK_SHIFT down}" "pywinauto" "{VK_SHIFT up}") # 按下 shift 输入字符串,再释放 shift
# keyboard.send_keys("{h down}" "{e down}" "{h up}" "{e up}" "llo") # to type hello
# keyboard.send_keys('{^}a{^}c{%}') #使用花括号来转义 ^ 键入字符串 "^a^c%"
# keyboard.send_keys('{{}ENTER{}}') #使用花括号来转义{ 和 } 两个符号
# 第二步 选择窗口 :app.window(筛选条件) 得到的窗口是 WindowSpecification对象
# 第三步:基于 WindowSpecification 对象使用其方法再往下查找,定位到具体的控件
# 第四步:使用控件执行我们需要的控制操作
# 获取应用对象两种方式
# 以直接打开方式
# app = Application(backend='uia').start('D:\Program Files (x86)\Tencent\WeChat\WeChat.exe')
# 以连接方式
# app = Application(backend='uia').connect(path = r"D:\Program Files (x86)\Tencent\WeChat\WeChat.exe")
# app = Application(backend='uia').connect(process = pid)
# app = Application(backend='uia').connect(handle = 0x010f0c) # 句柄
# app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", control_type="Window") # 参数组合
# Application 对象常用方法属性
# app.process 进程 id 属性
# app.kill(soft=False) # 强制关闭
# app.cpu_usage() # 返回指定秒数期间的CPU使用率百分比
# app.wait_cpu_usage_lower(threshold=2.5, timeout=None, usage_interval=None) # 等待进程CPU使用率百分比小于指定的阈值threshold
# app.is_process_running() 当前进程是否运行中
# app.is_process_running() 当前进程是否运行中
# app.top_window() 返回顶层窗口对象, app 特有
# pywinauto.application.process_module(app.process) 打印进程模块名
# 父级控件对象查找子控件的方法
# parent.window(**keyword)
# parent.windows(**keyword)
# parent.children(**keyword) #直系
# parent.child_window(**keyword) #能隔代
# parent.parent()
# parent.iter_children(**keyword)
# parent.descendants(**keyword) #查找所有后代中满足的
# 查找条件(keyword)关键字
# title_re 标题正则匹配
# class_name_re 类名正则匹配
# best_match 标题与此类似的元素
# top_level_only=True 仅限顶级元素(默认值=True)
# handle 要返回的元素的句柄
# ctrl_index 要返回的子元素的索引
# found_index 要返回的已过滤子元素的索引
# predicate_func 用户为自定义元素验证提供了钩子
# active_only=False 仅限活动元素
# control_id 具有此控件ID的元素
# control_type 具有此控件类型的元素(字符串;用于UIAutomation元素)
# auto_id 具有此自动化ID的元素(用于UIAutomation元素)
# framework_id 具有此框架ID的元素(用于UIAutomation元素)
# backend 搜索时使用的后端名称(默认=None表示当前活动后端)
# 关键字对应 inspect.exe(尽量找最新版本) 工具查看到的属性
# title_re = Name
# class_name_re = ClassName
# process = ProcessId
# auto_id = AutomationId
# handle = NativeWindowHandle 例如:handle=0x1038C
# framework_id = FrameworkId
# control_type = LocalizedControlType 不是中文的,control_type 值有 Window Pane MenuBar Button Image Edit MenuItem
# 支持窗口模式的控件对象方法
# control.close()
# control.minimize()
# control.maximize()
# control.restore() # 恢复
# control.get_show_state() # 正常0,最大化1,最小化2
# control.exists(timeout=None, retry_interval=None) # retry_interval:timeout内重试时间
# control.wait(state, timeout=None, retry_interval=None) #状态:'exists' 'visible' 'enabled' 'ready' 'active'
# control.wait_not(state, timeout=None, retry_interval=None)
# 控件的常用属性和操作
# control.window_text()
# control.class_name()
# control.children_texts()
# control.element_info.name
# control.element_info.class_name
# control.element_info.control_type
# control.is_child(parent)
# control.legacy_properties().get('Value') # 可以获取inspect界面LegacyIAccessible开头的一系列字段
# control.is_dialog()
# control.draw_outline(colour='red') # 给控件画轮廓线
# control.dump_tree(depth=None, filename=None) # 打印窗体内所有元素控件
# control.scroll(direction, amount, count=1,) # direction :"up", "down", "left", "right" 方向 # amount:"line" or "page" 行或页 # count:int 滚动次数
# control.capture_as_image().save(path) # 获取控件的 PIL image对象并保存
# control.rectangle() # 控件上下左右坐标
# control.exists()
# control.wrapper_object()
# 控件里的鼠标键盘操作
# control.click_input()
# control.right_click_input()
# control.type_keys(keys, pause = None, with_spaces = False,) # pause:输入间隔 with_spaces:是否保留空格
# control.double_click_input(button ="left", coords = (None, None)) # 左键双击
# control.press_mouse_input(coords = (None, None)) # 指定坐标按下左键,不传坐标默认左上角
# control.release_mouse_input(coords = (None, None)) # 指定坐标释放左键,不传坐标默认左上角
# control.move_mouse_input(coords=(0, 0)) # 将鼠标移动到指定坐标,不传坐标默认左上角
# control.drag_mouse_input(dst=(0, 0)) # 拖拽到指定坐标位置
# 自由鼠标键盘操作
# mouse.move(coords=(x, y))
# mouse.click(button='left', coords=(40, 40))
# mouse.double_click(button='left', coords=(140, 40))
# mouse.press(button='left', coords=(140, 40))
# mouse.release(button='left', coords=(300, 40))
# mouse.right_click(coords=(400, 400))
# mouse.wheel_click(coords=(400, 400)) # 中键单击
# mouse.scroll(coords=(1200,300),wheel_dist=-3) #滚轮滑动,正数往上,负数往下
# 按键名称
# {ESC} {F1} {SPACE} {BKSP} {DELETE} {HELP} {NUMLOCK} {TAB} {END} {BACK} {SCROLLLOCK} {VK_SCROLL} {VK_MULTIPLY} {VK_HOME} {VK_PAUSE} {VK_LSHIFT}
# {UP} {DOWN} {LEFT} {RIGHT} {VK_LSHIFT}, {VK_PAUSE}, {VK_MODECHANGE},
# {BACK}, {VK_HOME}, {VK_HANGEUL}, {VK_KANJI}, {VK_RIGHT}, {BS}, {HOME}, {VK_F4}, {VK_ACCEPT}, {VK_F18}, {VK_SNAPSHOT},
# {VK_PA1}, {VK_NONAME}, {VK_LCONTROL}, {ZOOM}, {VK_ATTN}, {VK_UP}, {NUMLOCK}, {VK_APPS}, {PGUP}, {VK_CONTROL},
# {VK_LEFT}, {PRTSC}, {VK_NUMPAD4}, {CAPSLOCK}, {VK_CONVERT}, {VK_PROCESSKEY},
# {ENTER}, {VK_SEPARATOR}, {VK_RWIN}, {VK_LMENU}, {VK_NEXT}, {VK_ADD}, {VK_RCONTROL},
# {VK_RETURN}, {BREAK}, {VK_NUMPAD9}, {VK_NUMPAD8}, {RWIN}, {VK_KANA},
# {PGDN}, {VK_NUMPAD3}, {VK_NUMPAD1}, {VK_NUMPAD0}, {VK_NUMPAD7},
# {VK_NUMPAD6}, {VK_NUMPAD5}, {VK_PRIOR}, {VK_SUBTRACT}, {HELP},
# {VK_PRINT}, {VK_BACK}, {CAP}, {VK_RBUTTON}, {VK_RSHIFT}, {VK_LWIN},
# {VK_HELP}, {VK_NONCONVERT}, {BACKSPACE}, {VK_SELECT}, {VK_TAB}, {VK_HANJA},
# {VK_NUMPAD2}, {INSERT}, {VK_DECIMAL}, {VK_FINAL}, {VK_EXSEL},
# {RMENU}, {VK_F3}, {VK_F2}, {VK_F1}, {VK_F7}, {VK_F6}, {VK_F5}, {VK_CRSEL},
# {VK_SHIFT}, {VK_EREOF}, {VK_CANCEL}, {VK_DELETE}, {VK_HANGUL}, {VK_MBUTTON},
# {VK_NUMLOCK}, {VK_CLEAR}, , {VK_MENU}, {VK_INSERT},
# {VK_CAPITAL}, {VK_LBUTTON}, {VK_OEM_CLEAR}, {VK_ESCAPE}, {VK_DIVIDE}, {INS}, {VK_JUNJA},
# {VK_EXECUTE}, {VK_PLAY}, {VK_RMENU}, {LWIN},
# 控制键别名:
# {VK_CONTROL}: ^
# {VK_SHIFT} : +
# {VK_MENU} (Alt键) : %
# keyboard.send_keys('^a^c') # Ctrl + A 和 Ctrl + C
# keyboard.send_keys('+{INS}') # Shift + Ins 从剪贴板插入
# keyboard.send_keys('%{F4}') # Alt + F4
# keyboard.send_keys(' {ENTER 2}') # 表示按两次Enter
# keyboard.send_keys("{VK_SHIFT down}" "pywinauto" "{VK_SHIFT up}") # 按下 shift 输入字符串,再释放 shift
# keyboard.send_keys("{h down}" "{e down}" "{h up}" "{e up}" "llo") # to type hello
# keyboard.send_keys('{^}a{^}c{%}') #使用花括号来转义 ^ 键入字符串 "^a^c%"
# keyboard.send_keys('{{}ENTER{}}') #使用花括号来转义{ 和 } 两个符号
教程就这么多,如果感觉能理解,下面我们上代码吧。
安装扩展包
pip install pywinauto pyautogui
入门代码,以打开 edge 浏览器为例
from pywinauto import Application
from pywinauto.application import process_module
app = Application(backend='uia').start(r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe")
# 也可以用连接的方式:
# app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", framework_id="Win32", control_type="Window") # 通过特征查找已经启动的进程
# app = Application(backend='uia').connect(process=pid) # 这个 pid 可能不好查, 进程管理器看到的 edge 有好几个进程
print(app.is_process_running())
print(process_module(app.process))
print(app.top_window())
app.top_window().dump_tree()
运行结果如下: 浏览器是打开了,不过代码居然报错了,哈哈。不要慌,这段代码写的就是打印状态,所以很容易排错。
意思就是一打开,进程就退出了。原因是我在边写博客别测试,所以浏览器是已经打开的。如果edge 已经启动,那么再启动,就好像进程一启动传个话,就退出了,也就造成报错,关掉所有与 edge 相关的,包括把页面作为应用安装的,然后重试就好了。
如果不出意思,你在运行窗口能看到如图所示:
下面再试个连接进程的代码,要求浏览器为已启动状态,这里又有注意两点:
1. 连接 Cheom 时,Edge不要打开,不然后会报 ElementAmbiguousError: There are 2 elements that match the criteria 居然会找到它们两个。
2. 浏览器不要最小化,不然代码会一直等待你手动切换浏览器状态。
from pywinauto import Application
from pywinauto.application import process_module
# 下面两个,二选一,由于 Edge 也是 chrome 内核,所以只的少许差别
app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", framework_id="Win32", control_type="Pane") # 以连接方式连接到 Chrome
# app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", framework_id="Win32", control_type="Window") # 以连接方式连接到 Edge
# print(app.is_process_running())
# print(process_module(app.process))
# print(app.top_window())
# app.top_window().dump_tree()
win = app.top_window()
win.maximize() #窗口最大化
win.wait('visible')
refresh_button = win.child_window(title="重新加载", control_type="Button") #刷新按钮
refresh_button.click_input() #单击按钮
不出意外的话,你会看到浏览器刷新了一下。然后,你就可以边看源码,边写代码了。看源码非常重要,刚开始,我就遇到哪个报错问题,因为一开始,我是通过 进程id 连接的,切换为 start 却报错,把我都整不会了。
我想写个功能,每当我保存好代码,自动刷新我的项目页面。免得每次要按 F5,好麻烦呀,大家不觉得吗?phpstrom 好像能支持 html 文件一保存就刷新网页,但 php 文件不行。
但是自动化是基于 UI 界面操作的,即查找元素定位元素,模拟键鼠操作。如果我一保存文件,光标立跑到别去了,显然不适用,最好还是绑定快捷键。本来想绑定键盘+鼠标组合快捷键的,但没查到资料。想想,也就绑定鼠标中键双击最适合了。
示例代码,还是改成通过进程连接才最保险,不然浏览器一多开,就会报错。还有要准一个 icon 图标,在同级目录中。生成系统托盘要用到。缺少的扩展包,pip 安装一下,下面是 edge的代码
from pywinauto.application import Application
import pystray
from PIL import Image
import mouse
class App(object):
def __init__(self):
app = Application(backend='uia').connect(class_name="Chrome_WidgetWin_1", framework_id="Win32", control_type="Window") # 连接到 Edge
self.win = app.top_window()
def _refresh(self, win):
x, y = mouse.get_position()
win.maximize()
win.wait('visible')
refresh = win.child_window(title="刷新", auto_id="view_1003", control_type="Button")
refresh.click_input()
mouse.move(x, y)# 还原位置
return self
def hotkey(self):
''' 中键双击 '''
mouse.on_button(lambda msg : self._refresh(self.win) , ('msg is ok',), 'middle', 'double')
return self
def listen(self):
''' 在系统托盘运行 '''
pystray.Icon('test', icon=Image.open('icon.png'), menu=pystray.Menu(
pystray.MenuItem('关闭', lambda icon, item : icon.stop())
)).run()
if __name__ == '__main__':
App().hotkey().listen()