自动化控制键盘和鼠标
知道用于编辑电子表格、下载文件和运行程序的各种 Python
模块,是很有用的。但有时候,就是没有模块对应你要操作的应用程序。在计算机上自动化任务的终极工具,就是写程序直接控制键盘和鼠标。这些程序可以控制其他应用,向它们发送虚拟的击键和鼠标点击,就像你自己坐在计算机前与应用交互一样。这种技术被称为“图形用户界面自动化”,或简称为“GUI
自动化”。有了 GUI
自动化,你的程序就像一个活人用户坐在计算机前一样,能做任何事情,除了将咖啡泼在键盘上。
请将 GUI
自动化看成是对一个机械臂编程。你可以对机械臂编程,让它敲键盘或移动鼠标。对于涉及许多无脑点击或填表的任务,这种技术特别有用。
pyautogui
模块包含了一些函数,可以模拟鼠标移动、按键和滚动鼠标滚轮。本章只介绍了 pyautogui
功能的子集。可以在 http://pyautogui.readthedocs.org/ 找到完整的文档。
安装 pyautogui
模块
pyautogui
模块可以向 Windows
、OS X
和 Linux
发送虚拟按键和鼠标点击。根据你使用的操作系统,在安装 pyautogui
之前,可能需要安装一些其他模块(称为依赖关系)。
- 在
Windows
上,不需要安装其他模块。 - 在 OS
X
上,运行sudo pip3 install pyobjc-framework-Quartz
,sudo pip3 install pyobjc-core
,然后sudo pip3 install pyobjc
。 - 在
Linux
上,运行sudo pip3 install python3-xlib
,sudo apt-get install scrot
,sudo apt-get install python3-tk
,以及sudo apt-get install python3-dev
(Scrot
是PyAutoGUI
使用的屏幕快照程序)。
在这些依赖安装后,运行 pip install pyautogui
(或在 OS X
和 Linux
上运行 pip3
),安装 pyautogui
。
附录 A
有安装第三方模块的完整信息。要测试 PyAutoGUI
是否正确安装,就在交互式环境运行 import pyautogui
,并检查出错信息。
前置操作
在开始 GUI
自动化之前,你应该知道如何避免可能发生的问题。Python
能以想象不到的速度移动鼠标并击键。实际上,它可能太快,导致其他程序跟不上。而且,如果出了问题,但你的程序继续到处移动鼠标,可能很难搞清楚程序到底在做什么,或者如何从问题中恢复。你的程序可能失去控制,即使它完美地执行你的指令。如果程序自己在移动鼠标,停止它可能很难。好在,有几种方法来防止或恢复 GUI
自动化问题。
通过注销关闭所有程序
停止失去控制的 GUI
自动化程序,最简单的方法可能是注销,这将关闭所有运行的程序。在 Windows
和 Linux
上,注销的热键是 Ctrl-Alt-Del
。在 OS X
,热键是 ⌘-Shift-Option-Q
。通过注销,你会丢失所有未保存的工作,但至少不需要等计算机完全重启。
暂停和自动防故障装置
你可以告诉脚本在每次函数调用后等一会儿,在出问题的时候,让你有很短的时间窗口来控制鼠标和键盘。要做到这一点,将pyautogui.PAUSE
变量设置为要暂停的秒数。例如,在设置 pyautogui.PAUSE = 1.5
之后,每个 PyAutoGUI
函数调用在执行动作之后,都会等待一秒半。非 PyAutoGUI
指令不会停顿。
pyautogui
也有自动防故障功能。将鼠标移到屏幕的左上角,这将导致 pyautogui
产生 pyautogui .FailSafeException
异常。你的程序可以用 try
和except
语句来处理这个异常,也可以让异常导致程序崩溃。这两种情况下,如果你尽可能快地向左上移动鼠标,自动防故障功能都将停止程序。可以设置 pyautogui. FAILSAFE = False
,禁止这项功能。
>>> import pyautogui
>>> pyautogui.PAUSE = 1
>>> pyautogui.FAILSAFE = True
这里我们导入 pyautogui
,并将 pyautogui.PAUSE
设置为 1,即每次函数调用后暂停一秒。将 pyautogui.FAILSAFE
设置为 True
,启动自动防故障功能。
控制鼠标移动
在本节中,你将学习如何利用 pyautogui
移动鼠标,追踪它在屏幕上的位置,但首先需要理解 pyautogui
如何处理坐标。
pyautogui
的鼠标函数使用 x、y
坐标。下图中展示了计算机屏幕的坐标系统。与坐标系统类似。原点的x
、y
都是零,在屏幕的左上角。向右 x
坐标增加,向下 y
坐标增加。所有坐标都是正整数,没有负数坐标。
图为分辨率为 1920 × 1080 的计算机屏幕上的坐标
分辨率是屏幕的宽和高有多少像素。如果屏幕的分辨率设置为 1920 × 1080,那么左上角的坐标是(0,0),右下角的坐标是(1919,1079)。
pyautogui.size() 函数返回两个整数的元组,包含屏幕的宽和高的像素数。在交互式环境中输入下面内容:
>>> import pyautogui
>>> pyautogui.size()
(1920, 1080)
>>> width, height = pyautogui.size()
在分辨率为 1920 × 1080
的计算机上,pyautogui.size()
返回(1920,1080)。根据屏幕分辨率的不同,返回值可能不一样。你可以将来自 pyautogui.size()
的宽和高存在变量中,如 width
和 height
,让程序的可读性更好。
移动鼠标
既然你理解了屏幕坐标,就让我们来移动鼠标。pyautogui.moveTo()
函数将鼠标立即移动到屏幕的指定位置。表示 x、y
坐标的整数值分别构成了函数的第一个和第二个参数。可选的 duration
整数或浮点数关键字参数,指定了将鼠标移到目的位置所需的秒数。如果不指定,默认值是零,表示立即移动(在 PyAutoGUI
函数中,所有的 duration
关键字参数都是可选的)。
>>> import pyautogui
>>> for i in range(10):
pyautogui.moveTo(100, 100, duration=0.25)
pyautogui.moveTo(200, 100, duration=0.25)
pyautogui.moveTo(200, 200, duration=0.25)
pyautogui.moveTo(100, 200, duration=0.25)
这个例子根据提供的坐标,以正方形的模式顺时针移动鼠标,移动了 10 次。每次移动耗时 0.25 秒,因为有关键字参数指定 duration=0.25
。如果没有指定函数调用的第三个参数,鼠标就会马上从一个点移到另一个点。
pyautogui.moveRel()
函数相对于当前的位置移动鼠标。下面的例子同样以正方形的模式移动鼠标,只是它从代码开始运行时鼠标所在的位置开始,按正方形移动:
>>> import pyautogui
>>> for i in range(10):
pyautogui.moveRel(100, 0, duration=0.25)
pyautogui.moveRel(0, 100, duration=0.25)
pyautogui.moveRel(-100, 0, duration=0.25)
pyautogui.moveRel(0, -100, duration=0.25)
pyautogui.moveRel()
也接受 3 个参数:向右水平移动多少个像素,向下垂直移动多少个像素,以及(可选的)花多少时间完成移动。为第一第二个参数提供负整数,鼠标将向左或向上移动。
获取鼠标位置
通过调用 pyautogui.position()
函数,可以确定鼠标当前的位置。它将返回函数调用时,鼠标 x
、y
坐标的元组。每次调用后请移动鼠标:
>>> pyautogui.position()
(311, 622)
>>> pyautogui.position()
(377, 481)
>>> pyautogui.position()
(1536, 637)
当然,返回值取决于鼠标的位置。
控制鼠标交互
既然你知道了如何移动鼠标,弄清楚了它在屏幕上的位置,就可以开始点击、拖动和滚动鼠标。
点击鼠标
要向计算机发送虚拟的鼠标点击,就调用 pyautogui.click()
方法。默认情况下,点击将使用鼠标左键,点击发生在鼠标当前所在位置。如果希望点击在鼠标当前位置以外的地方发生,可以传入x
、y
坐标作为可选的第一第二参数。
如果想指定鼠标按键,就加入 button
关键字参数,值分别为 left
、middle
或 right
。例如,pyautogui.click(100,150,button='left')
将在坐标(100,150)
处点击鼠标左键。而pyautogui.click(200,250,button='right')
将在坐标(200,250)
处点击右键。
>>> import pyautogui
>>> pyautogui.click(10, 5)
你应该看到鼠标移到屏幕左上角的位置,并点击一次。完整的“点击”是指按下鼠标按键,然后放开,同时不移动位置。实现点击也可以调用 pyautogui.mouseDown()
,这只是按下鼠标按键,再调用 pyautogui.mouseUp()
,这只是释放鼠标按键。这些函数的参数与 click()
相同。实际上,click()
函数只是这两个函数调用的方便封装。
为了进一步方便,pyautogui.doubleClick()
函数只执行双击鼠标左键。pyautogui.rightClick()
和 pyautogui.middleClick()
函数将分别执行双击右键和双击中键。
拖动鼠标
“拖动”意味着移动鼠标,同时按住一个按键不放。例如,可以通过拖动文件图标,在文件夹之间移动文件,或在日历应用中移动预约。
PyAutoGUI
提供了 pyautogui.dragTo()
和 pyautogui.dragRel()
函数,将鼠标拖动到一个新的位置,或相对当前位置的位置。dragTo()
和 dragRel()
的参数与 moveTo()
和 moveRel()
相同:x
坐标/水平移动,y
坐标/垂直移动,以及可选的时间间隔(在OS X
上,如果鼠标移动太快,拖动会不对,所以建议提供 duration
关键字参数)。
要尝试这些函数,请打开一个绘图应用,如 Windows
上的 Paint
,OS X
上的 Paintbrush
,或 Linux
上的GNU Paint
(如果没有绘图应用,可以使用在线绘图,网址是http://sumopaint.com/ )。我将使用 PyAutoGUI
在这些应用中绘图。
让鼠标停留在绘图应用的画布上,同时选中铅笔或画笔工具,在新的文件编辑窗口中输入以下内容
import pyautogui
pyautogui.click(600, 250) # click to put drawing program in focus
distance = 200
while distance > 0:
pyautogui.dragRel(distance, 0, duration=0.2) # move right
distance = distance - 5
pyautogui.dragRel(0, distance, duration=0.2) # move down
pyautogui.dragRel(-distance, 0, duration=0.2) # move left
distance = distance - 5
pyautogui.dragRel(0, -distance, duration=0.2) # move up
在运行这个程序时,选中铅笔或画笔工具,让鼠标停留在画图工具的窗口上。然后 spiralDraw.py
将控制鼠标,点击画图程序获得焦点。如果窗口有闪烁的光标,它就获得了“焦点”,这时你的动作(例如打字,或这个例子中的拖动鼠标),就会影响该窗口。画图程序获取焦点后,spiralDraw.py
将绘制一个正方形旋转图案
distance 变量从 200 开始,所以在 while 循环的第一次迭代中,第一次 dragRel() 调用将光标向右拖动 200 像素,花了 0.2 秒。然后 distance
降到 195,第二次 dragRel() 调用将光标向下拖动 195 像素。第三次 dragRel() 调用将光标水平拖动−195(向左 195),distance
降到 190,最后一次 dragRel
调用将光标向上拖动 190。每次迭代,鼠标都向右、向下、向左、向上拖动,distance
都比前一次迭代小一点。通过这段代码循环,就可以移动鼠标光标,画出正方形旋转图案。
可以手工画出这个漩涡(或者说用鼠标),但一定要画得很慢,才能这么精确。pyautogui
能够几秒钟就画完。
注意
你可以在代码中使用
pillow
模块的画图函数,画出这个图形,更多信息请参见第 17 章。但利用GUI
自动化就能使用画图程序提供的高级画图工具,如灰度、不同的画笔或填充工具。
滚动鼠标
最后一个 pyautogui
鼠标函数是 scroll() 。你向它提供一个整型参数,说明向上或向下滚动多少单位。单位的意义在每个操作系统和应用上不一样,所以你必须试验,看看在你的情况下滚动多远。滚动发生在鼠标的当前位置。传递正整数表示向上滚动,传递负整数表示向下滚动
打开一个新的文件编辑窗口,将文本粘贴进去。这将得到一个很大的文本窗口,让你尝试滚动。
import pyautogui
pyautogui.click(600, 250)
pyautogui.scroll(100)
按下回车运行代码后,pyautogui.scroll() 调用将导致文件编辑窗口向上滚动。
处理屏幕
你的 GUI
自动化程序没有必要盲目地点击和输入。pyautogui
拥有屏幕快照的功能,可以根据当前屏幕的内容创建图形文件。这些函数也可以返回一个 Pillow
的Image
对象,包含当前屏幕的内容。
在 Linux
计算机上,需要安装 scrot
程序,才能在 pyautogui
中使用屏幕快照功能。在终端窗口中,执行 sudo apt-get install scrot,安装该程序。如果你使用 Windows
或 OS X,就跳过这一步,继续本节的内容。
获取屏幕快照
要在 Python
中获取屏幕快照,就调用 pyautogui.screenshot() 函数。
>>> import pyautogui
>>> screen_img = pyautogui.screenshot()
im
变量将包含一个屏幕快照的 Image
对象。现在可以调用 im
变量中Image
对象的方法,就像所有其他 Image
对象一样。
>>> screen_img.getpixel((0, 0))
(176, 176, 175)
>>> screen_img.getpixel((50, 200))
(130, 135, 144)
向 getpixel() 函数传入坐标元组,如(0,0)或(50,200),它将告诉你图像中这些坐标处的像素颜色。getpixel() 函数的返回值是一个 RGB
元组,包含 3 个整数,表示像素的红绿蓝值(没有第四个值表示 alpha
,因为屏幕快照是完全不透明的)。这就是你的程序“看到”当前屏幕上内容的方法。
分析屏幕快照
假设你的 GUI
自动化程序中,有一步是点击灰色按钮。在调用 click() 方法之前,你可以获取屏幕快照,查看脚本要点击处的像素。如果它的颜色和灰色按钮不一样,那么程序就知道出问题了。也许窗口发生了意外的移动,或者弹出式对话框挡住了该按钮。这时,不应该继续(可能会点击到错误的东西,造成严重破坏),程序可以“看到”它没有点击在正确的东西上,并自行停止。
如果屏幕上指定的 x、y 坐标处的像素与指定的颜色匹配,PyAutoGUI 的 pixelMatchesColor() 函数将返回 True
。第一和第二个参数是整数,对应 x
和y
坐标。第三个参数是一个元组,包含 3 个整数,是屏幕像素必须匹配的 RGB
颜色。
"""分析屏幕快照"""
screen_img = pyautogui.screenshot()
# 获取具体一个点的像素
print(screen_img.getpixel((50, 200)))
# 检查点击位置的颜色是否相同
print(pyautogui.pixelMatchesColor(50, 200, (130, 135, 144)))
print(pyautogui.pixelMatchesColor(50, 200, (255, 135, 144)))
在获取屏幕快照,并用 getpixel() 函数取得特定坐标处像素颜色的 RGB
元组之后,将同样的坐标和 RGB
元组传递给 pixelMatchesColor() ,这应该返回 True
。然后改变 RBG
元组中的一个值,用同样的坐标再次调用 pixelMatches Color() ,这应该返回 False
。你的 GUI
自动化程序要调用 click() 之前,这种方法应该有用。请注意,给定坐标处的颜色应该“完全”匹配。即使只是稍有差异(例如,是(255,255,254)而不是(255,255,255)),那么函数也会返回 False
。
图像识别
但是,如果事先不知道应该点击哪里,怎么办?可以使用图像识别。向 PyAutoGUI
提供希望点击的图像,让它去弄清楚坐标。
例如,如果你以前获得了屏幕快照,截取了提交按钮的图像,保存为 submit.png
,那么 locateOnScreen() 函数将返回图像所在处的坐标。要了解 locateOnScreen() 函数的工作方式,请获取屏幕上一小块区域的屏幕快照,保存该图像,用你的屏幕快照文件名代替 'submit. png':
>>> import pyautogui
>>> pyautogui.locateOnScreen('submit.png')
(643, 745, 70, 29)
locateOnScreen() 函数返回4个整数的元组,是屏幕上首次发现该图像时左边的 x
坐标、顶边的 y
坐标、宽度以及高度。如果你用自己的屏幕快照,在你的计算机上尝试,那么返回值会和这里显示的不一样。
如果屏幕上找不到该图像,locateOnScreen() 函数将返回 None
。请注意要成功识别,屏幕上的图像必须与提供的图像完全匹配。即使只差一个像素,locateOn Screen() 函数也会返回 None
。
如果该图像在屏幕上能够找到多处,locateAllOnScreen() 函数将返回一个 Generator
对象。可以将它传递给 list() ,返回一个 4 整数元组的列表。继续在交互式环境的例子中输入以下内容(用你自己的图像文件名取代 'submit.png'):
>>> list(pyautogui.locateAllOnScreen('submit.png'))
[(643, 745, 70, 29), (1007, 801, 70, 29)]
每个4整数元组代表了屏幕上的一个区域。如果图像只找到一次,返回的列表就只包含一个元组。
在得到图像所在屏幕区域的 4 整数元组后,就可以点击这个区域的中心。将元组传递给 center() 函数,它将返回该区域中心的 x
、y
坐标。用你自己的文件名、4 整数元组和坐标对,来取代参数:
>>> pyautogui.locateOnScreen('submit.png')
(643, 745, 70, 29)
>>> pyautogui.center((643, 745, 70, 29))
(678, 759)
>>> pyautogui.click((678, 759))
用 center() 得到中心坐标后,将 click() 坐标传递给函数,就会点击屏幕上该区域的中心,这个区域匹配你传递给 locateOnScreen() 函数的图像。
控制键盘
pyautogui
也有一些函数向计算机发送虚拟按键,让你能够填充表格,或在应用中输入文本。
通过键盘发送一个字符串
pyautogui.typewrite() 函数向计算机发送虚拟按键。这些按键产生什么效果,取决于当前获得焦点的窗口和文本输入框。可能需要先向文本输入框发送一次鼠标点击,确保它获得焦点。
举一个简单的例子,让我们用 Python
自动化在文件编辑窗口中输入 Hello world! 。首先,打开一个新的文件编辑窗口,将它放在屏幕的左上角,以便 pyautogui
点击正确的位置,让它获得焦点。
pyautogui.click(100,100)
pyautogui.typewrite('Hello world!')
键名
不是所有的键都很容易用单个文本字符来表示。例如,如何把 Shift
键或左箭头键表示为单个字符?在 PyAutoGUI
中,这些键表示为短的字符串值:'esc' 表示 Esc
键,'enter' 表示 Enter
。
除了单个字符串参数,还可以向 typewrite() 函数传递这些键字符串的列表。例如,以下的调用表示按 a
键,然后是 b
键,然后是左箭头两次,最后是 X
和Y
键:
>>> pyautogui.typewrite(['a', 'b', 'left', 'left', 'X', 'Y'])
因为按下左箭头将移动键盘光标,所以这会输出 XYab
。下表列出了 pyautogui
的键盘键字符串,你可以将它们传递给 typewrite() 函数,模拟任何按键组合。
也可以查看 pyautogui.KEYBOARD_KEYS
列表,看看 pyautogui
接受的所有可能的键字符串。'shift' 字符串指的是左边的 Shift
键,它等价于 'shiftleft'。 'ctrl'
、'alt' 和 'win' 字符串也一样,它们都是指左边的键。
PyKeyboard
属性
键盘键字符串 | 含义 |
---|---|
'a', 'b', 'c', 'A', 'B', 'C','1', '2', '3', '!', '@','#',等等 | 单个字符的键 |
'enter'(or 'return' or '\n') | 回车键 |
'esc' | Esc 键 |
'shiftleft', 'shiftright' | 左右 Shift 键 |
'altleft', 'altright' | 左右 Alt 键 |
'ctrlleft', 'ctrlright' | 左右 Ctrl 键 |
'tab'(or '\t') | Tab 键 |
'backspace', 'delete' | Backspace 和 Delete 键 |
'pageup', 'pagedown' | Page Up 和 Page Down 键 |
'home', 'end' | Home 和 End 键 |
'up', 'down', 'left', 'right' | 上下左右箭头键 |
'f1', 'f2', 'f3',等等 | F1 至 F12 键 |
'volumemute', 'volumedown','volumeup' | 静音、减小音量、放大音量键(有些键盘没有这些键,但你的操作系统仍能理解这些模拟的按键) |
'pause' | Pause 键 |
'capslock', 'numlock', | Caps Lock,Num Lock 和 Scroll |
'scrolllock' | Lock 键 |
'insert' | Ins 或 Insert 键 |
'printscreen' | Prtsc 或 Print Screen 键 |
'winleft', 'winright' | 左右 Win 键(在 Windows 上) |
'command' | Command 键(在 OS X 上) |
'option' | Option 键(在 OS X 上) |
按下和释放键盘
就像 mouseDown() 和 mouseUp() 函数一样,pyautogui.keyDown() 和 pyautogui. keyUp() 将向计算发送虚拟的按键和释放。它们将根据参数发送键字符串(参见表 18-1)。方便起见,pyautogui
提供了 pyautogui.press() 函数,它调用这两个函数,模拟完整的击键。
运行下面的代码,它将打印出美元字符(通过按住 Shift
键并按 4 得到):
>>> pyautogui.keyDown('shift'); pyautogui.press('4'); pyautogui.keyUp('shift')
这行代码按下 Shift
,按下(并释放)4,然后再释放 Shift
。如果你需要在文本输入框内打一个字符串,typewrite() 函数就更适合。但对于接受单个按键命令的应用,press() 函数是更简单的方式。
热键组合
“热键”或“快捷键”是一种按键组合,它调用某种应用功能。拷贝选择内容的常用热键是 Ctrl-C(在 Windows
和Linux
上)或⌘-C(在 OS X
上)。用户按住 Ctrl
键,然后按 C
键,然后释放 C
和Ctrl
键。要用 pyautogui
的 keyDown() 和 keyUp() 函数来做到这一点,必须输入以下代码:
pyautogui.keyDown('ctrl')
pyautogui.keyDown('c')
pyautogui.keyUp('c')
pyautogui.keyUp('ctrl')
这相当复杂。作为替代,可以使用 pyautogui.hotkey() 函数,它接受多个键字符串参数,按顺序按下,再按相反的顺序释放。例如对于 Ctrl-C,代码就像下面这样简单:
pyautogui.hotkey('ctrl', 'c')
对于更大的热键组合,这个函数特别有用。在 Word
中,Ctrl-Alt-Shift-S
热键组合显示 Style(样式)窗口。不必使用 8 次不同的函数调用(4 次 keyDown() 调用和 4 次 keyUp() 调用),你只要调用 hotkey('ctrl', 'alt', 'shift', 's')。
def comment_after_delay():
pyautogui.click(100, 100)
pyautogui.hotkey('win', 'e')
comment_after_delay()
这定义了一个函数 comment_after_delay(),在被调用时,会调用 windows
系统的指令,打开文件管理器。
常用 PyAutoGUI 的函数
本章介绍了许多不同函数,下面是快速的汇总参考:
moveTo(x,y)将鼠标移动到指定的 x
、y
坐标。
moveRel(xOffset,yOffset)相对于当前位置移动鼠标。
dragTo(x,y)按下左键移动鼠标。
dragRel(xOffset,yOffset)按下左键,相对于当前位置移动鼠标。
click(x,y,button)模拟点击(默认是左键)。
rightClick() 模拟右键点击。
middleClick() 模拟中键点击。
doubleClick() 模拟左键双击。
mouseDown(x,y,button)模拟在 x
、y
处按下指定鼠标按键。
mouseUp(x,y,button)模拟在 x
、y
处释放指定键。
scroll(units)模拟滚动滚轮。正参数表示向上滚动,负参数表示向下滚动。
typewrite(message)键入给定消息字符串中的字符。
typewrite([key1,key2,key3])
键入给定键字符串。
press(key)按下并释放给定键。
keyDown(key)模拟按下给定键。
keyUp(key)模拟释放给定键。
hotkey([key1,key2,key3])
模拟按顺序按下给定键字符串,然后以相反的顺序释放。
screenshot() 返回屏幕快照的 Image
对象。