首页 » Python编程快速上手 » Python编程快速上手全文在线阅读

《Python编程快速上手》第18章 用GUI自动化控制键盘和鼠标

关灯直达底部

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

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

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

18.1 安装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,并检查出错信息。

18.2 走对路

在开始GUI自动化之前,你应该知道如何避免可能发生的问题。Python能以想象不到的速度移动鼠标并击键。实际上,它可能太快,导致其他程序跟不上。而且,如果出了问题,但你的程序继续到处移动鼠标,可能很难搞清楚程序到底在做什么,或者如何从问题中恢复。就像迪斯尼电影《魔法师的学徒》中的魔法扫帚,它不断地向米老鼠的浴缸注水(然后水溢出来),你的程序可能失去控制,即使它完美地执行你的指令。如果程序自己在移动鼠标,停止它可能很难,你不能点击IDLE窗口来关闭它。好在,有几种方法来防止或恢复GUI自动化问题。

18.2.1 通过注销关闭所有程序

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

18.2.2 暂停和自动防故障装置

你可以告诉脚本在每次函数调用后等一会儿,在出问题的时候,让你有很短的时间窗口来控制鼠标和键盘。要做到这一点,将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,启动自动防故障功能。

18.3 控制鼠标移动

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

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

图18-1 分辨率为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,让程序的可读性更好。

18.3.1 移动鼠标

既然你理解了屏幕坐标,就让我们来移动鼠标。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个参数:向右水平移动多少个像素,向下垂直移动多少个像素,以及(可选的)花多少时间完成移动。为第一第二个参数提供负整数,鼠标将向左或向上移动。

18.3.2 获取鼠标位置

通过调用pyautogui.position 函数,可以确定鼠标当前的位置。它将返回函数调用时,鼠标x、y坐标的元组。在交互式环境中输入以下内容,每次调用后请移动鼠标:

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

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

18.4 项目:“现在鼠标在哪里?”

能够确定鼠标的位置,对于建立GUI自动化脚本是很重要的。但光看屏幕,几乎不能弄清楚像素的准确坐标。如果有一个程序在移动鼠标时随时显示 x y坐标,就会很方便。

总的来说,你希望该程序做到:

  • 获得鼠标当前的xy坐标。
  • 当鼠标在屏幕上移动时,更新这些坐标。

这意味着代码需要做到下列事情:

  • 调用函数取得当前坐标。
  • 在屏幕上打印回退制服。删除以前打印的坐标。
  • 处理异常。让用户能按键退出。

打开一个新的文件编辑器窗口,将它保存为mouseNow.py。

第1步:导入模块

程序开始是这样的:

#! python3# mouseNow.py - Displays the mouse cursor's current position.import pyautoguiprint('Press Ctrl-C to quit.')#TODO: Get and print the mouse coordinates.  

程序开始导入了pyautogui模块,打印的内容提醒用户按Ctrl-C退出。

第2步:编写退出代码和无限循环

可以用无限 while 循环,不断打印通过mouse.position 获得的当前鼠标坐标。对于退出程序的代码,你需要捕捉 KeyboardInterrupt 异常,它会在用户按下 Ctrl-C 时抛出。如果不处理这个异常,它会向用户显示丑陋的调用栈和出错信息。将下面内容添加到程序中:

#! python3# mouseNow.py - Displays the mouse cursor's current position.import pyautoguiprint('Press Ctrl-C to quit.')try:     while True:  # TODO: Get and print the mouse coordinates.❶ except KeyboardInterrupt:❷      print('/nDone.')  

为了处理这个异常,将无限while循环放在一个try语句中。当用户按下Ctrl-C,程序执行将转到except子句❶,新行中将输出Done❷。

第3步:获取并打印鼠标坐标

while循环内的代码应该获取当前鼠标的坐标,提供好看的格式,并打印输出。在while循环内添加以下代码:

#! python3# mouseNow.py - Displays the mouse cursor's current position.import pyautoguiprint('Press Ctrl-C to quit.')--snip--  # Get and print the mouse coordinates.  x, y = pyautogui.position  positionStr = 'X: ' + str(x).rjust(4) + ' Y: ' + str(y).rjust(4)--snip--  

