Skip to content

自动化控制键盘和鼠标

知道用于编辑电子表格、下载文件和运行程序的各种 Python 模块,是很有用的。但有时候,就是没有模块对应你要操作的应用程序。在计算机上自动化任务的终极工具,就是写程序直接控制键盘和鼠标。这些程序可以控制其他应用,向它们发送虚拟的击键和鼠标点击,就像你自己坐在计算机前与应用交互一样。这种技术被称为“图形用户界面自动化”,或简称为“GUI 自动化”。有了 GUI 自动化,你的程序就像一个活人用户坐在计算机前一样,能做任何事情,除了将咖啡泼在键盘上。

请将 GUI 自动化看成是对一个机械臂编程。你可以对机械臂编程,让它敲键盘或移动鼠标。对于涉及许多无脑点击或填表的任务,这种技术特别有用。

pyautogui 模块包含了一些函数,可以模拟鼠标移动、按键和滚动鼠标滚轮。本章只介绍了 pyautogui 功能的子集。可以在 http://pyautogui.readthedocs.org/ 找到完整的文档。

安装 pyautogui 模块

pyautogui 模块可以向 WindowsOS XLinux 发送虚拟按键和鼠标点击。根据你使用的操作系统,在安装 pyautogui 之前,可能需要安装一些其他模块(称为依赖关系)。

  • Windows 上,不需要安装其他模块。
  • 在OS X 上,运行sudo pip3 install pyobjc-framework-Quartzsudo pip3 install pyobjc-core ,然后 sudo pip3 install pyobjc
  • Linux 上,运行sudo pip3 install python3-xlibsudo apt-get install scrotsudo apt-get install python3-tk ,以及sudo apt-get install python3-devScrotPyAutoGUI 使用的屏幕快照程序)。

在这些依赖安装后,运行 pip install pyautogui(或在OS XLinux 上运行 pip3),安装 pyautogui

附录 A 有安装第三方模块的完整信息。要测试 PyAutoGUI 是否正确安装,就在交互式环境运行 import pyautogui ,并检查出错信息。

前置操作

在开始 GUI 自动化之前,你应该知道如何避免可能发生的问题。Python 能以想象不到的速度移动鼠标并击键。实际上,它可能太快,导致其他程序跟不上。而且,如果出了问题,但你的程序继续到处移动鼠标,可能很难搞清楚程序到底在做什么,或者如何从问题中恢复。你的程序可能失去控制,即使它完美地执行你的指令。如果程序自己在移动鼠标,停止它可能很难。好在,有几种方法来防止或恢复 GUI 自动化问题。

通过注销关闭所有程序

停止失去控制的 GUI 自动化程序,最简单的方法可能是注销,这将关闭所有运行的程序。在 WindowsLinux 上,注销的热键是 Ctrl-Alt-Del 。在 OS X ,热键是 ⌘-Shift-Option-Q。通过注销,你会丢失所有未保存的工作,但至少不需要等计算机完全重启。

暂停和自动防故障装置

你可以告诉脚本在每次函数调用后等一会儿,在出问题的时候,让你有很短的时间窗口来控制鼠标和键盘。要做到这一点,将pyautogui.PAUSE 变量设置为要暂停的秒数。例如,在设置 pyautogui.PAUSE = 1.5 之后,每个 PyAutoGUI 函数调用在执行动作之后,都会等待一秒半。非 PyAutoGUI 指令不会停顿。

pyautogui 也有自动防故障功能。将鼠标移到屏幕的左上角,这将导致 pyautogui 产生 pyautogui .FailSafeException 异常。你的程序可以用 tryexcept 语句来处理这个异常,也可以让异常导致程序崩溃。这两种情况下,如果你尽可能快地向左上移动鼠标,自动防故障功能都将停止程序。可以设置 pyautogui. FAILSAFE = False ,禁止这项功能。

python
>>> import pyautogui
>>> pyautogui.PAUSE = 1
>>> pyautogui.FAILSAFE = True

这里我们导入 pyautogui ,并将 pyautogui.PAUSE 设置为1,即每次函数调用后暂停一秒。将 pyautogui.FAILSAFE 设置为 True ,启动自动防故障功能。

控制鼠标移动

在本节中,你将学习如何利用 pyautogui 移动鼠标,追踪它在屏幕上的位置,但首先需要理解 pyautogui 如何处理坐标。

pyautogui 的鼠标函数使用x、y 坐标。下图中展示了计算机屏幕的坐标系统。与坐标系统类似。原点的xy 都是零,在屏幕的左上角。向右 x 坐标增加,向下 y 坐标增加。所有坐标都是正整数,没有负数坐标。

