桌面应用控制基础
为什么桌面自动化更复杂?
与 Web 应用相比,桌面应用的自动化有几个额外挑战:没有 DOM 结构可以查询、窗口可能被遮挡或不在焦点、需要操作系统级别的 API 才能可靠地控制、不同平台(Windows/macOS/Linux)需要不同的实现。
应用程序启动
import subprocess
import time
import sys
def launch_application(app_path: str, args: list[str] = []) -> subprocess.Popen:
"""启动应用程序并等待其加载"""
if sys.platform == "darwin": # macOS
cmd = ["open", "-a", app_path] + args
elif sys.platform == "win32": # Windows
cmd = [app_path] + args
else: # Linux
cmd = [app_path] + args
process = subprocess.Popen(cmd)
time.sleep(2) # 等待应用启动
return process
# 常用应用启动示例
def open_in_vscode(path: str):
launch_application("code", [path])
def open_terminal():
if sys.platform == "darwin":
subprocess.Popen(["open", "-a", "Terminal"])
elif sys.platform == "linux":
subprocess.Popen(["gnome-terminal"])
窗口管理
pygetwindow:跨平台窗口控制
pip install pygetwindow # Windows/macOS
# Linux 需要额外的 X11 支持
pip install ewmh # Linux EWMH 支持
import pygetwindow as gw
import time
class WindowManager:
"""跨平台窗口管理工具"""
def find_window(self, title_contains: str) -> Optional[object]:
"""通过标题查找窗口"""
windows = gw.getWindowsWithTitle(title_contains)
if not windows:
return None
return windows[0]
def focus_window(self, window) -> bool:
"""将指定窗口带到前台并聚焦"""
try:
window.activate()
time.sleep(0.3) # 等待窗口激活
return True
except Exception:
return False
def maximize_window(self, window):
"""最大化窗口"""
window.maximize()
time.sleep(0.3)
def get_window_position(self, window) -> tuple[int, int, int, int]:
"""获取窗口位置和大小 (x, y, width, height)"""
return window.left, window.top, window.width, window.height
def list_all_windows(self) -> list[str]:
"""列出所有可见窗口的标题"""
return [w.title for w in gw.getAllWindows() if w.title]
macOS Accessibility API
使用 applescript 控制 macOS 应用
import subprocess
def run_applescript(script: str) -> str:
"""执行 AppleScript 并返回结果"""
result = subprocess.run(
["osascript", "-e", script],
capture_output=True,
text=True
)
return result.stdout.strip()
# 常用 AppleScript 操作
def activate_app_macos(app_name: str):
"""激活 macOS 应用程序"""
run_applescript(f'tell application "{app_name}" to activate')
time.sleep(0.5)
def get_frontmost_app_macos() -> str:
"""获取当前最前台的应用名称"""
return run_applescript(
'tell application "System Events" to get name of first process whose frontmost is true'
)
def click_menu_item_macos(app_name: str, menu: str, item: str):
"""点击 macOS 应用的菜单项"""
script = f"""
tell application "{app_name}" to activate
tell application "System Events"
tell process "{app_name}"
click menu item "{item}" of menu "{menu}" of menu bar 1
end tell
end tell
"""
run_applescript(script)
# 使用示例:打开 VS Code 的命令面板
activate_app_macos("Visual Studio Code")
click_menu_item_macos("Code", "View", "Command Palette...")
系统对话框处理
文件打开/保存对话框
import pyautogui
import time
def handle_file_dialog(file_path: str, dialog_type: str = "open") -> bool:
"""
处理系统文件对话框(打开或保存)。
Args:
file_path: 要打开或保存的文件路径
dialog_type: "open" 或 "save"
"""
time.sleep(1) # 等待对话框出现
if sys.platform == "darwin": # macOS
# 使用 AppleScript 直接在对话框中输入路径
script = f"""
tell application "System Events"
keystroke "g" using {{command down, shift down}}
delay 0.5
keystroke "{file_path}"
keystroke return
end tell
"""
run_applescript(script)
elif sys.platform == "win32": # Windows
# 在地址栏输入路径
pyautogui.typewrite(file_path, interval=0.05)
pyautogui.press("enter")
time.sleep(0.5)
return True
跨应用工作流示例
实战:Excel 数据处理工作流
"""
典型的跨应用工作流:
1. 打开邮件附件(Excel 文件)
2. 在 Excel 中处理数据
3. 将结果粘贴到内部 Web 系统
"""
async def process_excel_to_web_system(excel_path: str) -> None:
task = f"""
请执行以下工作流:
1. 打开文件:{excel_path}
2. 在 Excel/Numbers 中:
- 找到 A 列(姓名)和 B 列(金额)
- 计算 B 列的总和(放入 B 最后一行下方)
- 截图确认数据正确
3. 打开浏览器,导航到 http://internal.company.com/orders
4. 在"批量导入"功能中:
- 点击"新建导入"
- 将 Excel 中的数据逐行录入表单
- 在"备注"字段填写今天的日期
5. 提交并确认成功
"""
await run_computer_use_agent(task)
桌面应用操作的安全注意事项
桌面应用 Agent 拥有极大的权限,请特别注意:不要授权 Agent 在生产环境直接操作(先在测试环境验证);涉及文件删除/修改的操作要有备份;涉及金融操作(如转账)要增加人工确认步骤;运行 Agent 时全程监视屏幕。
窗口识别与 UI Automation
Windows UI Automation API
Windows 提供了 UI Automation(简称 UIA)API,可以用编程方式访问 Windows 应用的 UI 元素,类似于 Web 的 DOM。这比纯视觉操作更精确,但需要应用支持 UIA 接口:
# pip install pywinauto # Windows 专用库
from pywinauto import Application, Desktop
from pywinauto.findwindows import ElementNotFoundError
import time
class WindowsUIController:
"""Windows UI Automation 控制器(仅 Windows 平台)"""
def find_and_control_app(self, title_contains: str):
"""连接到已运行的应用程序"""
try:
app = Application(backend="uia").connect(title_re=f".*{title_contains}.*")
return app
except ElementNotFoundError:
return None
def click_button_by_name(self, app, window_title: str, button_name: str):
"""
通过按钮名称(Accessibility Name)点击按钮。
比坐标点击更可靠——即使 UI 布局变化,只要名称不变就能点到。
"""
window = app.window(title_re=f".*{window_title}.*")
button = window.child_window(title=button_name, control_type="Button")
button.click_input() # click_input() 模拟真实点击,wrapper_object().click() 是 API 调用
def fill_form_field(self, app, window_title: str, field_name: str, value: str):
"""
通过字段名称填写表单。
先获取 Edit 控件,然后设置文本值。
"""
window = app.window(title_re=f".*{window_title}.*")
edit = window.child_window(title=field_name, control_type="Edit")
edit.set_edit_text(value)
def get_all_controls(self, app, window_title: str) -> list[str]:
"""
列出窗口中所有可交互的控件及其名称和类型。
用于"探索"一个未知应用的 UI 结构。
"""
window = app.window(title_re=f".*{window_title}.*")
controls = []
for ctrl in window.descendants():
try:
name = ctrl.window_text()
ctrl_type = ctrl.element_info().control_type
if name:
controls.append(f"{ctrl_type}: {name}")
except:
pass
return controls
macOS Accessibility API(PyAutoGUI 替代方案)
# macOS 上更可靠的窗口控制方案
import subprocess
class MacOSAccessibilityController:
"""使用 AppleScript 和 Accessibility API 控制 macOS 应用"""
def get_window_elements(self, app_name: str) -> list:
"""
获取应用窗口的所有 UI 元素。
比截图+坐标更精确,特别是当 UI 会动态调整布局时。
"""
script = f"""
tell application "System Events"
tell process "{app_name}"
set uiElements to entire contents of window 1
set elementList to {{}}
repeat with elem in uiElements
try
set end of elementList to {{class of elem, name of elem, position of elem}}
end try
end repeat
return elementList
end tell
end tell
"""
result = subprocess.run(
["osascript", "-e", script],
capture_output=True, text=True
)
return result.stdout.strip()
def click_element_by_description(
self,
app_name: str,
element_description: str
) -> bool:
"""
通过描述(如按钮文字)点击元素。
比坐标点击更鲁棒,适合 UI 频繁更新的应用。
"""
script = f"""
tell application "System Events"
tell process "{app_name}"
try
click button "{element_description}" of window 1
return true
on error
try
click menu item "{element_description}" of menu bar 1
return true
end try
return false
end try
end tell
end tell
"""
result = subprocess.run(
["osascript", "-e", script],
capture_output=True, text=True
)
return "true" in result.stdout.lower()
常见桌面应用的操作模式
Excel/Numbers 操作
async def manipulate_excel(
agent_controller,
file_path: str,
task_description: str
) -> str:
"""
让 Computer Use Agent 操作 Excel 文件。
相比直接用 openpyxl 读写,适合需要执行宏、
使用 Excel 特殊功能(如数据透视表)的场景。
"""
# 首先通过 bash 工具检查文件是否存在
check_task = f"""
请检查文件 {file_path} 是否存在,如果存在请打开它。
使用 bash 工具执行:ls -la "{file_path}"
如果文件存在(返回码为 0),使用 computer 工具打开该文件。
打开后执行以下任务:{task_description}
"""
return await agent_controller.run_task(check_task)
# 常见 Excel 操作的提示词模板
EXCEL_TASK_TEMPLATES = {
"sum_column": """在 Excel 中:
1. 截图查看当前状态
2. 找到 {column_name} 列
3. 在该列最后一行的下一行输入 SUM 公式
4. 确认公式计算结果正确
5. 按 Ctrl+S 保存文件""",
"filter_and_export": """在 Excel 中:
1. 截图查看数据结构
2. 在 {column} 列上应用筛选,条件:{condition}
3. 全选筛选后的数据(Ctrl+A)
4. 复制(Ctrl+C)
5. 打开新的 Excel 文件
6. 粘贴数据(Ctrl+V)
7. 保存为 {output_file}"""
}
何时用 Accessibility API,何时用视觉操作
决策原则:如果目标应用支持 Accessibility API(Windows UIA 或 macOS NSAccessibility),优先使用 API 方式操作——更可靠、更快、不受 UI 布局变化影响。当 API 方式不可行(如老旧应用、游戏、自定义渲染引擎),才回退到视觉+坐标操作。Computer Use 的截图操作是"万能后备",不是首选方案。
章节小结
本章讲解了桌面应用 Agent 的核心技术和模式。关键要点:
- 桌面自动化比 Web 更复杂:无 DOM 结构、需要窗口管理、多平台 API 差异
- Windows UIA(pywinauto)和 macOS NSAccessibility(AppleScript)比坐标点击更可靠
- 应用启动后要等待足够时间(2-3s),窗口激活后要截图确认,再执行后续操作
- 文件对话框处理:macOS 用 Cmd+Shift+G 打开路径输入框,Windows 直接在地址栏输入
- Excel 等办公软件场景:能用 openpyxl 直接读写的就不要走 GUI,GUI 方式用于执行宏或透视表等特殊功能