利用多重赋值的技巧,变量x、y得到了pyautogui.position 返回元组中的两个整型值。将x、y传递给str 函数,可以得到整型坐标的字符串形式。rjust 字符串方法将对坐标右调整,让它们占据同样的宽度,不论坐标是一位、两位、三位或四位数字。连接右调准的字符串坐标,加上 'X: ' 和 ' Y: ' 标签,就得到了格式化好的字符串,保存在positionStr中。

在程序的末尾,添加以下代码:

#! python3# mouseNow.py - Displays the mouse cursor's current position.--snip--  print(positionStr, end='')❶   print('/b' * len(positionStr), end='', flush=True)  

这将在屏幕上打印positionStr。print 函数的关键字参数end='' 阻止了在打印行末添加默认的换行字符。这可能会擦除你已经在屏幕上打印的文本,但只是最近一行文本。如果你先打印了一个换行字符,就不会擦除以前打印的内容。

要擦除文本,就打印/b 退格转义字符。这个特殊字符擦除屏幕当前行末尾的字符。代码行❶利用字符串复制,得到了许多/b 字符构成的字符串,长度与positionStr中保存的字符串长度一样,效果就是擦除了前面打印的字符串。

print 调用打印/b退格键字符时,总是传入flush=True(其技术上的理由超出了本书的范围)。否则,屏幕可能不会按期望更新。

while循环重复非常快,用户实际上不会注意到你在屏幕上删除并重新打印整个数字。例如,如果x坐标是563,鼠标右移一个像素,看起来就像563中的3变成了4。

如果运行程序,只有两行打印输出。看起来像这样:

Press Ctrl-C to quit.X:   290 Y:   424  

第一行显示指令:按Ctrl-C退出。第二行显示鼠标坐标,当你在屏幕上移动鼠标时,会变化。利用这个程序,就能搞清楚鼠标坐标,用于你的GUI自动化脚本。

18.5 控制鼠标交互

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

18.5.1 点击鼠标

要向计算机发送虚拟的鼠标点击,就调用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 函数将分别执行双击右键和双击中键。

18.5.2 拖动鼠标

“拖动”意味着移动鼠标,同时按住一个按键不放。例如,可以通过拖动文件图标,在文件夹之间移动文件,或在日历应用中移动预约。

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

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

import pyautogui, time❶ time.sleep(5)❷ pyautogui.click # click to put drawing program in focusdistance = 200while 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  

在运行这个程序时,会有5秒钟的延迟❶,让你选中铅笔或画笔工具,并让鼠标停留在画图工具的窗口上。然后spiralDraw.py将控制鼠标,点击画图程序获得焦点❷。如果窗口有闪烁的光标,它就获得了“焦点”,这时你的动作(例如打字,或这个例子中的拖动鼠标),就会影响该窗口。画图程序获取焦点后,spiralDraw.py将绘制一个正方形旋转图案,如图18-2所示。

图18-2 pyautogui.dragRel 例子的结果

distance 变量从 200 开始,所以在 while 循环的第一次迭代中,第一次dragRel 调用将光标向右拖动200像素,花了0.2秒❸。然后distance降到195❹,第二次dragRel 调用将光标向下拖动195像素❺。第三次dragRel 调用将光标水平拖动−195(向左195)❻,distance降到190,最后一次dragRel调用将光标向上拖动190。每次迭代,鼠标都向右、向下、向左、向上拖动,distance都比前一次迭代小一点。通过这段代码循环,就可以移动鼠标光标,画出正方形旋转图案。

可以手工画出这个漩涡(或者说用鼠标),但一定要画得很慢,才能这么精确。pyautogui能够几秒钟就画完。

注意

你可以在代码中使用pillow模块的画图函数,画出这个图形,更多信息请参见第17章。但利用GUI自动化就能使用画图程序提供的高级画图工具,如灰度、不同的画笔或填充工具。

18.5.3 滚动鼠标

最后一个pyautogui鼠标函数是scroll。你向它提供一个整型参数,说明向上或向下滚动多少单位。单位的意义在每个操作系统和应用上不一样,所以你必须试验,看看在你的情况下滚动多远。滚动发生在鼠标的当前位置。传递正整数表示向上滚动,传递负整数表示向下滚动。将鼠标停留在IDLE窗口上,在IDLE的交互式环境中运行以下代码:

>>> pyautogui.scroll(200)  

你会看到IDLE轻松地向上滚动,然后又向下滚回来。发生向下滚动是因为,在执行完指令后,IDLE自动向下滚动到底部。输入以下代码作为替代:

>>> import pyperclip>>> numbers = ''>>> for i in range(200):       numbers = numbers + str(i) + '/n' >>> pyperclip.copy(numbers)  

这导入了pyperclip,并建立一个空字符串numbers。代码然后循环200个数字,将每个数字和换行符加入numbers。在pyperclip.copy(numbers)之后,剪贴板中将保存200行数字。打开一个新的文件编辑窗口,将文本粘贴进去。这将得到一个很大的文本窗口,让你尝试滚动。在交互式环境中输入以下代码:

>>> import time, pyautogui>>> time.sleep(5); pyautogui.scroll(100)  

在第二行,输入的两条命令以分号分隔,这告诉Python在运行这些命令时,就像它们在独立的行中一样。唯一的区别在于,交互式环境不会在两个命令之间提示你输入。这对于这个例子很重要,因为我们希望pyautogui.scroll 调用在等待之后自动发生(请注意,虽然在交互式环境中,将两条命令放在一行中可能有用,但在你的程序中,还是应该让每条命令独占一行)。

按下回车运行代码后,你有5秒钟的时间点击文件编辑窗口,让它获得焦点。在5秒钟的延迟结束后,pyautogui.scroll 调用将导致文件编辑窗口向上滚动。

18.6 处理屏幕

你的GUI自动化程序没有必要盲目地点击和输入。pyautogui拥有屏幕快照的功能,可以根据当前屏幕的内容创建图形文件。这些函数也可以返回一个Pillow的Image对象,包含当前屏幕的内容。如果你是跳跃式地阅读本书,可能需要阅读第17章,安装pillow模块,然后再继续本节的内容。

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

18.6.1 获取屏幕快照

要在Python中获取屏幕快照,就调用pyautogui.screenshot 函数。在交互式环境中输入以下内容:

>>> import pyautogui>>> im = pyautogui.screenshot  

im变量将包含一个屏幕快照的Image对象。现在可以调用im变量中Image对象的方法,就像所有其他Image对象一样。在交互式环境中输入以下内容:

>>> im.getpixel((0, 0))(176, 176, 175)>>> im.getpixel((50, 200))(130, 135, 144)  

向getpixel 函数传入坐标元组,如(0,0)或(50,200),它将告诉你图像中这些坐标处的像素颜色。getpixel 函数的返回值是一个RGB元组,包含3个整数,表示像素的红绿蓝值(没有第四个值表示alpha,因为屏幕快照是完全不透明的)。这就是你的程序“看到”当前屏幕上内容的方法。

18.6.2 分析屏幕快照

假设你的GUI自动化程序中,有一步是点击灰色按钮。在调用click 方法之前,你可以获取屏幕快照,查看脚本要点击处的像素。如果它的颜色和灰色按钮不一样,那么程序就知道出问题了。也许窗口发生了意外的移动,或者弹出式对话框挡住了该按钮。这时,不应该继续(可能会点击到错误的东西,造成严重破坏),程序可以“看到”它没有点击在正确的东西上,并自行停止。

如果屏幕上指定的 x、y 坐标处的像素与指定的颜色匹配,PyAutoGUI 的pixelMatchesColor 函数将返回True。第一和第二个参数是整数,对应x和y坐标。第三个参数是一个元组,包含3个整数,是屏幕像素必须匹配的RGB颜色。在交互式环境中输入以下内容:

 >>> import pyautogui >>> im = pyautogui.screenshot❶ >>> im.getpixel((50, 200)) (130, 135, 144)❷ >>> pyautogui.pixelMatchesColor(50, 200, (130, 135, 144)) True❸ >>> pyautogui.pixelMatchesColor(50, 200, (255, 135, 144)) False  