图为分辨率为 1920 × 1080 的计算机屏幕上的坐标

分辨率是屏幕的宽和高有多少像素。如果屏幕的分辨率设置为 1920 × 1080,那么左上角的坐标是(0,0),右下角的坐标是(1919,1079)。

pyautogui.size() 函数返回两个整数的元组,包含屏幕的宽和高的像素数。在交互式环境中输入下面内容:

python
>>> import pyautogui
>>> pyautogui.size()
(1920, 1080)
>>> width, height = pyautogui.size()

在分辨率为 1920 × 1080 的计算机上,pyautogui.size() 返回(1920,1080)。根据屏幕分辨率的不同,返回值可能不一样。你可以将来自 pyautogui.size() 的宽和高存在变量中,如 widthheight ,让程序的可读性更好。

移动鼠标

既然你理解了屏幕坐标,就让我们来移动鼠标。pyautogui.moveTo() 函数将鼠标立即移动到屏幕的指定位置。表示x、y 坐标的整数值分别构成了函数的第一个和第二个参数。可选的 duration 整数或浮点数关键字参数,指定了将鼠标移到目的位置所需的秒数。如果不指定,默认值是零,表示立即移动(在 PyAutoGUI 函数中,所有的 duration 关键字参数都是可选的)。

python
>>> 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() 函数相对于当前的位置移动鼠标。下面的例子同样以正方形的模式移动鼠标,只是它从代码开始运行时鼠标所在的位置开始,按正方形移动:

python
>>> 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() 函数,可以确定鼠标当前的位置。它将返回函数调用时,鼠标 xy 坐标的元组。每次调用后请移动鼠标:

python
>>> pyautogui.position()
(311, 622)
>>> pyautogui.position()
(377, 481)
>>> pyautogui.position()
(1536, 637)

当然,返回值取决于鼠标的位置。

控制鼠标交互

既然你知道了如何移动鼠标,弄清楚了它在屏幕上的位置,就可以开始点击、拖动和滚动鼠标。

点击鼠标

要向计算机发送虚拟的鼠标点击,就调用 pyautogui.click() 方法。默认情况下,点击将使用鼠标左键,点击发生在鼠标当前所在位置。如果希望点击在鼠标当前位置以外的地方发生,可以传入xy 坐标作为可选的第一第二参数。

如果想指定鼠标按键,就加入 button 关键字参数,值分别为 leftmiddleright 。例如,pyautogui.click(100,150,button='left')将在坐标(100,150) 处点击鼠标左键。而pyautogui.click(200,250,button='right')将在坐标(200,250)处点击右键。

python
>>> 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 在这些应用中绘图。

让鼠标停留在绘图应用的画布上,同时选中铅笔或画笔工具,在新的文件编辑窗口中输入以下内容

python
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() 。你向它提供一个整型参数,说明向上或向下滚动多少单位。单位的意义在每个操作系统和应用上不一样,所以你必须试验,看看在你的情况下滚动多远。滚动发生在鼠标的当前位置。传递正整数表示向上滚动,传递负整数表示向下滚动

打开一个新的文件编辑窗口,将文本粘贴进去。这将得到一个很大的文本窗口,让你尝试滚动。

python
import pyautogui
pyautogui.click(600, 250)
pyautogui.scroll(100)

按下回车运行代码后,pyautogui.scroll() 调用将导致文件编辑窗口向上滚动。

处理屏幕

你的 GUI 自动化程序没有必要盲目地点击和输入。pyautogui 拥有屏幕快照的功能,可以根据当前屏幕的内容创建图形文件。这些函数也可以返回一个 PillowImage 对象,包含当前屏幕的内容。

Linux 计算机上,需要安装 scrot 程序,才能在 pyautogui 中使用屏幕快照功能。在终端窗口中,执行sudo apt-get install scrot,安装该程序。如果你使用 Windows 或OS X,就跳过这一步,继续本节的内容。

获取屏幕快照

要在 Python 中获取屏幕快照,就调用pyautogui.screenshot() 函数。

python
>>> import pyautogui
>>> screen_img = pyautogui.screenshot()

im 变量将包含一个屏幕快照的 Image 对象。现在可以调用 im 变量中Image 对象的方法,就像所有其他 Image 对象一样。

python
>>> 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 。第一和第二个参数是整数,对应 xy 坐标。第三个参数是一个元组,包含3个整数,是屏幕像素必须匹配的 RGB 颜色。

python
"""分析屏幕快照"""

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':

python
>>> 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'):

