Python-自动化指南-繁琐工作自动化-第三版-十四-
Python 自动化指南(繁琐工作自动化)第三版(十四)
原文:
automatetheboringstuff.com/译者:飞龙
22 图像中的文本识别

文本识别,更正式地称为 光学字符识别 (OCR),是从图像中提取文本的过程。Python 拥有一系列丰富的字符串方法和正则表达式用于处理文本,但这些需要您首先将文本作为字符串输入。程序可以使用 OCR 来识别,例如,街道标志上的名称和存入自动取款机上的支票上的文字,或者扫描收据以创建电子副本。
与语音合成或语音识别一样,OCR 涉及执行高级计算机科学技术,但 Python 模块隐藏了这些细节,使得使用变得简单。本章介绍了 PyTesseract,这是一个与开源 Tesseract OCR 引擎一起工作的 Python 包。我们还将探讨 NAPS2 应用程序,这是一个免费的程序,Python 可以运行它来将 Tesseract OCR 应用于 PDF 文件。
安装 Tesseract 和 PyTesseract
要使用 PyTesseract,您必须按照本节中的说明在 Windows、macOS 或 Linux 计算机上安装免费的 Tesseract OCR 引擎软件。您还可以选择安装非英语语言包。之后,安装 PyTesseract 包,以便您的 Python 脚本可以与 Tesseract 交互。
Windows
在 Windows 上,打开您的浏览器访问 github.com/UB-Mannheim/tesseract/wiki 并按照页面说明下载最新的安装程序。然后,双击此安装程序来安装 Tesseract。
Tesseract 默认识别英语文本。在安装过程中,您可以可选地勾选“附加脚本数据(下载)”和“附加语言数据(下载)”复选框,以便 Tesseract 可以分别识别非英语字母和语言。安装所有语言会增加大约 600MB 的安装大小。这些语言包的文件名标识了语言,并具有 .traineddata 扩展名,例如 jpn.traineddata 用于日语。或者,您也可以勾选单个语言的复选框以节省空间。
安装完成后,将 C:\Program Files\Tesseract-OCR 文件夹(或您安装 Tesseract 的任何文件夹)添加到 PATH 环境变量中,以便 PyTesseract 可以访问 tesseract.exe 程序。第十二章介绍了如何修改 PATH 环境变量。
macOS
Homebrew 软件包管理器可以在 macOS 上安装 Tesseract。导航到 docs.brew.sh 来安装 Homebrew。通过打开终端窗口并运行 brew install tesseract 来安装 Tesseract,然后运行 brew install tesseract-lang 来安装非英语语言包。
Linux
要在 Linux 上安装 Tesseract,打开终端窗口并运行sudo apt install tesseract-ocr。运行此命令时,你必须输入管理员密码。
要为每种语言安装语言包,请在终端中运行sudo apt install tesseract-ocr-all。要仅安装你想要的语言包,将all替换为三个字符的 ISO 639 语言代码,例如fra代表法语,deu代表德语,或jpn代表日语。
PyTesseract
安装 Tesseract OCR 引擎后,你可以按照附录 A 中的说明安装 PyTesseract 的最新版本。PyTesseract 还安装了 Pillow 图像库。 ### OCR 基础
使用 PyTesseract 和 Pillow 图像库,你可以在四行代码中从图像中提取文本。你需要导入 PyTesseract 和 Pillow 库,使用Image.open()函数打开图像,然后将打开的图像传递给tess.image_to_string()函数。
让我们通过一个基本示例来了解:从我的书《Python 小项目大全书》(No Starch Press,2021 年)的引言截图中提取文本。从书的在线资源中下载 ocr-example.png 图像,网址为 nostarch.com/automate-boring-stuff-python-3rd-edition,然后在交互式外壳中输入以下内容以使用 Pillow 打开图像并使用 Tesseract 进行扫描:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('ocr-example.png')
>>> text = tess.image_to_string(img)
>>> print(text)
This book provides you with practice examples of how programming
concepts are applied, with a collection of over 80 games, simulations, and dig-
ital art programs. These aren't code snippets; they're full, runnable Python
programs. You can copy their code to become familiar with how they work,
experiment with your own changes, and then attempt to re-create them on
your own as practice. After a while, you'll start to get ideas for your own pro-
grams and, more importantly, know how to go about creating them.
# --snip--
将图像文件中的文本转换为字符串需要复杂的算法,但 Python 只需四行代码就能使这些算法变得可用!
图像预处理
上一节中从图像中提取的文本几乎完美地转换成了 Python 字符串。然而,OCR 有局限性。与计算机生成的图像(如截图)不同,扫描或拍摄的纸张可能包含缺陷,而现实场景的照片过于复杂,难以从中提取文本。你不能,比如说,拍摄一辆车的背面照片并期望 Tesseract 从中提取车牌号码。你首先需要裁剪车牌周围的图像;即使这样,它可能仍然无法阅读。因此,Tesseract 旨在用于印刷文档,而不是照片或手写文本。
即使在截图上,也始终要考虑 OCR 文本是不完美的,并且需要校正。特别是,你可能会遇到以下问题:
-
字符串保留了任何行尾的连字符拆分(例如,在“dig-”和“ital”或“pro-”和“grams”中)。
-
字符串不保留任何字体或大小信息。
-
字符串中的空白可能与文本不匹配。
-
字符串可能包含错误扫描的字符,例如容易混淆的小写j和小写i。
-
如果图像包含表格或多列文本,字符串可能会混合文本并按顺序错误地包含它。
特别注意数字中的错误,因为它们可能比拼写错误更难发现。
Tesseract 通过预处理步骤来减轻某些问题,但你可以通过使用图像编辑程序执行以下预处理步骤来提高其准确性:
-
不要扫描多栏图像;将每栏文本放入单独的图像中。
-
只使用打印文本,不要使用手写文本。
-
使用常规字体,而不是草书或风格化的字体。
-
旋转图像,使文本行完全垂直,而不是略微倾斜。
-
使用深色文本在浅色背景上,而不是白色文本在黑色背景上。
-
移除图像边缘的任何深色边框。
-
如果文本紧挨着图像边缘,添加一个小白边。
-
调整图像的亮度和对比度,使文本从背景中突出。
-
在扫描之前,移除图像中的小“噪声”像素以清理图像。
其中一些步骤可以使用 Python 和 OpenCV 库自动执行。查看博客文章“使用 Python 和 OpenCV 预处理图像进行 OCR”在autbor.com/preprocessingocr以获取更多示例。
使用大型语言模型修正错误
OCR 算法倾向于犯的错误涉及间距和单个字符。使用拼写检查算法不会找到 OCR 错误:它会指出原始图像中拼写错误的单词的准确识别字符,并错过导致正确拼写的单词的错误。识别这类字符错误需要理解上下文和对字符应该是什么的常识。
这正是大型语言模型(LLM)AI,如 ChatGPT、Gemini 和 LLaMA 可以解决的问题。例如,考虑图 22-1,玛丽·雪莱小说《弗兰肯斯坦》的原始扫描。这个特定的页面是在 1831 年印刷的,所以纸张是皱的,变黄了,字符印刷不均匀。你可以从书籍的在线资源中下载frankenstein.png。