在获取屏幕快照,并用getpixel 函数取得特定坐标处像素颜色的RGB元组之后❶,将同样的坐标和RGB元组传递给pixelMatchesColor ❷,这应该返回True。然后改变RBG元组中的一个值,用同样的坐标再次调用pixelMatches Color ❸,这应该返回False。你的GUI自动化程序要调用click 之前,这种方法应该有用。请注意,给定坐标处的颜色应该“完全”匹配。即使只是稍有差异(例如,是(255,255,254)而不是(255,255,255)),那么函数也会返回False。

18.7 项目:扩展mouseNow程序

可以扩展本章前面的mouseNow.py项目,让它不仅给出鼠标当前位置的x、 y坐标,也给出这个像素的RGB颜色。将mouseNow.py中while循环内的代码修改为:

#! python3# mouseNow.py - Displays the mouse cursor's current position.--snip--  positionStr = 'X: ' + str(x).rjust(4) + ' Y: ' + str(y).rjust(4)  pixelColor = pyautogui.screenshot.getpixel((x, y))  positionStr += ' RGB: (' + str(pixelColor[0]).rjust(3)  positionStr += ', ' + str(pixelColor[1]).rjust(3)  positionStr += ', ' + str(pixelColor[2]).rjust(3) + ')'  print(positionStr, end='')--snip--  

现在,如果运行mouseNow.py,那么输出将包括鼠标光标处像素的RGB颜色值。

Press Ctrl-C to quit.X:   406 Y:    17 RGB: (161, 50, 50)  

这个信息,配合pixelMatchesColor 函数,应该使得给GUI自动化脚本添加颜色检查变得容易。

18.8 图像识别

但是,如果事先不知道应该点击哪里,怎么办?可以使用图像识别。向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 函数的图像。

18.9 控制键盘

pyautogui也有一些函数向计算机发送虚拟按键,让你能够填充表格,或在应用中输入文本。

18.9.1 通过键盘发送一个字符串

pyautogui.typewrite 函数向计算机发送虚拟按键。这些按键产生什么效果,取决于当前获得焦点的窗口和文本输入框。可能需要先向文本输入框发送一次鼠标点击,确保它获得焦点。

举一个简单的例子,让我们用Python自动化在文件编辑窗口中输入Hello world!。首先,打开一个新的文件编辑窗口,将它放在屏幕的左上角,以便pyautogui点击正确的位置,让它获得焦点。然后,在交互式环境中输入以下内容:

>>> pyautogui.click(100, 100); pyautogui.typewrite('Hello world!')  

请注意,在同一行中放两条命令,用分号隔开,这让交互式环境不会在两个指令之间提示输入。这防止了你在click 和typewrite 调用之间,不小心让新的窗口获得焦点,从而让这个例子失败。

Python首先在坐标(100,100)处发出虚拟鼠标点击,这将点击文件编辑窗口,让它获得焦点。typewrite 函数调用将向窗口发送文本Hello world!,结果就像图18-3。现在有了替你打字的代码!

图18-3 用PyAutogGUI点击文件编辑器窗口,在其中输入Hello world!

默认情况下,typewrite 函数将立即打印出完整字符串。但是,你可以传入可选的第二参数,在每个字符之间添加短时间暂停。例如,pyautogui.typewrite('Helloworld!',0.25)将在打出H后等待1/4秒。打出e以后再等待1/4秒,如此等等。这种渐进的打字机效果,对于较慢的应用可能有用,它们处理击键的速度不够快,跟不上pyautogui。

对于A或!这样的字符,pyautogui将自动模拟按住Shift键。

18.9.2 键名

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

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

>>> pyautogui.typewrite(['a', 'b', 'left', 'left', 'X', 'Y'])  

因为按下左箭头将移动键盘光标,所以这会输出XYab。表18-1列出了pyautogui的键盘键字符串,你可以将它们传递给typewrite 函数,模拟任何按键组合。

也可以查看pyautogui.KEYBOARD_KEYS列表,看看pyautogui接受的所有可能的键字符串。'shift' 字符串指的是左边的Shift键,它等价于 'shiftleft'。'ctrl'、'alt' 和 'win' 字符串也一样,它们都是指左边的键。

