自动化控制键盘和鼠标
知道用于编辑电子表格、下载文件和运行程序的各种 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
对象。