图 22-1:玛丽·雪莱 1831 年印刷的《弗兰肯斯坦》扫描页面的上半部分
在没有任何预处理的情况下,Tesseract 识别以下文本:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein.png')
>>> text = tess.image_to_string(img)
>>> print(text)
THE MODERN PROMETHEUS. 43
CHAPTER V.
Iv was on a dreary night of November, that I beheld the
accomplishment of my toils. With an anxiety that almost
amounted to agony, I collected the instruments of life
around me, that I might infuse a spark of being into the
lifeless thing that lay at my feet. It was already one in
the morning ; the rain pattered dismally against the panes,
and my candle was nearly burnt out, when, by the glimmer
of the half-extinguished light, I saw the dull yellow eye
of the creature open ; it breathed hard, and a convulsive
Motion agitated its limbs.
它做得相当不错,但在段落开头错误地扫描了字符'Iv'而不是'IT',以及在最后一行将'Motion'错误地扫描为'motion'。(确切的结果可能会随着语言包的更新而变化。)
为了修复这些问题,尝试向 LLM 提供以下示例提示和扫描的弗兰肯斯坦文本的输出:
以下是从 OCR 扫描中输出的文本。纠正其中的间距、缺失字符或识别不准确字符。不要纠正原文中存在的拼写或语法错误。将段落放在单行上,并取消跨行断开的连字符单词。只提供纠正后的文本,不解释 OCR 是什么或任何其他前言。以下是文本:
现代普罗米修斯。43
第五章。
在一个阴沉的十一月之夜,我看到了……
当提供此提示时,LLM 生成了以下输出:
THE MODERN PROMETHEUS. 43
CHAPTER V.
It was on a dreary night of November that I beheld the accomplishment of
my toils. With an anxiety that almost amounted to agony, I collected the
instruments of life around me, that I might infuse a spark of being into
the lifeless thing that lay at my feet. It was already one in the morning;
the rain pattered dismally against the panes, and my candle was nearly burnt
out, when, by the glimmer of the half-extinguished light, I saw the dull
yellow eye of the creature open; it breathed hard, and a convulsive motion
agitated its limbs.
LLM 将 'Iv' 修正为 'It',将 'Motion' 修正为 'motion'。它还移除了每行末尾的连字符单词,以便可以删除换行符。这使得将文本复制粘贴到 Word 文档或电子邮件中变得更容易。为了自动化此过程,大多数在线 LLM 都有 API,以便您的程序可以直接发送提示并接收响应。除非您在自己的机器上运行 LLM(这超出了本书的范围),否则您必须注册这些在线 LLM 服务。这可能免费或需要订阅费。
总是记住,LLMs 容易过度自信。您应该始终验证它们的输出。它们返回的文本可能遗漏了一些错误,纠正了错误类型的错误,甚至引入了它们自己的新错误。您仍然需要一个人类来审查机器输出。(您可能还想让第二个人类审查第一个人类的工作,因为人类经常犯错误。)
在非英语语言中识别文本
Tesseract 默认假设它正在扫描的文本是英语,但您也可以指定其他语言。第 528 页的“安装 Tesseract 和 PyTesseract”有安装非英语语言包的说明。您可以通过在交互式外壳中输入以下内容来查看您已安装的语言包:
>>> import pytesseract as tess
>>> tess.get_languages()
['afr', 'amh', 'ara', 'asm', 'aze', 'aze_cyrl', 'bel', 'ben', 'bod', 'bos',
# --snip--
'ton', 'tur', 'uig', 'ukr', 'urd', 'uzb', 'uzb_cyrl', 'vie', 'yid', 'yor']
此列表中的字符串主要是三个字符的 ISO 639-3 语言代码,有一些例外。例如,虽然 'aze' 是使用拉丁字母的阿塞拜疆语的 ISO 639-3 代码,但 'aze_cyrl' 字符串是使用西里尔字母的阿塞拜疆语。有关详细信息,请参阅 Tesseract 文档。
要扫描包含非英语文本的图像,请为 lang 关键字参数传递这些字符串值之一。例如,frankenstein_jpn.png 包含了《弗兰肯斯坦》一节的日文翻译。从书籍的在线资源下载此文件,并在交互式外壳中输入以下内容:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein_jpn.png')
>>> text = tess.image_to_string(img, lang='jpn')
>>> print(text)
第 5 剖 私が自分の労苦の成果を目の当たりにしたのは、11 月の芝鬱な夜でした。 ほとんど苦
痛に等しい不安を抱えながら、 私は足元に横たわる生命のないものに存在の輝きを吹き込むこ
# --snip--
だ有目、 しわが寄った顔色、 そしてまっすぐな黒い大と、 より恐ろしいコントラストを形成した
だけでした。
如果您使用错误的语言,image_to_string() 将返回 Tesseract 对日本字符看起来像什么英语字符的最佳猜测。当然,由于这些字符不是英语,返回的文本将是乱码:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein_jpn.png')
>>> text = tess.image_to_string(img, lang='eng')
>>> print(text)
BS FABADOABOMEE AOYEVICLEDIL, 1 ADBBERKCLE, (ELA ER
WISE LW ABBA A TRA B, ALE TIRE DO EMO REVS DICED MS EKA
# --snip--
要识别多种语言的文本,您可以在传递给 image_to_string() 函数的 lang 关键字参数之前,将语言代码与一个 '+' 字符结合。例如,tess.image_to_string(img, lang='eng+jpn') 可以识别图像中的英语和日语字符。
NAPS2 扫描应用程序
虽然 PyTesseract 可以用于从图像中提取文本,但 OCR 的一个常见用例是创建包含可搜索文本的扫描图像 PDF 文档。尽管有应用程序可以做到这一点,但它们通常不提供自动化数百或数千个图像 PDF 生成所需的灵活性。我推荐开源的 Not Another PDF Scanner 2 (NAPS2) 应用程序,不仅因为它可以控制平板式扫描仪,还因为它能够运行 Tesseract 并向 PDF 文档添加文本。它是免费的,功能直观,可在 Windows、macOS 和 Linux 上使用。NAPS2 可以将多个图像组合成一个包含嵌入文本的 PDF 文件,而无需连接到物理扫描仪。它还知道如何使用 Tesseract 的高级功能,因此可以在 PDF 页面上正确位置嵌入文本字符串,并且您可以从 Python 脚本中运行它。
安装和设置 NAPS2
要安装 NAPS2,请导航到 www.naps2.com/download 并下载适用于您操作系统的安装程序。在 Windows 和 macOS 上,运行下载的安装程序。在 Linux 上,下载 Tesseract 的 Flatpak 安装程序。然后,打开终端窗口,并从下载文件夹中运行 flatpak install naps2-X.X.X-linux-x64.flatpak(或下载的安装程序文件名),您可能需要输入管理员密码以完成安装。
一旦安装完成,您就可以运行 NAPS2 桌面应用程序。在 Windows 上,您可以从开始菜单中选择 NAPS2。在 macOS 上,您可以从 Spotlight 运行 NAPS2。在 Linux 上,您需要打开一个新的终端窗口并运行 flatpak run com.naps2.Naps2。然而,这本书使用 Python 代码中的 subprocess 模块来运行 NAPS2 应用程序,而不是图形用户界面。
从 Python 运行 NAPS2
Python 脚本可以使用 subprocess 模块通过多个命令行参数运行 NAPS2 应用程序。以这种方式运行时,NAPS2 不会显示其应用程序窗口,这对于 Python 脚本中的自动化步骤来说非常理想。
让我们再次使用 frankenstein.png 图片,并让 NAPS2 生成一个嵌入 OCR 文本的 PDF 文件。NAPS2 程序的位置在每个操作系统上都不相同;以下交互式 shell 代码显示了 Windows 上的路径:
>>> import subprocess
>>> naps2_path = [r'C:\Program Files\NAPS2\NAPS2.Console.exe'] # Windows
>>> proc = subprocess.run(naps2_path + ['-i', 'frankenstein.png', '-o',
'output.pdf', '--install', 'ocr-eng', '--ocrlang', 'eng', '-n', '0', '-f',
'-v'], capture_output=True)
在 macOS 上,将设置路径的行替换为以下内容:naps2_path = ['/Applications/NAPS2.app/Contents/MacOS/NAPS2', 'console']。在 Linux 上,使用以下内容代替:naps2_path = ['flatpak', 'run', 'com.naps2.Naps2', 'console']。
代码创建一个名为 output.pdf 的新文件,其中包含来自 frankenstein.png 的扫描图像的单页。然而,如果您在 PDF 应用程序中打开此文件,您会注意到您可以突出显示文本并将其复制到剪贴板。许多 PDF 应用程序还允许您将 PDF 保存为包含 OCR 文本的.txt文本文件。
让我们来看看这个例子中的每个命令行参数。您可以根据自己的需要更改它们:
-i, 'frankenstein.png` 将输入设置为 frankenstein.png 图像文件。有关指定多种格式的多个输入的更多信息,请参阅下一节,“指定输入”。
-o, 'output.pdf` 创建一个名为 output.pdf 的文件来存储 OCR 结果。
--install, 'ocr-eng' 安装 OCR 的英语语言包。如果语言已安装,则此操作不会执行任何操作。如果您想安装不同的语言包,请使用ocr-前缀和另一个三位 ISO 639 语言代码。
--ocrlang, 'eng 将英语设置为 OCR 扫描识别的语言。此参数直接传递给 Tesseract 的命令行参数,因此您可以使用类似'eng+jpn+rus'`的参数来指定图像包含英语、日语和俄语文本。
-n, '0' 指定您想进行零扫描且不使用平板式扫描仪。这可以防止在没有物理平板式扫描仪连接到您的计算机时出现错误消息。
-f 强制 NAPS2 覆盖已存在的 output.pdf 输出文件。
-v 启用详细模式,以便在 NAPS2 创建您的 PDF 时显示状态文本。如果您想查看此状态文本,请将subprocess.run()的capture_output=True关键字参数更改为capture_output=False。
NAPS2 的命令行参数的在线文档位于www.naps2.com/doc/command-line。第十九章详细介绍了subprocess模块。
指定输入
NAPS2 允许您导入 PDF 和大多数图像格式以创建最终的合并 PDF。该应用程序有自己的迷你语言,用于指定多个输入作为单个命令行参数,紧随-i之后。这可能相当复杂,但您可以将其视为分号分隔,带有 Python 索引和切片表示法。
要指定多个文件,请用分号分隔它们。例如,传递'-i', 'cat.png;dog.png;moose.png'将创建一个 PDF 文件,其中cat.png用于第一页,dog.png用于第二页,moose.png用于第三页。
您还可以使用与 Python 列表切片语法相同的语法指定 PDF 中的单个页面。在 PDF 文件名后跟方括号,包含要使用的页面编号。与 Python 一样,0代表第一页。例如,传递'-i', 'spam.pdf[0];spam.pdf[5];eggs.pdf'将创建一个 PDF 文件,其中包含spam.pdf的第 1 页,接着是spam.pdf的第 6 页,然后是eggs.pdf的所有页面。
你可以使用这种切片符号指定一系列页面,或者使用负数来表示从 PDF 文档末尾开始的页面。例如,传递'-i', 'spam.pdf[0:2];eggs.pdf[-1]'将spam.pdf的前两页与eggs.pdf的最后一页合并。
NAPS2 通过其命令行参数提供了更多功能。查看其在线文档以了解更多信息。如果你发现 NAPS2 不适合你的需求,我还推荐pypi.org/project/ocrmypdf/上的 ocrmypdf 包来创建包含文本的 PDF。
摘要
在本章中,你学习了如何利用 Tesseract 从图像中提取文本。这是一种非常强大的能力,可以为你节省数小时的数据录入时间。然而,OCR 并非魔法,你的图像可能需要预处理才能获得准确的结果。Tesseract 也设计用于处理在浅色背景上的黑色打印文本,其中文本是水平的,并且你必须知道图像文本的语言才能获得良好的结果。大型语言模型 AI 可以帮助纠正错误识别的字符,但其输出仍需要人工监督。最后,开源的 NAPS2 应用程序提供了一种将多张图像合并为包含 OCR 文本的单个 PDF 的方法。OCR 是计算机科学的一个惊人突破,但你不需要高级学位就能使用它。Python 使 OCR 对每个人来说都变得可行。
练习问题
-
Tesseract 默认识别哪种语言?
-
请说出一个与 PyTesseract 兼容的 Python 图像库。
-
哪个 PyTesseract 函数接受一个图像对象并返回图像中的文本字符串?
-
如果你拍摄一个街牌的照片,Tesseract 能否识别照片中的街牌文本?
-
哪个函数返回 Tesseract 安装的语言包列表?
-
如果一个图像中包含英语和日语文本,你指定给 PyTesseract 的哪个关键字参数?
-
哪个应用程序允许你创建包含 OCR 文本的 PDF?
练习程序:浏览器文本抓取器
一些网站允许你查看它们的文本内容,但使保存或甚至复制粘贴文本到电脑变得困难。你可能看到它们作为嵌入在网页中的 PDF。一个例子是autbor.com/embeddedfrankenstein/,如图 22-2 所示。

图 22-2:一个包含嵌入文档的示例网页
第二十三章中介绍的 PyAutoGUI 库可以截取屏幕截图并将其保存为图像,而第二十一章中介绍的 Pillow 库可以裁剪图像。PyAutoGUI 还有一个 MouseInfo 应用程序,用于在屏幕上查找 XY 坐标。
编写一个名为 ocrscreen.py 的程序,该程序截取屏幕截图,将图像裁剪成屏幕截图中的文本部分,然后将它传递给 PyTesseract 进行 OCR。程序应将识别的文本追加到名为 output.txt 的文本文件的末尾。以下是 ocrscreen.py 程序的模板:
import pyautogui
# TODO - Add the additionally needed import statements.
# The coordinates for the text portion. Change as needed:
LEFT = 400
TOP = 200
RIGHT = 1000
BOTTOM = 800
# Capture a screenshot:
img = pyautogui.screenshot()
# Crop the screenshot to the text portion:
img = img.crop((LEFT, TOP, RIGHT, BOTTOM))
# Run OCR on the cropped image:
# TODO - Add the PyTesseract code here.
# Add the OCR text to the end of output.txt:
# TODO - Call open() in append mode and append the OCR text.
此程序应允许你在浏览器中滚动嵌入的不可保存的文本,运行程序,然后滚动 PDF 到下一页的内容。完成后,你将拥有文档文本的副本。(如果你阅读了第二十三章,你还将了解如何让你的脚本模拟按键来为你滚动网页。)### 安装 Tesseract 和 PyTesseract
要使用 PyTesseract,你必须按照本节中的说明在你的 Windows、macOS 或 Linux 计算机上安装免费的 Tesseract OCR 引擎软件。导航到 docs.brew.sh 来安装 Homebrew。通过打开终端窗口并运行 brew install tesseract 来安装 Tesseract,然后运行 brew install tesseract-lang 来安装非英文语言包。
Windows
在 Windows 上,打开你的浏览器到 github.com/UB-Mannheim/tesseract/wiki 并按照页面上的说明下载最新的安装程序。然后,双击此安装程序来安装 Tesseract。
Tesseract 默认识别英文文本。在安装过程中,你可以选择性地勾选“额外脚本数据(下载)”和“额外语言数据(下载)”复选框,以便 Tesseract 能够分别识别非英文字母和语言。安装所有语言会增加大约 600MB 的安装大小。这些语言包的文件名标识了语言,并带有 .traineddata 扩展名,例如 jpn.traineddata 用于日语。或者,你也可以勾选单个语言的复选框以节省空间。
安装完成后,将 C:\Program Files\Tesseract-OCR 文件夹(或你安装 Tesseract 的任何文件夹)添加到 PATH 环境变量中,以便 PyTesseract 可以访问 tesseract.exe 程序。第十二章介绍了如何修改 PATH 环境变量。
macOS
Homebrew 软件包管理器可以在 macOS 上安装 Tesseract。导航到 github.com/UB-Mannheim/tesseract/wiki 并按照页面上的说明下载最新的安装程序。然后,双击此安装程序来安装 Tesseract。
Linux
要在 Linux 上安装 Tesseract,打开终端窗口并运行 sudo apt install tesseract-ocr。你需要输入管理员密码才能运行此命令。
要为每种语言安装语言包,请在终端中运行 sudo apt install tesseract-ocr-all。要安装你想要的语言包,将 all 替换为三个字符的 ISO 639 语言代码,例如 fra 用于法语,deu 用于德语,或 jpn 用于日语。
PyTesseract
安装 Tesseract OCR 引擎后,可以按照附录 A 中的说明安装 PyTesseract 的最新版本。PyTesseract 还会安装 Pillow 图像库。
Windows
在 Windows 上,打开您的浏览器到github.com/UB-Mannheim/tesseract/wiki,并按照页面上的说明下载最新的安装程序。然后,双击此安装程序来安装 Tesseract。
Tesseract 默认识别英语文本。在安装过程中,您可以可选地勾选“附加脚本数据(下载)”和“附加语言数据(下载)”复选框,以便 Tesseract 可以分别识别非英语字母和语言。安装所有语言会增加大约 600MB 的安装大小。这些语言包的文件名标识了语言,并具有.traineddata扩展名,例如jpn.traineddata代表日语。或者,您也可以勾选单个语言的复选框以节省空间。
安装完成后,将tesseract.exe程序所在的文件夹(或您安装 Tesseract 的任何文件夹)添加到PATH环境变量中,以便 PyTesseract 可以访问tesseract.exe程序。第十二章介绍了如何修改PATH环境变量。
macOS
Homebrew 包管理器可以在 macOS 上安装 Tesseract。导航到docs.brew.sh来安装 Homebrew。通过打开终端窗口并运行brew install tesseract来安装 Tesseract,然后运行brew install tesseract-lang来安装非英语语言包。
Linux
在 Linux 上安装 Tesseract,打开终端窗口并运行sudo apt install tesseract-ocr。运行此命令时需要输入管理员密码。
要安装每种语言的语料库,请在终端中运行sudo apt install tesseract-ocr-all。要安装您想要的语料库,将all替换为三个字符的 ISO 639 语言代码,例如fra代表法语,deu代表德语,或jpn代表日语。
PyTesseract
安装 Tesseract OCR 引擎后,可以按照附录 A 中的说明安装 PyTesseract 的最新版本。PyTesseract 还会安装 Pillow 图像库。
OCR 基础
使用 PyTesseract 和 Pillow 图像库,您可以在四行代码中从图像中提取文本。您需要导入 PyTesseract 和 Pillow 库,使用Image.open()函数打开图像,然后将打开的图像传递给tess.image_to_string()函数。
让我们通过一个基本示例来了解:从我的书《Python 小项目大全书》(No Starch Press,2021 年)的引言截图提取文本。从书的在线资源中下载 ocr-example.png 图片,网址为 nostarch.com/automate-boring-stuff-python-3rd-edition,然后在交互式外壳中输入以下命令以使用 Pillow 打开图片并用 Tesseract 进行扫描:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('ocr-example.png')
>>> text = tess.image_to_string(img)
>>> print(text)
This book provides you with practice examples of how programming
concepts are applied, with a collection of over 80 games, simulations, and dig-
ital art programs. These aren't code snippets; they're full, runnable Python
programs. You can copy their code to become familiar with how they work,
experiment with your own changes, and then attempt to re-create them on
your own as practice. After a while, you'll start to get ideas for your own pro-
grams and, more importantly, know how to go about creating them.
# --snip--
将图像文件中的文本转换为字符串需要复杂的算法,但 Python 只需四行代码就能使这些算法变得可用!
预处理图像
上一节中从图像提取的文本几乎完美地转换成了 Python 字符串。然而,OCR 有局限性。与计算机生成的图像(如截图)不同,扫描或拍摄的纸张可能包含缺陷,而现实场景的照片过于复杂,难以提取文本。你不能,比如说,拍摄一辆车的背面照片并期望 Tesseract 从中提取车牌号码。你首先需要裁剪车牌周围的图像;即使这样,它可能仍然难以阅读。因此,Tesseract 旨在用于印刷文档,而不是照片或手写文本。
即使在截图上,也始终认为 OCR 文本是不完美的,需要校正。特别是,你可能会遇到以下问题:
-
字符串保留任何行尾连字符(例如,在“dig-”和“ital”或“pro-”和“grams”中)。
-
字符串不保留任何字体或大小信息。
-
字符串中的空白可能与文本不匹配。
-
字符串可能包含错误扫描的字符,例如容易混淆的小写 j 和小写 i。
-
如果图像包含表格或多列文本,字符串可能会混合文本并按顺序错误地包含它。
特别注意数字中的错误,因为它们可能比拼写错误更难发现。
Tesseract 通过预处理步骤来减轻某些问题,但你可以通过使用图像编辑程序执行以下预处理步骤来提高其准确性:
-
不要扫描多列图像;将每列文本放入单独的图像中。
-
只使用打印文本,不要使用手写文本。
-
使用常规字体,而不是手写体或艺术字体。
-
旋转图像,使文本行完全垂直,而不是略微倾斜。
-
使用深色文本在浅色背景上,而不是在黑色背景上的白色文本。
-
移除图像边缘的任何深色边框。
-
如果文本紧挨着图像边缘,添加一个小白边框。
-
调整图像的亮度和对比度,使文本从背景中突出。
-
在扫描之前移除图像中的小部分“噪声”像素以清理图像。
其中一些步骤可以使用 Python 和 OpenCV 库自动执行。查看博客文章“使用 Python 和 OpenCV 预处理图像进行 OCR”在autbor.com/preprocessingocr以获取更多示例。
使用大型语言模型修复错误
光学字符识别(OCR)算法倾向于犯的错误涉及间距和单个字符。使用拼写检查算法不会找到 OCR 错误:它会指出原始图像中拼写错误的单词的准确识别字符,并错过导致正确拼写的单词的错误。识别这类字符错误需要理解上下文和对字符应该是什么的常识。
这正是大型语言模型(LLM)AI,如 ChatGPT、Gemini 和 LLaMA 可以解决的问题类型。例如,考虑图 22-1,这是玛丽·雪莱小说《弗兰肯斯坦》的原始扫描图。这个特定的页面是在 1831 年印刷的,所以纸张已经起皱并泛黄,字符印刷不均匀。您可以从书籍的在线资源中下载frankenstein.png。

图 22-1:玛丽·雪莱 1831 年印刷的《弗兰肯斯坦》扫描页面的上半部分
在没有任何预处理的情况下,Tesseract 识别了以下文本:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein.png')
>>> text = tess.image_to_string(img)
>>> print(text)
THE MODERN PROMETHEUS. 43
CHAPTER V.
Iv was on a dreary night of November, that I beheld the
accomplishment of my toils. With an anxiety that almost
amounted to agony, I collected the instruments of life
around me, that I might infuse a spark of being into the
lifeless thing that lay at my feet. It was already one in
the morning ; the rain pattered dismally against the panes,
and my candle was nearly burnt out, when, by the glimmer
of the half-extinguished light, I saw the dull yellow eye
of the creature open ; it breathed hard, and a convulsive
Motion agitated its limbs.
它做得相当不错,但将段落开头的'Iv'错误地扫描为'IT',并将最后一行的'Motion'错误地扫描为'motion'。(确切结果可能因语言包更新而有所不同。)
为了修复这些问题,尝试向 LLM 提供以下示例提示和扫描的弗兰肯斯坦文本的文本输出:
以下是从 OCR 扫描中输出的文本。纠正其中的间距、缺失字符或识别不准确字符。不要纠正原始文本中存在的拼写或语法错误。将段落放在单行上,并撤销跨行末尾的连字符单词。只提供纠正后的文本,不解释 OCR 是什么或其他前言。以下是文本:
现代普罗米修斯。43
第五章。
在一个阴沉的 11 月夜晚,我看到了……
当输入这个提示时,LLM 生成了以下输出:
THE MODERN PROMETHEUS. 43
CHAPTER V.
It was on a dreary night of November that I beheld the accomplishment of
my toils. With an anxiety that almost amounted to agony, I collected the
instruments of life around me, that I might infuse a spark of being into
the lifeless thing that lay at my feet. It was already one in the morning;
the rain pattered dismally against the panes, and my candle was nearly burnt
out, when, by the glimmer of the half-extinguished light, I saw the dull
yellow eye of the creature open; it breathed hard, and a convulsive motion
agitated its limbs.
LLM 将'Iv'更正为'It',将'Motion'更正为'motion'。它还移除了每行末尾的连字符单词,以便可以移除换行符。这使得将文本复制粘贴到 Word 文档或电子邮件中变得更容易。为了自动化此过程,大多数在线 LLM 都提供 API,以便您的程序可以直接发送提示并接收响应。除非您在自己的机器上运行 LLM(这超出了本书的范围),否则您必须注册这些在线 LLM 服务。这可能免费或需要订阅费。
总是记住 LLMs 容易过度自信。你应该始终验证它们的输出。它们返回的文本可能遗漏了一些错误,修复了错误的错误类型,甚至引入了它们自己的新错误。你仍然需要一个人类来审查机器输出。(你也许还想让第二个人类审查第一个人类的工作,因为人类经常犯错误。)
预处理图像
上一节图像中的文本几乎完美地提取到了 Python 字符串中。然而,OCR 有局限性。与计算机生成的图像(如截图)不同,扫描或拍摄的纸张可能包含缺陷,而现实场景的照片过于复杂,难以提取文本。你不能,比如说,拍一辆车的背面照片并期望 Tesseract 从中提取车牌号码。你首先需要裁剪车牌周围的图像;即使这样,它可能仍然难以阅读。因此,Tesseract 旨在用于印刷文档,而不是照片或手写文本。
即使在屏幕截图上,也始终认为 OCR 文本是不完美的,需要校正。特别是,你可能会遇到以下问题:
-
字符串保留了任何行尾连字符(例如,“dig-”和“ital”或“pro-”和“grams”)。
-
字符串不保留任何字体或大小信息。
-
字符串中的空白可能与文本不匹配。
-
字符串可能扫描到错误字符,例如混淆小写j和小写i。
-
如果图像包含表格或多列文本,字符串可能会混合文本并按顺序错误地包含它。
特别注意数字中的错误,因为它们可能比拼写错误更难发现。
Tesseract 通过预处理步骤来减轻某些问题,但你可能通过使用图像编辑程序执行以下预处理步骤来提高其准确性:
-
不要扫描多列图像;将每列文本放入单独的图像中。
-
只使用打印文本,不要使用手写文本。
-
使用传统字体,而不是草书或风格化的字体。
-
将图像旋转,使文本行完全垂直,而不是略微倾斜。
-
使用深色文本在浅色背景上,而不是白色文本在黑色背景上。
-
移除图像边缘的任何暗色边框。
-
如果文本紧挨着图像边缘,添加一个小白边框。
-
调整图像的亮度和对比度,使文本从背景中突出。
-
在扫描之前,移除图像中的小块“噪声”像素以清理图像。
其中一些步骤可以使用 Python 和 OpenCV 库自动执行。查看博客文章“使用 Python 和 OpenCV 对 OCR 图像进行预处理”在autbor.com/preprocessingocr以获取更多示例。
使用大型语言模型修复错误
OCR 算法倾向于犯的错误类型涉及间距和单个字符。使用拼写检查算法找不到 OCR 错误:它会指出原始图像中拼写错误的单词的准确识别字符,并指出导致正确拼写的单词的错误。识别这类字符错误需要理解上下文和对字符应该是什么的常识。
这正是大型语言模型(LLM)AI,如 ChatGPT、Gemini 和 LLaMA 可以解决的问题。例如,考虑图 22-1,玛丽·雪莱小说 《弗兰肯斯坦》 的原始扫描。这个特定的页面是在 1831 年印刷的,所以纸张是皱的,变黄了,字符印刷不均匀。您可以从书籍的在线资源中下载 frankenstein.png。

图 22-1:玛丽·雪莱 1831 年版《弗兰肯斯坦》的扫描页面的上半部分
在没有任何预处理的情况下,Tesseract 识别以下文本:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein.png')
>>> text = tess.image_to_string(img)
>>> print(text)
THE MODERN PROMETHEUS. 43
CHAPTER V.
Iv was on a dreary night of November, that I beheld the
accomplishment of my toils. With an anxiety that almost
amounted to agony, I collected the instruments of life
around me, that I might infuse a spark of being into the
lifeless thing that lay at my feet. It was already one in
the morning ; the rain pattered dismally against the panes,
and my candle was nearly burnt out, when, by the glimmer
of the half-extinguished light, I saw the dull yellow eye
of the creature open ; it breathed hard, and a convulsive
Motion agitated its limbs.
它做得相当不错,但段落开头将 'Iv' 扫描成了 'IT',而在最后一行将 'Motion' 扫描成了 'motion'。(确切的结果可能会随着语言包的更新而变化。)
要修复这些问题,尝试向 LLM 提供以下示例提示和扫描的 《弗兰肯斯坦》 文本的文本输出:
以下是从 OCR 扫描中输出的文本。纠正其中的间距、缺失字符或识别不准确字符。不要纠正原始文本中存在的拼写或语法错误。将段落放在单行上,并撤销跨行末尾的连字符。只提供纠正后的文本,不要解释 OCR 是什么或任何其他前言。以下是文本:
现代普罗米修斯。43
第五章。
Iv was on a dreary night of November, that I beheld the ...
当输入此提示时,LLM 生成了以下输出:
THE MODERN PROMETHEUS. 43
CHAPTER V.
It was on a dreary night of November that I beheld the accomplishment of
my toils. With an anxiety that almost amounted to agony, I collected the
instruments of life around me, that I might infuse a spark of being into
the lifeless thing that lay at my feet. It was already one in the morning;
the rain pattered dismally against the panes, and my candle was nearly burnt
out, when, by the glimmer of the half-extinguished light, I saw the dull
yellow eye of the creature open; it breathed hard, and a convulsive motion
agitated its limbs.
LLM 修正了 'Iv' 为 'It',并将 'Motion' 修正为 'motion'。它还移除了每行末尾的连字符,以便可以移除换行符。这使得将文本复制粘贴到 Word 文档或电子邮件中变得更加容易。为了自动化此过程,大多数在线 LLM 都有 API,以便您的程序可以直接发送提示并接收响应。除非您在自己的机器上运行 LLM(这超出了本书的范围),否则您将不得不注册这些在线 LLM 服务。这可能免费或需要订阅费。
总是记住,LLM 容易过度自信。您应该始终验证它们的输出。它们返回的文本可能遗漏了一些错误,修复了错误类型不正确的错误,甚至引入了它们自己的新错误。您仍然需要人类来审查机器输出。(您可能还想让第二个人审查第一个人的工作,因为人类经常犯错误。)
识别非英语语言中的文本
Tesseract 默认假设它正在扫描的文本是英语,但您也可以指定其他语言。“安装 Tesseract 和 PyTesseract”在第 528 页上有安装非英语语言包的说明。您可以通过在交互式外壳中输入以下内容来查看您已安装的语言包:
>>> import pytesseract as tess
>>> tess.get_languages()
['afr', 'amh', 'ara', 'asm', 'aze', 'aze_cyrl', 'bel', 'ben', 'bod', 'bos',
# --snip--
'ton', 'tur', 'uig', 'ukr', 'urd', 'uzb', 'uzb_cyrl', 'vie', 'yid', 'yor']
此列表中的字符串主要是 ISO 639-3 的三位字符语言代码,有一些例外。例如,虽然 'aze' 是使用拉丁字母的阿塞拜疆语的 ISO 639-3 代码,但 'aze_cyrl' 字符串是使用西里尔字母的阿塞拜疆语。有关详细信息,请参阅 Tesseract 文档。
要扫描包含非英语文本的图像,请为 lang 关键字参数传递这些字符串值之一。例如,frankenstein_jpn.png 是《弗兰肯斯坦》一部分的日语翻译。从书籍的在线资源下载此文件,并在交互式外壳中输入以下内容:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein_jpn.png')
>>> text = tess.image_to_string(img, lang='jpn')
>>> print(text)
第 5 剖 私が自分の労苦の成果を目の当たりにしたのは、11 月の芝鬱な夜でした。 ほとんど苦
痛に等しい不安を抱えながら、 私は足元に横たわる生命のないものに存在の輝きを吹き込むこ
# --snip--
だ有目、 しわが寄った顔色、 そしてまっすぐな黒い大と、 より恐ろしいコントラストを形成した
だけでした。
如果您使用错误的语言,image_to_string() 将返回 Tesseract 对日本字符看起来像什么英语字符的最佳猜测。当然,由于这些字符不是英语,返回的文本将是乱码:
>>> import pytesseract as tess
>>> from PIL import Image
>>> img = Image.open('frankenstein_jpn.png')
>>> text = tess.image_to_string(img, lang='eng')
>>> print(text)
BS FABADOABOMEE AOYEVICLEDIL, 1 ADBBERKCLE, (ELA ER
WISE LW ABBA A TRA B, ALE TIRE DO EMO REVS DICED MS EKA
# --snip--
要识别多种语言中的文本,您可以在传递给 image_to_string() 函数的 lang 关键字参数之前使用 '+' 字符组合语言代码。例如,tess.image_to_string(img, lang='eng+jpn') 识别图像中的英语和日语字符。
NAPS2 扫描应用程序
虽然 PyTesseract 对于从图像中提取文本很有用,但 OCR 的一个常见用例是创建包含可搜索文本的扫描图像的 PDF 文档。尽管有应用程序可以做到这一点,但它们通常不提供自动化数百或数千个图像的 PDF 生成所需的灵活性。我推荐开源的 Not Another PDF Scanner 2 (NAPS2) 应用程序,不仅因为它可以控制平板式扫描仪,还因为它能够运行 Tesseract 并向 PDF 文档添加文本。它是免费的,功能直观,可在 Windows、macOS 和 Linux 上使用。NAPS2 可以将多个图像组合成一个包含嵌入文本的 PDF 文件,而无需连接到物理扫描仪。它还知道如何使用 Tesseract 的高级功能,因此可以在 PDF 页面上正确位置嵌入文本字符串,并且您可以从 Python 脚本中运行它。
安装和设置 NAPS2
要安装 NAPS2,请导航到 www.naps2.com/download 并下载适用于您操作系统的安装程序。在 Windows 和 macOS 上,运行下载的安装程序。在 Linux 上,下载 Tesseract 的 Flatpak 安装程序。然后,打开一个终端窗口,并从下载文件夹中运行 flatpak install naps2-X.X.X-linux-x64.flatpak(或下载的安装程序文件名)您可能需要输入管理员密码以完成安装。
一旦安装完成,您就可以运行 NAPS2 桌面应用程序。在 Windows 上,您可以从开始菜单中选择 NAPS2。在 macOS 上,您可以从 Spotlight 运行 NAPS2。在 Linux 上,您需要打开一个新的终端窗口并运行 flatpak run com.naps2.Naps2。然而,本书使用 Python 代码中的 subprocess 模块来使用 NAPS2,而不是图形用户界面。
从 Python 运行 NAPS2
Python 脚本可以使用 subprocess 模块以多个命令行参数运行 NAPS2 应用程序。以这种方式运行时,NAPS2 不会显示其应用程序窗口,这对于 Python 脚本中的自动化步骤来说非常理想。
让我们再次使用 frankenstein.png 图像,并让 NAPS2 生成包含嵌入式 OCR 文本的 PDF。NAPS2 程序的位置在每个操作系统上都不相同;以下交互式 shell 代码显示了 Windows 上的路径:
>>> import subprocess
>>> naps2_path = [r'C:\Program Files\NAPS2\NAPS2.Console.exe'] # Windows
>>> proc = subprocess.run(naps2_path + ['-i', 'frankenstein.png', '-o',
'output.pdf', '--install', 'ocr-eng', '--ocrlang', 'eng', '-n', '0', '-f',
'-v'], capture_output=True)
在 macOS 上,将设置路径的行替换为以下内容:naps2_path = ['/Applications/NAPS2.app/Contents/MacOS/NAPS2', 'console']。在 Linux 上,使用以下内容代替:naps2_path = ['flatpak', 'run', 'com.naps2.Naps2', 'console']。
代码创建一个名为 output.pdf 的新文件,其中包含来自 frankenstein.png 的扫描图像的单页。然而,如果您在 PDF 应用程序中打开此文件,您会注意到您可以突出显示文本并将其复制到剪贴板。许多 PDF 应用程序还允许您将 PDF 保存为 OCR 文本的 .txt 文本文件。
让我们查看此示例中的每个命令行参数。您可以根据自己的需要更改它们:
'-i', 'frankenstein.png' 将输入设置为 frankenstein.png 图像文件。有关指定多种格式的多个输入的更多信息,请参阅下一节,“指定输入”。
'-o', 'output.pdf' 创建一个名为 output.pdf 的文件来保存 OCR 结果。
'--install', 'ocr-eng' 安装 OCR 的英语语言包。如果语言已安装,则此操作不会执行任何操作。如果您想安装不同的语言包,请使用 ocr- 前缀和另一个三位字母 ISO 639 语言代码。
'--ocrlang', 'eng' 设置 OCR 扫描识别的语言为英语。此参数直接传递给 Tesseract 的命令行参数,因此您可以使用类似 'eng+jpn+rus' 的参数来指定图像包含英语、日语和俄语文本。
'-n', '0' 指定您想要进行零次扫描且不使用平板式扫描仪。这可以防止在没有物理平板式扫描仪连接到您的计算机时出现错误消息。
'-f' 强制 NAPS2 覆盖已存在的同名 output.pdf 输出文件。
'-v' 启用详细模式,以便在 NAPS2 创建您的 PDF 时显示状态文本。如果您想看到这些状态文本,请将 subprocess.run() 的 capture_output=True 关键字参数更改为 capture_output=False。
NAPS2 的命令行参数的在线文档在www.naps2.com/doc/command-line。第十九章更详细地介绍了subprocess模块。
指定输入
NAPS2 允许您导入 PDF 和大多数图像格式以创建最终的合并 PDF。该应用程序有自己的迷你语言,用于指定多个输入作为单个命令行参数,紧随-i之后。这可能相当复杂,但您可以将其视为分号分隔的,带有 Python 索引和切片符号。
要指定多个文件,请用分号分隔它们。例如,传递'-i', 'cat.png;dog.png;moose.png'将创建一个 PDF,其中cat.png用于第一页,dog.png用于第二页,moose.png用于第三页。
您还可以使用与 Python 列表切片语法相同的语法指定 PDF 中的单个页面。在 PDF 文件名后跟方括号,包含要使用的页面编号。与 Python 一样,0代表第一页。例如,传递'-i', 'spam.pdf[0];spam.pdf[5];eggs.pdf'将创建一个 PDF,其中包含spam.pdf的第 1 页,接着是spam.pdf的第 6 页,然后是eggs.pdf的所有页面。
您可以使用这种切片符号指定一系列页面,或者使用负数来表示从 PDF 文档末尾的页面。例如,传递'-i', 'spam.pdf[0:2];eggs.pdf[-1]'将spam.pdf的前两页与eggs.pdf的最后一页合并。
NAPS2 通过其命令行参数提供了更多功能。查看其在线文档以了解更多信息。如果你发现 NAPS2 不适合你的需求,我还推荐pypi.org/project/ocrmypdf/这个 ocrmypdf 包,用于创建嵌入文本的 PDF。
安装和设置 NAPS2
要安装 NAPS2,请导航到www.naps2.com/download并下载适用于您操作系统的安装程序。在 Windows 和 macOS 上,运行下载的安装程序。在 Linux 上,下载 Tesseract 的 Flatpak 安装程序。然后,打开一个终端窗口,并从下载文件夹中运行 flatpak install naps2-X.X.X-linux-x64.flatpak(或下载的安装程序文件名)您可能需要输入管理员密码以完成安装。
安装后,您可以运行 NAPS2 桌面应用程序。在 Windows 上,您可以从开始菜单中选择 NAPS2。在 macOS 上,您可以从 Spotlight 运行 NAPS2。在 Linux 上,您需要打开一个新的终端窗口并运行flatpak run com.naps2.Naps2。然而,这本书使用 Python 代码中的subprocess模块而不是图形用户界面来使用 NAPS2。
从 Python 运行 NAPS2
Python 脚本可以使用 subprocess 模块以多个命令行参数运行 NAPS2 应用程序。以这种方式运行时,NAPS2 不会显示其应用程序窗口,这对于 Python 脚本中的自动化步骤来说非常理想。
让我们再次使用 frankenstein.png 图像,并让 NAPS2 生成包含嵌入式 OCR 文本的 PDF。NAPS2 程序在每个操作系统上的位置都不同;以下交互式 shell 代码显示了 Windows 的路径:
>>> import subprocess
>>> naps2_path = [r'C:\Program Files\NAPS2\NAPS2.Console.exe'] # Windows
>>> proc = subprocess.run(naps2_path + ['-i', 'frankenstein.png', '-o',
'output.pdf', '--install', 'ocr-eng', '--ocrlang', 'eng', '-n', '0', '-f',
'-v'], capture_output=True)
在 macOS 上,将设置路径的行替换为以下内容:naps2_path = ['/Applications/NAPS2.app/Contents/MacOS/NAPS2', 'console']。在 Linux 上,使用以下内容代替:naps2_path = ['flatpak', 'run', 'com.naps2.Naps2', 'console']。
代码创建了一个名为 output.pdf 的新文件,其中包含来自 frankenstein.png 的扫描图像的单页。然而,如果您在 PDF 应用程序中打开此文件,您会注意到您可以突出显示文本并将其复制到剪贴板。许多 PDF 应用程序还允许您将 PDF 保存为 OCR 文本的 .txt 文件。
让我们来看看这个例子中的每个命令行参数。您可以根据自己的需求进行更改:
'-i', 'frankenstein.png' 将输入设置为 frankenstein.png 图像文件。有关指定多种格式的多个输入的更多信息,请参阅下一节,“指定输入”。
'-o', 'output.pdf' 创建一个名为 output.pdf 的文件来保存 OCR 结果。
'--install', 'ocr-eng' 安装 OCR 的英语语言包。如果语言已安装,则此操作不会执行任何操作。如果您想安装不同的语言包,请使用 ocr- 前缀加上另一个三位字母 ISO 639 语言代码。
'--ocrlang', 'eng' 将英语设置为 OCR 扫描识别的语言。此参数直接传递给 Tesseract 的命令行参数,因此您可以使用类似 'eng+jpn+rus' 的参数来指定图像包含英语、日语和俄语文本。
'-n', '0' 指定您想要进行零次扫描且不使用平板扫描仪。这可以防止在没有物理平板扫描仪连接到您的计算机时出现错误消息。
'-f' 强制 NAPS2 覆盖已存在的同名 output.pdf 输出文件。
'-v' 启用详细模式,以便在 NAPS2 创建您的 PDF 时显示状态文本。如果您想看到此状态文本,请将 subprocess.run() 的 capture_output=True 关键字参数更改为 capture_output=False。
NAPS2 命令行参数的在线文档位于 www.naps2.com/doc/command-line。第十九章更详细地介绍了 subprocess 模块。
指定输入
NAPS2 允许您导入 PDF 文件和大多数图像格式以创建最终的合并 PDF。该应用程序有自己的迷你语言,用于指定多个输入作为单个命令行参数,紧随 -i 之后。这可能会变得相当复杂,但您可以将其视为分号分隔的,带有 Python 索引和切片表示法。
要指定多个文件,请用分号分隔它们。例如,传递 '-i', 'cat.png;dog.png;moose.png' 将创建一个 PDF 文件,其中 cat.png 用于第一页,dog.png 用于第二页,moose.png 用于第三页。
您还可以使用与 Python 列表切片语法相同的语法指定 PDF 中的单个页面。在 PDF 文件名后跟方括号,包含要使用的页面编号。与 Python 一样,0 代表第一页。例如,传递 '-i', 'spam.pdf[0];spam.pdf[5];eggs.pdf' 将创建一个包含 spam.pdf 第 1 页、spam.pdf 第 6 页和 eggs.pdf 所有页面的 PDF 文件。
您可以使用这种切片表示法指定一系列页面,或者使用负数来表示从 PDF 文档末尾开始的页面。例如,传递 '-i', 'spam.pdf[0:2];eggs.pdf[-1]' 将 spam.pdf 的前两页与 eggs.pdf 的最后一页合并。
NAPS2 通过其命令行参数提供了更多功能。查看其在线文档以了解更多信息。如果您发现 NAPS2 不适合您的需求,我还推荐使用 ocrmypdf 软件包在 pypi.org/project/ocrmypdf/ 创建包含嵌入文本的 PDF 文件。
摘要
在本章中,您学习了如何利用 Tesseract 的力量从图像中提取文本。这是一种非常强大的能力,可以为您节省数小时的数据录入时间。然而,OCR 并非魔法,您的图像可能需要进行预处理才能获得准确的结果。Tesseract 还被设计用于处理在浅色背景上的打字机黑色文字,其中文字是水平的,并且您必须知道图像文本的语言才能获得良好的结果。大型语言模型 AI 可以帮助纠正错误识别的字符,但其输出仍需要人工监督。最后,开源的 NAPS2 应用程序提供了一种将多张图像组合成单个 PDF 文件的方法,其中包含嵌入的 OCR 文本。OCR 是计算机科学的一个令人难以置信的突破,但您不需要高级学位就能使用它。Python 使 OCR 对每个人来说都变得触手可及。
实践问题
1. Tesseract 默认识别哪种语言?
2. 请命名一个与 PyTesseract 兼容的 Python 图像库。
3. 哪个 PyTesseract 函数接受一个图像对象并返回图像中的文本字符串?
4. 如果您拍摄了一个街牌的照片,Tesseract 是否能够识别照片中的街牌文本?
5. 哪个函数返回 Tesseract 安装的语言包列表?
-
在图像同时包含英语和日语文本时,你指定给 PyTesseract 的哪个关键字参数?
-
哪个应用程序允许你创建包含嵌入式 OCR 文本的 PDF?
实践程序:浏览器文本抓取器
一些网站允许你查看它们的文本内容,但使保存或甚至将文本复制粘贴到电脑上变得困难。你可能看到它们作为嵌入网页中的 PDF。这种例子在autbor.com/embeddedfrankenstein/,如图 22-2 所示。

图 22-2:一个包含嵌入式文档的示例网页
第二十三章中介绍的 PyAutoGUI 库可以截取屏幕截图并将其保存为图像,而第二十一章中介绍的 Pillow 库可以裁剪图像。PyAutoGUI 还有一个 MouseInfo 应用程序,用于在屏幕上查找 XY 坐标。
编写一个名为 ocrscreen.py 的程序,该程序接受屏幕截图,将图像裁剪到仅包含屏幕截图中的文本部分,然后将它传递给 PyTesseract 进行 OCR。程序应将识别的文本追加到名为 output.txt 的文本文件的末尾。以下是 ocrscreen.py 程序的模板:
import pyautogui
# TODO - Add the additionally needed import statements.
# The coordinates for the text portion. Change as needed:
LEFT = 400
TOP = 200
RIGHT = 1000
BOTTOM = 800
# Capture a screenshot:
img = pyautogui.screenshot()
# Crop the screenshot to the text portion:
img = img.crop((LEFT, TOP, RIGHT, BOTTOM))
# Run OCR on the cropped image:
# TODO - Add the PyTesseract code here.
# Add the OCR text to the end of output.txt:
# TODO - Call open() in append mode and append the OCR text.
此程序应允许你在浏览器中将不可保存的嵌入式文本滚动到视图中,运行程序,然后将 PDF 滚动到下一页内容。完成后,你将拥有文档文本的副本。(如果你阅读了第二十三章,你还将了解如何让你的脚本模拟按键来为你滚动网页。)
23 控制键盘和鼠标

了解各种用于编辑电子表格、下载文件和启动程序的 Python 包很有用,但有时根本没有任何适用于您需要工作的应用程序的包。在您的计算机上自动化任务的终极工具是您编写的直接控制键盘和鼠标的程序。这些程序可以发送虚拟按键和鼠标点击到其他应用程序,就像您坐在电脑前与这些应用程序交互一样。
这种技术被称为 图形用户界面自动化,或简称为 GUI 自动化。使用 GUI 自动化,您的程序可以执行任何坐在电脑前的人类用户可以执行的操作,除了在键盘上洒咖啡。将 GUI 自动化想象成编程一个机械臂。您可以编程机械臂在您的键盘上输入并移动鼠标。这种技术特别适用于涉及大量无脑点击或填写表格的任务。这种强大的技术是为什么注册和登录网页有机器人检测的验证码挑战。否则,自动化程序可能会注册多个免费账户,用垃圾邮件淹没社交媒体,或猜测账户密码。
一些公司销售创新(且价格昂贵)的“自动化解决方案”,通常作为 机器人流程自动化 (RPA) 工具进行营销。这些产品实际上与您使用 PyAutoGUI 库自己制作的 Python 脚本没有太大区别,PyAutoGUI 库具有模拟鼠标移动、按钮点击和键盘输入的功能。本章仅涵盖 PyAutoGUI 功能的一部分;您可以在 pyautogui.readthedocs.io/ 找到完整文档。要安装与本书兼容的最新版本的 PyAutoGUI,请遵循附录 A 中的说明。
警告
不要将程序保存为 pyautogui.py。如果您这样做,那么当您运行 import pyautogui Python 将导入您的程序而不是 PyAutoGUI,您将收到类似 AttributeError: module 'pyautogui' has no attribute 'click'. 的错误消息。
在 macOS 上设置可访问性应用程序
作为安全措施,macOS 通常不允许程序控制鼠标或键盘。为了使 PyAutoGUI 在 macOS 上工作,您必须将运行您的 Python 脚本的程序设置为可访问性应用程序。如果不进行此步骤,您的 PyAutoGUI 函数调用将没有任何效果。
无论你从 Mu、IDLE 还是终端运行你的 Python 程序,都要保持该应用程序打开。然后,打开 系统偏好设置 并转到 辅助功能 选项卡。当前打开的应用程序将出现在“允许以下应用程序控制您的计算机”标签下。检查 Mu、IDLE、Terminal 或你用于运行 Python 脚本的应用程序。你将被提示输入密码以确认这些更改。
保持进度
在你跳入 GUI 自动化之前,你应该知道如何逃避可能出现的任何问题。Python 可以以惊人的速度移动鼠标和输入按键。事实上,它可能比其他程序快得多。此外,如果出了问题但你的程序仍在移动鼠标,将很难确切知道程序正在做什么或如何从问题中恢复。就像迪士尼《幻想曲》中“魔法师的学徒”序列中的魔法扫帚,它们不断地(然后是过度地)往米奇的浴缸里灌水,即使你的程序完美地遵循你的指示,它也可能失控。如果鼠标在自行移动,阻止你点击 Mu 编辑器窗口来关闭它,停止程序可能很困难。幸运的是,有几种方法可以防止或从 GUI 自动化问题中恢复。
暂停和保险丝
如果你的程序有错误,你无法使用键盘和鼠标将其关闭,你可以使用 PyAutoGUI 的保险丝功能。快速将鼠标滑到屏幕的四个角落之一。每个 PyAutoGUI 函数调用在其动作执行后都会有一个十分之一秒的暂停,以便你有足够的时间将鼠标移动到角落。如果 PyAutoGUI 发现鼠标光标在角落,它将引发 pyautogui.FailSafeException 异常。非 PyAutoGUI 指令不会有这个暂停。你可以通过将 pyautogui.PAUSE 设置为除 0.1 以外的值来调整这个暂停持续时间。
如果你发现自己处于需要停止你的 PyAutoGUI 程序的情况,只需将鼠标猛地朝向屏幕的一个角落即可停止它。
登出
停止失控的 GUI 自动化程序的最简单方法可能是注销,这将关闭所有正在运行的程序。在 Windows 和 Linux 上,注销快捷键是 CTRL-ALT-DEL。在 macOS 上,它是 -SHIFT-Q。通过注销,你将丢失任何未保存的工作,但至少你不必等待计算机完全重启。
控制鼠标移动
在本节中,你将学习如何使用 PyAutoGUI 移动鼠标并在屏幕上跟踪其位置,但首先你需要了解 PyAutoGUI 如何与坐标一起工作。
PyAutoGUI 的鼠标功能使用 x 和 y 坐标。第二十一章。原点,其中 x 和 y 都是零,位于屏幕的左上角。x 坐标向右增加,y 坐标向下增加。所有坐标都是正整数;没有负坐标。

图 23-1:1920×1080 分辨率的计算机屏幕坐标
你的 分辨率 是你的屏幕宽度和高度有多少像素。如果你的屏幕分辨率设置为 1920×1080,则屏幕左上角的坐标将是 (0, 0),而屏幕右下角的坐标将是 (1919, 1079)。
pyautogui.size() 函数返回一个名为 Size 的元组,包含屏幕宽度和高度(以像素为单位)。命名元组超出了本书的范围,但它们基本上是具有整数索引的元组,同时具有命名属性。在交互式外壳中输入以下内容:
>>> import pyautogui
>>> screen_size = pyautogui.size() # Obtain the screen resolution.
>>> screen_size
Size(width=1920, height=1080)
>>> screen_size[0], screen_size[1]
(1920, 1080)
>>> screen_size.width, screen_size.height
(1920, 1080)
>>> tuple(screen_size)
(1920, 1080)
pyautogui.size() 函数在具有 1920×1080 分辨率的计算机上返回 (1920, 1080) 的 Size 对象;根据你的屏幕分辨率,你的返回值可能不同。
移动鼠标
现在你已经理解了屏幕坐标,让我们移动鼠标。pyautogui.moveTo() 函数会立即将鼠标光标移动到屏幕上指定的位置。x 和 y 坐标的整数值分别构成函数的第一个和第二个参数。可选的 duration 整数或浮点关键字参数指定移动鼠标到目标位置所需的时间(秒数)。如果你省略了它,默认值为 0,表示瞬间移动。(PyAutoGUI 函数中的所有 duration 关键字参数都是可选的。)在交互式外壳中输入以下内容:
>>> import pyautogui
>>> for i in range(10): # Move the mouse in a square.
... 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 次。每次移动需要四分之一秒,这是由 duration=0.25 关键字参数指定的。如果你没有为任何 pyautogui.moveTo() 调用传递第三个参数,鼠标光标将瞬间从一点移动到另一点。
pyautogui.move() 函数将鼠标光标移动到其当前位置的相对位置。以下示例将鼠标以相同的正方形模式移动,但正方形从代码开始运行时鼠标在屏幕上的当前位置开始:
>>> import pyautogui
>>> for i in range(10):
... pyautogui.move(100, 0, duration=0.25) # Right
... pyautogui.move(0, 100, duration=0.25) # Down
... pyautogui.move(-100, 0, duration=0.25) # Left
... pyautogui.move(0, -100, duration=0.25) # Up
...
pyautogui.move() 函数也接受三个参数:向右水平移动多少像素,向下垂直移动多少像素,以及(可选的)完成移动所需的时间。第一个或第二个参数为负整数将导致鼠标向左或向上移动。
获取当前位置
您可以通过调用pyautogui.position()函数来确定鼠标的当前位置,该函数将返回一个名为Point的元组,包含鼠标光标在函数调用时的x和y位置。您可以通过Point元组的0和1整数索引或通过x和y属性来访问 x 和 y 坐标。(这与Size元组的width和height属性类似。)在交互式外壳中输入以下内容,并在每次调用后移动鼠标:
>>> pyautogui.position() # Get the current mouse position.
Point(x=311, y=622)
>>> pyautogui.position() # Get the current mouse position again.
Point(x=377, y=481)
>>> p = pyautogui.position() # And again
>>> p
Point(x=1536, y=637)
>>> p[0] # The x-coordinate is at index 0.
1536
>>> p.x # The x-coordinate is also in the x attribute.
1536
当然,您的返回值将取决于您的鼠标光标所在的位置。
控制鼠标交互
现在您已经知道了如何移动鼠标并确定它在屏幕上的位置,您就可以开始点击、拖动和滚动操作了。
点击
要向您的计算机发送虚拟鼠标点击,请调用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) # Move the mouse to (10, 5) and click.
您应该看到鼠标指针移动到屏幕的左上角附近并点击一次。完整的“点击”定义为按下鼠标按钮然后释放它而不移动光标。您也可以通过调用pyautogui.mouseDown()来执行点击,它只按下鼠标按钮,以及通过调用pyautogui.mouseUp()来执行点击,它只释放按钮。这些函数具有与click()相同的参数,实际上,click()函数只是这两个函数调用的便捷包装器。
为了进一步方便,pyautogui.doubleClick()函数将使用左鼠标按钮执行两次点击。pyautogui.rightClick()和pyautogui.middleClick()函数将分别使用右鼠标按钮和中鼠标按钮执行点击。
拖动
拖动意味着在按住鼠标按钮的同时移动鼠标。例如,您可以通过拖动文件夹图标在文件夹之间移动文件,或者您可以在日历应用中移动约会。
PyAutoGUI 提供了pyautogui.dragTo()和pyautogui.drag()函数来将鼠标光标拖动到新的位置或相对于当前位置的位置。dragTo()和drag()的参数与moveTo()和move()相同:x 坐标/水平移动,y 坐标/垂直移动,以及可选的时间持续时间。(当鼠标移动得太快时,macOS 操作系统无法正确拖动,因此建议传递duration关键字参数。)
要尝试这些函数,请在 Windows 上的 MS Paint、macOS 上的 Paintbrush 或 Linux 上的 GNU Paint 等图形绘图应用程序中打开。(如果你没有绘图应用程序,你可以使用在线的sumopaint.com。)我将使用 PyAutoGUI 在这些应用程序中绘图。
当鼠标光标位于绘图应用程序的画布上并且选择了铅笔或画笔工具时,将以下内容输入到一个新的文件编辑窗口中,并将其保存为spiralDraw.py:
import pyautogui
pyautogui.sleep(5) # ❶
pyautogui.click() # Click to make the window active. # ❷
distance = 300
change = 20
while distance > 0:
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
distance = distance - change
pyautogui.drag(0, -distance, duration=0.2) # Move up.
当你运行这个程序时,会有一个五秒的延迟 ❶,在这段时间内,你可以使用铅笔或画笔工具将鼠标光标移动到绘图程序的窗口上。PyAutoGUI 的sleep()函数与time.sleep()相同,但存在是为了让你不需要在你的代码中添加import time。然后,spiralDraw.py将控制鼠标并点击使绘图程序的窗口变为活动状态 ❷。活动窗口是当前接受键盘输入的窗口,你采取的动作(如输入或在这种情况下拖动鼠标)将影响该窗口。活动窗口也被称为焦点或前景窗口。一旦绘图程序变为活动状态,spiralDraw.py将绘制一个类似于图 23-2 左侧的方形螺旋图案。
distance变量从300开始,所以在while循环的第一次迭代中,第一次drag()调用将光标向右拖动 300 像素,耗时 0.2 秒 ❸。然后,distance减少到 280 ❹,第二次drag()调用将光标向下拖动 280 像素 ❺。第三次drag()调用将光标水平向左拖动-280 像素(向左 280 像素),distance减少到 260,最后drag()调用将光标向上拖动 260 像素。在每次迭代中,鼠标向右、向下、向左和向上拖动,distance略小于前一次迭代。通过循环这段代码,你可以移动鼠标光标绘制方形螺旋。
虽然你也可以通过在第二十一章中讨论的 Pillow 包创建方形螺旋图像,但通过控制鼠标在 MS Paint 中绘制图像来创建图像,让你可以利用该程序的各种画笔样式,如图 23-2 右侧所示,以及其他高级功能,如渐变或填充桶。你可以预先选择画笔设置(或者让你的 Python 代码选择这些设置),然后运行螺旋绘图程序。

图 23-2:pyautogui.drag()示例的结果,使用 MS Paint 的不同画笔绘制
你可以手动(或者更确切地说,通过鼠标)绘制这个螺旋,但你必须慢慢地工作才能如此精确。PyAutoGUI 可以在几秒钟内完成它!
滚动
PyAutoGUI 的最后一个鼠标函数是scroll(),你需要传递一个整数参数来指定你想要向上或向下滚动鼠标的单位数。单位的大小因操作系统和应用程序而异,因此你可能需要实验来确定在你的特定情况下滚动多远。滚动发生在鼠标光标当前的位置。传递正整数向上滚动,传递负整数向下滚动。在鼠标光标位于 Mu 编辑器窗口上时,在 Mu 编辑器的交互式外壳中运行以下代码:
>>> pyautogui.scroll(200)
如果你将鼠标光标放在可以向上滚动的文本字段上,你会看到 Mu 向上滚动。
规划你的鼠标移动
编写一个将自动化点击屏幕的程序的一个困难是找到你想要点击的 x 和 y 坐标。pyautogui.mouseInfo()函数可以帮助你完成这个任务。
pyautogui.mouseInfo()函数旨在从交互式外壳中调用,而不是作为程序的一部分。它启动一个名为 MouseInfo 的小应用程序,该应用程序包含在 PyAutoGUI 中。应用程序的窗口如图 23-3 所示。

图 23-3:MouseInfo 应用程序的窗口
在交互式外壳中输入以下内容:
>>> import pyautogui
>>> pyautogui.mouseInfo()
这会使 MouseInfo 窗口出现。这个窗口提供了关于鼠标光标当前位置的信息,以及鼠标光标下像素的颜色,作为一个三整数 RGB 元组和十六进制值。颜色本身出现在窗口的颜色框中。
为了帮助你记录这个坐标或像素信息,你可以点击八个复制或记录按钮中的一个。复制全部、复制 XY、复制 RGB 和复制 RGB 十六进制按钮将它们各自的信息复制到剪贴板。记录全部、记录 XY、记录 RGB 和记录 RGB 十六进制按钮将它们各自的信息写入窗口中的大文本字段。你可以通过点击保存日志按钮来保存日志文本字段中的文本。
默认情况下,3 秒按钮延迟复选框被选中,导致在点击复制或日志按钮和复制或记录操作之间有一个三秒的延迟。这给你一段短时间来点击按钮,然后将鼠标移动到你的目标位置。取消选中此框,将鼠标移动到位置,并按 F1 到 F8 键来复制或记录鼠标位置可能更容易。你可以在 MouseInfo 窗口的顶部查看复制和日志菜单,以找出哪个键映射到哪个按钮。
例如,取消选中 3 秒按钮延迟,然后在按住 F6 键的同时在屏幕上移动鼠标,注意鼠标的 x 和 y 坐标如何记录在窗口中间的大文本框中。你可以在你的 PyAutoGUI 脚本中使用这些坐标。
关于 MouseInfo 的更多信息,请查看完整的文档,链接为 mouseinfo.readthedocs.io/。
捕获屏幕截图
你的 GUI 自动化程序不必盲目地点击和输入。PyAutoGUI 具有截图功能,可以根据当前屏幕内容创建图像文件。这些函数还可以返回当前屏幕外观的 Pillow Image 对象。如果你在这本书中跳来跳去,你将想要在继续本节之前阅读第二十一章并安装 Pillow 包。
在 Python 中捕获屏幕截图,请调用 pyautogui.screenshot() 函数。在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> im = pyautogui.screenshot()
im 变量将包含截图的 Image 对象。你现在可以在 im 变量中的 Image 对象上调用方法,就像任何其他的 Image 对象一样。第二十一章有更多关于 Image 对象的信息。
假设你的 GUI 自动化程序中的一个步骤是点击一个灰色按钮。在调用 click() 方法之前,你可能想先捕获一个屏幕截图并查看脚本即将点击的像素。如果它不是与灰色按钮相同的灰色,那么你的程序就知道有问题。可能是窗口意外移动了,或者可能有一个弹出对话框阻止了按钮。在这种情况下,而不是继续,并可能通过点击错误的东西造成混乱,你的程序可以停止自己。
你可以使用 pixel() 函数获取屏幕上特定像素的 RGB 颜色值。在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> pyautogui.pixel(0, 0)
(176, 176, 175)
>>> pyautogui.pixel((50, 200))
(130, 135, 144)
向 pixel() 函数传递两个整数作为 XY 坐标,它将告诉你图像中这些坐标处的像素颜色。pixel() 函数的返回值是一个包含三个整数的 RGB 元组,分别代表像素中的红色、绿色和蓝色量。(没有第四个值用于 alpha,因为截图图像是完全不透明的。)
PyAutoGUI 的pixelMatchesColor()函数如果屏幕上给定 x 和 y 坐标的像素与给定的颜色匹配,将返回True。第一个和第二个参数是 x 和 y 坐标的整数,第三个参数是屏幕像素必须匹配的 RGB 颜色的三个整数的元组。在交互式外壳中输入以下内容:
>>> import pyautogui
>>> pyautogui.pixel((50, 200)) # ❶
(130, 135, 144)
>>> pyautogui.pixelMatchesColor(50, 200, (130, 135, 144)) # ❷
True
>>> pyautogui.pixelMatchesColor(50, 200, (255, 135, 144)) # ❸
False
在使用pixel()获取特定坐标像素的 RGB 元组颜色后❶,将相同的坐标和 RGB 元组传递给pixelMatchesColor()❷,它应该返回True。然后,更改 RGB 元组中的一个值,并再次调用相同坐标的pixelMatchesColor()❸。这应该返回false。这种方法可以在你的 GUI 自动化程序即将调用click()时非常有用。请注意,给定坐标处的颜色必须完全匹配。如果它甚至有一点点不同——例如,(255, 255, 254)而不是(255, 255, 255)——那么pixelMatchesColor()将返回False。
图像识别
但如果你事先不知道 PyAutoGUI 应该点击的位置?你可以使用图像识别代替。给 PyAutoGUI 一张你想要点击的图片,然后让它找出坐标。
例如,如果你之前已经截图来捕捉“提交”按钮的图像,并将其保存为submit.png,locateOnScreen()函数将返回该图像被找到的坐标。要了解locateOnScreen()是如何工作的,尝试截图你屏幕上的一个小区域;然后保存该图像,并在交互式外壳中输入以下内容,将'submit.png'替换为你的截图文件名:
>>> import pyautogui
>>> box = pyautogui.locateOnScreen('submit.png')
>>> box
Box(left=643, top=745, width=70, height=29)
>>> box[0]
643
>>> box.left
643
Box对象是locateOnScreen()返回的一个命名元组,包含屏幕上找到的第一个图像的左侧 x 坐标、顶部 y 坐标、宽度和高度。如果你在自己的电脑上尝试使用自己的截图,你的返回值将不同于这里显示的。
如果屏幕上找不到图像,locateOnScreen()将引发ImageNotFoundException异常。请注意,屏幕上的图像必须与提供的图像完全匹配才能被识别。如果图像甚至有一像素偏差,locateOnScreen()将引发ImageNotFoundException异常。如果你更改了屏幕分辨率,之前截图的图像可能不会与当前屏幕上的图像匹配,因为它们有不同的缩放因子。缩放因子超出了本书的范围,但它们有时用于现代高分辨率显示器。你可以在操作系统的显示设置中更改缩放,如图 23-4 所示。

图 23-4:Windows(左)和 macOS(右)的缩放显示设置
如果图像在屏幕的几个地方都能找到,locateAllOnScreen() 将返回一个 Generator 对象。生成器超出了本书的范围,但你可以将它们传递给 list() 以返回 Box 对象的列表。屏幕上每个找到图像的位置都会有一个 Box 对象。通过在交互式 shell 中输入以下内容(并将 'submit.png' 替换为你自己的图像文件名)来继续交互式 shell 示例:
>>> list(pyautogui.locateAllOnScreen('submit.png'))
[(643, 745, 70, 29), (1007, 801, 70, 29)]
在这个例子中,图像出现在两个位置。如果你的图像只在一个区域找到,调用 list(locateAllOnScreen()) 返回的列表中只包含一个 Box 对象。
一旦你有了要选择的特定图像的 Box 对象,你可以通过传递元组到 click() 来点击这个区域的中心。在交互式 shell 中输入以下内容:
>>> pyautogui.click((643, 745, 70, 29))
作为快捷方式,你也可以直接将图像文件名传递给 click() 函数:
>>> pyautogui.click('submit.png')
moveTo() 和 dragTo() 函数也接受图像文件名参数。记住,如果 locateOnScreen() 在屏幕上找不到图像,它会引发异常,因此你应该在 try 语句内部调用它:
try:
location = pyautogui.locateOnScreen('submit.png')
except pyautogui.ImageNotFoundException:
print('Image could not be found.')
没有使用 try 和 except 语句,未捕获的异常会导致你的程序崩溃。由于你无法确定你的程序是否总能找到图像,因此在调用 locateOnScreen() 时使用 try 和 except 语句是一个好主意。在 PyAutoGUI 1.0.0 之前的版本中,locateOnScreen() 会返回 None 而不是引发异常。在这些旧版本中,调用 pyautogui.useImageNotFoundException() 来引发异常,或者调用 pyautogui.useImageNotFoundException(False) 来为新版本返回 None。
获取窗口信息
图像识别是屏幕上查找事物的一种脆弱方式;如果单个像素的颜色不同,那么 pyautogui.locateOnScreen() 就找不到图像。如果你需要找到屏幕上特定窗口的位置,使用 PyAutoGUI 的窗口功能更快、更可靠。
注意
截至版本 1.0.0,PyAutoGUI 的窗口功能仅在 Windows 上工作,不在 macOS 或 Linux 上。这些功能来自 PyAutoGUI 包含的 PyGetWindow 包。
获取活动窗口
屏幕上的活动窗口是当前处于前台并接受键盘输入的窗口。如果你目前在 Mu 编辑器中编写代码,Mu 编辑器的窗口就是活动窗口。在屏幕上的所有窗口中,一次只有一个窗口是活动的。
在交互式 shell 中,调用 pyautogui.getActiveWindow() 函数以获取 Window 对象(在 Windows 上运行时实际上是 Win32Window 对象)。一旦你有了这个 Window 对象,你可以检索该对象的任何属性,这些属性描述了它的大小、位置和标题:
left, right, top, bottom 窗户边界的 x 或 y 坐标的单个整数
topleft, topright, bottomleft, bottomright 一个名为 Point 的包含两个整数的元组,表示窗口角落的 (x, y) 坐标
midleft, midright, midtop, midbottom:一个包含两个整数的Point元组,表示窗口边中间的(x, y)坐标
width, height:一个整数,表示窗口的尺寸之一,以像素为单位
大小:一个包含两个整数的Size元组,表示窗口的(width, height)
区域:表示窗口面积的单一整数,以像素为单位
中心:一个包含两个整数的Point元组,表示窗口中心的(x, y)坐标
centerx, centery:一个整数,表示窗口中心的 x 或 y 坐标
盒子:一个包含四个整数的Box元组,表示窗口的(left, top, width, height)尺寸
标题栏中的文本字符串
要从window对象获取窗口的位置、大小和标题信息,例如,在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win
Win32Window(hWnd=2034368)
>>> str(active_win)
'<Win32Window left="500", top="300", width="2070", height="1208", title="Mu 1.0.1 – test1.py">'
>>> active_win.title
'Mu 1.0.1 – test1.py'
>>> active_win.size
Size(width=2070, height=1208)
>>> active_win.left, active_win.top, active_win.right, active_win.bottom
(500, 300, 2570, 1508)
>>> active_win.topleft
Point(x=500, y=300)
>>> pyautogui.click(active_win.left + 10, active_win.top + 20)
你现在可以使用这些属性来计算窗口内的精确坐标。如果你知道你想要点击的按钮始终位于窗口左上角右侧 10 像素和下方 20 像素处,并且窗口的左上角位于屏幕坐标(300, 500),那么调用pyautogui.click(310, 520)(或者如果active_win包含窗口的Window对象,则调用pyautogui.click(active_win.left + 10, active_win.top + 20))将点击按钮。这样,你就不必依赖较慢、可靠性较低的locateOnScreen()函数来为你找到按钮。
使用其他函数查找窗口
虽然getActiveWindow()对于获取函数调用时的活动窗口很有用,但你还需要使用其他函数来获取屏幕上其他窗口的Window对象。以下三个函数返回一个Window对象列表。如果它们找不到任何窗口,则返回空列表:
pyautogui.getAllWindows():返回屏幕上每个可见窗口的Window对象列表
pyautogui.getWindowsAt(x, y):返回包含点(x, y)的每个可见窗口的Window对象列表
pyautogui.getWindowsWithTitle(title):返回包含标题栏中字符串标题的每个可见窗口的Window对象列表
PyAutoGUI 还提供了一个pyautogui.getAllTitles()函数,该函数返回每个可见窗口的字符串列表。
窗口操作
窗口属性不仅可以告诉你窗口的大小和位置,你还可以设置它们的值以调整窗口大小或移动窗口。例如,在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.width # Gets the current width of the window # ❶
1669
>>> active_win.topleft # Gets the current position of the window # ❷
Point(x=174, y=153)
>>> active_win.width = 1000 # Resizes the width # ❸
>>> active_win.topleft = (800, 400) # Moves the window # ❹
首先,我们使用Window对象的属性来获取窗口的大小 ❶ 和位置 ❷。在 Mu 编辑器中调用这些函数后,窗口应该变窄 ❸ 并移动 ❹,如图 23-5 所示。

图 23-5:使用窗口对象属性调整大小和移动 Mu 编辑器窗口前(顶部)和后(底部)的截图
你还可以找出并更改窗口的最小化、最大化以及激活状态。尝试在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.isMaximized # Returns True if the window is maximized
False
>>> active_win.isMinimized # Returns True if the window is minimized
False
>>> active_win.isActive # Returns True if the window is the active window
True
>>> active_win.maximize() # Maximizes the window
>>> active_win.isMaximized
True
>>> active_win.restore() # Undoes a minimize/maximize action
>>> active_win.minimize() # Minimizes the window
>>> import time
>>> # Waits 5 seconds while you activate a different window:
>>> time.sleep(5); active_win.activate()
>>> active_win.close() # This will close the window you're typing in.
isMaximized、isMinimized 和 isActive 属性包含布尔值,指示窗口当前是否处于该状态。maximize()、minimize()、activate() 和 restore() 方法会改变窗口的状态。使用 maximize() 或 minimize() 改变窗口大小和位置后,restore() 方法会将窗口恢复到之前的大小和位置。
close() 方法会关闭一个窗口。在使用此方法时要小心,因为它可能会绕过在退出应用程序之前要求你保存工作的消息对话框。
有关 PyAutoGUI 的窗口控制功能的完整详细信息,请参阅其文档。
控制键盘
PyAutoGUI 还提供了向你的计算机发送虚拟按键操作的功能,这使你能够填写表单或向应用程序中输入文本。
发送按键字符串
pyautogui.write() 函数向计算机发送虚拟按键操作。这些按键操作的具体效果取决于哪个窗口是激活的以及哪个文本字段具有焦点。你可能需要首先向你想要的文本字段发送一个鼠标点击,以确保它具有焦点。
作为简单的示例,让我们用 Python 自动将单词 Hello, world! 输入到一个文件编辑器窗口中。首先,打开一个新的文件编辑器窗口,并将其放置在屏幕的左上角,以便 PyAutoGUI 能够在正确的位置点击以将其置于焦点。接下来,在交互式外壳中输入以下内容:
>>> pyautogui.click(100, 200); pyautogui.write('Hello, world!')
注意,将两个命令放在同一行,并用分号分隔,可以防止在运行两个指令之间提示你输入,这可以防止你在 click() 和 write() 调用之间意外地将新窗口置于焦点,这会搞乱示例。
Python 首先会向坐标 (100, 200) 发送一个虚拟鼠标点击,这应该会点击文件编辑器窗口并将其置于焦点。write() 调用会将文本 Hello, world! 发送到窗口,使其看起来像图 23-6 所示。你现在有了可以为你输入代码的代码!

图 23-6:使用 PyAutoGUI 点击文件编辑器窗口并输入 Hello, world!
默认情况下,write() 函数会立即输入整个字符串。但是,您可以通过传递一个可选的第二个参数在每个字符之间添加一个短暂的暂停。这个第二个参数是表示暂停秒数的整数或浮点值。例如,pyautogui.write('Hello, world!', 0.25) 将在输入 H 后等待四分之一秒,输入 e 后再等待四分之一秒,依此类推。这种逐渐的打字机效果可能对速度较慢的应用程序有用,这些应用程序无法快速处理按键以跟上 PyAutoGUI。
对于像 A 或 ! 这样的字符,PyAutoGUI 将自动模拟按下 SHIFT 键。
指定键名
并非所有键都容易用单个文本字符表示。例如,如何用一个字符来表示 SHIFT 或左箭头键?在 PyAutoGUI 中,这些键盘键由短字符串值表示:'esc' 表示 ESC 键或 'enter' 表示 ENTER 键。
除了单个字符串参数外,还可以将这些键盘键字符串的列表传递给 write()。例如,以下调用按下了 A 键,然后是 B 键,然后是左箭头键两次(将光标移到“a”之前),最后是 X 和 Y 键:
>>> pyautogui.write(['a', 'b', 'left', 'left', 'X', 'Y'])
因为按下左箭头键会移动键盘光标,所以这将输出 XYab。表 23-1 列出了可以传递给 write() 来模拟按下任何键组合的 PyAutoGUI 键盘键字符串。
您还可以检查 pyautogui.KEYBOARD_KEYS 列表,以查看 PyAutoGUI 将接受的所有可能的键盘键字符串。'shift' 字符串表示左侧 SHIFT 键,等同于 'shiftleft'。同样适用于 'ctrl'、'alt' 和 'win' 字符串;它们都指左侧的键。
表 23-1:键盘键的字符串值
| 键盘键字符串 | 含义 |
|---|---|
'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '!', '@', '#' 以及如此等等 |
单个字符的键 |
'enter' (或 'return' 或 '\n') |
ENTER 键 |
'esc' |
ESC 键 |
'shiftleft', 'shiftright' |
左侧和右侧的 SHIFT 键 |
'altleft', 'altright' |
左侧和右侧的 ALT 键 |
'ctrlleft', 'ctrlright' |
左侧和右侧的 CTRL 键 |
'tab' (或 '\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()键(在 macOS 上) |
'option' |
OPTION 键(在 macOS 上) |
按住并释放键盘
与mouseDown()和mouseUp()函数类似,pyautogui.keyDown()和pyautogui.keyUp()会将虚拟按键按下和释放发送到计算机。它们接受一个键盘键字符串(见表 23-1)作为它们的参数。为了方便,PyAutoGUI 提供了pyautogui.press()函数,它调用这两个函数来模拟完整的按键。
运行以下代码,将输入美元符号($)字符(通过按住 SHIFT 键并按下 4 得到):
>>> pyautogui.keyDown('shift'); pyautogui.press('4'); pyautogui.keyUp('shift')
这一行按住 SHIFT 键,按下(并释放)4,然后释放 SHIFT 键。如果你需要将字符串输入到文本字段中,write()函数更合适。但对于接受单个键命令的应用程序,press()函数是更简单的方法。
运行热键组合
热键或快捷键是一组按键组合,用于调用某些应用程序功能。常见的复制选择热键是 CTRL-C(在 Windows 和 Linux 上)或-C(在 macOS 上)。用户按下并保持 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 热键组合显示样式面板。而不是进行八个不同的功能调用(四个keyDown()调用和四个keyUp()调用),你只需调用hotkey('ctrl', 'alt', 'shift', 's')。
设置 GUI 自动化脚本
GUI 自动化脚本是一种自动化无聊工作的好方法,但你的脚本也可能很挑剔。如果窗口在桌面上位置不正确或意外出现一些弹出窗口,你的脚本可能会在屏幕上点击错误的东西。以下是一些设置你的 GUI 自动化脚本的技巧:
-
每次运行脚本时都使用相同的屏幕分辨率,这样窗口的位置就不会改变。
-
你的脚本点击的应用程序窗口应该最大化,这样每次运行脚本时其按钮和菜单都在相同的位置。
-
在等待内容加载时添加适当的暂停;你不想在应用程序准备好之前脚本就开始点击。
-
使用
locateOnScreen()来查找要点击的按钮和菜单,而不是依赖于坐标。如果你的脚本找不到需要点击的东西,请停止程序,而不是让它盲目地继续点击。 -
使用
getWindowsWithTitle()确保你认为你的脚本正在点击的应用程序窗口确实存在,并使用activate()方法将该窗口置于前台。 -
使用第五章中的
logging模块来记录脚本的日志文件。这样,如果你在处理过程中不得不中途停止脚本,你可以将其更改为从上次停止的地方继续。 -
在你的脚本中添加尽可能多的检查。考虑一下,如果出现意外的弹出窗口或计算机失去互联网连接,它可能会如何失败。
-
你可能想在脚本刚开始运行时对其进行监督,以确保它运行正确。
你可能还希望在脚本开始时添加一个暂停,以便用户可以设置脚本将要点击的窗口。PyAutoGUI 有一个 sleep() 函数,其行为与 time.sleep() 相同(但让你不必在脚本中添加 import time)。还有一个 countdown() 函数,它会打印数字倒计时,给用户一个视觉提示,表明脚本很快将继续。在交互式外壳中输入以下内容:
>>> import pyautogui
>>> pyautogui.sleep(3) # Pauses the program for 3 seconds
>>> pyautogui.countdown(10) # Counts down over 10 seconds
10 9 8 7 6 5 4 3 2 1
>>> print('Starting in ', end=''); pyautogui.countdown(3)
Starting in 3 2 1
这些技巧可以帮助使你的 GUI 自动化脚本更容易使用,并且能够更好地从不可预见的情况中恢复。
显示消息框
你迄今为止编写的所有程序都倾向于使用纯文本输出(使用 print() 函数)和输入(使用 input() 函数)。然而,PyAutoGUI 程序将使用你的整个桌面作为其游乐场。你的程序运行的基于文本的窗口,无论是 Mu 还是终端窗口,都可能在你使用 PyAutoGUI 程序点击和与其他窗口交互时丢失。如果 Mu 或终端窗口被其他窗口隐藏,这可能会使得从用户那里获取输入和输出变得困难。
为了解决这个问题,PyAutoGUI 包含了 PyMsgBox 模块,用于向用户创建弹出通知并从他们那里接收输入。有四个消息框函数:
pyautogui.alert(text) 显示文本并具有单个“确定”按钮
pyautogui.confirm(text) 显示文本并具有“确定”和“取消”按钮,根据点击的按钮返回 'OK' 或 'Cancel'
pyautogui.prompt(text) 显示文本并有一个文本字段供用户输入,它将其作为字符串返回
pyautogui.password(text) 与 prompt() 相同,但显示星号,以便用户可以输入敏感信息,如密码
这些函数与第十二章中“使用 PyMsgBox 弹出消息框”中提到的四个函数相同。
摘要
使用 PyAutoGUI 包进行 GUI 自动化,允许您通过控制鼠标和键盘与计算机上的应用程序进行交互。虽然这种方法足够灵活,可以完成任何人类用户可以做的事情,但缺点是这些程序对它们点击或输入的内容相当盲目。在编写 GUI 自动化程序时,请确保如果给出错误的指令,它们会迅速崩溃。崩溃是令人烦恼的,但比程序继续错误运行要好得多。
您可以使用 PyAutoGUI 在屏幕上移动鼠标光标,并模拟鼠标点击、按键和键盘快捷键。PyAutoGUI 包还可以检查屏幕上的颜色,这可以为您的 GUI 自动化程序提供足够的信息,以了解屏幕内容是否偏离了轨道。您甚至可以向 PyAutoGUI 提供一个截图,让它确定您想要点击的区域坐标。
您可以将所有这些 PyAutoGUI 功能结合起来,以自动化您计算机上任何无意识的重复性任务。实际上,观看鼠标光标自行移动并在屏幕上自动出现文本,这本身就是一种催眠般的感觉。为什么不花些时间坐下来,看着您的程序为您做所有工作呢?看到您的聪明才智让您免于无聊的事情,这是一种满足感。
练习问题
-
如何触发 PyAutoGUI 的故障安全机制来停止程序?
-
哪个函数返回当前屏幕分辨率?
-
哪个函数返回鼠标光标当前位置的坐标?
-
pyautogui.moveTo()和pyautogui.move()之间的区别是什么? -
可以使用哪些函数来拖动鼠标?
-
哪个函数调用将输出
"Hello, world!"字符? -
如何进行特殊键(如键盘的左箭头键)的按键操作?
-
如何将当前屏幕内容保存到名为 screenshot.png 的图像文件中?
-
什么代码会在每个 PyAutoGUI 函数调用后设置两秒的暂停?
-
如果您想在网页浏览器内自动化点击和按键,应该使用 PyAutoGUI 还是 Selenium?
-
什么因素使 PyAutoGUI 容易出错?
-
如何找到屏幕上包含“Notepad”一词的每个窗口的大小?
-
如何使 Firefox 浏览器在屏幕上所有其他窗口之前变得活跃?
练习程序
为了练习,编写程序来完成以下任务。
看起来很忙
许多即时通讯程序通过检测一段时间内鼠标移动的缺失来判定你是否空闲,或者是否离开了电脑——比如,10 分钟。也许你离开了电脑,但不想让别人看到你的即时通讯状态进入空闲模式,给人一种你偷懒的印象。编写一个脚本,每 10 秒将鼠标光标向左移动一个像素,然后在 10 秒后向右移动一个像素。这种移动应该足够小且不频繁,以免在脚本运行时你需要使用电脑时造成干扰。
使用剪贴板读取文本字段
虽然你可以使用 pyautogui.write() 函数向应用程序的文本字段发送按键,但你不能仅使用 PyAutoGUI 读取文本字段中已有的文本。这时,pyperclip 模块就能派上用场。你可以使用 PyAutoGUI 获取文本编辑器(如 Mu 或记事本)的窗口,通过点击将其置于屏幕最前方,然后在文本字段内点击,接着发送 CTRL-A 或 -A 快捷键以“全选”文本,以及 CTRL-C 或 -C 快捷键以“复制到剪贴板”。然后,你的 Python 脚本可以通过运行 import pyperclip 和 pyperclip.paste() 来读取剪贴板中的文本。
编写一个程序,按照以下步骤从窗口的文本字段中复制文本。使用 pyautogui.getWindowsWithTitle('Notepad')(或你选择的任何文本编辑器)来获取一个 Window 对象。这个 Window 对象的 top 和 left 属性可以告诉你窗口的位置,而 activate() 方法将确保它位于屏幕最前方。然后,你可以通过在 top 和 left 属性值上添加 100 或 200 像素,使用 pyautogui.click() 击中文本编辑器的主文本字段,以将键盘焦点放在那里。调用 pyautogui.hotkey('ctrl', 'a') 和 pyautogui.hotkey('ctrl', 'c') 来选择所有文本并将其复制到剪贴板。最后,调用 pyperclip.paste() 从剪贴板检索文本并将其粘贴到你的 Python 程序中。从那里,你可以按需使用这个字符串,但现在只需将其传递给 print() 即可。
注意,截至 PyAutoGUI 版本 1.0.0,PyAutoGUI 的窗口功能仅在 Windows 上工作,不支持 macOS 或 Linux。
编写一个游戏机器人
有一款名为“寿司转盘”的旧 Flash 游戏。这款游戏需要点击正确的配料按钮来填写顾客的寿司订单。你越快无误地完成订单,就能获得越多分数。这是一个非常适合 GUI 自动化程序的任务——以及一种获得高分的方法!尽管 Flash 作为产品已经停止开发,但有一些说明可以在你的电脑上离线玩它,以及一个列出托管“寿司转盘”游戏的网站列表,网址为github.com/asweigart/sushigoroundbot。那个 GitHub 仓库还包含了游戏玩法的 Python 源代码。一个机器人玩游戏的视频在youtu.be/lfk_T6VKhTE。
在 macOS 上设置辅助功能应用程序
作为安全措施,macOS 通常不允许程序控制鼠标或键盘。为了使 PyAutoGUI 在 macOS 上工作,你必须将运行 Python 脚本的程序设置为辅助功能应用程序。不进行此步骤,你的 PyAutoGUI 函数调用将没有任何效果。
无论你是从 Mu、IDLE 还是终端运行 Python 程序,都要保持该应用程序打开。然后,打开系统偏好设置并转到辅助功能标签。当前打开的应用程序将出现在“允许以下应用程序控制您的计算机”标签下。检查 Mu、IDLE、Terminal 或你用来运行 Python 脚本的任何应用程序。你将被提示输入密码以确认这些更改。
保持方向
在你开始 GUI 自动化之前,你应该知道如何逃避可能出现的问题。Python 可以以惊人的速度移动鼠标和输入按键。实际上,它可能对其他程序来说太快,以至于无法跟上。此外,如果出了问题但你的程序还在移动鼠标,将很难确切知道程序在做什么或如何从问题中恢复。就像迪士尼《幻想曲》中“魔法师学徒”序列中的魔法扫帚,它们不断地(然后是过度地)往米奇的浴缸里灌水,即使你的程序完美地遵循你的指示,它也可能失去控制。如果鼠标在自行移动,阻止你点击 Mu 编辑器窗口来关闭它,停止程序可能很困难。幸运的是,有几种方法可以防止或从 GUI 自动化问题中恢复。
暂停和保险丝
如果您的程序有错误并且您无法使用键盘和鼠标来关闭它,您可以使用 PyAutoGUI 的应急功能。快速将鼠标滑到屏幕的四个角落之一。每个 PyAutoGUI 函数调用在其动作执行后都会暂停十分之一秒,以便您有足够的时间将鼠标移动到角落。如果 PyAutoGUI 随后发现鼠标光标位于角落,它将引发pyautogui.FailSafeException异常。非 PyAutoGUI 指令不会有这个暂停。您可以通过将pyautogui.PAUSE设置为除0.1之外的其他值来调整这个暂停持续时间。
如果您发现自己处于需要停止 PyAutoGUI 程序的情况,只需将鼠标猛地向屏幕角落砸去即可停止它。
注销
可能停止失控的 GUI 自动化程序的最简单方法就是注销,这将关闭所有正在运行的程序。在 Windows 和 Linux 上,注销快捷键是 CTRL-ALT-DEL。在 macOS 上,它是-SHIFT-Q。通过注销,您将丢失任何未保存的工作,但至少您不必等待计算机完全重启。
暂停和应急
如果您的程序有错误并且您无法使用键盘和鼠标来关闭它,您可以使用 PyAutoGUI 的应急功能。快速将鼠标滑到屏幕的四个角落之一。每个 PyAutoGUI 函数调用在其动作执行后都会暂停十分之一秒,以便您有足够的时间将鼠标移动到角落。如果 PyAutoGUI 随后发现鼠标光标位于角落,它将引发pyautogui.FailSafeException异常。非 PyAutoGUI 指令不会有这个暂停。您可以通过将pyautogui.PAUSE设置为除0.1之外的其他值来调整这个暂停持续时间。
如果您发现自己处于需要停止您的 PyAutoGUI 程序的情况,只需将鼠标猛地向屏幕角落砸去即可停止它。
注销
可能停止失控的 GUI 自动化程序的最简单方法就是注销,这将关闭所有正在运行的程序。在 Windows 和 Linux 上,注销快捷键是 CTRL-ALT-DEL。在 macOS 上,它是-SHIFT-Q。通过注销,您将丢失任何未保存的工作,但至少您不必等待计算机完全重启。
控制鼠标移动
在本节中,您将学习如何使用 PyAutoGUI 移动鼠标并在屏幕上跟踪其位置,但首先您需要了解 PyAutoGUI 如何与坐标一起工作。
PyAutoGUI 的鼠标功能使用 x 和 y 坐标。第二十一章。原点,其中 x 和 y 都为零,位于屏幕的左上角。x 坐标向右增加,y 坐标向下增加。所有坐标都是正整数;没有负坐标。

图 23-1:1920×1080 分辨率计算机屏幕的坐标
你的分辨率是指你的屏幕有多宽和多高(以像素为单位)。如果你的屏幕分辨率设置为 1920×1080,则屏幕左上角的坐标将是(0, 0),而屏幕右下角的坐标将是(1919, 1079)。
pyautogui.size()函数返回一个表示屏幕宽度和高度的Size命名元组(以像素为单位)。命名元组超出了本书的范围,但它们基本上是具有整数索引的元组,同时具有命名属性。在交互式外壳中输入以下内容:
>>> import pyautogui
>>> screen_size = pyautogui.size() # Obtain the screen resolution.
>>> screen_size
Size(width=1920, height=1080)
>>> screen_size[0], screen_size[1]
(1920, 1080)
>>> screen_size.width, screen_size.height
(1920, 1080)
>>> tuple(screen_size)
(1920, 1080)
pyautogui.size()函数在 1920×1080 分辨率的计算机上返回(1920, 1080)的Size对象;根据你的屏幕分辨率,你的返回值可能不同。
移动鼠标
现在你已经理解了屏幕坐标,让我们移动鼠标。pyautogui.moveTo()函数会立即将鼠标光标移动到屏幕上的指定位置。x 和 y 坐标的整数值分别构成函数的第一个和第二个参数。可选的duration整型或浮点型关键字参数指定了移动鼠标到目标位置所需的时间(秒数)。如果你省略了它,默认值为0,表示瞬间移动。(PyAutoGUI 函数中的所有duration关键字参数都是可选的。)在交互式外壳中输入以下内容:
>>> import pyautogui
>>> for i in range(10): # Move the mouse in a square.
... 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 次。每次移动持续四分之一秒,由duration=0.25关键字参数指定。如果你没有为任何pyautogui.moveTo()调用传递第三个参数,鼠标光标将瞬间从一点传送到另一点。
pyautogui.move()函数将鼠标光标移动到其当前位置的相对位置。以下示例以相同的正方形模式移动鼠标,但正方形从代码开始运行时鼠标在屏幕上的当前位置开始:
>>> import pyautogui
>>> for i in range(10):
... pyautogui.move(100, 0, duration=0.25) # Right
... pyautogui.move(0, 100, duration=0.25) # Down
... pyautogui.move(-100, 0, duration=0.25) # Left
... pyautogui.move(0, -100, duration=0.25) # Up
...
pyautogui.move()函数也接受三个参数:水平向右移动多少像素,垂直向下移动多少像素,以及(可选的)完成移动所需的时间。第一个或第二个参数为负整数时,鼠标将向左或向上移动。
获取当前位置
您可以通过调用 pyautogui.position() 函数来确定鼠标的当前位置,该函数将返回一个名为 Point 的元组,包含鼠标光标在函数调用时的 x 和 y 位置。您可以通过 Point 元组的 0 和 1 整数索引或通过 x 和 y 属性来访问 x 和 y 坐标。(这与 Size 元组的 width 和 height 属性类似。)在交互式外壳中输入以下内容,并在每次调用后移动鼠标:
>>> pyautogui.position() # Get the current mouse position.
Point(x=311, y=622)
>>> pyautogui.position() # Get the current mouse position again.
Point(x=377, y=481)
>>> p = pyautogui.position() # And again
>>> p
Point(x=1536, y=637)
>>> p[0] # The x-coordinate is at index 0.
1536
>>> p.x # The x-coordinate is also in the x attribute.
1536
当然,您的返回值将取决于您的鼠标光标位置。
移动鼠标
现在您已经了解了屏幕坐标,让我们移动鼠标。pyautogui.moveTo() 函数将立即将鼠标光标移动到屏幕上的指定位置。x 和 y 坐标的整数值分别构成函数的第一个和第二个参数。可选的 duration 整数或浮点关键字参数指定移动鼠标到目的地所需的时间。如果您省略它,默认为 0,表示瞬间移动。(PyAutoGUI 函数中的所有 duration 关键字参数都是可选的。)在交互式外壳中输入以下内容:
>>> import pyautogui
>>> for i in range(10): # Move the mouse in a square.
... 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 次。每次移动持续四分之一秒,由 duration=0.25 关键字参数指定。如果您没有为任何 pyautogui.moveTo() 调用传递第三个参数,鼠标光标将瞬间从一点传送到另一点。
pyautogui.move() 函数将鼠标光标移动到其当前位置的相对位置。以下示例将鼠标移动到相同的正方形图案中,但正方形是从代码开始运行时鼠标在屏幕上的当前位置开始的:
>>> import pyautogui
>>> for i in range(10):
... pyautogui.move(100, 0, duration=0.25) # Right
... pyautogui.move(0, 100, duration=0.25) # Down
... pyautogui.move(-100, 0, duration=0.25) # Left
... pyautogui.move(0, -100, duration=0.25) # Up
...
pyautogui.move() 函数也接受三个参数:向右水平移动多少像素,向下垂直移动多少像素,以及(可选的)完成移动所需的时间。第一个或第二个参数为负整数时,鼠标将分别向左或向上移动。
获取当前位置
您可以通过调用 pyautogui.position() 函数来确定鼠标的当前位置,该函数将返回一个名为 Point 的元组,包含鼠标光标在函数调用时的 x 和 y 位置。您可以通过 Point 元组的 0 和 1 整数索引或通过 x 和 y 属性来访问 x 和 y 坐标。(这与 Size 元组的 width 和 height 属性类似。)在交互式外壳中输入以下内容,并在每次调用后移动鼠标:
>>> pyautogui.position() # Get the current mouse position.
Point(x=311, y=622)
>>> pyautogui.position() # Get the current mouse position again.
Point(x=377, y=481)
>>> p = pyautogui.position() # And again
>>> p
Point(x=1536, y=637)
>>> p[0] # The x-coordinate is at index 0.
1536
>>> p.x # The x-coordinate is also in the x attribute.
1536
当然,您的返回值将取决于您的鼠标光标位置。
控制鼠标交互
现在您已经知道了如何移动鼠标并确定它在屏幕上的位置,您就可以开始点击、拖动和滚动操作了。
点击
要向您的计算机发送虚拟鼠标点击,请调用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) # Move the mouse to (10, 5) and click.
您应该看到鼠标指针移动到屏幕的左上角附近并点击一次。完整的“点击”是指按下鼠标按钮然后释放它而不移动光标。您也可以通过调用pyautogui.mouseDown()来执行点击,它只会按下鼠标按钮,以及pyautogui.mouseUp(),它只会释放按钮。这些函数具有与click()相同的参数,实际上,click()函数只是这两个函数调用的便利包装器。
作为进一步的便利,pyautogui.doubleClick()函数将使用左鼠标按钮执行两次点击。pyautogui.rightClick()和pyautogui.middleClick()函数将分别使用右键和中间鼠标按钮执行点击。
拖动
拖动意味着在按住鼠标按钮的同时移动鼠标。例如,您可以通过拖动文件夹图标在文件夹之间移动文件,或者您可以在日历应用中移动约会。
PyAutoGUI 提供了pyautogui.dragTo()和pyautogui.drag()函数,用于将鼠标光标拖动到新位置或相对于当前位置的位置。dragTo()和drag()的参数与moveTo()和move()相同:x 坐标/水平移动,y 坐标/垂直移动,以及可选的时间持续时间。(当鼠标移动得太快时,macOS 操作系统无法正确拖动,因此建议传递duration关键字参数。)
要尝试这些函数,请打开 Windows 上的 MS Paint、macOS 上的 Paintbrush 或 Linux 上的 GNU Paint 等图形绘图应用程序。(如果您没有绘图应用程序,您可以使用在线的sumopaint.com。)我将使用 PyAutoGUI 在这些应用程序中绘图。
将鼠标光标置于绘图应用程序的画布上,并选择铅笔或画笔工具后,在新的文件编辑窗口中输入以下内容,并将其保存为spiralDraw.py:
import pyautogui
pyautogui.sleep(5) # ❶
pyautogui.click() # Click to make the window active. # ❷
distance = 300
change = 20
while distance > 0:
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
distance = distance - change
pyautogui.drag(0, -distance, duration=0.2) # Move up.
当你运行这个程序时,会有五秒的延迟❶,在这段时间内,你可以选择铅笔或画笔工具,将鼠标光标移动到绘图程序的窗口上。PyAutoGUI 的sleep()函数与time.sleep()相同,但存在是为了你不需要在你的代码中添加import time。然后,spiralDraw.py将控制鼠标并点击使绘图程序的窗口变为活动状态❷。活动窗口是当前接受键盘输入的窗口,你采取的行动(如输入或在此例中拖动鼠标)将影响该窗口。活动窗口也被称为焦点或前景窗口。一旦绘图程序变为活动状态,spiralDraw.py将绘制一个类似于图 23-2 左侧的方形螺旋图案。
distance变量从300开始,所以在while循环的第一次迭代中,第一次drag()调用将光标拖动 300 像素向右,耗时 0.2 秒❸。然后,distance减少到 280❹,第二次drag()调用将光标拖动 280 像素向下❺。第三次drag()调用将光标水平拖动-280(向左 280 像素)❻,distance减少到 260,最后一次drag()调用将光标向上拖动 260 像素。在每次迭代中,鼠标向右、向下、向左和向上拖动,distance略小于前一次迭代中的值。通过循环这段代码,你可以移动鼠标光标来绘制方形螺旋。
虽然你也可以通过第二十一章中讨论的 Pillow 包创建方形螺旋图像,但通过控制鼠标在 MS Paint 中绘制它来创建图像,让你可以利用这个程序的各种画笔样式,如图 23-2 右侧所示,以及其他高级功能,如渐变或填充桶。你可以预先选择画笔设置(或者让你的 Python 代码选择这些设置),然后运行螺旋绘制程序。

图 23-2:pyautogui.drag()示例的结果,使用 MS Paint 的不同画笔绘制
你可以手动(或者更确切地说,通过鼠标)绘制这个螺旋线,但你必须慢慢地工作才能如此精确。PyAutoGUI 可以在几秒钟内完成它!
滚动
PyAutoGUI 的最后一个鼠标函数是scroll(),你需要传递一个整数参数来指定向上或向下滚动多少单位。单位的大小因操作系统和应用而异,所以你需要实验以确定在你的特定情况下滚动多远。滚动发生在鼠标光标当前位置。传递正整数向上滚动,传递负整数向下滚动。在 Mu 编辑器的交互式外壳中运行以下代码,同时鼠标光标位于 Mu 编辑器窗口上:
>>> pyautogui.scroll(200)
如果你将鼠标光标悬停在可以向上滚动的文本字段上,你会看到 Mu 向上滚动。
单击
要向你的计算机发送虚拟鼠标单击,请调用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) # Move the mouse to (10, 5) and click.
你应该看到鼠标指针移动到屏幕的左上角附近并单击一次。完整的“单击”是指按下鼠标按钮然后释放它,同时不移动光标。你也可以通过调用pyautogui.mouseDown()来执行单击,它只会按下鼠标按钮,以及通过调用pyautogui.mouseUp()来执行单击,它只会释放按钮。这些函数与click()函数具有相同的参数,实际上,click()函数只是这两个函数调用的便捷包装器。
作为进一步方便,pyautogui.doubleClick()函数将使用左鼠标按钮执行两次单击。pyautogui.rightClick()和pyautogui.middleClick()函数将分别使用右鼠标按钮和中鼠标按钮执行单击。
拖动
拖动意味着在按住鼠标按钮的同时移动鼠标。例如,你可以通过拖动文件夹图标在文件夹之间移动文件,或者你可以在日历应用程序中移动约会。
PyAutoGUI 提供了pyautogui.dragTo()和pyautogui.drag()函数来将鼠标光标拖动到新位置或相对于当前位置的位置。dragTo()和drag()函数的参数与moveTo()和move()函数相同:x 坐标/水平移动,y 坐标/垂直移动,以及可选的时间持续时间。(当鼠标移动得太快时,macOS 操作系统无法正确地拖动,因此建议传递duration关键字参数。)
要尝试这些函数,请打开 Windows 上的 MS Paint、macOS 上的 Paintbrush 或 Linux 上的 GNU Paint 等图形绘图应用程序。(如果你没有绘图应用程序,你可以使用在线的sumopaint.com。)我将使用 PyAutoGUI 在这些应用程序中绘图。
在选择铅笔或画笔工具的情况下,将鼠标光标放在绘图应用程序的画布上,并在新的文件编辑窗口中输入以下内容,并将其保存为spiralDraw.py:
import pyautogui
pyautogui.sleep(5) # ❶
pyautogui.click() # Click to make the window active. # ❷
distance = 300
change = 20
while distance > 0:
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
pyautogui.drag(distance, 0, duration=0.2) # Move right. # ❸
distance = distance - change
pyautogui.drag(0, -distance, duration=0.2) # Move up.
当你运行这个程序时,会有一个五秒的延迟❶,在这段时间内,你可以使用铅笔或画笔工具将鼠标光标移动到绘图程序窗口上。PyAutoGUI 的sleep()函数与time.sleep()相同,但存在是为了让你不需要在你的代码中添加import time。然后,spiralDraw.py将控制鼠标并点击使绘图程序窗口变为活动状态❷。活动窗口是当前接受键盘输入的窗口,你采取的行动(如输入或在这种情况下拖动鼠标)将影响该窗口。活动窗口也被称为焦点或前景窗口。一旦绘图程序变为活动状态,spiralDraw.py将绘制一个类似于图 23-2 左侧的方形螺旋图案。
distance变量从300开始,所以在while循环的第一次迭代中,第一次drag()调用将光标向右拖动 300 像素,耗时 0.2 秒❸。然后,distance减少到 280❹,第二次drag()调用将光标向下拖动 280 像素❺。第三次drag()调用将光标水平向左拖动-280 像素(向左 280 像素),distance减少到 260,最后drag()调用将光标向上拖动 260 像素。在每次迭代中,鼠标向右、向下、向左和向上拖动,distance略小于前一次迭代中的值。通过循环这段代码,你可以移动鼠标光标来绘制方形螺旋。
虽然你也可以通过第二十一章中讨论的 Pillow 包创建方形螺旋图像,但通过控制鼠标在 MS Paint 中绘制它来创建图像,让你可以利用这个程序的各种画笔样式,如图 23-2 右侧所示,以及其他高级功能,如渐变或填充桶。你可以预先选择画笔设置(或者让你的 Python 代码选择这些设置),然后运行螺旋绘制程序。