表18-1 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', 'scrolllock'

Caps Lock,Num Lock和Scroll Lock键

'insert'

Ins或Insert键

'printscreen'

Prtsc或Print Screen键

'winleft', 'winright'

左右Win键(在Windows上)

'command'

Command键(在OS X上)

'option'

Option键(在OS X上)

18.9.3 按下和释放键盘

就像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 函数是更简单的方式。

18.9.4 热键组合

“热键”或“快捷键”是一种按键组合,它调用某种应用功能。拷贝选择内容的常用热键是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')。

在屏幕的左上角打开一个新的IDLE文件编辑窗口,在交互式环境中输入以下内容(在OS X中,用 'ctrl' 代替 'alt'):

 >>> import pyautogui, time >>> def commentAfterDelay:❶ pyautogui.click(100, 100)❷ pyautogui.typewrite('In IDLE, Alt-3 comments out a line.')   time.sleep(2)❸ pyautogui.hotkey('alt', '3') >>> commentAfterDelay  

这定义了一个函数commentAfterDelay,在被调用时,将点击文件编辑窗口,让它获得焦点❶,输出“In IDLE,Alt-3 comments out a line”❷,暂停2秒钟,然后模拟按下Alt-3热键(或OS X上的ctrl-3)❸。这个快捷键在当前行加上两个#字符,将它注释掉(在IDLE中编写你自己的代码时,这是一个有用的技巧,应该知道)。

18.10 复习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对象(参见第17章关于Image对象的信息)。

18.11 项目:自动填表程序

在所有无聊的任务中,填表是最烦人的。到了现在,在最后一章的项目中,你将搞定它。假设你在电子表格中有大量的数据,必须重复将它输入到另一个应用的表单界面中,没有实习生帮你完成。尽管有些应用有导入功能,让你上传包含信息的电子表格,但有时候似乎没有其他方法,只好不动脑子地点击和输入几个小时。读到了本书的这一章,你“当然”知道会有其他方法。

本项目的表单是 Google Docs 表单,你可以在 http://nostarch.com/automatestuff找到,如图18-4所示。

图18-4 本项目用到的表单

总的来说,你的程序应该做到:

  • 点击表单的第一个文本字段。
  • 遍历表单,在每个输入栏键入信息。
  • 点击Submit按钮。
  • 用下一组数据重复这个过程。

这意味着代码需要做下列事情:

  • 调用pyautogui.click 函数,点击表单和Submit按钮。
  • 调用pyautogui.typewrite 函数,在输入栏输入文本。
  • 处理KeyboardInterrupt异常,这样用户能按Ctrl-C键退出。

打开一个新的文件编辑器窗口,将它保存为formFiller.py。

第1步:弄清楚步骤

在编写代码之前,你需要弄清楚填写一次表格时,需要的准确击键和鼠标点击。18.4节中的mouseNow.py脚本可以帮助你弄清楚确切的鼠标坐标。你只需要知道第一个文本输入栏的坐标。在点击第一个输入栏之后,你可以Tab键,将焦点移到下一个输入栏。这让你不必弄清楚每一个输入栏的x、y坐标。

下面是在表单中输入数据的步骤:

1.点击Name输入栏(在将浏览器窗口最大化后,用mouseNow.py程序来确定坐标。在OS X上,可能需要点击两次:一次让浏览器获得焦点,第二次让Name输入栏获得焦点)。

2.键入名称,然后按Tab键。

3.键入最大的恐惧(greatest fear),然后按Tab键。

4.按向下键适当的次数,选择魔力源(wizard power source):一次是Wand,两次是Amulet,三次是Crystal ball,四次是money。然后按Tab键(请注意,在OS X中,你必须为每次选择多按一次向下键。对于某些浏览器,你也需要按回车键)。

5.按向右键,选择RoboCop问题的答案。按一次是2,两次是3,三次是4,四次是5,或按空格键选择1(它是默认加亮的)。然后按Tab键。

6.键入附加的备注,然后按Tab键。

7.按回车键,点击“Submit”按钮。

8.在提交表单后,浏览器将转到一个页面。然后你需要点击一个链接,返回到表单页面。

