Windows 自动化工具 pywinauto 使用指南
pywinauto 使用指南
简介
pywinauto 是一个用于 Windows 系统下的 GUI 自动化测试和操作的 Python 库。它允许您通过代码自动化控制 Windows 应用程序,执行点击、输入文本、获取界面元素等操作,非常适合 GUI 自动化测试和重复性操作的自动化。
当前版本: 0.6.9
主要功能
- 自动化启动和连接 Windows 应用程序
- 查找和操作窗口及控件
- 模拟用户操作(点击、输入、拖拽等)
- 获取窗口和控件的属性和状态
- 支持多种后端(backend)模式,包括 UIA(UI Automation)和传统的 win32
环境准备
系统要求
- Windows 操作系统(Windows 7/8/10/11)
- Python 3.6 或更高版本
- 仅支持 Windows 平台,不支持 Linux 或 macOS
安装依赖
# 安装主要库
pip install pywinauto
# 安装其他可能用到的依赖
pip install pillow # 用于图像处理,截图功能需要
pip install opencv-python # 用于图像识别(可选)
pip install python-cv # 用于复杂图像处理(可选)
后端选择
pywinauto 支持两种后端:
UIA (UI Automation) - 推荐用于 Windows 7 及以上系统,支持更多的控件类型
app = Application(backend="uia")
Win32 - 用于较老的应用程序或特殊情况
app = Application(backend="win32")
开发工具推荐
为了更方便地识别和定位 Windows 应用程序中的控件,推荐使用以下工具:
- Inspect.exe - Windows SDK 自带的 UI 检查工具
- Spy++ - Visual Studio 自带的窗口检查工具
- UISpy - Windows SDK 工具
- Accessibility Insights for Windows - 微软开发的辅助工具
安装方法
pip install pywinauto
基本使用
导入必要模块
from pywinauto.application import Application
from pywinauto.findwindows import find_elements, find_element
import time
启动应用程序
# 使用 UIA 后端启动应用程序
app = Application(backend="uia").start("应用程序路径")
# 示例:启动记事本
app = Application(backend="uia").start("notepad.exe")
连接到已运行的应用程序
# 通过窗口标题连接
app = Application(backend="uia").connect(title="窗口标题")
# 通过窗口标题的正则表达式连接
app = Application(backend="uia").connect(title_re="窗口标题.*")
# 通过进程 ID 连接
app = Application(backend="uia").connect(process=1234)
窗口和控件操作
# 获取顶层窗口
main_window = app.top_window()
# 等待窗口准备就绪
main_window.wait('ready', timeout=10)
# 打印窗口内所有控件的层次结构
print(main_window.dump_tree())
# 通过标题和控件类型查找特定控件
button = main_window.child_window(title="确定", control_type="Button")
# 点击按钮
button.click()
# 向文本框输入内容
edit = main_window.child_window(control_type="Edit")
edit.type_keys("Hello World!")
# 绘制红色边框以突出显示控件(调试时很有用)
button.draw_outline(colour='red', thickness=2)
Desktop 对象
可以使用 Desktop 对象直接访问桌面上的窗口:
from pywinauto import Desktop
# 获取桌面窗口
desktop = Desktop(backend="uia")
# 获取所有可见窗口
windows = desktop.windows()
# 通过标题访问特定窗口
notepad_window = desktop['记事本']
进阶功能详解
1. 保存控件菜单树结构 print_control_identifiers()
此方法可以打印出窗口中所有控件的标识符和属性,是调试和开发自动化脚本的重要工具:
# 打印窗口所有控件的标识符信息
main_window.print_control_identifiers()
# 也可以针对特定控件打印其子控件的标识符
panel = main_window.child_window(title="面板", control_type="Pane")
panel.print_control_identifiers()
输出示例:
Control Identifiers:
Button - '确定' (L1373, T636, R1455, B666)
['确定Button', '确定', 'Button', 'Button0', 'Button1']
Button - '取消' (L1373, T589, R1455, B619)
['取消Button', '取消', 'Button2']
2. 设置全局等待时间 Timings
pywinauto 查找窗口和控件时会有超时时间与轮询机制,可以通过 timings 模块设置全局等待时间:
from pywinauto import timings
# 设置默认超时时间(秒)
timings.Timings.window_find_timeout = 5.0 # 查找窗口超时
timings.Timings.app_start_timeout = 20.0 # 启动应用程序超时
timings.Timings.app_connect_timeout = 5.0 # 连接应用程序超时
timings.Timings.after_click_wait = 0.5 # 点击后等待时间
# 恢复默认时间设置
timings.Timings.defaults()
timings 模块提供了三种预设模式:
# 快速模式 - 减少各种操作的等待时间
timings.Timings.fast()
# 默认模式 - 使用默认等待时间设置
timings.Timings.defaults()
# 慢速模式 - 增加各种操作的等待时间,适用于较慢的应用程序
timings.Timings.slow()
以下是可以调整的各个时序设置:
window_find_timeout (default 5) # 查找窗口的超时时间
window_find_retry (default .09) # 查找窗口的重试间隔
app_start_timeout (default 10) # 启动应用程序的超时时间
app_start_retry (default .90) # 启动应用程序的重试间隔
app_connect_timeout (default 5.) # 连接应用程序的超时时间
app_connect_retry (default .1) # 连接应用程序的重试间隔
cpu_usage_interval (default .5) # CPU使用率检查间隔
cpu_usage_wait_timeout (default 20) # 等待CPU使用率降低的超时时间
exists_timeout (default .5) # 检查元素存在的超时时间
exists_retry (default .3) # 检查元素存在的重试间隔
after_click_wait (default .09) # 点击后等待时间
after_clickinput_wait (default .09) # 使用点击输入后等待时间
after_menu_wait (default .1) # 菜单操作后等待时间
after_sendkeys_key_wait (default .01) # 发送按键后每个键的等待时间
after_button_click_wait (default 0) # 按钮点击后等待时间
before_closeclick_wait (default .1) # 关闭点击前等待时间
closeclick_retry (default .05) # 关闭点击重试间隔
closeclick_dialog_close_wait (default 2) # 关闭对话框后等待时间
after_closeclick_wait (default .2) # 关闭点击后等待时间
after_windowclose_timeout (default 2) # 窗口关闭后超时时间
after_windowclose_retry (default .5) # 窗口关闭后重试间隔
after_setfocus_wait (default .06) # 设置焦点后等待时间
setfocus_timeout (default 2) # 设置焦点的超时时间
setfocus_retry (default .1) # 设置焦点的重试间隔
after_setcursorpos_wait (default .01) # 设置光标位置后等待时间
sendmessagetimeout_timeout (default .01) # 发送消息超时时间
after_tabselect_wait (default .05) # 选项卡选择后等待时间
after_listviewselect_wait (default .01) # 列表视图选择后等待时间
after_listviewcheck_wait (default .001) # 列表视图勾选后等待时间
listviewitemcontrol_timeout (default 1.5) # 列表视图项控制的超时时间
after_treeviewselect_wait (default .1) # 树视图选择后等待时间
after_toobarpressbutton_wait (default .01) # 工具栏按钮按下后等待时间
after_updownchange_wait (default .1) # 上下更改后等待时间
after_movewindow_wait (default 0) # 移动窗口后等待时间
after_buttoncheck_wait (default 0) # 按钮勾选后等待时间
after_comboboxselect_wait (default .001) # 组合框选择后等待时间
after_listboxselect_wait (default 0) # 列表框选择后等待时间
after_listboxfocuschange_wait (default 0) # 列表框焦点更改后等待时间
after_editsetedittext_wait (default 0) # 编辑框设置文本后等待时间
after_editselect_wait (default .02) # 编辑框选择后等待时间
drag_n_drop_move_mouse_wait (default .1) # 拖放移动鼠标等待时间
before_drag_wait (default .2) # 拖动前等待时间
before_drop_wait (default .1) # 放下前等待时间
after_drag_n_drop_wait (default .1) # 拖放后等待时间
scroll_step_wait (default .1) # 滚动步骤等待时间
适当调整这些时间设置可以提高自动化脚本的稳定性和成功率。对于较慢的应用程序,增加等待时间;对于响应迅速的应用程序,可以减少等待时间以提高效率。
3. 鼠标操作与滚动列表
鼠标操作包括点击、右键、双击、拖拽等:
# 直接使用鼠标模块进行操作
from pywinauto import mouse
# 单击鼠标左键(指定坐标位置)
mouse.click(coords=(100, 100))
# 鼠标右击
mouse.right_click(coords=(100, 200))
# 鼠标双击
mouse.double_click(coords=(100, 200))
# 鼠标长按、拖动、释放(拖拽操作)
mouse.press(coords=(200, 400))
mouse.move(coords=(100, 0)) # 相对移动
mouse.release(coords=(300, 400))
# 鼠标滚轮操作
mouse.scroll(coords=(0, 0), wheel_dist=1) # 正数向上滚动,负数向下滚动
# 鼠标中键单击
mouse.wheel_click(coords=(0, 0))
# 控件级别的鼠标操作
button.click() # 左键点击控件
button.click_input() # 模拟真实鼠标点击(移动鼠标到位置再点击)
button.right_click() # 右键点击控件
button.right_click_input() # 模拟真实鼠标右键点击
button.double_click() # 双击控件
button.double_click_input() # 模拟真实鼠标双击
滚动列表控件
滚动列表的方法有两种:
# 方法1:使用控件的滚动方法(某些控件支持)
list_control = main_window.child_window(control_type="List")
list_control.scroll("down", "page", count=2) # 向下滚动2页
list_control.scroll("up", "line", count=5) # 向上滚动5行
# 方法2:使用鼠标滚轮滚动(更通用)
# 首先获取控件的矩形区域
list_rect = list_control.rectangle()
# 然后在该区域内使用鼠标滚轮
mouse.scroll(coords=(list_rect.left + 20, list_rect.top + 30), wheel_dist=-2) # 向下滚动,负数表示向下
# 实际应用示例:滚动微信聊天记录
app = Application(backend='uia').connect(path=r'C:\Program Files (x86)\Tencent\WeChat\WeChat.exe')
win = app.window(title="微信", class_name='WeChatMainWndForPC')
chat_list = win.child_window(title="会话", control_type="List").rectangle()
mouse.scroll(coords=(chat_list.left + 20, chat_list.top + 30), wheel_dist=-2) # 向下滚动
注意:wheel_dist
参数的值为正表示向上滚动,为负表示向下滚动,数值大小决定滚动的距离。
4. 查找多个相同控件使用 found_index
当同一个窗口中有多个相同属性的控件时,直接使用child_window()
可能会引发ElementAmbiguousError
错误。这时可以使用found_index
参数来指定第几个匹配的控件:
# 获取第一个匹配的按钮
first_button = main_window.child_window(title="下一步", control_type="Button", found_index=0)
# 获取第二个匹配的按钮
second_button = main_window.child_window(title="下一步", control_type="Button", found_index=1)
# 获取所有匹配的按钮
all_buttons = main_window.children(title="下一步", control_type="Button")
实际应用示例 - 处理记事本中的多个菜单栏:
from pywinauto import Application
# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
# 输入内容
win.child_window(title="文本编辑器").set_text("hello world")
# 查找MenuBar - 如果直接查找会报错
# menu = win.child_window(control_type="MenuBar") # 这会引发错误
# 查找第一个MenuBar
menu0 = win.child_window(control_type="MenuBar", found_index=0)
print(menu0.window_text())
# 查找第二个MenuBar
menu1 = win.child_window(control_type="MenuBar", found_index=1)
print(menu1.window_text())
错误示例:当有多个匹配元素但没有使用found_index
时,会引发以下错误:
pywinauto.findwindows.ElementAmbiguousError: There are 2 elements that match the criteria
{'control_type': 'MenuBar', 'top_level_only': False, 'parent': <uia_element_info.UIAElementInfo - '无标题 - 记事本', Notepad, 460858>, 'backend': 'uia'}
注意:found_index
参数的索引从 0 开始,即 0 表示第一个匹配的控件,1 表示第二个,以此类推。
5. 等待方法 wait() 和 wait_not()
详细的等待方法使用:
# 等待窗口处于特定状态
window.wait('ready') # 等待窗口准备就绪
window.wait('exists') # 等待窗口存在
window.wait('visible') # 等待窗口可见
window.wait('enabled') # 等待窗口启用
# 等待窗口不处于特定状态
window.wait_not('visible') # 等待窗口不可见
window.wait_not('exists') # 等待窗口不存在
# 使用超时参数
window.wait('ready', timeout=30) # 最多等待30秒
window.wait_not('exists', timeout=10) # 最多等待10秒
6. 组合框控件 ComboBox 操作
组合框控件(ComboBox)是 Windows 应用程序中常见的下拉选择控件。pywinauto 提供了两种操作组合框的方法:
# 方法1: 使用select方法直接选择选项
# 获取ComboBox控件
combo_box = main_window.child_window(control_type="ComboBox")
# 展开下拉列表
combo_box.expand()
# 选择特定选项(直接选择,但屏幕可能会闪烁)
combo_box.select("选项文本")
# 获取所有选项
options = combo_box.item_texts()
# 通过索引选择
combo_box.select(2) # 选择第三个选项(索引从0开始)
# 方法2: 更接近真实操作的方法(推荐)
# 1.先点开选项
combo_box.click_input()
# 2.选择内容
main_window.child_window(title="选项文本", control_type="ListItem").click_input()
实际应用示例 - 操作记事本的编码选择:
from pywinauto import Application
# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
# 输入内容
win.child_window(title="文本编辑器").set_text("hello world")
# 文件-另存为
win.menu_select('文件(F) -> 另存为(A)...')
# 操作弹出文件选择框
save_win = win.child_window(title="另存为", control_type="Window")
# 方法1: 使用select方法(可能导致屏幕闪烁)
save_win.child_window(title="编码(E):", control_type="ComboBox").select("UTF-8")
# 方法2: 更接近真实操作的方法(推荐)
# 1.先点开选项
save_win.child_window(title="编码(E):", control_type="ComboBox").click_input()
# 2.选择内容
save_win.child_window(title="UTF-8", control_type="ListItem").click_input()
注意:select()
方法虽然简便,但可能会导致屏幕闪烁;而使用click_input()
方法分两步操作更接近真实手工操作,通常是更稳定的选择。
7. 窗口和控件截图 capture_as_image()
pywinauto 提供了对窗口和控件进行截图的功能,这在调试和报告生成中非常有用:
# 安装必要的依赖
# pip install Pillow # PIL库是必需的,用于图像处理
from pywinauto import Application
# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
# 对整个窗口截图
window_image = win.capture_as_image()
window_image.save("window_screenshot.png")
# 对特定控件截图
menu = win.child_window(title="应用程序", auto_id="MenuBar", control_type="MenuBar")
menu_image = menu.capture_as_image()
menu_image.save("menu_screenshot.png")
# 对按钮控件截图
button_image = button.capture_as_image()
button_image.save("button_screenshot.png")
# 截取特定区域(需要使用PIL库的ImageGrab)
from PIL import ImageGrab
region_image = ImageGrab.grab(bbox=(100, 100, 300, 300)) # (left, top, right, bottom)
region_image.save("region_screenshot.png")
注意事项:
- 使用
capture_as_image()
功能需要安装 PIL 库 (pip install Pillow
) - 窗口截图可能会包含其他窗口的背景,特别是当窗口有透明部分时
- 控件截图更加精确,只截取控件本身的区域
- 可以使用 PIL 库的其他功能对截图进行处理,如裁剪、缩放、添加文字等
8. 键盘快捷键操作 send_keys()
pywinauto 提供了强大的键盘模拟功能,可以发送各种键盘按键和组合键:
from pywinauto.keyboard import send_keys
# 基本按键
send_keys("Hello World") # 输入文本
send_keys("{ENTER}") # 回车键
send_keys("{TAB}") # Tab键
send_keys("{F1}") # F1键
# 组合键
send_keys("^a") # Ctrl+A(全选)
send_keys("^c") # Ctrl+C(复制)
send_keys("^v") # Ctrl+V(粘贴)
send_keys("+{END}") # Shift+End(选择到行尾)
send_keys("%{F4}") # Alt+F4(关闭窗口)
# 特殊按键
send_keys("{VK_SPACE}") # 空格键
send_keys("{BACKSPACE}") # 退格键
send_keys("{DELETE}") # 删除键
send_keys("{PAUSE}") # 暂停键
# 直接向控件发送按键
edit_box.type_keys("文本内容{ENTER}")
控件文本输入的两种方法对比:
set_text方法:
- 直接设置控件的文本值
- 光标位置在最左边
- 按 enter 后,文本被换行
- 不支持快捷键操作
- 示例:
edit_box.set_text("hello world")
type_keys方法:
- 模拟真实的键盘输入
- 支持中文输入
- 光标位置在最右边
- 支持各种快捷键操作
- 示例:
edit_box.type_keys("hello world^s")
# 输入文本并保存(Ctrl+S)
示例 - 在记事本中使用快捷键:
from pywinauto import Application
from pywinauto.keyboard import send_keys
import time
# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
# 使用type_keys输入文本
win.type_keys("Hello World!^a") # 输入文本并全选(Ctrl+A)
time.sleep(1)
# 复制文本
send_keys("^c")
time.sleep(1)
# 粘贴文本
send_keys("^v^v") # 粘贴两次
time.sleep(1)
# 使用Alt+F4关闭记事本
send_keys("%{F4}")
推荐:对于文本输入,通常建议使用type_keys()
方法,因为它更接近真实用户操作,支持更多功能,并且在大多数情况下更可靠。
9. 操作 MenuItem 菜单项
pywinauto 提供了多种操作应用程序菜单的方法:
# 方法1: 使用menu_select方法(最简便的方法)
# 通过路径操作菜单项
main_window.menu_select("文件->打开") # 点击"文件"菜单下的"打开"子菜单
# 对于包含快捷键标记的菜单项
main_window.menu_select("文件(F)->打开(O)") # 带有快捷键标记的菜单项
main_window.menu_select('文件(&F)->打开(&O)') # 另一种快捷键标记形式
# 方法2: 获取菜单对象,然后操作
# 获取菜单对象
menu = main_window.menu()
# 获取主菜单项并点击
file_menu = menu.item_by_path("文件")
file_menu.click()
# 获取子菜单项并点击
open_menu = menu.item_by_path("文件->打开")
open_menu.click()
# 方法3: 使用快捷键访问菜单
from pywinauto.keyboard import send_keys
send_keys("%f") # Alt+F 打开"文件"菜单
send_keys("o") # 选择"打开"子菜单
实际应用示例 - 操作记事本的菜单:
from pywinauto import Application
import time
# 启动记事本
app = Application('uia').start("notepad.exe")
win = app.window(title_re="无标题 - 记事本")
win.wait('ready')
# 方法1: 使用menu_select操作"文件"菜单中的"页面设置"
win.menu_select("文件(F)->页面设置(U)...")
time.sleep(1)
# 关闭页面设置对话框
page_setup = app.window(title="页面设置")
page_setup.child_window(title="确定", control_type="Button").click()
# 方法2: 使用menu对象操作"编辑"菜单
menu = win.menu()
edit_menu = menu.item_by_path("编辑(E)")
edit_menu.click()
time.sleep(0.5)
# 点击"编辑"菜单下的"时间/日期"
date_menu = menu.item_by_path("编辑(E)->时间/日期(D)")
date_menu.click()
# 方法3: 使用快捷键
send_keys("^a") # Ctrl+A 全选
send_keys("^x") # Ctrl+X 剪切
注意事项:
- 在使用
menu_select()
时,菜单路径使用"->"分隔 - 如果菜单项有特殊字符,如"查找和替换",也可以直接包含在路径中
- 对于一些应用程序,菜单项可能包含访问键标记(如"文件(&F)"),需要正确包含这些标记
- 如果菜单项没有文本或文本识别有问题,可以使用索引方式,如
"#0->#1"
表示第一个主菜单的第二个子菜单
10. 获取控件属性和文本内容
# 获取控件文本
text = edit_box.texts() # 返回文本列表
text_str = edit_box.window_text() # 返回文本字符串
# 获取控件属性
enabled = button.is_enabled() # 控件是否启用
visible = button.is_visible() # 控件是否可见
rect = button.rectangle() # 控件矩形区域
# 获取特定属性
control_type = button.element_info.control_type
automation_id = button.element_info.automation_id
class_name = button.element_info.class_name
# 获取复选框或单选按钮状态
checked = checkbox.is_checked() # 复选框是否勾选
11. 操作弹出文件选择框
# 点击"打开文件"按钮
open_button.click()
# 等待文件选择对话框出现
file_dialog = app.window(title="打开")
file_dialog.wait('ready', timeout=10)
# 输入文件路径
file_path_edit = file_dialog.child_window(control_type="Edit")
file_path_edit.set_text("C:\\path\\to\\file.txt")
# 或者直接输入文件名
file_name_edit = file_dialog.child_window(control_type="Edit", found_index=0)
file_name_edit.set_text("file.txt")
# 点击"打开"按钮确认
open_dialog_button = file_dialog.child_window(title="打开", control_type="Button")
open_dialog_button.click()
12. 操作弹出新窗口上按钮
# 点击会弹出新窗口的按钮
button_popup = main_window.child_window(title="设置", control_type="Button")
button_popup.click()
# 等待新窗口出现
popup_window = app.window(title="设置对话框")
popup_window.wait('ready', timeout=10)
# 操作新窗口上的控件
ok_button = popup_window.child_window(title="确定", control_type="Button")
ok_button.click()
# 等待新窗口关闭
popup_window.wait_not('exists', timeout=5)
13. 操作级联菜单 menu_select()
# 复杂的多级菜单操作
main_window.menu_select("文件->导出->PDF文件")
# 如果菜单项有特殊字符或空格,使用引号
main_window.menu_select("编辑->查找和替换->高级替换")
# 使用索引操作菜单(如菜单项没有文本)
main_window.menu_select("#0->#1->#0") # 第一个主菜单->第二个子菜单->第一个子子菜单
14. 操作窗口控件 child_window()
更多控件查找和操作方法:
# 使用多种属性组合查找控件
button = main_window.child_window(title="保存", control_type="Button", auto_id="saveButton")
# 使用部分标题匹配
button = main_window.child_window(title_re="保存.*", control_type="Button")
# 获取所有特定类型的控件
all_buttons = main_window.children(control_type="Button")
all_edit_boxes = main_window.children(control_type="Edit")
# 获取直接子控件vs所有后代控件
direct_children = main_window.children()
all_descendants = main_window.descendants()
# 使用相对位置查找
above_control = main_window.child_window(control_type="Button", top_level_only=True)
below_control = above_control.get_next()
previous_control = below_control.get_previous()
15. 操作指定 window 窗口
# 通过标题获取窗口对象
notepad_window = app.window(title="无标题 - 记事本")
# 通过类名获取窗口
dialog = app.window(class_name="#32770")
# 通过自动化ID获取窗口
main_window = app.window(auto_id="mainWindow")
# 窗口操作
notepad_window.minimize() # 最小化窗口
notepad_window.maximize() # 最大化窗口
notepad_window.restore() # 还原窗口
notepad_window.close() # 关闭窗口
# 窗口状态检查
is_minimized = notepad_window.is_minimized()
is_maximized = notepad_window.is_maximized()
is_normal = notepad_window.is_normal()
16. 启动指定应用程序
# 简单启动(使用系统默认路径)
app = Application(backend="uia").start("notepad.exe")
# 指定完整路径启动
app = Application(backend="uia").start(r"C:\Windows\System32\notepad.exe")
# 启动并带参数
app = Application(backend="uia").start(r"notepad.exe C:\temp\test.txt")
# 启动并等待初始化完成
app = Application(backend="uia").start("explorer.exe")
app.wait_cpu_usage_lower(threshold=5, timeout=30) # 等待CPU使用率低于5%
# 以管理员权限启动
app = Application(backend="uia").start("cmd.exe", wait_for_idle=False)
错误处理与调试
1. 异常处理
使用 try-except 捕获常见异常:
from pywinauto.findwindows import ElementNotFoundError, ElementAmbiguousError
try:
button = main_window.child_window(title="确定", control_type="Button")
button.click()
except ElementNotFoundError:
print("未找到指定按钮")
# 进行替代操作
except ElementAmbiguousError:
print("找到多个匹配的按钮,请使用更精确的条件")
# 尝试使用更多条件进行查找
2. 调试技巧
调试自动化脚本时的有用方法:
# 1. 打印应用程序中所有顶层窗口
print(app.windows())
# 2. 打印当前窗口所有控件
main_window.print_control_identifiers(depth=None) # 打印所有层级
main_window.print_control_identifiers(depth=3) # 只打印3层深度
# 3. 将控件结构保存到文件
with open('controls.txt', 'w', encoding='utf-8') as f:
f.write(main_window.print_control_identifiers(depth=None, filename=None))
# 4. 高亮显示找到的控件
button.draw_outline(colour='red', thickness=2)
# 5. 在弹出对话框前暂停脚本执行
input("按Enter键继续...")
3. 性能优化
提高自动化脚本性能的方法:
# 1. 减少不必要的查找操作
button = main_window.child_window(title="确定", control_type="Button")
button.click()
button.wait_not('visible') # 使用同一个button对象
# 2. 缓存窗口对象
notepad_window = app.window(title="无标题 - 记事本")
# 在多次操作同一窗口时重用此对象
# 3. 使用更精确的查找条件
button = main_window.child_window(title="确定", control_type="Button", auto_id="OKButton")
# 添加auto_id等属性可以加快查找速度
# 4. 设置适当的超时时间
from pywinauto import timings
timings.Timings.window_find_timeout = 2.0 # 默认为5秒,可根据需要调整
实际应用场景
1. 文本编辑器自动化
# 自动化操作记事本
app = Application(backend="uia").start("notepad.exe")
notepad = app.window(title_re=".*记事本")
notepad.wait('ready')
# 输入文本
notepad.type_keys("这是一段自动输入的文本", with_spaces=True)
# 保存文件
notepad.menu_select("文件->保存")
save_dialog = app.window(title="另存为")
save_dialog.wait('ready')
file_name_edit = save_dialog.child_window(control_type="Edit")
file_name_edit.set_text("test.txt")
save_button = save_dialog.child_window(title="保存", control_type="Button")
save_button.click()
2. 表单填写自动化
# 连接到已打开的应用程序
app = Application(backend="uia").connect(title_re="注册表单.*")
form_window = app.window(title_re="注册表单.*")
# 填写表单字段
name_edit = form_window.child_window(title="姓名:", control_type="Edit", found_index=1)
name_edit.set_text("张三")
email_edit = form_window.child_window(title="电子邮件:", control_type="Edit", found_index=1)
email_edit.set_text("zhangsan@example.com")
# 选择下拉框选项
province_combo = form_window.child_window(title="省份:", control_type="ComboBox")
province_combo.select("北京")
# 选择单选按钮
gender_male = form_window.child_window(title="男", control_type="RadioButton")
gender_male.click()
# 勾选复选框
agreement_check = form_window.child_window(title="我同意服务条款", control_type="CheckBox")
if not agreement_check.is_checked():
agreement_check.click()
# 提交表单
submit_button = form_window.child_window(title="提交", control_type="Button")
submit_button.click()
3. 处理系统对话框
# 处理文件复制确认对话框
confirm_dialog = app.window(title="确认文件替换")
if confirm_dialog.exists():
yes_button = confirm_dialog.child_window(title="是", control_type="Button")
yes_button.click()
# 处理警告对话框
warning_dialog = app.window(title="警告")
if warning_dialog.exists():
ok_button = warning_dialog.child_window(title="确定", control_type="Button")
ok_button.click()
# 处理UAC提升权限对话框
uac_dialog = Desktop(backend="uia").window(title="用户帐户控制")
if uac_dialog.exists():
yes_button = uac_dialog.child_window(title="是", control_type="Button")
yes_button.click()
4. 复杂控件处理
# 处理日期选择器
date_picker = window.child_window(control_type="DateTimePicker")
date_picker.click_input() # 打开日期选择器
# 选择月份
month_combo = date_picker.child_window(control_type="ComboBox", found_index=0)
month_combo.select("12月")
# 选择日期
day_button = date_picker.child_window(title="25", control_type="Button")
day_button.click()
# 处理树形控件
tree_view = window.child_window(control_type="Tree")
tree_item = tree_view.get_item(['父节点', '子节点', '叶子节点'])
tree_item.click()
# 处理列表视图
list_view = window.child_window(control_type="List")
list_items = list_view.items()
for item in list_items:
print(item.texts())
常见问题与解决方案
找不到窗口或控件:
- 问题:
ElementNotFoundError
错误 解决方案:
- 使用
print(window.dump_tree())
或print_control_identifiers()
查看控件层次结构 - 检查控件的标识符是否正确(标题、ID、类名等)
- 增加等待时间,某些控件可能需要时间加载
- 尝试使用不同的属性组合来定位控件
- 使用
- 问题:
操作过快导致失败:
- 问题:自动化操作太快,应用程序反应不过来
解决方案:
- 在关键操作之间添加
time.sleep()
适当延时 - 增加全局等待时间
timings.Timings.slow()
- 使用
wait()
和wait_not()
等待控件状态变化
- 在关键操作之间添加
匹配到多个控件:
- 问题:
ElementAmbiguousError
错误 解决方案:
- 使用
found_index
参数指定具体的控件 - 添加更多的属性条件缩小匹配范围
- 使用
children()
获取所有匹配控件,然后遍历处理
- 使用
- 问题:
后端选择问题:
- 问题:某些应用程序用一种后端无法正常工作
解决方案:
- 现代应用通常使用 "uia" 后端:
Application(backend="uia")
- 旧应用程序可能需要 "win32" 后端:
Application(backend="win32")
- 尝试两种后端,看哪种更适合你的应用
- 现代应用通常使用 "uia" 后端:
控件识别不稳定:
- 问题:同一个控件有时能找到,有时找不到
解决方案:
- 使用多个属性结合定位控件,如
title
、class_name
、control_type
结合 - 使用
title_re
进行模糊匹配:child_window(title_re=".*保存.*")
- 先定位父控件,再从父控件中查找子控件
- 使用多个属性结合定位控件,如
权限不足:
- 问题:无法操作需要管理员权限的应用
解决方案:
- 以管理员身份运行 Python 脚本
- 处理 UAC 对话框:
Desktop().window(title="用户帐户控制").child_window(title="是").click()
文本输入问题:
- 问题:
type_keys()
或set_text()
无法正确输入文本 解决方案:
- 对于复杂输入,优先使用
type_keys()
- 对于中文等非 ASCII 字符,确保正确的编码
- 尝试先清空文本框:
edit_box.set_text("")
然后再输入
- 对于复杂输入,优先使用
- 问题:
菜单操作失败:
- 问题:
menu_select()
无法找到或点击菜单项 解决方案:
- 确认菜单路径是否正确,包括访问键标记如 "(F)"
- 尝试使用快捷键操作菜单
- 使用菜单对象直接操作:
menu.item_by_path("文件").click()
- 问题:
动态变化的 UI:
- 问题:UI 元素位置或属性动态变化
解决方案:
- 使用更稳定的属性(如
automation_id
)而非位置来定位 - 实现异常处理和重试机制
- 使用条件检查确认 UI 状态后再操作
- 使用更稳定的属性(如
性能问题:
- 问题:自动化脚本运行缓慢
解决方案:
- 缓存常用窗口和控件对象,避免重复查找
- 使用更精确的查找条件加快查找速度
- 调整 timings 设置,减少不必要的等待时间
- 对于大型应用程序,使用
wait_cpu_usage_lower()
等待 CPU 使用率降低
截图失败:
- 问题:
capture_as_image()
返回 None 或截图不完整 解决方案:
- 确保已安装 PIL 库:
pip install Pillow
- 确保窗口在前台且可见
- 对于部分最小化的窗口,先恢复窗口状态
- 确保已安装 PIL 库:
- 问题:
ComboBox 操作问题:
- 问题:下拉框选择项目失败
解决方案:
- 使用两步操作:先
click_input()
展开,再选择项目 - 检查 ComboBox 类型,不同类型的 ComboBox 可能需要不同操作方式
- 使用两步操作:先
实用技巧
- 使用
spy++
或 Windows 自带的 "Inspect" 工具帮助识别窗口和控件属性 - 使用
print(control.print_control_identifiers())
打印控件的所有可能标识符 - 对于复杂的窗口结构,使用
dump_tree()
方法查看完整的控件层次 - 对于调试,使用
draw_outline()
方法高亮显示找到的控件 - 捕获异常以处理可能出现的窗口或控件未找到情况
- 对于性能敏感的应用,尝试使用
app.wait_cpu_usage_lower()
等待 CPU 使用率降低
示例代码
from pywinauto.application import Application
import time
# 启动计算器
app = Application(backend="uia").start("calc.exe")
time.sleep(1)
# 获取主窗口
calc = app.window(title="计算器")
# 点击数字和运算符
calc.child_window(title="1", control_type="Button").click()
calc.child_window(title="加", control_type="Button").click()
calc.child_window(title="2", control_type="Button").click()
calc.child_window(title="等于", control_type="Button").click()
# 获取结果
result = calc.child_window(auto_id="CalculatorResults").texts()[0]
print(f"计算结果: {result}")