python
>>> list(pyautogui.locateAllOnScreen('submit.png'))
[(643, 745, 70, 29), (1007, 801, 70, 29)]

每个4整数元组代表了屏幕上的一个区域。如果图像只找到一次,返回的列表就只包含一个元组。

在得到图像所在屏幕区域的4整数元组后,就可以点击这个区域的中心。将元组传递给center() 函数,它将返回该区域中心的 xy 坐标。用你自己的文件名、4整数元组和坐标对,来取代参数:

python
>>> 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 点击正确的位置,让它获得焦点。

python
pyautogui.click(100100)
pyautogui.typewrite('Hello world!')

键名

不是所有的键都很容易用单个文本字符来表示。例如,如何把 Shift 键或左箭头键表示为单个字符?在 PyAutoGUI 中,这些键表示为短的字符串值:'esc' 表示 Esc 键,'enter' 表示 Enter

除了单个字符串参数,还可以向typewrite() 函数传递这些键字符串的列表。例如,以下的调用表示按 a 键,然后是 b 键,然后是左箭头两次,最后是 XY 键:

python
>>> 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'BackspaceDelete
'pageup', 'pagedown'Page Up 和Page Down
'home', 'end'HomeEnd
'up', 'down', 'left', 'right'上下左右箭头键
'f1', 'f2', 'f3',等等F1至F12键
'volumemute', 'volumedown','volumeup'静音、减小音量、放大音量键(有些键盘没有这些键,但你的操作系统仍能理解这些模拟的按键)
'pause'Pause
'capslock', 'numlock',Caps Lock,Num Lock 和Scroll
'scrolllock'Lock
'insert'InsInsert
'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得到):

python
>>> pyautogui.keyDown('shift'); pyautogui.press('4'); pyautogui.keyUp('shift')

这行代码按下 Shift ,按下(并释放)4,然后再释放 Shift 。如果你需要在文本输入框内打一个字符串,typewrite() 函数就更适合。但对于接受单个按键命令的应用,press() 函数是更简单的方式。

热键组合

“热键”或“快捷键”是一种按键组合,它调用某种应用功能。拷贝选择内容的常用热键是Ctrl-C(在 WindowsLinux 上)或⌘-C(在OS X 上)。用户按住 Ctrl 键,然后按 C 键,然后释放 CCtrl 键。要用 pyautogui 的keyDown() 和keyUp() 函数来做到这一点,必须输入以下代码:

python
pyautogui.keyDown('ctrl')
pyautogui.keyDown('c')
pyautogui.keyUp('c')
pyautogui.keyUp('ctrl')

这相当复杂。作为替代,可以使用pyautogui.hotkey() 函数,它接受多个键字符串参数,按顺序按下,再按相反的顺序释放。例如对于Ctrl-C,代码就像下面这样简单:

python
pyautogui.hotkey('ctrl', 'c')

对于更大的热键组合,这个函数特别有用。在 Word 中,Ctrl-Alt-Shift-S 热键组合显示Style(样式)窗口。不必使用8次不同的函数调用(4次keyDown() 调用和4次keyUp() 调用),你只要调用hotkey('ctrl', 'alt', 'shift', 's')。

python
def comment_after_delay():
    pyautogui.click(100, 100)
    pyautogui.hotkey('win', 'e')


comment_after_delay()

这定义了一个函数comment_after_delay(),在被调用时,会调用 windows 系统的指令,打开文件管理器。

常用 PyAutoGUI 的函数

本章介绍了许多不同函数,下面是快速的汇总参考:

moveTo(x,y)将鼠标移动到指定的 xy 坐标。

moveRel(xOffset,yOffset)相对于当前位置移动鼠标。

dragTo(x,y)按下左键移动鼠标。

dragRel(xOffset,yOffset)按下左键,相对于当前位置移动鼠标。

click(x, y ,button)模拟点击(默认是左键)。

rightClick() 模拟右键点击。

middleClick() 模拟中键点击。

doubleClick() 模拟左键双击。

mouseDown(x, y,button)模拟在 xy 处按下指定鼠标按键。

mouseUp(x, y,button)模拟在 xy 处释放指定键。

scroll(units)模拟滚动滚轮。正参数表示向上滚动,负参数表示向下滚动。

typewrite(message)键入给定消息字符串中的字符。

typewrite([key1,key2,key3]) 键入给定键字符串。

press(key)按下并释放给定键。

keyDown(key)模拟按下给定键。

keyUp(key)模拟释放给定键。

hotkey([key1,key2,key3]) 模拟按顺序按下给定键字符串,然后以相反的顺序释放。

screenshot() 返回屏幕快照的 Image 对象。