请注意,如果你稍后再次运行这个程序,可能需要更新鼠标点击的坐标,因为浏览器窗口可能已经改变了位置。要避免这一点,请一直确保浏览器窗口最大化,然后再寻找第一个表单输入框的坐标。而且,不同操作系统上的不同浏览器,工作起来可能与这里的步骤稍有不同,所以在运行程序之前,要确保这些击键组合适合你的计算机。

第2步:建立坐标

在浏览器中载入示例表单(图18-4),并将浏览器窗口最大化。打开一个新的终端窗口或命令行窗口,来运行mouseNow.py脚本,然后将鼠标放在输入框上,弄清楚它的x、y坐标。这些数字将赋给程序中的变量。同时,找出蓝色Submit按钮的x、y坐标和RBG值。这些值将分别赋给变量submitButton和submitButtonColor。

接下来,在表单中填入一些假的数据,点击Submit。你需要看到下一个页面的样子,以便使用程序mouseNow.py寻找这个页面中Submit another response链接的坐标。

让你的源代码看起来像下面的样子。确保用自己测试得到的坐标代替斜体的值:

 #! python3 # formFiller.py - Automatically fills in the form. import pyautogui, time # Set these to the correct coordinates for your computer. nameField = (648, 319) submitButton = (651, 817) submitButtonColor = (75, 141, 249) submitAnotherLink = (760, 224) # TODO: Give the user a chance to kill the script. # TODO: Wait until the form page has loaded. # TODO: Fill out the Name Field. # TODO: Fill out the Greatest Fear(s) field. # TODO: Fill out the Source of Wizard Powers field. # TODO: Fill out the RoboCop field. # TODO: Fill out the Additional Comments field. # TODO: Click Submit. # TODO: Wait until form page has loaded. # TODO: Click the Submit another response link.  

现在你需要实际想要输入这张表格的数据。在真实世界中,这些数据可能来自电子表格、纯文本文件或某个网站。可能需要额外的代码,将数据加载到程序中。但对于这个项目,只需要将这些数据硬编码给一个变量。在程序中加入以下代码:

#! python3# formFiller.py - Automatically fills in the form.--snip--formData = [{'name': 'Alice', 'fear': 'eavesdroppers', 'source': 'wand',       'robocop': 4, 'comments': 'Tell Bob I said hi.'},       {'name': 'Bob', 'fear': 'bees', 'source': 'amulet', 'robocop': 4,       'comments': 'n/a'},       {'name': 'Carol', 'fear': 'puppets', 'source': 'crystal ball',       'robocop': 1, 'comments': 'Please take the puppets out of the       break room.'},       {'name': 'Alex Murphy', 'fear': 'ED-209', 'source': 'money',       'robocop': 5, 'comments': 'Protect the innocent. Serve the public       trust. Uphold the law.'},       ] --snip--  

formData列表包含4个字典,针对4个不同的名字。每个字典都有文本字段的名字作为键,响应作为值。最后一点准备是设置pyautogui的PAUSE变量,在每次函数调用后等待半秒钟。在程序的formData赋值语句后,添加下面的代码:

pyautogui.PAUSE = 0.5

第3步:开始键入数据

for循环将迭代formData列表中的每个字典,将字典中的值传递给pyautogui函数,最后在文本输入区输入。

在程序中添加以下代码:

 #! python3 # formFiller.py - Automatically fills in the form. --snip-- for person in formData:      # Give the user a chance to kill the script.      print('>>> 5 SECOND PAUSE TO LET USER PRESS CTRL-C <<<')❶      time.sleep(5)      # Wait until the form page has loaded.❷      while not pyautogui.pixelMatchesColor(submitButton[0], submitButton[1],      submitButtonColor):   time.sleep(0.5)   --snip--  

作为一个小的安全功能,该脚本有 5 秒暂停❶。如果发现程序在做一些预期之外的事,这让用户有机会按 Ctrl-C(或将鼠标移到屏幕的左上角,触发FailSafeException异常),从而关闭程序。然后程序等待,直到Submit按钮的颜色可见❷,这让程序知道,表单页面已经加载了。回忆一下,你在第2步中已经弄清楚了坐标和颜色信息,并将它们保存在submitButton和submitButtonColor变量中。要调用pixelMatchesColor,就传递坐标submitButton[0] 和submitButton[1],以及颜色submitButtonColor。