图 23-2:pyautogui.drag()示例的结果,使用 MS Paint 的不同画笔绘制
你可以手动(或者更确切地说,通过鼠标)绘制这个螺旋线,但你需要慢慢地工作才能如此精确。PyAutoGUI 可以在几秒钟内完成它!
滚动
PyAutoGUI 的最后一个鼠标函数是scroll(),你需要传递一个整数参数来指定你想要向上或向下滚动多少单位。单位的大小因操作系统和应用程序而异,所以你可能需要实验来确定在你的特定情况下它滚动多远。滚动发生在鼠标光标当前的位置。传递一个正整数向上滚动,传递一个负整数向下滚动。在鼠标光标位于 Mu 编辑器窗口上时,在 Mu 编辑器的交互式外壳中运行以下代码:
>>> pyautogui.scroll(200)
如果鼠标光标在可以向上滚动的文本字段上,你会看到 Mu 向上滚动。
规划你的鼠标移动
编写一个将自动点击屏幕的程序的一个困难是找到你想要点击的 x 和 y 坐标。pyautogui.mouseInfo()函数可以帮助你完成这个任务。
pyautogui.mouseInfo()函数旨在从交互式外壳中调用,而不是作为程序的一部分。它启动一个名为 MouseInfo 的小应用程序,该应用程序包含在 PyAutoGUI 中。应用程序的窗口看起来像图 23-3。

