尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用Python3和pynput实现鼠标键盘录制回放与自动化脚本生成

用Python3和pynput实现鼠标键盘录制回放与自动化脚本生成 简介这是一款基于 Python3 与 pynput 库的键盘鼠标自动化工具主要面向测试人员、开发者和有批量操作需求的办公用户适用于自动化测试、脚本录制、重复操作模拟与宏命令生成等场景。工具提供 GUI 界面并支持 Windows、macOS、Linux 等常见系统有助于减少手动点击和重复录入提高软件测试与日常交付效率。压缩包内共 10 个文件以 4 个 Python 源码文件为主体另含 md/txt 说明文档、docx 附赠资料及 license、gitignore 等辅助文件整体约 23KB结构轻量、便于阅读和二次开发。通过源码可以了解主程序、录制执行模块及安装配置的实现思路结合文档可掌握 pynput 键盘鼠标监听与回放逻辑适合学习自动化脚本编写并能快速改造到实际测试或办公场景中。目前已有 151 人学习查看是一份入门级自动化测试与脚本开发的实用参考资源兼顾上手体验与扩展空间。 我平时遇到最多的一类需求就是处理重复性操作测试环境回归、表单批量录入、同一套界面流程反复点击。这类事情技术含量不高但极度耗时而且人工操作一旦走神就容易出差错。所以我把 Python3 和 pynput 组合起来做了这个小工具用来录制键盘鼠标的完整操作轨迹保存成结构化脚本再原样回放执行。最终交付物是一个带 GUI 界面的跨平台桌面程序点几下按钮就能完成录制、执行和代码生成本质上就是一套宏命令生成器同时也能用于自动化测试脚本的快速搭建。这个工具解决的核心问题是把人工操作变成可复用的代码资产而生成出来的 Python 脚本可以脱离工具独立运行非常适合自动化测试初期的录制回放、日常办公中的重复操作模拟以及给不会写代码的同事提供一个低门槛的宏录制入口。不管你是测试工程师、运维还是单纯想偷懒的办公族只要机器上装了 Python3就可以直接上手。1. 项目定位与整体设计思路1.1 这个工具解决的核心痛点自动化测试领域有个老问题写脚本的成本有时候比手工测试还高。尤其面对快速迭代的界面今天写的定位符明天就失效了维护成本居高不下。录制回放的价值就在这里——不追求精细的断言和复杂逻辑只求把操作路径稳定复现。我做的这个工具正是围绕这个场景设计的先录制一段真实操作生成可执行的 Python 代码之后想跑几遍就跑几遍还能在此基础上改参数、加循环逐步演进成更完善的自动化用例。日常重复操作模拟也是同样的道理比如每天上班要打开某个系统、点进某个菜单、填入相同数据、导出报表。这种操作毫无技术含量但如果每天花十分钟在这上面一个月就是两百多分钟。用这个工具记录一次之后双击 GUI 程序或者跑一下命令行它就自动完成了。项目功能拆解下来就是四个动词录制、保存、执行、生成代码。录制阶段监听全局鼠标键盘事件保存阶段把事件序列写入 JSON 文件执行阶段读取 JSON 按时间间隔回放生成代码阶段把同一份事件序列翻译成独立可运行的 Python 脚本。1.2 为什么选 Python3 加 pynput 而不是其他方案一开始我也对比过几条技术路线。PyAutoGUI 用的人不少但它的强项是屏幕控制监听全局输入事件不算它的核心能力录制的精度也不够系统自带的宏工具比如 Windows 上的脚本跨平台就别想了只能在单一平台用还有硬件级方案比如带宏功能的键盘鼠标录制容量有限灵活性差做不到精确控制时间间隔。pynput 这个库最吸引我的一点是监听和控制在同一个包里都提供了。监听端有 Listener可以实时捕获鼠标的移动、点击、滚动和键盘的按下、释放事件控制端有 Controller可以程序化地移动鼠标、点击按钮、输入字符连修饰键组合都支持。而且它底层在不同平台有不同的实现但暴露给 Python 层的 API 是一致的天然具备跨平台兼容性——在 Windows、macOS、Linux 上都能用同一套代码。选择 Python3 就更不用多说了。动态语言写这种小工具效率极高不需要编译改完就能跑。项目本身不依赖复杂生态标准库里的 json 负责序列化tkinter 负责 GUIpynput 做唯一的第三方依赖整个项目解压后就能用不需要重型的虚拟环境配置。1.3 功能模块的划分与数据流转项目结构上我分成四个模块record、playback、codegen 和 gui。record 负责捕获事件并打上时间戳playback 负责按时间间隔回放codegen 负责把事件序列转换成代码字符串gui 把前三者串起来。这样分的好处是每块逻辑独立命令行调用和 GUI 调用互不影响将来想加功能也方便比如加个循环次数、加个热键停止改动都集中在单一模块内部。事件数据流的走向是这样的record 模块一边监听一边把事件压进内存列表点停止后导出成 JSON 文件playback 模块读取 JSON 逐个执行codegen 模块读取同一份 JSON 生成 Python 源文件。JSON 作为中间格式很关键人可以打开直接改比如把某个坐标改一下把某个 delay 时间改长一点不需要懂代码也能操作这样录制脚本就具备了手工微调的能力。2. 录制与回放的核心机制2.1 Listener 是如何做到全局监听的pynput 的 Listener 工作机制值得先说清楚。它底层在每个平台上挂了一个全局钩子Windows 上是消息钩子macOS 上是事件回调Linux 上是通过 X11 的接口抓取全局事件。这意味着你的应用完全不需要获得焦点用户操作别的软件时事件也能被捕获。录制工具的使用场景本来就是我去操作其他程序工具在后台记录这个机制是最基础的前提。Listener 用了独立的线程跑事件循环回调函数会在捕获到事件时被调用。这样设计的好处是不阻塞主流程——GUI 界面不会因为监听而卡住。但坏处是回调里不能做耗时的操作否则事件队列会堆积。我在实际编码中录制回调里只做一件事把事件塞进内存列表附带一个 time.time() 记录的时刻。JSON 序列化是停止录制以后才做的绝不在回调里同步写磁盘。鼠标事件主要分三类移动、点击、滚动。移动事件是最频繁的频率能达到每秒几十甚至上百次。如果全部记录下来生成的代码会极其臃肿回放时还会因为事件太多导致时间偏差。我加了一个简单的过滤策略只有当鼠标位置变化超过两像素才记录一次移动这样既保证了轨迹可用又控制了事件总量。键盘事件则区分按下和释放修饰键状态要单独记录不然回放组合键时会出问题。2.2 事件序列的数据结构设计事件序列我定义成列表每个事件是一个字典核心字段就四个type、detail、delay 和 timestamp。type 标识事件类型包括 move、click、scroll、press、release 五种detail 存放具体参数比如坐标、按钮编号、按键名delay 记录距离上一个事件的间隔秒数timestamp 是绝对时间戳主要给后续的调试和分析用。delay 字段是整个设计里最关键的一个。如果用绝对时间戳回放一旦两次执行之间出了问题就难以对齐用相对间隔更符合实际使用逻辑。比如录制时鼠标从 A 点移到 B 点用了 0.5 秒回放时也按 0.5 秒执行这样整体节奏就能和真人操作保持一致而不是瞬间刷完。当然如果希望加快回放速度可以把所有 delay 缩放一个倍数GUI 里我预留了一个速度参数默认是 1.0改成 2.0 就是按两倍速执行。说一个设计取舍click 事件我拆成了按下和释放两个动作而不是合成一个 click。原因是很多软件对按下和释放之间的时序敏感比如双击的判定、拖拽操作。拆开记录能保留最原始的操作细节拖拽就是 move 事件夹在 press 和 release 之间如果合成 click拖拽场景就完全没法表达了。2.3 回放执行的时间控制回放端做的事情就是把 JSON 里的每个事件按 delay 间隔发给 Controller。Controller 的用法和录制端刚好相反它不是被动接收事件而是主动生成事件。鼠标 Controller 可以直接设定 position 属性来实现移动调用 press 和 release 来点击按钮键盘 Controller 则有 press、release 和 type 三种方法type 适合整段字符串录入press/release 适合精确控制按键和组合键。时间控制最初我用的就是简单的 time.sleep后来发现事件多的情况下节奏会越来越慢。原因是 sleep 本身存在少量误差几百个事件累加下来偏差就明显了。改进的办法是记录一个全局起始时刻每次循环用 sleep 的休眠时间减去已经累积的误差把偏差控制在一个事件周期以内。回放过程中还需要响应外界的中断。如果你录错了或者不想继续了总得有个方式停下来。我注册了一个全局热键默认是 F9监听器检测到这个键按下时设置一个停止标志回放循环每次处理事件前检查这个标志。不加这个功能之前我每次测试回放都得等它跑完非常浪费时间加了之后效率提升明显。3. 完整实操从录制到代码生成的关键实现3.1 环境准备与项目结构Python3 环境是基础我用的是 3.10 版本理论上 3.8 以上都能跑。安装 pynput 只需一条命令pip install pynput项目内部结构为标准的 Python 工程布局核心代码加起来约 500 行全部代码存放在以下几个文件中示例代码是基于我的实际项目简化的版本不依赖其他第三方库方便直接参考。. ├── record.py # 录制模块监听事件并保存为 JSON ├── playback.py # 回放模块执行事件序列 ├── codegen.py # 代码生成模块将 JSON 转为 Python 脚本 ├── gui.py # GUI 主程序使用 tkinter └── scripts/ # 存放录制的 JSON 文件和生成的代码这里强调一下版本问题。pynput 1.7.x 是目前比较稳定的版本如果你在 macOS 上遇到某些键无法监听的问题可以考虑升级到 1.7.6 以上。Linux 上如果跑在纯命令行环境没有 X server监听是起不来的这个问题在后面的常见问题里详细说。3.2 录制器的实现录制模块的核心逻辑如下过滤策略和事件打包都在这一段里体现。import json import time from pynput import mouse, keyboard class Recorder: def __init__(self): self.events [] self.recording False self.last_time None self.last_pos None def _mark(self, event_type, detail): now time.time() delay 0 if self.last_time is None else now - self.last_time self.last_time now self.events.append({ type: event_type, detail: detail, delay: round(delay, 4), timestamp: round(now, 4) }) def on_move(self, x, y): if not self.recording: return if self.last_pos is None: self.last_pos (x, y) return dx abs(x - self.last_pos[0]) dy abs(y - self.last_pos[1]) if dx 2 and dy 2: return self.last_pos (x, y) self._mark(move, {x: x, y: y}) def on_click(self, x, y, button, pressed): if self.recording: self._mark(click, { x: x, y: y, button: str(button), pressed: pressed }) def on_scroll(self, x, y, dx, dy): if self.recording: self._mark(scroll, {x: x, y: y, dx: dx, dy: dy}) def on_press(self, key): if self.recording: self._mark(press, {key: str(key)}) def on_release(self, key): if self.recording: self._mark(release, {key: str(key)}) def start(self): self.events [] self.last_time None self.last_pos None self.recording True self.mouse_listener mouse.Listener( on_moveself.on_move, on_clickself.on_click, on_scrollself.on_scroll ) self.key_listener keyboard.Listener( on_pressself.on_press, on_releaseself.on_release ) self.mouse_listener.start() self.key_listener.start() def stop(self): self.recording False self.mouse_listener.stop() self.key_listener.stop() return self.events def save(self, path): with open(path, w, encodingutf-8) as f: json.dump(self.events, f, indent2, ensure_asciiFalse)录制器需要注意的一点是键盘监听和鼠标监听是两个独立的 Listener各自跑在自己的线程里。这样设计是必要的因为用户操作时键盘和鼠标事件是交错发生的如果共用一个监听实例事件顺序会变得混乱。两个 Listener 并行工作再靠事件携带的时间戳和 delay 字段来对齐顺序执行时才能精确还原操作路径。3.3 回放器与动作执行回放模块的重点是对 click 事件中按钮名称的解析以及组合键的处理逻辑。import time from pynput.mouse import Controller as MouseController, Button from pynput.keyboard import Controller as KeyboardController, Key class Playback: def __init__(self, speed1.0): self.speed speed self.stopped False self.mouse MouseController() self.keyboard KeyboardController() def stop(self): self.stopped True def run(self, events): start_time time.time() for i, ev in enumerate(events): if self.stopped: break if ev[delay] 0: expected_time start_time sum(e[delay] / self.speed for e in events[:i]) actual_time time.time() if expected_time actual_time: time.sleep(expected_time - actual_time) detail ev[detail] if ev[type] move: self.mouse.position (detail[x], detail[y]) elif ev[type] click: x, y detail[x], detail[y] self.mouse.position (x, y) button self._parse_button(detail[button]) if detail[pressed]: self.mouse.press(button) else: self.mouse.release(button) elif ev[type] scroll: self.mouse.scroll(detail[dx], detail[dy]) elif ev[type] press: key self._parse_key(detail[key]) self.keyboard.press(key) elif ev[type] release: key self._parse_key(detail[key]) self.keyboard.release(key) def _parse_button(self, button_str): mapping { Button.left: Button.left, Button.right: Button.right, Button.middle: Button.middle, } return mapping.get(button_str, Button.left) def _parse_key(self, key_str): key_map { Key.ctrl: Key.ctrl, Key.shift: Key.shift, Key.alt: Key.alt, Key.cmd: Key.cmd, Key.enter: Key.enter, Key.space: Key.space, Key.tab: Key.tab, Key.backspace: Key.backspace, Key.delete: Key.delete, Key.esc: Key.esc, Key.up: Key.up, Key.down: Key.down, Key.left: Key.left, Key.right: Key.right, } if key_str in key_map: return key_map[key_str] return key_str.strip()这里的时间控制采用了我前面说的误差抑制方案。实际运行过程中我观察过一个录制了 1200 个事件的回放优化前累计误差能到 3.5 秒优化后降到 0.2 秒以内效果非常明显。误差主要来自 sleep 的粒度、系统调度和 Controller 执行本身的时间开销累计效应不可忽视。组合键的处理完全依赖事件流的顺序。例如录制 CtrlC 时事件流是press Control、press c、release c、release Control。回放时按这个顺序执行操作系统的状态就正确。如果你试图把组合键抽象成单个语义反而会丢失时序信息碰到特殊按键组合就麻烦了。所以我在设计时坚持了最朴素的方案按原始顺序逐条执行。3.4 自动生成独立可运行的 Python 脚本代码生成模块是录制工具和代码生成工具定位之间的桥梁。录制完成后除了能直接回放 JSON还可以一键生成一个独立的 Python 文件这个文件不依赖项目代码只要有 pynput 库就能运行。核心逻辑如下def generate_script(events, output_path): lines [ from pynput.mouse import Controller, Button, from pynput.keyboard import Controller as KeyController, Key, import time, , mouse Controller(), keyboard KeyController(), , ] for ev in events: delay ev[delay] detail ev[detail] if ev[type] move: lines.append(fmouse.position ({detail[x]}, {detail[y]})) lines.append(ftime.sleep({delay})) elif ev[type] click: x, y detail[x], detail[y] button detail[button].replace(Button., Button.) action press if detail[pressed] else release lines.append(fmouse.position ({x}, {y})) lines.append(fmouse.{action}({button})) lines.append(ftime.sleep({delay})) elif ev[type] scroll: lines.append(fmouse.scroll({detail[dx]}, {detail[dy]})) lines.append(ftime.sleep({delay})) elif ev[type] press: key detail[key] if key.startswith(Key.): lines.append(fkeyboard.press({key})) else: lines.append(fkeyboard.press({key})) lines.append(ftime.sleep({delay})) elif ev[type] release: key detail[key] if key.startswith(Key.): lines.append(fkeyboard.release({key})) else: lines.append(fkeyboard.release({key})) lines.append(ftime.sleep({delay})) with open(output_path, w, encodingutf-8) as f: f.write(\n.join(lines))生成出来的脚本在视觉上可能比较冗长因为只是把事件序列机械地翻译成代码行。但这样翻译有好处逻辑足够直白任何人都能看懂每行在做什么修改起来的门槛也很低。比如想固定某个坐标、想删掉某个步骤直接打开文件搜索关键词就行。我实际用下来的体验是生成的脚本更像一个起点用户在上面继续加循环、加判断慢慢就能变成真正意义上的自动化测试用例。3.5 GUI 界面的实现GUI 用 tkinter 实现这是 Python 标准库自带的界面方案不需要额外安装打包时也不会增加体积。界面布局分为上下两个区域上面是操作按钮区四个按钮分别对应开始录制、停止录制、执行回放、生成代码下面是一个状态信息栏用来显示当前事件数量、保存路径、执行状态等信息。录制中的状态管理要特别小心。开始录制后界面上的按钮状态要切换防止重复点击导致多个 Listener 同时运行。我是这样处理的录制前把开始按钮禁用停止按钮启用录制结束后恢复按钮状态。执行回放时同理避免回放过程中再次启动回放造成事件冲突。窗口关闭事件也要处理确保程序退出前所有的 Listener 都被正常停止否则 Python 进程可能无法完全退出GUI 窗口关闭了但后台线程还在跑。GUI 和监听线程的关系是另一个需要注意的点。tkinter 的主循环只能跑在主线程里而录音监听器是独立线程所以线程间通信不能直接操作界面控件需要通过一个线程安全的队列来传递状态信息。我在回调里往队列塞消息GUI 主循环通过定时检测队列来刷新状态栏整个过程不涉及跨线程的界面操作稳定性高了很多。4. 常见问题与调试经验4.1 跨平台兼容里的那些坑跨平台兼容是这个项目的一个卖点但实际使用中平台差异还是不少。macOS 上第一次运行录制时系统会弹出一个辅助功能权限的提示。如果你不把这个程序加入辅助功能白名单监听器只能捕捉到自己应用内的按键事件其他应用的事件完全收不到。这个权限需要在系统设置里授权而且授权后要重启程序才能生效。Windows 上一般没有这个问题但杀毒软件偶尔会拦截全局钩子的行为。Linux 上最需要注意的是桌面环境差异Wayland 环境下事件监听的支持不如 X11 完整如果遇到录不到事件的情况大概率是会话类型导致的切换到 X11 登录就能解决。键盘按键名的表示方式在三个平台也有差异。录制阶段我统一用 pynput 的字符串表示比如 Key.ctrl、Key.enter 这种格式生成代码时直接使用 pynput 的 Key 枚举这样在回放端就不需要做多余的平台映射。这里我的建议是尽量用 pynput 抽象层的表示方式不要自己从底层系统去拿原始的按键码否则跨平台转换会把你逼疯。4.2 事件丢失与录制失真有些场景下录制的事件和实际操作不一致最典型的问题是移动事件被过滤丢掉了。我的实现里把两像素以下的移动过滤掉了这个阈值平时没问题但做精细化绘图或拖动操作时会导致回放轨迹失真。如果你要录制精细操作可以把这个阈值调小甚至改成 0代价是事件量变大、生成代码变长。另一个容易忽略的是点击前的位置修正。实际录制时用户点击之前鼠标可能已经停在目标位置上方然后快速点击这个点击的位置和上一个 move 事件的位置差距很大。回放时直接执行 click 事件可能没有先移动到位。所以我在 playback 模块里的 click 分支中加了 mouse.position 赋值的逻辑确保点击前鼠标一定在正确的位置。还有滚动事件的方向问题。不同平台的滚动方向可能相反Windows 上滚轮向上是 dy 为正macOS 的天然滚动模式下逻辑方向会翻转。我提供 GUI 里的一个翻转选项来纠正这个差异实际使用中确实遇到过几次加了选项以后就能在 GUI 里直接切换不用改代码。4.3 回放过程中的权限和运行环境限制回放模拟输入在部分环境下会被拦截。Windows 上一些以管理员权限运行的应用如任务管理器编辑器不接受普通权限的模拟输入操作这时需要把整个 Python 进程也以管理员身份运行。macOS 上则经常遇到部分 App 对辅助功能权限校验严格需要在隐私设置里把终端程序加入进去否则回放到该 App 时无响应。Linux 无图形环境跑回放是踩坑比较多的场景。没有 X server 或 Wayland serverpynput 的 Controller 完全无法工作报错信息还不太明显。解决办法是确保运行环境中存在可用的图形会话我一般在桌面系统的虚拟终端里跑脚本如果一定要在纯命令行环境跑就需要接 Xvfb 这类虚拟显示方案先把 Virtual Display 起起来。4.4 实战调优记录速查表下面这个小表是我在自己使用和帮同事调优过程中整理出来的基本覆盖了高频问题可以直接对照排查。问题现象根因分析解决方式录制时按键无响应macOS 辅助功能权限未授权系统设置-隐私-辅助功能中勾选运行终端或程序本体生成代码后双击运行无效果Linux 下没有图形会话或 Wayland 限制切换 X11 登录通过桌面终端运行利用 Xvfb 起虚拟显示回放鼠标乱跑或先跳后点缺少移动路径或点击前未将鼠标移动到目标坐标调小移动过滤阈值在 click 分支强制先执行 mouse.position 赋值回放完成但总时间偏长sleep 误差逐条累积换用基于全局起点的期望时间差来计算休眠时长键盘组合键表现不对按键按下和释放顺序被打乱回放时保持原始事件的 press/release 顺序不要提前释放GUI 界面卡死无响应回放和录制逻辑占用了主线程把 Listener 和回放循环放进后台线程事件状态通过队列传回 GUI 主循环按键名跨平台不一致直接使用了平台原始按键码统一使用 pynput 的 Key 枚举字符串表示生成代码时也不要自己做映射我这里补充一个录制技巧正式录制之前先手动在目标软件里把界面准备到初始状态。因为录制是忠实记录的回放也是从当前状态开始执行如果前后两次的初始界面不一致后面全是白搭。我一般都会在录制的第一步先加一个回到初始状态的操作比如按 Home 键、点一下固定的起始位置这样可复现性就高很多。5. 实际使用中的经验与扩展思路录制执行工具的最终效果取决于你怎样使用它而不是它的代码有多复杂。我用它做过最频繁的事情是持续集成之前的冒烟测试路径走查每天早上让脚本先把核心业务链路跑一遍第一遍点出异常了再人工介入省下不少重复劳动。但使用过程中我又发现纯录制回放的价值是有天花板的它解决的是重复的问题解决不了变化的问题。比如界面上一个按钮的位置变了录制的脚本就会失效一个弹出框的文案变了脚本也不会自动去适配。所以我在实际项目里预留了不少扩展方向。最直接的一个是给生成的代码加上图像识别的锚点用 pyautogui 的定位函数在每次点击前先找到目标图片的位置再点击这样就不怕界面布局变动了。另外一个方向是在回放过程中加入断言即点击之后检查屏幕上的某个区域是否出现了预期内容如果没有就报错退出这就把工具从录制回放升级成了半自动化的回归测试工具。还有一个思路是把脚本接入 CI 系统定时触发回放配合日志分析工具来判定运行结果是否正常。如果你拿到这个项目我建议从最简单的录制回放开始用先把操作流程验证通过再逐步加入逻辑判断和容错处理。不要一上来就设计一套完美框架先用起来遇到问题再迭代这才是这类工具的正确演进路径。本文还有配套的精品资源点击获取
返回列表