在等待Submit按钮颜色可见的代码之后,添加以下代码:

 #! python3 # formFiller.py - Automatically fills in the form.- -snip--❶      print('Entering %s info...' % (person['name']))❷      pyautogui.click(nameField[0], nameField[1])      # Fill out the Name field.❸      pyautogui.typewrite(person['name'] + '/t')      # Fill out the Greatest Fear(s) field.❹      pyautogui.typewrite(person['fear'] + '/t') --snip--  

我们添加了偶尔的print 调用,在终端窗口中显示程序的状态,让用户知道进展。❶

既然程序知道表格已经加载,就可以调用click,点击Name输入框❷,并调用typewrite,输入person['name'] 中的字符串❸。字符串末尾加上了 '/t' 字符,模拟按下Tab键,它将输入焦点转向下一个输入框,Greatest Fear(s)。另一次typewrite 调用,将在这个输入框中输入person['fear'] 中的字符串,然后用Tab跳到表格的下一个输入框❹。

第4步:处理选择列表和单选按钮

“wizard powers”问题的下拉菜单和RoboCop字段的单选按钮,处理起来比文本输入框需要更多技巧。要用鼠标点选这些选项,你必须搞清楚每个可能选项的x、y坐标。然而,用箭头键来选择会比较容易。

在程序中加入以下代码:

 #! python3 # formFiller.py - Automatically fills in the form. --snip--      # Fill out the Source of Wizard Powers field.❶      if person['source'] == 'wand':❷   pyautogui.typewrite(['down', '/t'])      elif person['source'] == 'amulet':   pyautogui.typewrite(['down', 'down', '/t'])      elif person['source'] == 'crystal ball':   pyautogui.typewrite(['down', 'down', 'down', '/t'])      elif person['source'] == 'money':   pyautogui.typewrite(['down', 'down', 'down', 'down', '/t'])        # Fill out the RoboCop field.❸      if person['robocop'] == 1:❹   pyautogui.typewrite([' ', '/t'])      elif person['robocop'] == 2:   pyautogui.typewrite(['right', '/t'])      elif person['robocop'] == 3:   pyautogui.typewrite(['right', 'right', '/t'])      elif person['robocop'] == 4:   pyautogui.typewrite(['right', 'right', 'right', '/t'])      elif person['robocop'] == 5:   pyautogui.typewrite(['right', 'right', 'right', 'right', '/t'])   --snip--  

在下拉菜单获得焦点后(回忆一下,你写了代码,在填充Greatest Fear(s)输入框后模拟了按 Tab 键),按下向下箭头,就会移动到选择列表的下一项。根据person['source'] 中的值,你的程序应该发出几次向下按键,然后再切换到下一个输入区。如果这个用户词典中的 'source' 值是 'wand' ❶,我们模拟按向下键一次(选择Wand),并按Tab键❷。如果 'source' 键的值是 'amulet',模拟按向下键两次,并按Tab键。对其他可能的值也是类似。

RoboCop问题的单选按钮,可以用向右键来选择。或者,如果你想选择第一个选项❸,就按空格键❹。

第5步:提交表单并等待

可以用函数typewrite 填写备注输入框,将person['comments'] 作为参数。你可以另外输入 '/t',将焦点移到下一个输入框或Submit按钮。当Submit按钮获得焦点后,调用pyautogui.press('enter'),模拟按下回车键,提交表单。在提交表单之后,程序将等待5秒,等下一页加载。

在新页面加载之后,它会有一个Submit another response链接,让浏览器转向一个新的、全空的表单页面。在第二步,你已将这个链接的坐标作为元组保存在submitAnotherLink中,所以将这些坐标传递给pyautogui.click,点击这个链接。

新的表单准备好后,脚本的外层for循环将继续下一次迭代,在表单中输入下一个人的信息。

添加以下代码,完成你的程序:

