如何使用Python和AppleScript读取macOS应用菜单栏选项
读取macOS应用菜单栏选项的实现方法
完全可以通过AppleScript和Python实现自动读取任意应用全量菜单栏选项的需求,核心是调用macOS系统自带的无障碍(Accessibility)接口,不需要提前预知目标应用的菜单结构,具体实现方案如下:
方案1:原生AppleScript实现(无额外依赖)
该方案不需要安装任何第三方工具,直接通过系统自带的osascript即可运行,核心逻辑是递归遍历菜单栏的层级结构,自动抓取所有层级的菜单项文本。
运行前需要先给执行脚本的程序(终端、脚本编辑器等)开启辅助功能权限:打开系统设置>隐私与安全性>辅助功能,勾选对应程序即可,否则系统会拦截读取请求。
示例代码:
-- 修改为你要读取的目标应用名称 set targetAppName to "Safari" tell application "System Events" tell application process targetAppName set frontmost to true -- 递归遍历菜单层级的通用方法 on traverseMenuElements(elements, indentLevel) set outputList to {} repeat with elem in elements try set elemName to name of elem if elemName is not missing value and elemName ≠ "" then -- 用缩进标识菜单层级 set indentText to "" repeat indentLevel times set indentText to indentText & " " end repeat copy (indentText & elemName) to end of outputList -- 存在子菜单则继续向下遍历 if exists menu of elem then set subItems to every menu item of menu 1 of elem set subOutput to my traverseMenuElements(subItems, indentLevel + 1) set outputList to outputList & subOutput end if end if end try end repeat return outputList end traverseMenuElements -- 读取顶层菜单栏入口 set topMenus to every menu of menu bar 1 set allMenuOptions to my traverseMenuElements(topMenus, 0) end tell end tell -- 格式化输出结果 set AppleScript's text item delimiters to linefeed return allMenuOptions as text
运行后会返回带缩进的层级化菜单列表,完整覆盖应用的所有菜单栏选项。
方案2:Python实现
Python侧有两种实现路径,不需要额外学习AppleScript语法也可以完成需求:
- 轻量方案:通过
subprocess模块调用系统osascript命令,执行上面的AppleScript逻辑并拿回解析结果,不需要额外安装第三方依赖,示例代码如下:
import subprocess def get_app_menu_items(app_name: str) -> list[str]: as_script = f''' set targetAppName to "{app_name}" tell application "System Events" tell application process targetAppName set frontmost to true on traverseMenuElements(elements, indentLevel) set outputList to {{}} repeat with elem in elements try set elemName to name of elem if elemName is not missing value and elemName ≠ "" then set indentText to "" repeat indentLevel times set indentText to indentText & " " end repeat copy (indentText & elemName) to end of outputList if exists menu of elem then set subItems to every menu item of menu 1 of elem set subOutput to my traverseMenuElements(subItems, indentLevel + 1) set outputList to outputList & subOutput end if end if end try end repeat return outputList end traverseMenuElements set topMenus to every menu of menu bar 1 set allMenuOptions to my traverseMenuElements(topMenus, 0) end tell end tell set AppleScript's text item delimiters to linefeed return allMenuOptions as text ''' exec_result = subprocess.run( ["osascript", "-e", as_script], capture_output=True, text=True ) if exec_result.returncode != 0: raise PermissionError(f"读取失败,请检查是否已授予辅助功能权限,错误信息:{exec_result.stderr}") return [line for line in exec_result.stdout.split("\n") if line.strip()] # 调用示例 if __name__ == "__main__": menu_items = get_app_menu_items("Safari") for item in menu_items: print(item)
- 原生API方案:安装
pyobjc相关依赖后直接调用Cocoa层的无障碍接口,不需要内嵌AppleScript,灵活度更高,可以额外读取菜单项的可用状态、绑定快捷键等属性,适合复杂自动化场景,依赖安装命令为pip install pyobjc-framework-ApplicationServices pyobjc-framework-Cocoa。
注意事项
- 权限是最常见的报错原因:第一次运行时系统会自动弹出权限申请,按指引开启后需要重启脚本/终端才能生效。
- 目标应用必须处于运行状态,脚本会自动将应用切到前台再读取,应用处于最小化、全屏状态时可能出现部分菜单读取不全的问题,建议保持普通窗口状态运行。
- 少数使用自定义绘制实现的非标准菜单项(未对接系统无障碍接口)无法被读取,这是应用本身的实现限制,没有通用绕过方法。
内容的提问来源于stack exchange,提问作者user43535
相关产品推荐
相关产品推荐