图 23-3:鼠标信息应用程序的窗口
在交互式外壳中输入以下内容:
>>> import pyautogui
>>> pyautogui.mouseInfo()
这将使鼠标信息窗口出现。此窗口提供有关鼠标光标当前位置的信息,以及鼠标光标下像素的颜色,作为一个三整数 RGB 元组以及十六进制值。颜色本身在窗口的颜色框中显示。
为了帮助你记录此坐标或像素信息,你可以点击八个复制或记录按钮中的一个。复制全部、复制 XY、复制 RGB 和复制 RGB 十六进制按钮将它们各自的信息复制到剪贴板。记录全部、记录 XY、记录 RGB 和记录 RGB 十六进制按钮将它们各自的信息写入窗口中的大文本字段。你可以通过点击保存日志按钮来保存日志文本字段中的文本。
默认情况下,3 秒按钮延迟复选框被选中,导致在点击复制或日志按钮和复制或记录操作发生之间有一个三秒的延迟。这给你一小段时间来点击按钮,然后移动鼠标到你的目标位置。取消选中此框,将鼠标移动到位置,并按 F1 到 F8 键来复制或记录鼠标位置可能会更容易。你可以在鼠标信息窗口顶部的复制和日志菜单中查找哪个键映射到哪个按钮。
例如,取消选中3 秒按钮延迟,然后在按 F6 键的同时在屏幕上移动鼠标,注意鼠标的 x 和 y 坐标是如何记录在窗口中间的大文本字段中的。你可以在你的 PyAutoGUI 脚本中使用这些坐标。
有关 MouseInfo 的更多信息,请参阅mouseinfo.readthedocs.io/的完整文档。
捕获屏幕截图
你的 GUI 自动化程序不需要盲目点击和输入。PyAutoGUI 具有截图功能,可以根据当前屏幕内容创建图像文件。这些函数还可以返回当前屏幕外观的 Pillow Image对象。如果你在这本书中跳来跳去,你将想要在继续本节之前阅读第二十一章并安装 Pillow 包。
在 Python 中截取屏幕截图,请调用pyautogui.screenshot()函数。在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> im = pyautogui.screenshot()
im变量将包含屏幕截图的Image对象。你现在可以在im变量中的Image对象上调用方法,就像任何其他Image对象一样。第二十一章有关于Image对象更多信息。
假设你 GUI 自动化程序中的一步是点击一个灰色按钮。在调用click()方法之前,你可能想要截取屏幕截图并查看脚本即将点击的像素。如果它不是与灰色按钮相同的灰色,那么你的程序就知道有问题了。也许窗口意外移动了,或者可能有一个弹出对话框阻挡了按钮。在这种情况下,而不是继续执行,并可能通过点击错误的东西造成混乱,你的程序可以停止自己。
你可以使用pixel()函数获取屏幕上特定像素的 RGB 颜色值。在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> pyautogui.pixel(0, 0)
(176, 176, 175)
>>> pyautogui.pixel((50, 200))
(130, 135, 144)
向pixel()传递两个整数作为 XY 坐标,它将告诉你图像中该坐标处的像素颜色。pixel()的返回值是一个包含三个整数的 RGB 元组,表示像素中的红色、绿色和蓝色量。(没有第四个值用于 alpha,因为截图图像是完全不透明的。)
PyAutoGUI 的pixelMatchesColor()函数会在屏幕上给定 x 和 y 坐标处的像素与给定颜色匹配时返回True。第一个和第二个参数是 x 和 y 坐标的整数,第三个参数是一个包含三个整数的元组,表示屏幕像素必须匹配的 RGB 颜色。在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> pyautogui.pixel((50, 200)) # ❶
(130, 135, 144)
>>> pyautogui.pixelMatchesColor(50, 200, (130, 135, 144)) # ❷
True
>>> pyautogui.pixelMatchesColor(50, 200, (255, 135, 144)) # ❸
False
在使用pixel()获取特定坐标处的像素颜色 RGB 元组❶之后,将相同的坐标和 RGB 元组传递给pixelMatchesColor()❷,它应该返回True。然后,更改 RGB 元组中的值,并再次调用pixelMatchesColor()❸,对于相同的坐标。这应该返回false。这种方法可以在你的 GUI 自动化程序即将调用click()时非常有用。请注意,给定坐标处的颜色必须完全匹配。如果它有丝毫不同——例如,(255, 255, 254)而不是(255, 255, 255)——那么pixelMatchesColor()将返回False。
图像识别
但如果你事先不知道 PyAutoGUI 应该点击的位置,你可以使用图像识别。给 PyAutoGUI 一个你想要点击的图像,让它自己找出坐标。
例如,如果你之前已经截取了一个屏幕截图来捕捉submit.png中提交按钮的图像,locateOnScreen()函数将返回该图像被找到的坐标。要了解locateOnScreen()是如何工作的,尝试截取屏幕上一个小区域的屏幕截图;然后保存该图像,并在交互式 shell 中输入以下内容,将'submit.png'替换为你的屏幕截图文件名:
>>> import pyautogui
>>> box = pyautogui.locateOnScreen('submit.png')
>>> box
Box(left=643, top=745, width=70, height=29)
>>> box[0]
643
>>> box.left
643
Box对象是一个命名元组,由locateOnScreen()返回,包含屏幕上第一次找到图像的左侧边 x 坐标、顶部边 y 坐标、宽度和高度。如果你在自己的电脑上使用自己的截图尝试这个,你的返回值将不同于这里显示的。
如果图像在屏幕上找不到,locateOnScreen()会引发一个ImageNotFoundException异常。请注意,屏幕上的图像必须与提供的图像完全匹配才能被识别。如果图像甚至偏离一个像素,locateOnScreen()也会引发一个ImageNotFoundException异常。如果你更改了屏幕分辨率,之前的截图中的图像可能不会与当前屏幕上的图像匹配,因为它们有不同的缩放因子。缩放因子超出了本书的范围,但它们有时用于现代高分辨率显示器。你可以在操作系统的显示设置中更改缩放,如图 23-4 所示。