#! python3# formFiller.py - Automatically fills in the form.--snip--     # Fill out the Additional Comments field.     pyautogui.typewrite(person['comments'] + '/t')      # Click Submit.     pyautogui.press('enter')      # Wait until form page has loaded.     print('Clicked Submit.')     time.sleep(5)      # Click the Submit another response link.     pyautogui.click(submitAnotherLink[0], submitAnotherLink[1])  

在主for循环完成后,程序应该已经插入了每个人的信息。在这个例子中,只有4个人要输入。但如果有4000个人,那么编程来完成这个任务将节省大量的输入时间。

18.12 小结

用pyautogui模块实现GUI自动化,通过控制键盘和鼠标,让你与计算机上的应用程序交互。虽然这种方式相当灵活,可以做任何人类用户做的事情,但也有不足之处,即这些程序对它们的点击和键入是相当盲目的。在编写GUI自动化程序时,请试着确保它们在得到错误指令时快速崩溃。崩溃很烦人,但比程序继续错误要好得多。

利用pyautogui,你可以在屏幕上移动鼠标,模拟鼠标点击、击键和快捷键。pyautogui模块也能检查屏幕上的颜色,让GUI自动化程序对屏幕内容有足够的了解,知道它是否有偏差。甚至可以向它提供一个屏幕快照,让它找出你希望点击的区域坐标。

可以组合使用所有这些pyautogui功能,在计算机上自动化各种无脑的重复任务。实际上,看着鼠标自己移动,看着文本自动出现在屏幕上,这是彻头彻尾的催眠。为什么不用节省下来的时间,舒舒服服地坐着,看着程序为你工作?看着你的聪明才智帮你省去无聊的工作,肯定会让你感到满意。

18.13 习题

1.如何触发pyautogui的失效保护来停止程序?

2.什么函数返回当前的分辨率?

3.什么函数返回鼠标当前位置的坐标?

4.pyautogui.moveTo 和pyautogui.moveRel 函数之间的区别是什么?

5.什么函数用于拖放鼠标?

6.调用什么函数将替你键入字符串"Hello world!"?

7.如何模拟按下向左键这样的特殊键?

8.如何将当前屏幕的内容保存为图形文件并命名为screenshot.png?

9.什么代码能够设置每次pyautogui函数调用后暂停两秒钟?

18.14 实践项目

作为实践,编程完成下面的内容。

18.14.1 看起来很忙

许多即时通信程序通过一段时间鼠标不动(例如10分钟),来判断你空闲或离开了计算机。也许你想从桌子边溜走一段时间,但不想让别人看到你的即时通信软件转为空闲状态。请编写一段脚本,每隔10秒钟稍微动一下鼠标。这种移动应该相当小,以便在脚本运行时,如果你需要使用计算机,它也不会给你制造麻烦。

18.14.2 即时通信机器人

Google Talk、Skype、Yahoo Messenger、AIM和其它即时通信应用通常使用专有协议,让其他人很难通过编写Python模块与这些程序交互。但即使这些专有协议,也不能阻止你编写GUI自动化工具。

Google Talk应用有一个搜索条,让你在输入朋友列表中的用户名并按下回车时,打开一个消息窗口。键盘焦点自动移到那个新的窗口。其他即时通信应用也有类似的方式,来打开新的消息窗口。请编写一个应用程序,向朋友列表中选定的一组人发出一条通知消息。程序应该能够处理异常情况,比如朋友离线,聊天窗口出现在屏幕上不同的位置,或确认对话框打断输入消息。程序必须使用屏幕快照,指导它的GUI交互,并在虚拟按键发送之前采用各种检测方式。

注意

你可能需要建立一些假的测试账户,这样就不会在编写这个程序时,不小心打扰真正的朋友。

18.14.3 玩游戏机器人指南

有一个很不错的指南名为“How to Build a Python Bot That Can Play Web Games”,网址是http://nostarch.com/automatestuff/。这份指南解释了如何用Python创建一个GUI自动化程序,玩一个名为Sushi Go Round的Flash游戏。这个游戏需要点击正确的成分按钮,填写客户的寿司订单。填写无错订单越快,得分就越高。这个任务特别适合GUI自动化程序,因为可以作弊得到高分!这份指南包含了本章介绍的许多主题,也涉及PyAutoGUI的基本图像识别功能。