PyAutoGUI 0.9.26 桌面自动化:安装避坑、坐标定位与时序控制
简介:PyAutoGUI-0.9.26.zip 面向 Python 开发者与自动化测试人员,是一份 GUI 自动控制库的完整源码包:用脚本模拟鼠标移动、点击、拖拽、滚动与键盘输入,把重复的桌面操作交给程序执行,支持 Windows、Mac OS X 与 Linux;初学者可借此入门,有经验者可用于自动化测试、数据采集与界面流程开发。压缩包共 37 个文件、约 53KB,以 14 个 py 源码为主体,另有 12 个 rst 文档、4 个 txt 说明及 pkg-info、setup.py、Makefile 等打包配置;源码按平台拆分为 win、osx、x11 等实现,配合缓动、截图定位、弹窗与窗口模块,目录分层清晰。已有 486 人学习。读者可了解分平台实现的思路、截图定位元素与热键触发的写法,并借助详尽文档与示例完成从安装到编写脚本的过渡。
1. 桌面自动化的第一块砖:PyAutoGUI 0.9.26 压缩包到底能替谁干活
接手一个没有接口的老系统,让你每天把两百条数据手工录进去,第一反应往往不是写爬虫,而是找个软件包替你把鼠标挪过去点。PyAutoGUI 0.9.26 就是干这个的:一个纯 Python 的跨平台 GUI 自动化软件包,压缩包形态分发,解压即源码,不依赖任何私有运行时。它能做的三件事很朴素——控制鼠标、模拟键盘、截图并在一张图里找另一张图的位置。
适合谁:要写桌面端回归脚本、给没有 API 的老系统做批量录入、把某个只在 GUI 上开放的流程做成半自动流水线的人。不适合谁:目标是网页内部的数据抓取,或者需要识别文字、理解界面语义的活儿,那得上别的工具链。这一版的老底子决定了它有些参数跟新教程对不上,后面几章会把能跑的路径和会翻车的地方分开讲。
2. 解压到 import:把 0.9.26 这个软件包装进可控环境
拿到 zip 之后大多数人直接pip install,然后在一台已经装了新版 Pillow 的机器上撞到第一个报错。0.9.26 这版的依赖解析比较老,它自己不带截图后端,全靠下游包撑着,所以安装这一步不是敲一条命令就完事,得先看清楚依赖链再决定用哪条路线落地。
2.1 包里装了什么:按平台拆分的后端和四条依赖链
解压后进到PyAutoGUI-0.9.26/目录,会看到pyautogui/这个主包。它的组织方式是按操作系统拆后端:Windows 一份、macOS 一份、X11 一份,__init__.py在 import 时根据sys.platform挑一个加载。这意味着你在 Windows 上排查问题时,看_pyautogui_win.py就够了,其余两个后端的代码对你完全无效,别在那儿浪费时间。
我一般会先扫一眼这几个位置,判断这个包能不能直接在自己的环境里跑起来:
| 路径 | 作用 | 什么时候需要动它 |
|---|---|---|
pyautogui/__init__.py | 对外 API 汇总,PAUSE、FAILSAFE、FAILSAFE_POINTS这些全局开关都定义在这 | 调全局节奏时改,不改文件本身 |
pyautogui/_pyautogui_win.py | Windows 后端,用 ctypes 直接调系统输入接口 | 排查「命令发出去了但系统没反应」时才看 |
pyautogui/_pyautogui_osx.py | macOS 后端,走 Quartz 事件源 | 权限问题排查时看 |
pyautogui/_pyautogui_x11.py | Linux 后端,走 Xlib | 报 DISPLAY 相关错误时看 |
setup.py | 打包元信息,声明的依赖版本区间很宽松 | 安装前必看,决定要不要锁版本 |
关键在setup.py里那几条依赖:pyscreeze负责截图和图像查找,pymsgbox负责弹窗,pytweening负责缓动曲线,mouseinfo是可选工具。四条里最容易出事的是pyscreeze,因为它依赖 Pillow,而 Pillow 在 10.0 版本把Image.ANTIALIAS这个常量删了,老的 pyscreeze 还在用它做缩放。这条坑后面第 5 章会展开,这里先记住一个结论:装 0.9.26 的时候,要么把 Pillow 锁在 10 以下,要么把 pyscreeze 单独升到新版,二选一,别让 pip 自由发挥。
2.2 三条安装路线:本地 pip、源码直跑、虚拟环境隔离
路线怎么选,取决于你是「只想用」还是「可能要改源码排错」。三种我都用过,代价不一样。
第一条,本地 pip 安装,最省事,适合把 PyAutoGUI 当成一个普通依赖引到项目里:
# 建一个干净的虚拟环境,避免和系统里已有的 Pillow 打架 python -m venv venv source venv/bin/activate # Windows 下换成 venv\Scripts\activate # 先锁 Pillow,再装本地 zip,顺序不能反 pip install "Pillow<10" pip install ./PyAutoGUI-0.9.26.zip # 验证装的是不是这一版 pip show pyautogui | head -3这里的顺序是故意的。如果先装 PyAutoGUI,pip 会顺带把最新版 Pillow 拉下来,等你再想降级就得处理依赖冲突。先装Pillow<10相当于提前把地基打牢,后面 pip 解析依赖时看到已满足就不会再动它。pip install ./xxx.zip这种写法 pip 会自动解压到临时目录再构建,你不用手动解压,但缺点是装完之后你手上没有源码副本,想改后端逻辑还得重新下一遍。
第二条,源码直跑,适合需要断点调试或者临时打补丁的场景:
# 解压后在包目录的父级执行,让 Python 直接找到 pyautogui 包 unzip PyAutoGUI-0.9.26.zip -d ~/vendor/ cd ~/vendor # 不做安装,只把路径塞进 PYTHONPATH export PYTHONPATH=$HOME/vendor/PyAutoGUI-0.9.26:$PYTHONPATH python -c "import pyautogui; print(pyautogui.__file__)"PYTHONPATH的优先级高于 site-packages,所以这种写法会盖过任何已安装的版本。好处是你改一行源码立刻生效,不用反复pip install;坏处是正式交付时容易忘记把路径依赖写进部署文档,换台机器就 import 不到。我一般只在排错阶段这么干,确认没问题后还是回到 pip 安装。
第三条,虚拟环境隔离,其实是前两条的配套动作,单独拎出来说是因为桌面自动化脚本经常要打包分发。这类脚本通常还要带pyperclip、opencv-python之类的包,混在系统环境里迟早出问题。给每个自动化项目单开一个 venv,是所有踩过环境冲突的人的共同习惯。
提示:不要在系统 Python 或者 conda base 环境里装 PyAutoGUI。它对 Pillow 版本敏感,而 Pillow 几乎是所有图像相关项目的公共依赖,在全局环境里降级会牵连一大片。
2.3 冒烟测试:八行代码验证屏幕、鼠标、键盘三条链路
装完之后别急着写业务逻辑,先跑一个冒烟测试。PyAutoGUI 的能力可以拆成三条独立链路:屏幕读取、鼠标输出、键盘输出。任何一条不通,后面的代码都是白写。这个测试我一般写成独立脚本,每换一台机器都重跑一遍。
import sys import pyautogui # 链路一:屏幕读取,能拿到分辨率说明截图后端正常 w, h = pyautogui.size() print(f"screen: {w}x{h}") # 链路二:鼠标读取,拿当前坐标,同时验证坐标系统是否可用 print(f"cursor: {pyautogui.position()}") # 链路三:键盘输出,用一个无害的按键确认输入通道打通 # 这里故意不用 write,避免在编辑器里插入一堆字符 pyautogui.press('shift') # 附加检查:截图能不能落地成文件 img = pyautogui.screenshot() img.save('smoke.png') print(f"screenshot saved: {img.size}")size()返回的是主屏逻辑分辨率,注意是逻辑不是物理,高 DPI 屏上这个值会小于你显示器标称的分辨率,第 3 章会专门讲。position()返回Point(x=..., y=...),是个具名元组,可以直接解包成两个变量。press('shift')挑 shift 是因为它是修饰键,单独按下再松开对绝大多数应用没有任何副作用,比按字母键安全得多。
如果这四步都过了,说明包本身和环境没问题,可以开始写业务。如果卡在某一步,顺序对照:size()报错多半是截图后端没装好,position()报错是平台后端加载失败,screenshot()报错就是 Pillow 和 pyscreeze 的版本冲突,直接跳第 5 章。
3. 坐标、截图与模板匹配:让脚本真的「看见」那个按钮
写死坐标的脚本是最脆弱的,窗口挪了十个像素就全废。稍微靠谱一点的做法是让脚本自己去屏幕上找目标,找到再点。这条路要走通,得先解决坐标基准的问题,再解决图像匹配的问题,两个环节的坑完全不一样。
3.1 坐标系和高 DPI:为什么你量的坐标总是差一截
PyAutoGUI 的坐标原点在屏幕左上角,x 向右,y 向下,单位是像素——但这里说的像素是「逻辑像素」,不是显示器物理像素。在一台 4K 屏、系统缩放设成 150% 的机器上,size()返回的可能是 2560×1440,而实际物理分辨率是 3840×2160。如果你的脚本在别的机器上量好坐标写死,换到这台机器就会整体偏移。
Windows 上还有个更隐蔽的问题:进程默认不是 DPI 感知的,系统会对它做一次虚拟化缩放。表现是截图画出来的图比实际屏幕小一圈,locateOnScreen找到的位置乘上缩放比才是真正的鼠标坐标,但 PyAutoGUI 不会帮你乘。这个偏差在 125% 缩放下是四分之一,足够让你点在按钮边缘外面。
常见做法是在程序入口最前面、import pyautogui之前把 DPI 感知打开:
import ctypes import sys if sys.platform == 'win32': try: # 2 = PER_MONITOR_AWARE,多显示器不同缩放时也正确 ctypes.windll.shcore.SetProcessDpiAwareness(2) except AttributeError: # Win7 及更早没有 shcore,退回系统级 DPI 感知 ctypes.windll.user32.SetProcessDPIAware() # 这行必须在 DPI 感知设置之后 import pyautoguiSetProcessDpiAwareness(2)里的 2 是「每显示器感知」,意思是程序自己负责处理不同显示器的缩放差异,系统不再插手。如果只写 1(系统级感知),多屏且缩放不同时仍然会错。try/except那层兜底是为了兼容老系统,虽然现在很少遇到,但成本只有三行代码。这段必须放在import pyautogui前面,因为后端模块在 import 时就会读取一次系统参数,晚了就不生效。
macOS 上的表现不同,它是 Retina 屏的逻辑分辨率机制,size()返回逻辑值,截图默认也是逻辑尺寸,一般不需要额外处理,但需要在「系统设置 → 隐私与安全性 → 辅助功能」里给终端或者 Python 解释器授权,否则所有输入操作静默失败,不报错。
3.2 position / size / pixel:三个自检函数怎么用
在写任何定位逻辑之前,先用这三个函数把环境摸清楚。它们的共同点是只读,不会对系统产生任何影响,可以放心在调试时反复调用。
import pyautogui # size:屏幕逻辑分辨率 screen_w, screen_h = pyautogui.size() # position:当前鼠标位置,返回 Point(x, y) mx, my = pyautogui.position() # pixel:读取指定坐标的颜色,返回 (r, g, b) r, g, b = pyautogui.pixel(mx, my) # pixelMatchesColor:颜色比对,tolerance 控制容差 same = pyautogui.pixelMatchesColor(mx, my, (r, g, b), tolerance=10) print(f"screen={screen_w}x{screen_h} mouse=({mx},{my}) color=({r},{g},{b}) match={same}")pixel()的返回是 RGB 三元组,取值范围 0-255。pixelMatchesColor()的tolerance参数默认是 0,也就是精确匹配,实际用的时候建议给到 10-20,因为不同显卡渲染出的颜色会有细微差异,尤其是带抗锯齿的文字和渐变按钮。
这套组合最实用的场景是「点完之后验证」。比如点了一个开关按钮,点完读一下按钮中心的颜色,从灰变绿就说明点成功了。这比截图比对快得多,也不依赖模板图片。我一般在关键操作后都加一个颜色断言,相当于给自己留个后悔药。
注意:
pixel()在部分 Linux 桌面环境下读取速度很慢,逐像素扫描的循环里慎用。真要扫一片区域,用screenshot(region=...)一次性截下来再用 Pillow 处理。
3.3 locateOnScreen 的三个参数:region、grayscale、confidence
图像查找是 PyAutoGUI 里最像「智能」的部分,本质是模板匹配:你给一张小图,它在屏幕大图里滑窗找最相似的区域。0.9.26 这版通过 pyscreeze 实现,参数不多但每个都影响成败。
import pyautogui # region:限定搜索范围,左上角 x, y 和宽高 # grayscale:转灰度再匹配,速度快 2-3 倍,对彩色变化更宽容 box = pyautogui.locateOnScreen( 'submit_btn.png', region=(0, 0, 1200, 800), grayscale=True, ) if box is None: print('not found') else: # center 把左上角坐标转成中心点,点击要用中心 cx, cy = pyautogui.center(box) pyautogui.click(cx, cy)region是最该用的参数。全屏匹配在 2560×1440 上单次耗时可能到一两秒,限定到一块 1200×800 的区域能砍掉一多半。更重要的是它能排除干扰——同一个「确定」按钮在弹窗和主界面上可能长得一模一样,不限区域就会点到错的那个。用region的前提是你知道目标大概在屏幕的哪个方位,这个信息一般来自窗口固定位置或者上一步操作的结果。
grayscale=True是个性价比很高的开关。模板匹配的本质是逐像素算差异,三通道 RGB 的计算量是单通道的三倍。开了灰度之后,只要形态一致,颜色略有偏差也能匹配上,反而更鲁棒。除非你的目标靠颜色区分(比如一排只有颜色不同的标签),否则都建议开。
第三个参数confidence在 0.9.26 这个版本里要小心。它需要 pyscreeze 的新版本配合,如果你装的是随包附带的老 pyscreeze,传这个参数会直接抛TypeError: locateOnScreen() got an unexpected keyword argument 'confidence'。这条我单独放在第 5 章讲,因为解法涉及装 OpenCV,属于环境层面的决策。
locateOnScreen返回的是Box(left, top, width, height),不是坐标点,直接丢给click()会点到区域的左上角。center()做的是纯算术,把 left + width/2 算出来,没有任何副作用,可以在循环里放心调。还有一点,找不到时返回None而不是抛异常,所以每个调用点都得判空,这是最常见的漏判来源。
4. 时序控制:PAUSE、duration 与 tween 怎么配才不翻车
坐标对了不代表能跑稳。我见过太多脚本单步调试全绿,连续跑三遍就出错,最后查出来是某个点击发生在窗口动画还没结束的时候。GUI 自动化跟接口测试最大的区别在于,你面对的是一个有渲染延迟、有消息队列、有焦点竞争的系统,时序控制不是锦上添花,是能不能用的分水岭。
4.1 裸写 click 为什么会翻车
pyautogui.click(x, y)这一行执行完,函数返回了,但系统那边的点击事件只是刚进队列。如果你紧接着调click点下一个按钮,两次事件间隔可能只有几毫秒,而目标应用的 UI 线程还在处理第一次点击引发的界面刷新,第二次点击就落到了旧的界面元素上。
更麻烦的是焦点问题。键盘输入走的是「当前焦点窗口」,如果你的脚本在两步之间做了什么导致焦点切换的事(比如截图触发了某个预览窗口,或者上一个操作弹出的对话框抢了焦点),后面所有write都会打到错误的地方,而且不报错,只是把字打丢了。
PyAutoGUI 提供了三层机制来对付这个:
| 机制 | 作用范围 | 典型值 | 代价 |
|---|---|---|---|
pyautogui.PAUSE | 全局,每个 pyautogui 函数调用后自动等待 | 0.1 - 0.5 秒 | 整体变慢,但最稳 |
duration参数 | 单次鼠标移动的耗时 | 0.2 - 0.8 秒 | 只影响移动,不影响点击后等待 |
time.sleep() | 你自己插的等待,位置完全可控 | 按场景定 | 需要你判断等多久 |
PAUSE是兜底方案,默认值是 0.1 秒。它的实现是在每个公开函数返回前time.sleep(PAUSE),所以对click、write、press全都生效。调大到 0.3 之后,一个两百步的流程会多花一分钟,但出错率能降一个数量级。这笔账在小批量场景下很划算。
duration只影响鼠标移动过程,不产生任何点击后的等待。它的真正价值不是「慢」,而是让移动轨迹变成连续的插值点,而不是瞬间跳过去。有些应用会监听鼠标移动事件来做悬停高亮,瞬移会让它来不及反应,菜单还没展开你就点了。设成 0.3-0.5 秒,让它「看起来像人在动」。
4.2 把 PAUSE 变成配置项,局部再用 sleep 补刀
我习惯把节奏参数全部外置,不写死在代码里。同一份脚本在开发机上跑和在生产机上跑,机器性能差异可能让合适的等待时间差一倍,硬编码等于给自己埋雷。
import os import time import pyautogui # 全局节奏从环境变量读,默认 0.25 秒 pyautogui.PAUSE = float(os.getenv('PYAGUI_PAUSE', '0.25')) # 保险开关:鼠标甩到屏幕左上角立即抛异常,强制中断 pyautogui.FAILSAFE = True def safe_click(x, y, wait_after=0.5, desc=''): """带前置校验和事后等待的点击封装""" # 点击前确认坐标在屏幕内,避免负坐标或越界 w, h = pyautogui.size() if not (0 <= x < w and 0 <= y < h): raise ValueError(f'coord out of screen: {(x, y)}') # duration 让移动有轨迹,不是瞬移 pyautogui.moveTo(x, y, duration=0.3, tween=pyautogui.easeInOutQuad) pyautogui.click() # 点击后的等待由调用方按业务定,不塞进全局 PAUSE time.sleep(wait_after) if desc: print(f'clicked: {desc} @ ({x}, {y})') safe_click(640, 420, wait_after=1.2, desc='submit')safe_click里有三个设计点值得说。第一,坐标越界校验放在前面,负坐标或者超出屏幕的坐标会被系统静默忽略,加个检查能省掉大量「为什么没点上」的排查。第二,tween=pyautogui.easeInOutQuad让移动曲线两头慢中间快,接近人的操作习惯,对某些做行为检测的应用更友好。第三,wait_after是必填的显式参数,逼着调用方考虑「这一步之后要等多久」,而不是指望全局 PAUSE 兜住一切。
全局 PAUSE 解决的是「函数之间别贴太紧」,wait_after解决的是「业务逻辑上要等多久」。前者是毫秒级,后者经常是秒级——等一个文件加载、等一个弹窗出现、等一个进度条走完。这两类等待混在一起调,最后就是个谁都看不懂的数字。
FAILSAFE单独提一下。开启状态下,只要鼠标被移动到屏幕左上角 (0, 0) 附近,函数会抛FailSafeException中断执行。这是你唯一的紧急刹车,写循环的时候尤其要留着。别为了「让它跑完」把它关掉,会后悔。
4.3 write 与 hotkey:字符集边界和修饰键残留
键盘这块有两个边界必须清楚。第一个是字符集:write()内部维护一个支持按键的列表,只包含 ASCII 可见字符和一些特殊键名。你传中文进去,它不会报错,但会把不认识的字符直接跳过或者打出乱七八糟的东西。第二个是修饰键状态:keyDown之后如果没有对应的keyUp,那个修饰键会一直处于按下状态,影响后续所有输入。
import pyautogui # ASCII 路径:write 直接可用 pyautogui.write('hello_world', interval=0.05) # 组合键:hotkey 内部保证按下顺序和逆序释放 pyautogui.hotkey('ctrl', 'a') pyautogui.hotkey('ctrl', 'c') # 非 ASCII 路径:走剪贴板,这是唯一可靠的做法 import pyperclip pyperclip.copy('需要输入的中文内容') pyautogui.hotkey('ctrl', 'v') # 手动管理修饰键时,必须 try/finally 保证释放 try: pyautogui.keyDown('shift') pyautogui.press('left') # 选中一个字符 finally: pyautogui.keyUp('shift')write的interval参数控制每个字符之间的间隔,默认 0,设成 0.03-0.08 能显著降低某些编辑器丢字的情况。这个参数和全局PAUSE不是一回事,PAUSE只在函数调用之间生效,interval是在一次write内部生效。
hotkey会自动处理按下和释放的顺序,比如hotkey('ctrl', 'c')实际是按下 ctrl、按下 c、释放 c、释放 ctrl,顺序不会错。所以能用hotkey就别手动keyDown/keyUp。
必须手动管理修饰键时,try/finally是硬性要求。中间任何一句抛异常(哪怕是你自己写的断言),finally都会把键松开,否则后面整台机器都在 shift 按下的状态里,你会看到输入法行为诡异、字母全大写,然后花半小时才反应过来。
中文输入走剪贴板这个方案不是权宜之计,是当前版本的唯一正确解。pyperclip在小文件量的场景下足够用,缺点是会覆盖用户剪贴板原有内容,如果你的脚本要在别人正在工作的机器上跑,最好在开头把原内容备份下来,结束后还原。
5. 避坑与排查:五条我踩过的坑,现象、原因、解法
下面这五条都是能稳定复现的,按「现象 → 原因 → 解决」写,遇到同类问题时直接照着对。
5.1 装完就逃不掉的三条:依赖与平台
坑一:AttributeError: module 'PIL.Image' has no attribute 'ANTIALIAS'
现象:pip install一切正常,import pyautogui也过,第一次调screenshot()就崩,报错行在 pyscreeze 里。
原因:pyscreeze 老版本用Image.ANTIALIAS做缩放,这个常量在 Pillow 10.0 被移除了,替代品叫Image.Resampling.LANCZOS。0.9.26 的依赖声明没锁 Pillow 上限,pip 装最新版就撞上。
解决:两条路选一条。省事的做法是把 Pillow 降级到 10 以下,pip install "Pillow<10",然后重装 pyautogui 确认依赖没被顶回去。另一条是升级 pyscreeze,pip install -U pyscreeze,新版已经改用新的常量名,但要注意升级后locateOnScreen的行为可能有细微变化,需要回归一遍。
坑二:TypeError: locateOnScreen() got an unexpected keyword argument 'confidence'
现象:照着网上教程写locateOnScreen('x.png', confidence=0.8),直接抛 TypeError。
原因:confidence参数不是 PyAutoGUI 自己的,是 pyscreeze 加了 OpenCV 后端之后才支持的。0.9.26 时期配套的 pyscreeze 版本还没这个能力。
解决:先pip install opencv-python,再pip install -U pyscreeze,两步都做完confidence才生效。如果因为环境限制不能装 OpenCV,就退回默认的精确匹配路径,靠region缩小范围、靠清理模板图片(裁掉多余背景)来提升准确率。这个替代方案在大多数固定布局的界面上够用。
坑三:Linux / 远程桌面下所有操作静默失败
现象:脚本不报错,size()返回的分辨率也正常,但鼠标就是不动,键盘也没反应。
原因:Linux 后端依赖 X11 协议,Wayland 会话下 Xlib 拿不到真正的输入控制权限。远程桌面场景同理,某些远程协议下注入的输入事件会被吞掉。另外没有DISPLAY环境变量的纯 headless 环境会直接抛KeyError: 'DISPLAY'。
解决:先确认会话类型,echo $XDG_SESSION_TYPE,返回x11才有戏,返回wayland就别在这条路上耗了。远程桌面下建议在目标机器本地起一个会话跑脚本,而不是通过远程注入。headless 服务器上做 GUI 自动化本身就不成立,需要的是虚拟显示服务配合。
5.2 跑起来点偏的两条:坐标与时序
坑四:高 DPI 屏上所有点击整体偏移,偏差比例恒定为缩放比
现象:在 100% 缩放的机器上写的坐标,拿到 150% 缩放的机器上跑,所有点击都偏,而且偏移量随坐标增大而增大——左上角附近基本准,右下角偏得离谱。
原因:进程没有声明 DPI 感知,系统对它做虚拟化缩放。screenshot()出来的图是缩放后的尺寸,鼠标坐标却是物理坐标,两者不在同一个坐标系里。
解决:在程序最前面加第 3.1 节那段SetProcessDpiAwareness(2)。加完之后size()返回值和实际物理分辨率一致,截图尺寸也和鼠标坐标对得上。这条必须在import pyautogui之前执行,顺序错了不生效。
坑五:修饰键残留,后续输入全部变形
现象:脚本跑到一半开始出现大写字母、快捷键误触发、输入法状态混乱,重启脚本就好了。
原因:某次keyDown('ctrl')之后,中间抛了异常,keyUp没执行到,ctrl 一直处于按下状态。之后所有的press和write都被解释成组合键。
解决:所有手动管理修饰键的地方套try/finally。更彻底的做法是写一个上下文管理器,with modifier('ctrl'):,进出自动管理。另外在脚本的顶层except里加一段「释放所有修饰键」的兜底逻辑,把 ctrl、shift、alt、win/cmd 挨个keyUp一遍,这类清理成本很低但收益很高。
6. 从能跑到能交付:失败回滚、截图留证与日志三件套
脚本能跑通只是起点。真正交付出去的时候,你要回答的问题是「它半夜跑挂了,我怎么知道挂在哪一步」。我的做法是给每一步都留痕迹,成本不高,但省下来的排查时间是以小时计的。
import traceback from datetime import datetime from pathlib import Path import pyautogui ARTIFACT_DIR = Path('./artifacts') ARTIFACT_DIR.mkdir(exist_ok=True) def step(name, fn): """执行一个步骤,失败时截图留证并记录现场""" ts = datetime.now().strftime('%H%M%S') try: fn() print(f'[OK] {name}') except Exception as e: # 失败瞬间的屏幕是最有价值的证据 shot = ARTIFACT_DIR / f'{ts}_{name}.png' pyautogui.screenshot(str(shot)) # 记录鼠标位置和异常栈,还原当时的上下文 print(f'[FAIL] {name} mouse={pyautogui.position()} shot={shot}') traceback.print_exc() raise这个step封装做了三件事:失败时截当前屏、记录鼠标坐标、打印完整异常栈。截图文件名带时间戳和步骤名,跑完之后按名字排序就是一条完整的时间线,能直接看出是在哪个界面卡住的。raise保留是为了让上层决定是重试还是终止,不要在底层把异常吞掉。
再补一个我自己踩出来的习惯:跑长流程之前先做一次环境校验,把size()、position()、screenshot()各调一遍,确认三条链路都通再开始点。因为有几次是在笔记本外接了显示器、分辨率变了的情况下跑的,脚本从头错到尾,最后才发现是坐标基准变了。这种前置校验花不到两秒,能挡掉一整轮无效执行。
从那以后我每次把脚本交给别人之前,都强制走一遍「换一台机器、换一个分辨率、重跑冒烟测试」的流程,宁可多花十分钟,也不想到现场去猜它为什么点歪了。希望帮到你。
本文还有配套的精品资源,点击获取