图 23-4:Windows(左)和 macOS(右)的缩放显示设置
如果图像在屏幕的几个地方都可以找到,locateAllOnScreen()将返回一个Generator对象。生成器超出了本书的范围,但你可以将它们传递给list()函数以返回一个Box对象列表。屏幕上每个找到图像的位置都会有一个Box对象。继续交互式 shell 示例,输入以下内容(并将'submit.png'替换为你自己的图像文件名):
>>> list(pyautogui.locateAllOnScreen('submit.png'))
[(643, 745, 70, 29), (1007, 801, 70, 29)]
在这个例子中,图像出现在两个位置。如果你的图像只在一个区域找到,调用list(locateAllOnScreen())将返回一个只包含一个Box对象的列表。
一旦你有了想要选择的特定图像的Box对象,你可以通过传递元组到click()函数来点击这个区域的中心。在交互式 shell 中输入以下内容:
>>> pyautogui.click((643, 745, 70, 29))
作为快捷方式,你也可以直接将图像文件名传递给click()函数:
>>> pyautogui.click('submit.png')
moveTo()和dragTo()函数也接受图像文件名参数。记住,如果locateOnScreen()在屏幕上找不到图像,它会引发一个异常,所以你应该在try语句中调用它:
try:
location = pyautogui.locateOnScreen('submit.png')
except pyautogui.ImageNotFoundException:
print('Image could not be found.')
没有使用try和except语句,未捕获的异常会导致你的程序崩溃。由于你无法确定你的程序是否总能找到图像,因此在调用locateOnScreen()时使用try和except语句是个好主意。在 PyAutoGUI 1.0.0 之前的版本中,locateOnScreen()会返回None而不是抛出异常。在这些旧版本中,调用pyautogui.useImageNotFoundException()来抛出异常,或者调用pyautogui.useImageNotFoundException(False)来返回None(对于新版本)。
获取窗口信息
图像识别是屏幕上查找事物的一种脆弱方式;如果单个像素的颜色不同,那么pyautogui.locateOnScreen()将找不到该图像。如果你需要找到屏幕上特定窗口的位置,使用 PyAutoGUI 的窗口功能更快、更可靠。
注意
截至版本 1.0.0,PyAutoGUI 的窗口功能仅在 Windows 上工作,不在 macOS 或 Linux 上。这些功能来自 PyAutoGUI 包含的 PyGetWindow 包。
获取活动窗口
屏幕上当前活跃的窗口是处于前台并接受键盘输入的窗口。如果你目前在 Mu 编辑器中编写代码,Mu 编辑器的窗口就是活跃窗口。在屏幕上的所有窗口中,一次只有一个窗口是活跃的。
在交互式 shell 中,调用pyautogui.getActiveWindow()函数来获取Window对象(在 Windows 上运行时实际上是Win32Window对象)。一旦你有了这个Window对象,你可以检索对象的任何属性,这些属性描述了其大小、位置和标题:
left, right, top, bottom 表示窗口边界的 x 或 y 坐标的单个整数
topleft, topright, bottomleft, bottomright 表示窗口角落的(x, y)坐标的整数元组Point
midleft, midright, midtop, midbottom 表示窗口边界中间的(x, y)坐标的整数元组Point
width, height 表示窗口尺寸的单个整数,以像素为单位
size 一个表示窗口宽度(width)和高度(height)的整数元组Size
area 表示窗口面积的单一整数,以像素为单位
center 一个表示窗口中心(x, y)坐标的整数元组Point
centerx, centery 表示窗口中心 x 或 y 坐标的单个整数
box 一个表示窗口左(left)、上(top)、宽(width)、高(height)尺寸的整数元组Box
title 窗口顶部标题栏中的文本字符串
要从window对象获取窗口的位置、大小和标题信息,例如,在交互式 shell 中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win
Win32Window(hWnd=2034368)
>>> str(active_win)
'<Win32Window left="500", top="300", width="2070", height="1208", title="Mu 1.0.1 – test1.py">'
>>> active_win.title
'Mu 1.0.1 – test1.py'
>>> active_win.size
Size(width=2070, height=1208)
>>> active_win.left, active_win.top, active_win.right, active_win.bottom
(500, 300, 2570, 1508)
>>> active_win.topleft
Point(x=500, y=300)
>>> pyautogui.click(active_win.left + 10, active_win.top + 20)
你现在可以使用这些属性来计算窗口内的精确坐标。如果你知道你想要点击的按钮始终位于窗口左上角右侧 10 像素和下方 20 像素处,并且窗口的左上角位于屏幕坐标(300, 500),那么调用pyautogui.click(310, 520)(或如果active_win包含窗口的Window对象,则为pyautogui.click(active_win.left + 10, active_win.top + 20))将点击按钮。这样,你就不必依赖较慢、可靠性较低的locateOnScreen()函数来为你找到按钮。
使用其他功能查找窗口
虽然getActiveWindow()对于获取函数调用时的活动窗口很有用,但你还需要使用其他函数来获取屏幕上其他窗口的Window对象。以下三个函数返回Window对象列表。如果它们找不到任何窗口,则返回空列表:
pyautogui.getAllWindows() 返回屏幕上每个可见窗口的Window对象列表
pyautogui.getWindowsAt(x, y) 返回包含点(x, y)的每个可见窗口的Window对象列表
pyautogui.getWindowsWithTitle(title) 返回包含标题栏中字符串 title 的每个可见窗口的Window对象列表
PyAutoGUI 还有一个pyautogui.getAllTitles()函数,它返回每个可见窗口的字符串列表。
操作窗口
窗口属性不仅可以告诉你窗口的大小和位置。你还可以设置它们的值以调整窗口大小或移动窗口。例如,在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.width # Gets the current width of the window # ❶
1669
>>> active_win.topleft # Gets the current position of the window # ❷
Point(x=174, y=153)
>>> active_win.width = 1000 # Resizes the width # ❸
>>> active_win.topleft = (800, 400) # Moves the window # ❹
首先,我们使用Window对象的属性来获取有关窗口大小❶和位置❷的信息。在 Mu 编辑器中调用这些函数后,窗口应该变窄❸并移动❹,如图 23-5 所示。

图 23-5:使用窗口对象属性调整大小和移动之前(顶部)和之后(底部)的 Mu 编辑器窗口
你还可以找出并更改窗口的最小化、最大化和激活状态。尝试在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.isMaximized # Returns True if the window is maximized
False
>>> active_win.isMinimized # Returns True if the window is minimized
False
>>> active_win.isActive # Returns True if the window is the active window
True
>>> active_win.maximize() # Maximizes the window
>>> active_win.isMaximized
True
>>> active_win.restore() # Undoes a minimize/maximize action
>>> active_win.minimize() # Minimizes the window
>>> import time
>>> # Waits 5 seconds while you activate a different window:
>>> time.sleep(5); active_win.activate()
>>> active_win.close() # This will close the window you're typing in.
isMaximized、isMinimized和isActive属性包含布尔值,表示窗口当前是否处于该状态。maximize()、minimize()、activate()和restore()方法会改变窗口的状态。使用maximize()或minimize()最大化或最小化窗口后,restore()方法将窗口恢复到其原始大小和位置。
close()方法将关闭窗口。请注意此方法,因为它可能会绕过在退出应用程序之前要求你保存工作的消息对话框。
请参阅 PyAutoGUI 文档以获取其窗口控制功能的完整详细信息。
获取活动窗口
您屏幕上的活动窗口是当前处于前台并接受键盘输入的窗口。如果您目前在 Mu 编辑器中编写代码,Mu 编辑器的窗口就是活动窗口。在您屏幕上的所有窗口中,一次只有一个窗口是活动的。
在交互式外壳中调用 pyautogui.getActiveWindow() 函数以获取 Window 对象(在 Windows 上运行时实际上是 Win32Window 对象)。一旦您有了那个 Window 对象,您就可以检索对象的任何属性,这些属性描述了它的大小、位置和标题:
left, right, top, bottom 窗口边界的 x 或 y 坐标的单一整数
topleft, topright, bottomleft, bottomright 一个包含两个整数的 Point 命名元组,用于窗口角落的 (x, y) 坐标
midleft, midright, midtop, midbottom 一个包含两个整数的 Point 命名元组,用于窗口边界的中间 (x, y) 坐标
width, height 窗口尺寸的一个整数,以像素为单位
size 一个包含两个整数的 Size 命名元组,用于窗口的 (width, height)
area 表示窗口面积的单一整数,以像素为单位
center 一个包含两个整数的 Point 命名元组,用于窗口中心的 (x, y) 坐标
centerx, centery 窗口中心 x 或 y 坐标的单个整数
box 一个包含四个整数的 Box 命名元组,用于窗口的 (left, top, width, height) 测量
title 窗口顶部标题栏中的文本字符串
要从 window 对象获取窗口的位置、大小和标题信息,例如,在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win
Win32Window(hWnd=2034368)
>>> str(active_win)
'<Win32Window left="500", top="300", width="2070", height="1208", title="Mu 1.0.1 – test1.py">'
>>> active_win.title
'Mu 1.0.1 – test1.py'
>>> active_win.size
Size(width=2070, height=1208)
>>> active_win.left, active_win.top, active_win.right, active_win.bottom
(500, 300, 2570, 1508)
>>> active_win.topleft
Point(x=500, y=300)
>>> pyautogui.click(active_win.left + 10, active_win.top + 20)
您现在可以使用这些属性来计算窗口内的精确坐标。如果您知道要点击的按钮始终位于窗口左上角右侧 10 像素和下方 20 像素处,并且窗口左上角位于屏幕坐标 (300, 500),那么调用 pyautogui.click(310, 520)(或 pyautogui.click(active_win.left + 10, active_win.top + 20) 如果 active_win 包含窗口的 Window 对象)将点击按钮。这样,您就不必依赖较慢、可靠性较低的 locateOnScreen() 函数来为您查找按钮。
使用其他函数查找窗口
虽然 getActiveWindow() 对于获取函数调用时的活动窗口很有用,但您需要使用其他函数来获取屏幕上其他窗口的 Window 对象。以下三个函数返回一个 Window 对象列表。如果它们无法找到任何窗口,则返回空列表:
pyautogui.getAllWindows() 返回屏幕上每个可见窗口的 Window 对象列表
pyautogui.getWindowsAt(x, y) 返回包含点 (x, y) 的每个可见窗口的 Window 对象列表
pyautogui.getWindowsWithTitle(title) 返回包含标题栏中字符串 title 的每个可见 Window 对象的列表
PyAutoGUI 还有一个 pyautogui.getAllTitles() 函数,它返回每个可见窗口的字符串列表。
窗口操作
窗口属性不仅可以告诉你窗口的大小和位置,你还可以设置它们的值以调整大小或移动窗口。例如,在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.width # Gets the current width of the window # ❶
1669
>>> active_win.topleft # Gets the current position of the window # ❷
Point(x=174, y=153)
>>> active_win.width = 1000 # Resizes the width # ❸
>>> active_win.topleft = (800, 400) # Moves the window # ❹
首先,我们使用 Window 对象的属性来获取有关窗口大小 ❶ 和位置 ❷ 的信息。在 Mu 编辑器中调用这些函数后,窗口应该变窄 ❸ 并移动 ❹,如图 23-5 所示。

图 23-5:使用窗口对象属性调整大小和移动之前(顶部)和之后(底部)的 Mu 编辑器窗口
你还可以找出并更改窗口的最小化、最大化以及激活状态。尝试在交互式外壳中输入以下内容:
>>> import pyautogui
>>> active_win = pyautogui.getActiveWindow()
>>> active_win.isMaximized # Returns True if the window is maximized
False
>>> active_win.isMinimized # Returns True if the window is minimized
False
>>> active_win.isActive # Returns True if the window is the active window
True
>>> active_win.maximize() # Maximizes the window
>>> active_win.isMaximized
True
>>> active_win.restore() # Undoes a minimize/maximize action
>>> active_win.minimize() # Minimizes the window
>>> import time
>>> # Waits 5 seconds while you activate a different window:
>>> time.sleep(5); active_win.activate()
>>> active_win.close() # This will close the window you're typing in.
isMaximized、isMinimized 和 isActive 属性包含布尔值,指示窗口当前是否处于该状态。maximize()、minimize()、activate() 和 restore() 方法更改窗口的状态。使用 maximize() 或 minimize() 调整窗口大小后,restore() 方法将窗口恢复到其原始大小和位置。
close() 方法将关闭窗口。请注意此方法,因为它可能会绕过在退出应用程序之前要求你保存工作的消息对话框。
请参阅 PyAutoGUI 文档以获取其窗口控制功能的完整详细信息。
控制键盘
PyAutoGUI 还具有向计算机发送虚拟按键的功能,这使你能够填写表单或将文本输入到应用程序中。
发送按键字符串
pyautogui.write() 函数向计算机发送虚拟按键。这些按键的作用取决于哪个窗口是活动的以及哪个文本字段具有焦点。你可能需要首先向你想要的文本字段发送鼠标点击,以确保它具有焦点。
作为简单的示例,让我们用 Python 自动将单词 Hello, world! 打入文件编辑器窗口。首先,打开一个新的文件编辑器窗口,并将其定位在屏幕的左上角,以便 PyAutoGUI 能够在正确的地方点击以将其聚焦。接下来,在交互式外壳中输入以下内容:
>>> pyautogui.click(100, 200); pyautogui.write('Hello, world!')
注意将两个命令放在同一行,并用分号分隔,可以防止在运行两个指令之间提示你输入,这可以防止你在 click() 和 write() 调用之间意外地将新窗口聚焦,这会搞乱示例。
Python 首先发送一个虚拟鼠标点击到坐标 (100, 200),这应该会点击文件编辑器窗口并将其置于焦点。write() 调用会将文本 Hello, world! 发送到窗口,使其看起来像图 23-6 所示。现在您有了可以为您打字的代码!

图 23-6:使用 PyAutoGUI 点击文件编辑器窗口并输入 Hello, world!
默认情况下,write() 函数会立即输入整个字符串。但是,您可以传递一个可选的第二个参数,在每个字符之间添加短暂的暂停。这个第二个参数是表示暂停秒数的整数或浮点值。例如,pyautogui.write('Hello, world!', 0.25) 将在输入 H 后等待四分之一秒,输入 e 后再等待四分之一秒,依此类推。这种逐渐的打字机效果可能对处理按键速度不够快的应用程序很有用。
对于像 A 或 ! 这样的字符,PyAutoGUI 将自动模拟按下 SHIFT 键。
指定按键名称
并非所有按键都容易用单个文本字符表示。例如,如何用一个字符来表示 SHIFT 或左箭头键?在 PyAutoGUI 中,这些键盘按键由简短的字符串值表示:'esc' 表示 ESC 键或 'enter' 表示 ENTER 键。
除了单个字符串参数外,还可以将这些键盘按键字符串的列表传递给 write() 函数。例如,以下调用先按下 A 键,然后按下 B 键,接着两次按下左箭头键(将光标移动到“a”之前),最后按下 X 和 Y 键:
>>> pyautogui.write(['a', 'b', 'left', 'left', 'X', 'Y'])
因为按下左箭头键会移动键盘光标,这将输出 XYab。表 23-1 列出了 PyAutoGUI 可以传递给 write() 函数以模拟按下任何组合键的键盘按键字符串。
您还可以检查 pyautogui.KEYBOARD_KEYS 列表,以查看 PyAutoGUI 可以接受的所有可能的键盘按键字符串。'shift' 字符串表示左 SHIFT 键,等同于 'shiftleft'。同样适用于 'ctrl'、'alt' 和 'win' 字符串;它们都指代左侧的键。
表 23-1:键盘按键的字符串值
| 键盘按键字符串 | 含义 |
|---|---|
'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '!', '@', '#' 和等等 |
单个字符的按键 |
'enter' (或 'return' 或 '\n') |
ENTER 键 |
'esc' |
ESC 键 |
'shiftleft', 'shiftright' |
左右 SHIFT 键 |
'altleft', 'altright' |
左右 ALT 键 |
'ctrlleft', 'ctrlright' |
左右 CTRL 键 |
'tab' (或 '\t') |
TAB 键 |
'backspace', 'delete' |
BACKSPACE 和 DELETE 键 |
'pageup', 'pagedown' |
PAGE UP 和 PAGE DOWN 键 |
'home', 'end' |
HOME 和 END 键 |
'up', 'down', 'left', 'right' |
The up, down, left, and right arrow keys |
'f1', 'f2', 'f3', and so on |
The F1 to F12 keys |
'volumemute', 'volumedown', 'volumeup' |
The mute, volume down, and volume up keys (some keyboards do not have these keys, but your operating system will still be able to understand these simulated key presses) |
'pause' |
The PAUSE key |
'capslock', 'numlock', 'scrolllock' |
The CAPS LOCK, NUM LOCK, and SCROLL LOCK keys |
'insert' |
The INS or INSERT key |
'printscreen' |
The PRTSC or PRINT SCREEN key |
'winleft', 'winright' |
The left and right WIN keys (on Windows) |
'command' |
The COMMAND () key (on macOS) |
'option' |
The OPTION key (on macOS) |
按下和释放键盘
与 mouseDown() 和 mouseUp() 函数类似,pyautogui.keyDown() 和 pyautogui.keyUp() 会向计算机发送虚拟按键的按下和释放。它们接受一个键盘键字符串(见表 23-1)作为它们的参数。为了方便,PyAutoGUI 提供了 pyautogui.press() 函数,该函数调用这两个函数来模拟完整的按键。
运行以下代码,将输入美元符号($)字符(通过按住 SHIFT 键并按 4 得到):
>>> pyautogui.keyDown('shift'); pyautogui.press('4'); pyautogui.keyUp('shift')
这一行会按住 SHIFT,按下(并释放)4,然后释放 SHIFT。如果你需要将字符串输入到文本字段中,write() 函数更为合适。但对于接受单个键命令的应用程序,press() 函数是更简单的方法。
运行热键组合
热键或快捷键是一组按键组合,用于调用某些应用程序功能。常见的复制选择热键是 CTRL-C(在 Windows 和 Linux 上)或 -C(在 macOS 上)。用户按下并保持 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 热键组合会显示样式面板。而不是进行八个不同的功能调用(四个 keyDown() 调用和四个 keyUp() 调用),你只需调用 hotkey('ctrl', 'alt', 'shift', 's') 即可。
发送按键字符串
The pyautogui.write() function sends virtual key presses to the computer. What these key presses do depends on what window is active and what text field has focus. You may want to first send a mouse click to the text field you want in order to ensure that it has focus.
作为简单的示例,让我们用 Python 自动将单词 Hello, world! 输入到文件编辑窗口中。首先,打开一个新的文件编辑窗口,并将其定位在屏幕的左上角,以便 PyAutoGUI 可以点击正确的位置以将其置于焦点。接下来,在交互式 shell 中输入以下内容:
>>> pyautogui.click(100, 200); pyautogui.write('Hello, world!')
注意,将两个命令放在同一行,并用分号分隔,可以防止交互式 shell 在运行两个指令之间提示输入。这可以防止你在 click() 和 write() 调用之间意外地将新窗口置于焦点,这会搞乱示例。
Python 将首先向坐标(100,200)发送虚拟鼠标点击,这应该会点击文件编辑窗口并将其置于焦点。write() 调用将文本 Hello, world! 发送到窗口,使其看起来像图 23-6。现在你有了可以为你打字的代码!

图 23-6:使用 PyAutoGUI 点击文件编辑窗口并将“Hello, world!”输入其中
默认情况下,write() 函数会立即输入整个字符串。然而,你可以传递一个可选的第二个参数,在每个字符之间添加一个短暂的暂停。这个第二个参数是表示暂停秒数的整数或浮点值。例如,pyautogui.write('Hello, world!', 0.25) 将在输入 H 后等待四分之一秒,然后在 e 后再等待四分之一秒,依此类推。这种逐渐的打字机效果可能对速度较慢的应用程序有用,这些应用程序无法快速处理按键以跟上 PyAutoGUI。
对于像 A 或 ! 这样的字符,PyAutoGUI 将自动模拟按下 SHIFT 键。
指定键名
并非所有按键都容易用单个文本字符表示。例如,如何用一个字符表示 SHIFT 键或左箭头键?在 PyAutoGUI 中,这些键盘键由短字符串值表示:'esc' 表示 ESC 键或'enter' 表示 ENTER 键。
除了单个字符串参数外,还可以将这些键盘按键字符串的列表传递给 write()。例如,以下调用先按下 A 键,然后按下 B 键,然后两次按下左箭头键(将光标移动到“a”之前),最后按下 X 和 Y 键:
>>> pyautogui.write(['a', 'b', 'left', 'left', 'X', 'Y'])
因为按下左箭头键会移动键盘光标,这将输出 XYab。表 23-1 列出了可以传递给 write() 函数以模拟按下任何键组合的 PyAutoGUI 键盘按键字符串。
你还可以检查 pyautogui.KEYBOARD_KEYS 列表,以查看 PyAutoGUI 将接受的所有可能的键盘按键字符串。'shift' 字符串表示左 SHIFT 键,等同于 'shiftleft'。同样适用于 'ctrl'、'alt' 和 'win' 字符串;它们都指代左侧的键。
表 23-1:键盘按键的字符串值
| 键盘按键字符串 | 含义 |
|---|---|
'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '!', '@', '#' 等等 |
单个字符的键 |
'enter'(或 'return' 或 '\n') |
ENTER 键 |
'esc' |
ESC 键 |
'shiftleft', 'shiftright' |
左侧和右侧的 SHIFT 键 |
'altleft', 'altright' |
左侧和右侧的 ALT 键 |
'ctrlleft', 'ctrlright' |
左侧和右侧的 CTRL 键 |
'tab'(或 '\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()键(在 macOS 上) |
'option' |
OPTION 键(在 macOS 上) |
按下和释放键盘
与 mouseDown() 和 mouseUp() 函数类似,pyautogui.keyDown() 和 pyautogui.keyUp() 将向计算机发送虚拟按键的按下和释放。它们通过键盘键字符串(见表 23-1)作为它们的参数。为了方便,PyAutoGUI 提供了 pyautogui.press() 函数,它调用这两个函数来模拟完整的按键。
运行以下代码,将输入美元符号($)字符(通过按住 SHIFT 键并按下 4 获得):
>>> pyautogui.keyDown('shift'); pyautogui.press('4'); pyautogui.keyUp('shift')
这一行按住 SHIFT 键,按下(并释放)4,然后释放 SHIFT 键。如果你需要将字符串输入到文本字段中,write() 函数更为合适。但对于接受单个键命令的应用程序,press() 函数是一个更简单的方法。
运行热键组合
热键或快捷键是一组按键组合,用于调用某些应用程序功能。常见的复制选择的热键是 CTRL-C(在 Windows 和 Linux 上)或 -C(在 macOS 上)。用户按下并保持 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 快捷键组合会显示样式面板。你不必进行八个不同的函数调用(四个 keyDown() 调用和四个 keyUp() 调用),只需调用 hotkey('ctrl', 'alt', 'shift', 's') 即可。
设置 GUI 自动化脚本
GUI 自动化脚本是一种自动化无聊工作的好方法,但你的脚本也可能很挑剔。如果桌面上的窗口位置不正确或意外出现一些弹出窗口,你的脚本可能会在屏幕上点击错误的对象。以下是一些设置你的 GUI 自动化脚本的技巧:
-
每次运行脚本时使用相同的屏幕分辨率,这样窗口的位置就不会改变。
-
你的脚本点击的应用程序窗口应该最大化,这样每次运行脚本时其按钮和菜单都在相同的位置。
-
在等待内容加载时添加足够的暂停;你不想在应用程序准备好之前让脚本开始点击。
-
使用
locateOnScreen()来查找要点击的按钮和菜单,而不是依赖于坐标。如果你的脚本找不到它需要点击的对象,停止程序而不是让它盲目地继续点击。 -
使用
getWindowsWithTitle()确保你认为你的脚本正在点击的应用程序窗口存在,并使用activate()方法将该窗口置于前台。 -
使用第五章中的
logging模块来记录脚本的日志文件。这样,如果你在处理过程中不得不中途停止脚本,你可以将其设置为从上次停止的地方继续。 -
在你的脚本中添加尽可能多的检查。考虑一下,如果出现意外的弹出窗口或计算机失去互联网连接,它可能会如何失败。
-
你可能想在脚本刚开始运行时进行监督,以确保它运行正确。
你可能还希望在脚本开始时加入一个暂停,以便用户可以设置脚本将要点击的窗口。PyAutoGUI 有一个 sleep() 函数,其作用与 time.sleep() 相同(但让你不必在脚本中添加 import time)。还有一个 countdown() 函数,它会打印倒计时数字,给用户一个视觉提示,表明脚本很快将继续。在交互式外壳中输入以下内容:
>>> import pyautogui
>>> pyautogui.sleep(3) # Pauses the program for 3 seconds
>>> pyautogui.countdown(10) # Counts down over 10 seconds
10 9 8 7 6 5 4 3 2 1
>>> print('Starting in ', end=''); pyautogui.countdown(3)
Starting in 3 2 1
这些技巧可以帮助使你的 GUI 自动化脚本更容易使用,并且能够更好地从不可预见的情况中恢复。
显示消息框
您迄今为止编写的程序都倾向于使用纯文本输出(使用print()函数)和输入(使用input()函数)。然而,PyAutoGUI 程序将使用您的整个桌面作为其游乐场。您的程序运行的基于文本的窗口,无论是 Mu 还是终端窗口,都可能因为 PyAutoGUI 程序点击和与其他窗口交互而丢失。如果 Mu 或终端窗口被其他窗口隐藏,这可能会使从用户那里获取输入和输出变得困难。
为了解决这个问题,PyAutoGUI 包含 PyMsgBox 模块,用于向用户创建弹出通知并从他们那里接收输入。有四个消息框函数:
pyautogui.alert(text) 显示文本并有一个 OK 按钮
pyautogui.confirm(text) 显示文本并有一个 OK 和取消按钮,根据点击的按钮返回'OK'或'Cancel'
pyautogui.prompt(text) 显示文本,并为用户提供一个文本输入框,它将输入的内容作为字符串返回
pyautogui.password(text) 与 prompt() 相同,但会显示星号,以便用户可以输入敏感信息,如密码
这些函数与第十二章中“使用 PyMsgBox 的弹出消息框”中提到的四个函数相同。
概述
使用 PyAutoGUI 包进行 GUI 自动化允许您通过控制鼠标和键盘与计算机上的应用程序进行交互。虽然这种方法足够灵活,可以完成任何人类用户可以做的事情,但缺点是这些程序对它们点击或输入的内容相当盲目。在编写 GUI 自动化程序时,请确保它们在接收到错误指令时会快速崩溃。崩溃是令人烦恼的,但比程序继续错误运行要好得多。
您可以使用 PyAutoGUI 在屏幕上移动鼠标光标并模拟鼠标点击、按键和键盘快捷键。PyAutoGUI 包还可以检查屏幕上的颜色,这可以为您的 GUI 自动化程序提供足够的信息,以了解屏幕内容是否偏离了轨道。您甚至可以向 PyAutoGUI 提供一个截图,并让它确定您想要点击的区域坐标。
您可以将所有这些 PyAutoGUI 功能结合起来,自动化您计算机上的任何无脑重复性任务。事实上,观看鼠标光标自行移动并在屏幕上自动出现文本可能会非常吸引人。为什么不花些时间坐下来,看着您的程序为您做所有工作呢?看到您的聪明才智让您免于无聊的事情,会有一种满足感。
练习问题
1. 如何触发 PyAutoGUI 的故障安全机制以停止程序?
2. 哪个函数返回当前屏幕分辨率?
3. 哪个函数返回鼠标光标当前位置的坐标?
4. pyautogui.moveTo() 和 pyautogui.move() 之间的区别是什么?
-
哪些函数可以用来拖动鼠标?
-
哪个函数调用可以输出
"Hello, world!"的字符? -
如何进行特殊键(如键盘的左箭头键)的按键操作?
-
如何将当前屏幕内容保存到名为 screenshot.png 的图像文件中?
-
以下代码会在每个 PyAutoGUI 函数调用后设置两秒的暂停?
-
如果你想在网页浏览器内自动化点击和按键操作,应该使用 PyAutoGUI 还是 Selenium?
-
什么因素使得 PyAutoGUI 容易出错?
-
如何找到屏幕上每个标题包含 Notepad 的窗口的大小?
-
如何使 Firefox 浏览器在屏幕上的所有其他窗口之前变得活跃并置于最前?
练习程序
为了练习,编写程序来完成以下任务。
看起来很忙
许多即时通讯程序通过检测一段时间内鼠标移动的缺乏来判断你是否空闲,或者是否离开了电脑——比如说,10 分钟。也许你离开了电脑,但不想让别人看到你的即时消息状态进入空闲模式,给人一种你偷懒的印象。编写一个脚本,每 10 秒将鼠标光标向左移动一个像素,然后在 10 秒后向右移动一个像素。这种移动应该足够小且不频繁,以免在脚本运行时你需要使用电脑时造成干扰。
使用剪贴板读取文本字段
虽然你可以使用 pyautogui.write() 将按键发送到应用程序的文本字段,但你不能仅使用 PyAutoGUI 读取文本字段中已有的文本。这时,pyperclip 模块可以提供帮助。你可以使用 PyAutoGUI 获取文本编辑器(如 Mu 或记事本)的窗口,通过点击将其带到屏幕前,然后在文本字段内点击,接着发送 CTRL-A 或 -A 快捷键“全选”和 CTRL-C 或 -C 快捷键“复制到剪贴板”。然后,你的 Python 脚本可以通过运行 import pyperclip 和 pyperclip.paste() 来读取剪贴板中的文本。
编写一个程序,按照以下步骤从窗口的文本字段中复制文本。使用pyautogui.getWindowsWithTitle('Notepad')(或你选择的任何文本编辑器)来获取一个Window对象。这个Window对象的top和left属性可以告诉你这个窗口的位置,而activate()方法将确保它位于屏幕最前面。然后你可以通过添加,比如说,100或200像素到top和left属性值,使用pyautogui.click()点击文本编辑器的主文本字段,以将键盘焦点放在那里。调用pyautogui.hotkey('ctrl', 'a')和pyautogui.hotkey('ctrl', 'c')来选择所有文本并将其复制到剪贴板。最后,调用pyperclip.paste()从剪贴板检索文本并将其粘贴到你的 Python 程序中。从那里,你可以按需使用这个字符串,但现在只需将其传递给print()即可。
注意,截至 PyAutoGUI 版本 1.0.0,PyAutoGUI 的窗口功能仅在 Windows 上工作,不在 macOS 或 Linux 上工作。
编写游戏机器人
有一款老式的 Flash 游戏叫做“寿司转盘”。这款游戏涉及点击正确的配料按钮来填写顾客的寿司订单。你越快无误地填写订单,就能得到更多的分数。这是一个非常适合 GUI 自动化程序的任务——以及一种获得高分的方法!尽管 Flash 作为一个产品已经停止开发,但有一些在电脑上离线玩游戏的说明,以及一个列出托管“寿司转盘”游戏的网站列表,网址为github.com/asweigart/sushigoroundbot。那个 GitHub 仓库还包含了游戏机器人的 Python 源代码。机器人玩游戏的视频在youtu.be/lfk_T6VKhTE。
看起来很忙
许多即时通讯程序通过检测一段时间内鼠标移动的缺乏来判断你是否空闲,或者是否离开了电脑,比如说,10 分钟。也许你离开了电脑,但不想让别人看到你的即时消息状态进入空闲模式,给人一种你偷懒的印象。编写一个脚本,每 10 秒将鼠标光标向左移动一个像素,然后在 10 秒后再向右移动一个像素。这种推动应该足够小且不频繁,以免在脚本运行时你确实需要使用电脑时造成干扰。
使用剪贴板读取文本字段
虽然您可以使用 pyautogui.write() 向应用程序的文本字段发送按键,但您不能仅使用 PyAutoGUI 读取文本字段中已经存在的文本。这就是 pyperclip 模块可以提供帮助的地方。您可以使用 PyAutoGUI 获取文本编辑器(如 Mu 或 Notepad)的窗口,通过点击将其带到屏幕最前方,然后在文本字段内点击,然后发送 CTRL-A 或 -A 快捷键“全选”和 CTRL-C 或 -C 快捷键“复制到剪贴板”。然后,您的 Python 脚本可以通过运行 import pyperclip 和 pyperclip.paste() 来读取剪贴板文本。
编写一个程序,按照以下步骤从窗口的文本字段中复制文本。使用 pyautogui.getWindowsWithTitle('Notepad')(或您选择的任何文本编辑器)来获取一个 Window 对象。这个 Window 对象的 top 和 left 属性可以告诉您窗口的位置,而 activate() 方法将确保它位于屏幕最前方。然后,您可以通过添加 100 或 200 像素到 top 和 left 属性值,使用 pyautogui.click() 来点击文本编辑器的主文本字段,以将键盘焦点放在那里。调用 pyautogui.hotkey('ctrl', 'a') 和 pyautogui.hotkey('ctrl', 'c') 来选择所有文本并将其复制到剪贴板。最后,调用 pyperclip.paste() 从剪贴板检索文本并将其粘贴到您的 Python 程序中。从那里,您可以根据需要使用这个字符串,但现在只需将其传递给 print() 即可。
注意,截至 PyAutoGUI 版本 1.0.0,PyAutoGUI 的窗口功能仅在 Windows 上工作,不在 macOS 或 Linux 上工作。
编写一个游戏机器人
有一款名为 Sushi Go Round 的旧 Flash 游戏。这款游戏涉及点击正确的配料按钮来填写顾客的寿司订单。您越快无误地完成订单,得分就越高。这是一个非常适合 GUI 自动化程序的任务——以及一种获得高分的方法!尽管 Flash 作为产品已经停止开发,但有一些说明可以在您的计算机上离线玩它,以及一个列出托管 Sushi Go Round 游戏的网站列表,网址为 github.com/asweigart/sushigoroundbot。该 GitHub 仓库还包含一个游戏机器人玩游戏的 Python 源代码。机器人玩游戏的视频在 youtu.be/lfk_T6VKhTE。
24 文本到语音和语音识别引擎

本章介绍了文本到语音软件包 pyttsx3 和语音识别软件包 Whisper。文本到语音软件包可以将文本字符串转换为语音,然后将它们发送到您的计算机的扬声器或保存到音频文件中。通过为您的程序添加这个新维度,您可以释放用户从屏幕上阅读文本的需求。例如,烹饪食谱应用可以在您穿过厨房时大声读出配料清单,而您的日常脚本可以抓取新闻文章(或您的电子邮件),然后在您的早晨通勤时准备一个 MP3 文件播放。
在另一端,语音识别技术可以将口语音频文件转换为文本字符串值。您可以使用此功能向您的程序添加语音命令或自动化播客的转录。而且,与人类不同,您总是可以关闭说话太多的电脑的音量。
Both pyttsx3 and Whisper are free to use and don’t require an internet connection. The text-to-speech and speech recognition engines featured in this chapter aren’t limited to English, and work with most widely spoken human languages.
文本到语音引擎
要生成语音音频,pyttsx3 第三方软件包使用您操作系统的内置文本到语音引擎:Windows 上的 Microsoft Speech API (SAPI5),macOS 上的 NSSpeechSynthesizer,以及 Linux 上的 eSpeak。在 Linux 上,您可能需要通过在终端窗口中运行sudo apt install espeak来安装引擎。您可以通过在终端中运行pip install pyttsx3来安装 pyttsx3。附录 A 包含了安装第三方软件包的完整说明。
该软件包的名称基于py代表 Python,tts代表文本到语音,x因为它是从原始pytts软件包扩展而来的,以及3因为它是为 Python 3 设计的。
生成语音
在计算机科学中,生成计算机语音是一个复杂的话题。幸运的是,操作系统的文本到语音引擎为我们做了艰苦的工作,与这个引擎交互是直接的。打开一个新的文件编辑器,输入以下代码,并将其保存为hello_tts.py:
import pyttsx3
engine = pyttsx3.init()
engine.say('Hello. How are you doing?')
engine.runAndWait() # The computer speaks.
feeling = input('>')
engine.say('Yes. I am feeling ' + feeling + ' as well.')
engine.runAndWait() # The computer speaks again.
在导入pyttsx3模块后,我们调用pyttsx3.init()函数来初始化语音引擎。此函数返回一个Engine对象。我们可以将一段文本传递给其say()方法来告诉引擎要说什么,但实际的说话不会开始,直到我们调用runAndWait()方法。此方法会阻塞(即不会返回),直到计算机完成整个字符串的说话。
程序不会产生任何文本输出,因为它从未调用print()函数。相反,你应该听到你的电脑说,“你好。你怎么样?”(确保音量没有静音。)用户可以从键盘输入一个回应,电脑应该用语音回复,“是的。我也感觉<你的回应>。”
pyttsx3模块允许您对电脑声音进行一些更改。您可以将字符串'rate'、'volume'和'voices'传递给Engine对象的getProperty()方法,以查看其当前设置。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.getProperty('volume')
1.0
>>> engine.getProperty('rate')
200
>>> engine.getProperty('voices')
[<pyttsx3.voice.Voice object at 0x0000029DA7FB4B10>,
<pyttsx3.voice.Voice object at 0x0000029DAA3DAAD0>]
注意,输出可能因您的计算机而异。音量设置是一个浮点数,其中1.0表示 100%。电脑的声音说话速度为每分钟 200 个单词。使用以下代码继续此示例:
>>> for voice in engine.getProperty('voices'): # List all the available voices.
... print(voice.name, voice.gender, voice.age, voice.languages)
...
Microsoft David Desktop - English (United States) None None []
Microsoft Zira Desktop - English (United States) None None []
在我的带有英语(美国)语言的 Windows 笔记本电脑上,getProperty('voices')返回两个Voice对象。(注意,这个字符串是复数'voices'而不是单数'voice'。)这些Voice对象有name、gender和age属性,尽管当操作系统不存储该信息时,gender和age被设置为None。languages属性是支持的语言的字符串列表,如果该信息未知,则为空列表。
让我们通过调用setProperty()方法来继续交互式外壳示例,以更改这些设置:
>>> engine.setProperty('rate', 300)
>>> engine.setProperty('volume', 0.5)
>>> voices = engine.getProperty('voices')
>>> engine.setProperty('voice', voices[1].id)
>>> engine.say('The quick brown fox jumps over the yellow lazy dog.')
>>> engine.runAndWait()
在这个例子中,我们将说话速度更改为每分钟 300 个单词,并将音量设置为 50%,通过传递0.5作为'volume'参数。然后,我们通过传递Voice对象在getProperty('voices')返回的列表中的索引1的id属性,将声音改为 Windows 提供的女性“Zira”声音。注意,为了设置声音,我们使用单数字符串'voice'而不是复数字符串'voices'。
将语音保存到 WAV 文件
pyttsx3模块的save_to_file()方法可以将生成的语音保存到 WAV 文件(带有.wav文件扩展名)。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.save_to_file('Hello. How are you doing?', 'hello.wav')
>>> engine.runAndWait() # The computer creates hello.wav.
save_to_file()的第一个参数是要生成的语音的字符串,而第二个字符串参数是.wav文件的文件名。文本字符串可以是简短的句子,如交互式外壳示例中所示,也可以是页面的文本。在我的电脑上,pyttsx3能够在大约两秒钟内将 1800 个单词的字符串转换为 10 分钟的音频文件。重要的是要注意,仅调用save_to_file()是不够的。您还必须在 Python 创建.wav文件之前调用runAndWait()方法。
pyttsx3模块只能保存.wav文件,不能保存.mp3文件或其他音频格式。
语音识别
Whisper 是一个可以识别多种语言的语音识别系统。给定一个音频或视频文件,Whisper 可以将语音作为 Python 字符串返回。它还返回单词组的开始和结束时间,你可以使用这些时间生成字幕文件。
通过终端运行 pip install openai-whisper 来安装 Whisper。 (注意,语音识别包的名称是openai-whisper;PyPI 网站上的whisper包指的是其他东西。) 这是一个大文件下载,可能需要几分钟才能安装。此外,第一次调用load_model()函数时,您的计算机将下载语音识别模型,其大小可能为数百兆字节或更多。
假设你当前工作目录中有一个名为hello.wav的音频文件。(Whisper 也可以处理.mp3和其他几种音频格式。)你可以在交互式外壳中输入以下内容:
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
>>> print(result['text'])
Hello. How are you doing?
在导入whisper模块后,你必须通过调用whisper.load_model()函数并传递你想要使用的训练机器学习模型的字符串('tiny'、'base'、'small'、'medium'或'large-v3')来加载要使用的语音识别模型。(新模型也将继续发布。)这些模型中较小的一个可以更快地转录音频,但较大的模型在音频有背景噪声的情况下也能更准确地转录。
第一次加载模型时,您的计算机必须连接到互联网,以便whisper模块可以从 OpenAI 的服务器下载它。表 24-1 列出了可以作为字符串传递给whisper.load_model()函数的模型名称,以及它们的文件大小、内存使用情况和在我笔记本电脑上进行的某些运行时测试的结果。
表 24-1:Whisper 语音识别模型属性
| 模型名称 | 模型文件大小 | 估计所需内存 | 10 词,3 秒音频样本的运行时间 | 1,800 词,15 分钟音频样本的运行时间 |
|---|---|---|---|---|
'tiny' |
74MB | 1GB | 1.9s | 1m 20s |
'base' |
142MB | 1GB | 3.0s | 2m 34s |
'small' |
472MB | 2GB | 9.6s | 6m 37s |
'medium' |
1.5GB | 5GB | 28.6s | 20m 17s |
'large-v3' |
3GB | 10GB | 51.9s | 32m 58s |
我个人的观点是,对于 99%的情况,'base'模型应该足够用,当需要更高的精度时,'medium'模型也足够好。所有模型都会产生错误,所以输出应该总是经过人工审查。如果你在文本中发现了许多转录错误,可以尝试使用更大的模型,或者如果 Whisper 转录音频太慢,可以尝试使用更小的模型。然而,正如你在表 24-1 中可以看到的,'base'模型转录 15 分钟音频所需的两分多钟和'large-v3'模型转录所需近 33 分钟之间有显著差异。
使用whisper.load_model()返回的model.Whisper对象,您可以调用transcribe()方法来执行实际的转录。将音频文件的名称字符串传递给该方法。这个方法可能需要几秒到几小时的时间来运行,具体取决于模型和音频文件长度。Whisper 可以接受任何音频或视频文件,并自动将其转换为所需的格式。
Whisper 可以自动检测音频的语言,但您可以通过传递一个语言关键字参数到transcribe来指定语言,例如model.transcribe('hello.wav', language='English')。要查找 Whisper 支持的语言,您可以从终端运行whisper --help。Whisper 在猜测应该插入标点符号和首字母大写专有名词方面相当不错(但从不完美)。然而,您应该始终检查输出以清理任何错误。
model.transcribe()返回的字典包含多个键值对,但'text'键包含转录的字符串。
默认情况下,Whisper 使用您的 CPU 来转录文本,但如果您的计算机有 3D 图形卡,您可以通过将其设置为使用图形处理单元(GPU)来大大加快转录速度。您可以在github.com/openai/whisper的在线文档中找到这些设置说明。如果您的计算机有 NVIDIA 图形卡,您可以按照附录 A 中的说明安装用于更快语音识别的包。要使用 GPU,将本章中的whisper.load_model('base')代码替换为whisper.load_model('base', device='cuda')。
您可以在 Whisper 的在线文档中找到更多关于 Whisper 的选项。
创建字幕文件
除了转录的音频外,Whisper 的结果字典还包含时序信息,用于标识文本在音频文件中的位置。您可以使用这些文本和时序数据生成其他软件可以处理的字幕文件。最常见的两种字幕文件格式是 SRT SubRip 字幕(带有.srt扩展名)和 VTT Web 视频文本轨道(带有.vtt文件扩展名)。SRT 是一个较老且更广泛使用的标准,而现代视频网站通常使用 VTT。这些格式相似。例如,以下是 SRT 文件的第一部分:
1
00:00:00,000 --> 00:00:05,640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
2
00:00:05,640 --> 00:00:14,960
appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
将其与相同字幕的 VTT 文件的第一部分进行比较:
WEBVTT
00:00.000 --> 00:05.640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
00:05.640 --> 00:14.960
appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
这两个文件都表明,单词“Dinosaurs are a diverse group ...”出现在转录音频文件的开始(0 秒)和 5.640 秒标记之间。
Whisper 还可以将其结果输出为 TSV 数据(带有.tsv扩展名)或 JSON 数据(带有.json扩展名)。TSV 不是官方的字幕格式,但如果您需要将文本和时序数据导出到,例如,另一个可以使用第十八章中介绍的csv模块读取的 Python 程序,那么它可能很有用。TSV 格式的字幕看起来如下所示:
start end text
0 5640 Dinosaurs are a diverse group of reptiles of the clade dinosauria. They
5640 14960 appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
在调用 model.transcribe() 之后,添加两行额外的代码来创建这些字幕文件:
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
>>> write_function = whisper.utils.get_writer('srt', '.') # ❶
>>> write_function(result, 'audio') # ❷
whisper.utils.get_writer() 函数 ❶ 接受字幕文件格式作为字符串('srt', 'vtt', 'txt', 'tsv', 或 'json')以及保存文件的文件夹(使用 '.' 字符串表示当前工作目录)。get_writer() 函数返回一个函数,用于传递转录结果。(这是一种相当奇特创建转录文件的方式,但这是 whisper 模块的设计方式。)我们将其存储在一个名为 write_function 的变量中,然后我们可以将其视为一个函数并调用它,传递 result 字典和字幕文件的文件名 ❷。这两行代码会在当前工作目录中生成一个名为 audio.srt 的 SRT 格式文件,使用 result 字典中的文本和时序信息。
从网站下载视频
当使用 Whisper 的语音识别下载音频文件进行转录时通常很直接,但像 YouTube 这样的视频网站往往不便于下载其内容。yt-dlp 模块允许 Python 脚本从 YouTube 和数百个其他视频网站下载视频,这样你就可以离线观看。附录 A 包含安装 yt-dlp 的说明。一旦安装完成,以下代码将下载给定 URL 的视频:
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> with yt_dlp.YoutubeDL() as ydl:
... ydl.download([video_url])
...
注意,ydl.download() 函数期望一个视频 URL 列表,这就是为什么我们在将其传递给函数调用之前将 video_url 字符串放在一个列表中。视频的文件名基于视频网站上的标题,可能具有 .mp4、.mkv 或其他视频格式文件扩展名。你会在视频下载时看到大量的调试输出。
视频网站可能会因为年龄、登录要求、地理限制或反网络爬虫措施而拒绝下载。如果你遇到错误,你应该尝试的第一步是安装最新版本的 yt-dlp,它将更新以保持与视频网站布局变化的兼容性。
你可以在pypi.org/project/yt-dlp/在线文档中了解 yt-dlp 的许多配置选项。例如,你可以通过传递一个配置设置字典给 yt_dlp.YoutubeDL() 函数来从 YouTube 视频中提取音频:
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... 'quiet': True, # Suppress the output. # ❶
... 'no_warnings': True, # Suppress warnings.
... 'quiet': True, # Suppress the output. # ❶
... 'format': 'm4a/bestaudio/best',
... 'postprocessors': [{ # Extract audio using ffmpeg.
... 'key': 'FFmpegExtractAudio',
... 'preferredcodec': 'm4a',
... }]
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... ydl.download([video_url])
...
'quiet': True 和 'no_warnings': True 这两个键值对❶可以防止冗长的调试输出。传递给 yt_dlp.Youtube() 的 options 字典告诉它下载视频,然后将音频提取到名为 downloaded_content.m4a 的文件中❷。 (如果视频有不同的音频格式,文件扩展名可能不同,尽管 .m4a 格式是最流行的。) 如果我们没有在 options 字典中设置 'outtmpl': 'downloaded_content.%(ext)s' 键值对,下载的文件名将基于视频标题(排除文件名中不允许的字符,如问号和冒号)。
要获取确切的文件名,我们可以使用第十章中讨论的 glob 模式。我们知道文件的主要部分是 'downloaded_content',但文件扩展名可以是任何音频格式。以下代码使用 Path 对象来查找确切的下载文件名:
>>> from pathlib import Path
>>> matching_filenames = list(Path().glob('downloaded_content.*'))
>>> downloaded_filename = str(matching_filenames[0])
>>> downloaded_filename
'downloaded_content.m4a'
设置文件名使得代码以后更容易使用此文件,例如通过运行它通过 Whisper 语音识别。'base' 和 'medium' 模型创建的子标题质量比 YouTube 当前自动生成的子标题高得多。
如果你只想下载特定视频的信息,你可以告诉 yt-dlp 跳过文件,并使用以下代码仅下载其元数据:
>>> import yt_dlp, json
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... 'quiet': True, # Suppress the output.
... 'no_warnings': True, # Suppress warnings.
... 'quiet': True, # Suppress the output. # ❶
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... 'quiet': True, # Suppress the output. # ❶
... 'quiet': True, # Suppress the output. # ❶
... print('TITLE:', json_info['title']) # Print the video title.
... print('KEYS:', json_info.keys())
... with open('metadata.json', 'w', encoding='utf-8') as json_file:
... 'quiet': True, # Suppress the output. # ❶
...
TITLE: Beyond the Basic Stuff with Python - Al Sweigart - Part 1
KEYS: dict_keys(['id', 'title', 'formats', 'thumbnails', 'thumbnail',
'description', 'channel_id', 'channel_url', 'duration', 'view_count',
'average_rating', 'age_limit', 'webpage_url',
--`snip`--
如果我们只想获取视频的元数据而不是视频本身,我们可以在传递给 yt_dlp.YoutubeDL() 的 options 字典中包含 'skip_download': True 键值对❶。ydl.extract_info() 方法调用返回有关视频的信息字典❷。其中一些数据可能没有正确格式化为 JSON(第十八章讨论),但我们可以通过调用 ydl.sanitize()❸来获取与 JSON 兼容的形式。sanitize() 方法返回的字典有几个键,包括 'title' 用于视频名称,'duration' 用于视频的秒数长度,等等。我们这里的代码还将这些 JSON 数据写入名为 metadata.json 的文件中❹。
摘要
Python 的巨大优势之一是其庞大的第三方包生态系统,这些包用于文本到语音和语音识别等任务。这些包将计算机科学中最难的问题之一简化,只需几行代码即可将其提供给程序。
The pyttsx3 package does text-to-speech using your computer’s speech engine to create audio that you can either play from the speakers or save to a .wav file. The Whisper speech recognition system uses several underlying models to transcribe the words of an audio file. These models have different sizes; the smaller models transcribe faster with less accuracy, while larger models are slower but more accurate. They work for many human languages, not just English. Whisper runs on your computer and doesn’t connect to online servers except to download the model on first use.
这些 Python 包使用的语音引擎在 2020 年代之前的质量有了大幅提升。Python 是一种优秀的“粘合”语言,允许你的脚本与该软件连接,这样你只需几行代码就能将语音功能添加到自己的程序中。如果你想了解更多关于文本到语音和语音识别的信息,你可以在李马克的《Make Python Talk》(No Starch Press,2021 年)中找到许多有趣的示例项目。
练习问题
1. 如何让 pyttsx3 的语音说得更快?
2. pyttsx3 保存到什么音频格式?
3. pyttsx3 和 Whisper 是否依赖于在线服务?
4. pyttsx3 和 Whisper 支持除英语以外的其他语言吗?
5. Whisper 的默认语音识别机器学习模型叫什么名字?
6. 常见的字幕文本文件格式有哪些?
7. yt-dlp 能否从除 YouTube 以外的网站下载视频?
练习程序
为了练习,编写程序来完成以下任务。
为猜数字游戏添加语音
回顾第三章中的猜数字游戏,并为其添加语音功能。将所有对 print() 的调用替换为对名为 speak() 的函数的调用。接下来,定义 speak() 函数,使其接受一个字符串参数(就像 print() 一样),但既要在屏幕上打印字符串,也要大声说出。例如,你会替换这一行代码
print('I am thinking of a number between 1 and 20.')
使用以下代码行:
speak('I am thinking of a number between 1 and 20.')
为了充分利用语音生成功能,让我们将 'Your guess is too low.' 和 'Your guess is too high.' 文本改为说出玩家的猜测。例如,计算机应该说出,“你的猜测,42,太低了。”你还可以将语音功能添加到本书中的其他项目中,例如剪刀石头布游戏。
唱“99 瓶啤酒”
累积歌曲 是那些歌词重复并带有添加或轻微变化的歌曲。例如,“99 瓶啤酒”和“圣诞节的 12 天”就是累积歌曲的例子。编写一个程序来演唱(或至少说出)“99 瓶啤酒”的歌词:
99 bottles of beer on the wall,
99 bottles of beer,
Take one down, pass it around,
98 bottles of beer on the wall.
这些歌词会重复,每次少一瓶。歌曲会一直唱到零瓶,此时最后一行是“墙上没有更多的啤酒瓶。”(你可能希望程序从 2 或 3 开始而不是 99,这样测试起来更容易。)
YouTube 转录器
编写一个程序,将 yt-dlp 和 Whisper 的功能结合起来,自动下载 YouTube 视频并生成 .srt 格式的字幕文件。输入可以是下载和转录的 URL 列表。你还可以添加选项以生成不同的字幕格式。Python 是一种将不同模块功能结合起来的优秀“粘合语言”。
文本到语音引擎
为了产生语音,pyttsx3 第三方包使用你的操作系统的内置文本到语音引擎:Windows 上的 Microsoft Speech API (SAPI5),macOS 上的 NSSpeechSynthesizer,以及 Linux 上的 eSpeak。在 Linux 上,你可能需要从终端窗口运行 sudo apt install espeak 来安装引擎。你可以从终端运行 pip install pyttsx3 来安装 pyttsx3。附录 A 包含安装第三方包的完整说明。
包名基于 py 代表 Python,tts 代表文本到语音,x 因为它是从原始 pytts 包扩展而来的,而 3 则是因为它是为 Python 3 设计的。
生成语音
生成计算机语音是计算机科学中的一个复杂主题。幸运的是,操作系统的文本到语音引擎为我们做了艰苦的工作,与这个引擎交互是直接的。打开一个新的文件编辑器,输入以下代码,并将其保存为 hello_tts.py:
import pyttsx3
engine = pyttsx3.init()
engine.say('Hello. How are you doing?')
engine.runAndWait() # The computer speaks.
feeling = input('>')
engine.say('Yes. I am feeling ' + feeling + ' as well.')
engine.runAndWait() # The computer speaks again.
在导入 pyttsx3 模块后,我们调用 pyttsx3.init() 函数来初始化语音引擎。此函数返回一个 Engine 对象。我们可以将一段文本传递给其 say() 方法来告诉引擎要说什么,但实际说话不会开始,直到我们调用 runAndWait() 方法。此方法会阻塞(即,不会返回)直到计算机完成整个字符串的说话。
程序不会产生任何文本输出,因为它从未调用 print() 函数。相反,你应该听到你的计算机说,“你好。你怎么样?”(确保音量没有静音。)用户可以从键盘输入一个响应,计算机应该口头回答,“是的。我也感觉<你的回答>。”
pyttsx3 模块允许你对计算机语音进行一些修改。你可以将字符串 'rate'、'volume' 和 'voices' 传递给 Engine 对象的 getProperty() 方法来查看其当前设置。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.getProperty('volume')
1.0
>>> engine.getProperty('rate')
200
>>> engine.getProperty('voices')
[<pyttsx3.voice.Voice object at 0x0000029DA7FB4B10>,
<pyttsx3.voice.Voice object at 0x0000029DAA3DAAD0>]
注意,输出可能因计算机而异。音量设置是一个浮点数,其中 1.0 表示 100%。计算机语音的语速为每分钟 200 个单词。以下代码继续此示例:
>>> for voice in engine.getProperty('voices'): # List all the available voices.
... print(voice.name, voice.gender, voice.age, voice.languages)
...
Microsoft David Desktop - English (United States) None None []
Microsoft Zira Desktop - English (United States) None None []
在我的 Windows 笔记本电脑上,使用 英语(美国) 语言,getProperty('voices') 返回两个 Voice 对象。(注意,这个字符串是复数 'voices' 而不是单数 'voice'。)这些 Voice 对象有 name、gender 和 age 属性,尽管当操作系统不存储这些信息时,gender 和 age 被设置为 None。languages 属性是支持的语言的字符串列表,如果该信息未知,则为空列表。
让我们通过调用 setProperty() 方法来更改这些设置继续交互式外壳示例:
>>> engine.setProperty('rate', 300)
>>> engine.setProperty('volume', 0.5)
>>> voices = engine.getProperty('voices')
>>> engine.setProperty('voice', voices[1].id)
>>> engine.say('The quick brown fox jumps over the yellow lazy dog.')
>>> engine.runAndWait()
在这个例子中,我们将说话速度更改为每分钟 300 个单词,并将音量设置为 50%,通过传递 'volume' 速率的 0.5。然后,我们通过传递 getProperty('voices') 返回的列表中索引为 1 的 Voice 对象的 id 属性,将语音更改为 Windows 提供的女性“Zira”语音。请注意,为了设置语音,我们使用单数 'voice' 字符串,而不是复数 'voices' 字符串。
将语音保存到 WAV 文件
pyttsx3 模块的 save_to_file() 方法可以将生成的语音保存到 WAV 文件(带有 .wav 扩展名)。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.save_to_file('Hello. How are you doing?', 'hello.wav')
>>> engine.runAndWait() # The computer creates hello.wav.
save_to_file() 的第一个参数是要生成的语音的字符串,而第二个字符串参数是 .wav 文件的文件名。文本字符串可以是简短的句子,如交互式外壳示例中所示,也可以是几页的文本。在我的计算机上,pyttsx3 能够在约两秒钟内将 1,800 个单词的字符串转换为 10 分钟长的音频文件。重要的是要注意,仅调用 save_to_file() 是不够的。您还必须在 Python 创建 .wav 文件之前调用 runAndWait() 方法。
pyttsx3 模块只能保存 .wav 文件,不能保存 .mp3 文件或其他音频格式。
生成语音
在计算机科学中,生成计算机语音是一个复杂的话题。幸运的是,操作系统的文本到语音引擎为我们完成了这项艰苦的工作,与这个引擎交互是直接的。打开一个新的文件编辑器,输入以下代码,并将其保存为 hello_tts.py:
import pyttsx3
engine = pyttsx3.init()
engine.say('Hello. How are you doing?')
engine.runAndWait() # The computer speaks.
feeling = input('>')
engine.say('Yes. I am feeling ' + feeling + ' as well.')
engine.runAndWait() # The computer speaks again.
在导入 pyttsx3 模块后,我们调用 pyttsx3.init() 函数来初始化语音引擎。此函数返回一个 Engine 对象。我们可以将一段文本传递给其 say() 方法来告诉引擎要说什么,但实际的说话不会开始,直到我们调用 runAndWait() 方法。此方法会阻塞(即不会返回),直到计算机完成整个字符串的说话。
程序不会产生任何文本输出,因为它从未调用 print() 函数。相反,你应该听到你的计算机说,“你好。你怎么样?”(确保音量没有静音。)用户可以从键盘输入一个响应,计算机应该口头回答,“是的。我也感觉 <你的响应>。”
pyttsx3 模块允许你对计算机语音进行一些修改。你可以将字符串 'rate'、'volume' 和 'voices' 传递给 Engine 对象的 getProperty() 方法来查看其当前设置。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.getProperty('volume')
1.0
>>> engine.getProperty('rate')
200
>>> engine.getProperty('voices')
[<pyttsx3.voice.Voice object at 0x0000029DA7FB4B10>,
<pyttsx3.voice.Voice object at 0x0000029DAA3DAAD0>]
注意,输出可能因您的计算机而异。音量设置是一个浮点数,其中 1.0 表示 100%。计算机语音的语速为每分钟 200 个单词。继续使用以下代码的示例:
>>> for voice in engine.getProperty('voices'): # List all the available voices.
... print(voice.name, voice.gender, voice.age, voice.languages)
...
Microsoft David Desktop - English (United States) None None []
Microsoft Zira Desktop - English (United States) None None []
在我的带有English (United States)语言的 Windows 笔记本电脑上,getProperty('voices')返回两个Voice对象。(请注意,这个字符串是复数'voices'而不是单数'voice'。)这些Voice对象具有name、gender和age属性,尽管当操作系统不存储该信息时,gender和age被设置为None。languages属性是支持的语言的字符串列表,如果该信息未知,则为空列表。
让我们通过调用setProperty()方法来改变这些设置,继续交互式外壳示例:
>>> engine.setProperty('rate', 300)
>>> engine.setProperty('volume', 0.5)
>>> voices = engine.getProperty('voices')
>>> engine.setProperty('voice', voices[1].id)
>>> engine.say('The quick brown fox jumps over the yellow lazy dog.')
>>> engine.runAndWait()
在这个示例中,我们将说话速度更改为每分钟 300 个单词,并通过传递0.5作为'volume'速率将音量设置为 50%。然后,我们通过传递列表getProperty('voices')返回的索引为1的Voice对象的id属性,将声音更改为 Windows 提供的女性“Zira”声音。请注意,为了设置声音,我们使用单数字符串'voice'而不是复数字符串'voices'。
将语音音频保存到 WAV 文件
pyttsx3模块的save_to_file()方法可以将生成的语音保存到 WAV 文件(带有.wav文件扩展名)。在交互式外壳中输入以下内容:
>>> import pyttsx3
>>> engine = pyttsx3.init()
>>> engine.save_to_file('Hello. How are you doing?', 'hello.wav')
>>> engine.runAndWait() # The computer creates hello.wav.
save_to_file()的第一个参数是要生成的语音的字符串,而第二个字符串参数是.wav文件的文件名。文本字符串可以是简短的句子,如交互式外壳示例中所示,也可以是页面的文本。在我的电脑上,pyttsx3能在大约两秒钟内将 1,800 个单词的字符串转换为 10 分钟的音频文件。重要的是要注意,仅调用save_to_file()是不够的。您还必须在 Python 创建.wav文件之前调用runAndWait()方法。
pyttsx3模块只能保存.wav文件,不能保存.mp3文件或其他任何音频格式。
语音识别
Whisper 是一个能够识别多种语言的语音识别系统。给定一个音频或视频文件,Whisper 可以将语音作为 Python 字符串中的文本返回。它还返回单词组的开始和结束时间,您可以使用这些时间生成字幕文件。
通过在终端中运行 pip install openai-whisper 来安装 Whisper。请注意,语音识别软件包的名称是openai-whisper;PyPI 网站上的whisper软件包指的是其他东西。这是一个大文件下载,可能需要几分钟才能安装。此外,您第一次调用load_model()函数时,您的电脑将下载语音识别模型,该模型可能大小为数百兆字节或更多。
假设您在当前工作目录中有一个名为hello.wav的音频文件。(Whisper 还可以处理.mp3和几种其他音频格式。)您可以在交互式外壳中输入以下内容:
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
>>> print(result['text'])
Hello. How are you doing?
在导入whisper模块后,你必须通过调用whisper.load_model()函数并传递你想要使用的训练机器学习模型的字符串('tiny'、'base'、'small'、'medium'或'large-v3')来加载要使用的语音识别模型。 (新模型将继续发布。)这些模型中较小的可以更快地转录音频,但较大的模型在音频背景有环境噪音时也能更准确地转录。
第一次加载模型时,你的电脑必须连接到互联网,以便whisper模块可以从 OpenAI 的服务器下载它。表 24-1 列出了可以作为字符串传递给whisper.load_model()函数的模型名称,以及它们的大小、内存使用情况以及在我笔记本电脑上进行的某些运行时测试结果。
表 24-1:Whisper 语音识别模型的属性
| 模型名称 | 模型文件大小 | 估计所需内存 | 10 词,3 秒音频样本的运行时间 | 1,800 词,15 分钟音频样本的运行时间 |
|---|---|---|---|---|
'tiny' |
74MB | 1GB | 1.9s | 1m 20s |
'base' |
142MB | 1GB | 3.0s | 2m 34s |
'small' |
472MB | 2GB | 9.6s | 6m 37s |
'medium' |
1.5GB | 5GB | 28.6s | 20m 17s |
'large-v3' |
3GB | 10GB | 51.9s | 32m 58s |
我个人的观点是,对于 99%的情况,'base'模型应该足够适用,当需要更高的精度时,'medium'模型也足够好。所有模型都会产生错误,因此输出应该始终经过人工审查。如果你在文本中发现许多转录错误,可以尝试使用更大的模型,或者如果 Whisper 转录音频花费时间过长,可以尝试使用更小的模型。然而,正如你在表 24-1 中可以看到的,'base'模型转录 15 分钟音频所需的两分零 34 秒与'large-v3'模型转录所需近 33 分钟之间存在显著差异。
使用whisper.load_model()返回的model.Whisper对象,你可以调用transcribe()方法来执行实际的转录。将音频文件名传递给该方法。这个方法将根据模型和音频文件长度从几秒到几小时不等。Whisper 可以接受任何音频或视频文件,并自动将其转换为所需的格式。
Whisper 可以自动检测音频的语言,但你可以通过传递一个语言关键字参数到transcribe来指定语言,例如model.transcribe('hello.wav', language='English')。要查找 Whisper 支持的语言,你可以在终端运行whisper --help。Whisper 在猜测应该插入标点符号和正确地大写专有名词方面相当不错(但从不完美)。然而,你应该始终审查输出以清理任何错误。
model.transcribe() 返回的字典包含多个键值对,但 'text' 键包含转录的字符串。
默认情况下,Whisper 使用您的 CPU 来转录文本,但如果您的计算机配备了 3D 图形卡,您可以通过设置它使用图形处理单元(GPU)来大大加快转录速度。您可以在在线文档中找到这些设置说明,网址为 github.com/openai/whisper。如果您的计算机配备了 NVIDIA 图形卡,您可以按照附录 A 中的说明安装用于更快语音识别的软件包。要使用 GPU,将本章中的 whisper.load_model('base') 代码替换为 whisper.load_model('base', device='cuda')。
您可以在 Whisper 的在线文档中找到几个额外的选项。
创建字幕文件
除了转录的音频外,Whisper 的结果字典还包含标识文本在音频文件中位置的时序信息。您可以使用这些文本和时序数据生成其他软件可以处理的字幕文件。最常见的两种字幕文件格式是 SRT SubRip Subtitle(带有 .srt 扩展名)和 VTT Web Video Text Tracks(带有 .vtt 文件扩展名)。SRT 是一个较老且更广泛使用的标准,而现代视频网站通常使用 VTT。这些格式相似。例如,以下是 SRT 文件的第一部分:
1
00:00:00,000 --> 00:00:05,640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
2
00:00:05,640 --> 00:00:14,960
appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
将其与同一字幕的 VTT 文件的第一部分进行比较:
WEBVTT
00:00.000 --> 00:05.640
Dinosaurs are a diverse group of reptiles of the clade dinosauria. They first
00:05.640 --> 00:14.960
appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
这两个文件都表明,“Dinosaurs are a diverse group ...” 这句话出现在转录音频文件的开始(0 秒)和 5.640 秒标记之间。
Whisper 也可以将其结果输出为 TSV 数据(带有 .tsv 扩展名)或 JSON 数据(带有 .json 扩展名)。TSV 不是一个官方的字幕格式,但如果您需要将文本和时序数据导出到,比如说,另一个可以使用第十八章中提到的 csv 模块读取的 Python 程序,那么它可能很有用。TSV 格式的字幕看起来如下所示:
start end text
0 5640 Dinosaurs are a diverse group of reptiles of the clade dinosauria. They
5640 14960 appeared during the triassic period. Between 245 and 233.23 million years ago.
# --snip--
要创建这些字幕文件,在调用 model.transcribe() 之后添加两行额外的代码:
>>> import whisper
>>> model = whisper.load_model('base')
>>> result = model.transcribe('hello.wav')
>>> write_function = whisper.utils.get_writer('srt', '.') # ❶
>>> write_function(result, 'audio') # ❷
whisper.utils.get_writer() 函数 ❶ 接受字幕文件格式作为字符串('srt'、'vtt'、'txt'、'tsv' 或 'json')以及要保存文件的文件夹(使用 '.' 字符串表示当前工作目录)。get_writer() 函数返回一个函数,用于传递转录结果。(这是一种相当奇怪创建转录文件的方式,但这是 whisper 模块的设计方式。)我们将其存储在一个名为 write_function 的变量中,然后我们可以将其作为函数调用,传递 result 字典和字幕文件的文件名 ❷。这两行代码在当前工作目录中生成一个名为 audio.srt 的 SRT 格式文件,使用 result 字典中的文本和时序信息。
从网站下载视频
虽然 Whisper 语音识别下载音频文件进行转录通常很简单,但像 YouTube 这样的视频网站通常不会让下载内容变得容易。yt-dlp 模块允许 Python 脚本从 YouTube 和数百个其他视频网站下载视频,以便你可以在离线状态下观看。附录 A 包含安装 yt-dlp 的说明。一旦安装,以下代码将下载给定 URL 的视频:
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> with yt_dlp.YoutubeDL() as ydl:
... ydl.download([video_url])
...
注意,ydl.download() 函数期望一个视频 URL 列表,这就是为什么我们在将其传递给函数调用之前将 video_url 字符串放在一个列表中。视频的文件名基于视频网站上的标题,可能具有 .mp4、.mkv 或其他视频格式文件扩展名。在视频下载过程中,你会看到大量的调试输出。
视频网站可能会因为年龄限制、登录要求、地理限制或反网页抓取措施而拒绝下载。如果你遇到错误,你应该尝试的第一步是安装最新版本的 yt-dlp,它将更新以保持与视频网站布局变化的兼容性。
你可以在在线文档中了解 yt-dlp 的许多配置选项,网址为 pypi.org/project/yt-dlp/。例如,你可以通过传递配置设置字典给 yt_dlp.YoutubeDL() 函数来从 YouTube 视频中提取音频:
>>> import yt_dlp
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... 'quiet': True, # Suppress the output. # ❶
... 'no_warnings': True, # Suppress warnings.
... 'quiet': True, # Suppress the output. # ❶
... 'format': 'm4a/bestaudio/best',
... 'postprocessors': [{ # Extract audio using ffmpeg.
... 'key': 'FFmpegExtractAudio',
... 'preferredcodec': 'm4a',
... }]
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... ydl.download([video_url])
...
'quiet': True 和 'no_warnings': True 键值对 ❶ 阻止详细的调试输出。传递给 yt_dlp.Youtube() 的 options 字典告诉它下载视频,然后将音频提取到名为 downloaded_content.m4a ❷ 的文件中。(如果视频有不同的音频格式,文件扩展名可能会有所不同,尽管 .m4a 格式是最流行的。)如果我们没有在 options 字典中设置 'outtmpl': 'downloaded_content.%(ext)s' 键值对,下载的文件名将基于视频的标题(排除文件名中不允许的字符,如问号和冒号)。
要获取确切的文件名,我们可以使用第十章中讨论的 glob 模式。我们知道文件的主要部分是 'downloaded_content',但文件扩展名可以是任何音频格式。以下代码使用 Path 对象来查找确切的下载文件名:
>>> from pathlib import Path
>>> matching_filenames = list(Path().glob('downloaded_content.*'))
>>> downloaded_filename = str(matching_filenames[0])
>>> downloaded_filename
'downloaded_content.m4a'
设置文件名使得代码在以后使用此文件时更加方便,例如通过运行 Whisper 语音识别。'base' 和 'medium' 模型创建的子标题质量远高于 YouTube 当前自动生成的子标题。
如果你只想下载特定视频的信息,你可以告诉 yt-dlp 跳过文件,并使用以下代码仅下载其元数据:
>>> import yt_dlp, json
>>> video_url = 'https://www.youtube.com/watch?v=kSrnLbioN6w'
>>> options = {
... 'quiet': True, # Suppress the output.
... 'no_warnings': True, # Suppress warnings.
... 'quiet': True, # Suppress the output. # ❶
...}
...
>>> with yt_dlp.YoutubeDL(options) as ydl:
... 'quiet': True, # Suppress the output. # ❶
... 'quiet': True, # Suppress the output. # ❶
... print('TITLE:', json_info['title']) # Print the video title.
... print('KEYS:', json_info.keys())
... with open('metadata.json', 'w', encoding='utf-8') as json_file:
... 'quiet': True, # Suppress the output. # ❶
...
TITLE: Beyond the Basic Stuff with Python - Al Sweigart - Part 1
KEYS: dict_keys(['id', 'title', 'formats', 'thumbnails', 'thumbnail',
'description', 'channel_id', 'channel_url', 'duration', 'view_count',
'average_rating', 'age_limit', 'webpage_url',
--`snip`--
如果我们只想获取视频的元数据而不是视频本身,我们可以在传递给 yt_dlp.YoutubeDL() 的 options 字典中包含 'skip_download': True 键值对 ❶。ydl.extract_info() 方法调用返回有关视频的信息字典 ❷。其中一些数据可能没有正确格式化为 JSON(在第十八章中讨论),但我们可以通过调用 ydl.sanitize() ❸ 来获取与 JSON 兼容的形式。sanitize() 方法返回的字典有几个键,包括 'title' 用于视频名称,'duration' 用于视频长度(以秒为单位),等等。我们的代码还额外将此 JSON 数据写入名为 metadata.json ❹ 的文件。
摘要
Python 的一个巨大优势是其庞大的第三方包生态系统,这些包用于诸如文本到语音和语音识别等任务。这些包将计算机科学中最难的问题之一简化,只需几行代码即可使您的程序可用。
pyttsx3 包使用计算机的语音引擎进行文本到语音转换,创建您可以播放或保存到 .wav 文件的音频。Whisper 语音识别系统使用几个底层模型来转录音频文件的单词。这些模型有不同的尺寸;较小的模型转录速度更快但准确性较低,而较大的模型速度较慢但准确性更高。它们适用于许多人类语言,而不仅仅是英语。Whisper 在您的计算机上运行,除了首次使用时下载模型外,不会连接到在线服务器。
这些 Python 包使用的语音引擎在 2020 年代之前的质量有了大幅提升。Python 是一种出色的“胶水”语言,允许您的脚本与该软件连接,这样您只需几行代码即可将语音功能添加到自己的程序中。如果您想了解更多关于文本到语音和语音识别的信息,您可以在 Mark Liu 的《Make Python Talk》(No Starch Press,2021 年)中找到许多有趣的示例项目。
练习问题
-
如何让 pyttsx3 的语音说话更快?
-
pyttsx3 保存到什么音频格式?
-
pyttsx3 和 Whisper 是否依赖于在线服务?
-
pyttsx3 和 Whisper 是否支持除了英语以外的其他语言?
-
Whisper 的默认语音识别机器学习模型叫什么名字?
-
两种常见的字幕文本文件格式是什么?
-
yt-dlp 是否可以从除了 YouTube 以外的网站下载视频?
练习程序
为了练习,编写程序来完成以下任务。
添加语音到猜数字
回顾第三章中的猜数字游戏,并为其添加语音功能。将所有对 print() 函数的调用替换为对名为 speak() 的函数的调用。接下来,定义 speak() 函数以接受一个字符串参数(就像 print() 一样),但它既会在屏幕上打印字符串,也会大声说出。例如,您将替换此行代码
print('I am thinking of a number between 1 and 20.')
为此行代码:
speak('I am thinking of a number between 1 and 20.')
为了充分利用语音生成功能,让我们将 'Your guess is too low.' 和 'Your guess is too high.' 文本改为说出玩家的猜测。例如,电脑应该这样说,“你的猜测,42,太低了。”你还可以为这本书中的其他项目添加语音功能,例如石头、剪刀、布游戏。
唱“99 瓶啤酒”
累积歌曲 是那些歌词重复并带有添加或轻微变化的歌曲。“99 瓶啤酒”和“圣诞节的 12 天”是累积歌曲的例子。编写一个程序,唱(或至少说出)“99 瓶啤酒”的歌词:
99 bottles of beer on the wall,
99 bottles of beer,
Take one down, pass it around,
98 bottles of beer on the wall.
这些歌词重复,每次少一瓶。歌曲继续进行,直到达到零瓶,此时最后一行是“墙上没有更多的啤酒瓶。”(你可能希望程序从 2 或 3 开始而不是 99,以便更容易进行测试。)
YouTube 转录器
编写一个程序,将 yt-dlp 和 Whisper 的功能结合在一起,自动下载 YouTube 视频并生成 .srt 格式的字幕文件。输入可以是下载和转录的 URL 列表。你还可以添加选项以生成不同的字幕格式。Python 是一种优秀的“粘合语言”,用于结合不同模块的功能。
为猜数字游戏添加语音
回顾第三章中的猜数字游戏,并为其添加语音功能。将所有对 print() 函数的调用替换为对名为 speak() 的函数的调用。接下来,定义 speak() 函数以接受一个字符串参数(就像 print() 一样),但它既会在屏幕上打印字符串,也会大声说出。例如,你会替换以下代码行
print('I am thinking of a number between 1 and 20.')
使用以下代码行:
speak('I am thinking of a number between 1 and 20.')
为了充分利用语音生成功能,让我们将 'Your guess is too low.' 和 'Your guess is too high.' 文本改为说出玩家的猜测。例如,电脑应该这样说,“你的猜测,42,太低了。”你还可以为这本书中的其他项目添加语音功能,例如石头、剪刀、布游戏。
唱“99 瓶啤酒”
累积歌曲 是那些歌词重复并带有添加或轻微变化的歌曲。“99 瓶啤酒”和“圣诞节的 12 天”是累积歌曲的例子。编写一个程序,唱(或至少说出)“99 瓶啤酒”的歌词:
99 bottles of beer on the wall,
99 bottles of beer,
Take one down, pass it around,
98 bottles of beer on the wall.
这些歌词重复,每次少一瓶。歌曲继续进行,直到达到零瓶,此时最后一行是“墙上没有更多的啤酒瓶。”(你可能希望程序从 2 或 3 开始而不是 99,以便更容易进行测试。)
YouTube 转录器
编写一个程序,将 yt-dlp 和 Whisper 的功能结合在一起,以自动下载 YouTube 视频并生成 .srt 格式的字幕文件。输入可以是下载和转录的 URL 列表。您还可以添加选项以生成不同的字幕格式。Python 是一种优秀的“粘合语言”,用于结合不同模块的功能。


浙公网安备 33010602011771号