AutoIt-秘籍-全-
AutoIt 秘籍(全)
原文:
zh.annas-archive.org/md5/8f8a70ed95a9c4e2d339b67f1fd45d15译者:飞龙
前言
业务流程自动化是通过技术实现的,旨在完成特定功能或工作流程的活动自动化。它是一种旨在推动流程效率、一致性和可重复性的组织转型。许多业务流程,如人力资源管理、潜在客户管理、财务报告和发票等,都可以通过使用 Python 模块轻松自动化,以实现组织内的这些目标。
本书各章节中涵盖的 Python 脚本将帮助你获取知识,并鼓励你思考如何自动化你的业务流程。我们采用经典的问题解决模式,探讨不同领域,如人力资源管理、市场营销、客户支持等,在这些领域中,我们可以借助 Python 脚本来自动化和创新。
本书涵盖的内容
第一章, 使用网络,讨论了万维网的有趣世界,并涵盖了使用 Python 模块与网络交互的多种方式。
它从介绍 HTTP 网络请求的基础开始,然后逐渐引导读者进入更高级的主题,例如网络爬取和从网络下载内容。本章还为你提供了编写自己的异步网络服务器的方法,并帮助你理解和构建网络自动化。
在上一节中,本章帮助你使用 Python 脚本来自动化潜在客户的生成——这是营销人员经常面临的一个经典情况。
第二章, 使用 CSV 和 Excel 工作表,通过 Python 脚本带你了解如何简化并自动化使用 CSV 和 Excel 表格的重复性任务。在计算机成为我们日常生活的一部分之前,办公记录都存储在文件中,并在办公桌上管理。多亏了 Excel 工作表的世界,我们能够以更好的方式管理我们的数据。
本章的第一部分帮助你执行 CSV 文件的读写操作。它还帮助你编写自己的 CSV 语法。不知道我们所说的语法是什么意思?好吧,在本章中找到答案!你还将学习如何使用 CSV 文件和 Python 代码自动化管理员工信息,这是人力资源管理的一个关键部分。
本章的第二部分为你提供了执行诸如在 Excel 工作表中检索和插入数据等操作的知识。它还涵盖了某些高级操作,例如单元格格式化、使用公式执行数学任务以及插入图表。最后,通过一个很好的例子,它解释了如何自动化财务团队对不同年份的损益表分析。
第三章,用 PDF 文件和文档发挥创意,讨论了 Word 文档和 PDF 文件如何成为商业专业人士最常用的文件格式之一,以及您如何使用 Python 自动化 PDF 和 Word 文档中的日常任务。您想向客户发送发票或向供应商发送一组要求吗?企业通常最终会使用 PDF 文件和 Word 文档来满足此类需求。
本章首先介绍了您可以使用 Python 脚本在 PDF 文件上执行的操作。您将获得生成和读取 PDF 文件、复制它们以及甚至操纵它们以构建自己的页眉和页脚格式的知识。您知道您可以用简单的 Python 脚本来合并多个 PDF 文件吗?您想自动化生成您组织工资单的过程吗?听起来很有趣?那么这一章绝对适合您!
本章还带您进入与 Word 文档一起工作的旅程。它帮助您建立将数据读入和写入 Word 文件的知识。添加表格、图像、图表;您想叫什么,这一章就涵盖了什么。如果还不够,这一章还从人力资源流程中举了一个例子,并帮助您根据业务单元为新员工构建定制的入职计划日程表。
第四章,玩转短信和语音通知,通过短信和语音通知开辟了一个全新的自动化世界。它从对云电话的简介开始,并涵盖了其实际用例。
在本章的初始部分,我们讨论了在某些情况下短信文本消息的有用性。您将学习如何使用 Python 脚本发送短信消息和接收传入的短信。关于短信通知的部分以展示如何自动化多米诺披萨店客户服务流程结束。
本章还详细介绍了语音通知;您将熟悉语音工作流程,例如使用 Python 代码片段发送语音消息和接收传入的语音电话。您知道您可以通过构建自己的联系中心来自动化客户支持吗?感兴趣吗?您必须查看这一章。
第五章,电子邮件的乐趣,涵盖了有趣的 Python 脚本,例如发送电子邮件消息、使用 MIME 美化电子邮件内容,甚至处理附件。电子邮件在过去 20-30 年中无处不在;您无处不在都能看到它们!您已经习惯了与它们一起工作,并且出于许多原因。但是,您是否曾经意识到您可以用 Python 片段来操纵您的收件箱?
本章还将帮助您获取和阅读电子邮件对话,并通过删除消息清理您的收件箱。您是否想为选定的消息添加标签,或者您想了解更多关于电子邮件加密的信息?嗯,本章为您涵盖了这些内容。当然,本章以一个使用 Python 脚本自动化客户支持流程的示例结束。
第六章,演示和更多,探讨了您可以使用 Python 以自动化方式生成自己的演示的各种方法。本章展示了您如何创建一个新的演示文稿并添加内容或幻灯片,还展示了读取或更新现有演示文稿以及插入图表、表格和图片的方法——基本上,您需要的所有操作。当然,我们使用 Python 脚本为销售经理自动化每周的销售报告。这一章是为所有销售经理而设的。
第七章,API 的力量,带您进入 API 的有趣世界。API 是当今万维网的关键部分。跨服务交流、共享信息以及许多其他操作都依赖于 API 和 Webhooks。
本章从 REST API 的介绍开始,涵盖了 REST 哲学的基础,并为您提供了开发自己的 API 所需的知识。它还展示了您如何使用 Python 脚本和 Twitter REST API 自动化社交媒体上产品更新的调度——这是营销团队的一个基本用例。
在下一节中,本章涵盖了 Webhooks,这是当今网络的一个关键方面。它帮助您实现 Webhooks,并展示了商业专业人士如何利用 Webhooks 通过 Python 脚本自动化潜在客户管理。
第八章,与机器人对话,带您进入全新的机器人世界。它首先根据机器人的能力进行分类,并展示了您如何在 Telegram 等应用程序上构建和使用机器人。
本章还简要介绍了无状态和有状态的概念,并让您体验了在机器人中集成人工智能算法。最后,本章以一个图书出版网站的例子为例,展示了机器人如何用于与客户进行相关互动——这是客户成功团队在日常工作中处理的问题。
第九章,想象图像,为您提供了处理图像的入门指南。它展示了如何将您的图像转换为不同的格式(考虑压缩),调整图像大小和裁剪,以及如何使用 Python 生成缩略图。我想您已经在考虑一些用例了。
不仅于此,它还为你提供了一个基础,帮助你找到图像之间的差异并比较它们,这对于你构建基于图像的搜索算法非常有用。最后,本章鼓励你通过自动化扫描文档和用 Python 对其进行索引的过程,将你的公司转变为无纸化模式。
第十章, 数据分析与可视化,从数据分析过程的介绍开始,以简单的方式涵盖基本方面。它展示了如何使用过滤和数据聚合等技术读取和选择相关数据。
它还帮助你使用 Python 食谱进行数据解释和可视化,以生成见解。最后,本章涵盖了一个分析社交媒体数据并为杂志文章生成见解的商业用例。有趣的用例,对吧?你必须阅读本章以了解更多信息。
第十一章, 进入状态的时间,介绍了一系列 Python 食谱,用于处理日期和时间对象。它展示了如何向日期添加时间或天数,比较日期,以及以多种格式表示日期和时间。它还教你如何使用 Python 处理夏令时以及进行时区计算。
最后,本章通过一个自动开具发票的例子,并讨论时区问题,来强调在自动化业务流程时考虑时区的重要性。
你需要这本书的什么
本书涵盖了旨在帮助你自动化业务流程的 Python 食谱。你所需要的只是一个合理的计算机配置(1 GB RAM,10 GB HDD)以及安装了 Python v2.7.10。
我相信你会在这本书中找到至少几个可以用书中涵盖的想法来自动化的用例。所以,继续在你所在领域的专业知识中寻找自动化的可能性,并且肯定,这本书中的 Python 食谱将非常有帮助。
这本书适合谁
自动化它! 是一本面向商业专业人士和开发者的书。它为你提供了一系列食谱,以 Python 自动化你的业务流程和开发任务。它为你提供了一个平台,让你自动化重复和耗时的业务任务,并使它们更高效。
由于本书也针对来自 HR、销售、营销和客户支持背景的商业专业人士,因此技术主题以详细的方式介绍,以便他们能够理解技术并利用 Python 食谱使他们的生活自动化。
部分
在这本书中,你会发现几个频繁出现的标题(准备工作、如何做、工作原理、还有更多和另请参阅)。
为了清楚地说明如何完成一个食谱,我们使用以下部分如下:
准备工作
本节告诉你可以在食谱中期待什么,并描述如何设置任何软件或任何为食谱所需的初步设置。
如何做到这一点...
本节包含遵循食谱所需的步骤。
它是如何工作的……
本节通常包含对上一节发生事件的详细解释。
还有更多……
本节包含有关食谱的附加信息,以便使读者对食谱有更多的了解。
相关内容
本节提供了一些有用的链接,指向其他有用的食谱信息。
惯例
在这本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“我们通过fromstring()方法获得了树,该方法将页面内容(字符串格式)转换为 HTML 格式。”
代码块如下设置:
try:
r = requests.get("http://www.google.com/")
except requests.exceptions.RequestException as e:
print("Error Response:", e.message)
任何命令行输入或输出都应如下编写:
pip install -U requests
新术语和重要词汇以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“现在我们已经输入了电子邮件和密码,最后一步是提交表格并点击登录按钮。”
注意
警告或重要注意事项如下所示。
小贴士
小技巧和窍门如下所示。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或不喜欢什么。读者反馈对我们来说非常重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。
要向我们发送一般反馈,只需发送电子邮件至feedback@packtpub.com,并在邮件主题中提及书的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从www.packtpub.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持选项卡上。
-
点击代码下载与勘误表。
-
在搜索框中输入书的名称。
-
选择您想要下载代码文件的书籍。
-
从下拉菜单中选择您购买这本书的地方。
-
点击代码下载。
您还可以通过点击 Packt Publishing 网站上书籍网页上的代码文件按钮来下载代码文件。您可以通过在搜索框中输入书籍名称来访问此页面。请注意,您需要登录到您的 Packt 账户。
文件下载后,请确保使用最新版本的以下软件解压缩或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
7-Zip / PeaZip for Linux
本书代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Automate-it。我们还有来自我们丰富图书和视频目录的其他代码包可供使用,网址为github.com/PacktPublishing/。请查看它们!
下载本书的颜色图像
我们还为您提供了一个包含本书中使用的截图/图表的颜色图像的 PDF 文件。这些颜色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/AutomateIt_ColorImages.pdf下载此文件。
勘误
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误表,请访问www.packtpub.com/books/content/support并在搜索字段中输入书籍名称。所需信息将出现在勘误部分。
盗版
互联网上版权材料的盗版是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以追究补救措施。
请通过copyright@packtpub.com与我们联系,并提供疑似盗版材料的链接。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
询问
如果您在这本书的任何方面遇到问题,您可以通过questions@packtpub.com联系我们,我们将尽力解决问题。
第一章:使用网络
你能想象一个没有互联网的生活吗?对于几乎所有的事情,从交换信息到订购食物,我们今天都严重依赖互联网。让我们深入了解万维网的世界,并探讨我们如何使用 Python 模块与之交互的多种方式。
在本章中,我们将涵盖以下食谱:
-
发送 HTTP 请求
-
简要了解网络爬虫
-
解析和提取网页内容
-
从网络下载内容
-
使用第三方 REST API 进行工作
-
Python 中的异步 HTTP 服务器
-
使用 selenium 绑定进行网络自动化
-
使用网络爬虫自动化生成潜在客户
简介
互联网使生活变得如此简单,有时您甚至没有意识到它的力量。查看朋友的动态,给父母打电话,回复重要的商务电子邮件,或玩游戏——我们今天几乎在所有事情上都依赖于万维网(WWW)。
幸运的是,Python 拥有一套丰富的模块,帮助我们执行网络上的各种任务。哇!您不仅可以发送简单的 HTTP 请求从网站检索数据或下载页面和图片,还可以解析页面内容以收集信息,并使用 Python 进行分析以生成有意义的见解。等等;我提到过您可以通过自动化方式启动浏览器来执行日常例行任务吗?
本章的食谱将主要关注在执行上述网络操作时可以作为首选工具的 Python 模块。具体来说,在本章中,我们将重点关注以下 Python 模块:
-
requests(docs.python-requests.org/en/master/) -
urllib2(docs.python.org/2/library/urllib2.html) -
lxml(pypi.python.org/pypi/lxml) -
BeautifulSoup4(pypi.python.org/pypi/beautifulsoup4) -
selenium(selenium-python.readthedocs.org/)
注意
尽管本章的食谱将为您概述如何使用 Python 模块与网络交互,但我鼓励您尝试并开发适用于多种用例的代码,这将使您个人以及您的项目在组织规模上受益。
发送 HTTP 请求
在本章接下来的食谱中,我们将使用 Python v2.7 和 Python 的 requests (v2.9.1) 模块。本食谱将向您展示如何向互联网上的网页发送 HTTP 请求。
但在深入之前,让我们简要了解超文本传输协议(HTTP)。HTTP 是一种无状态的应用协议,用于在 WWW 上进行数据通信。典型的 HTTP 会话涉及一系列请求或响应事务。客户端在专用的 IP 和端口上向服务器发起TCP连接;当服务器收到请求时,它会以响应代码和文本进行响应。HTTP 定义了请求方法(如GET、POST等 HTTP 动词),这些方法指示对给定 Web URL 要采取的操作。
在这个菜谱中,我们将学习如何使用 Python 的requests模块进行 HTTP GET/POST请求。我们还将学习如何 POST json数据并处理 HTTP 异常。太酷了,让我们开始吧。
准备工作
要逐步完成这个菜谱,你需要安装 Python v2.7。安装后,你需要安装 Python pip。PIP代表Pip Installs Packages,是一个可以用于在计算机上下载和安装所需 Python 包的程序。最后,我们需要requests模块来发送 HTTP 请求。
我们将首先安装requests模块(我将把 Python 和pip的安装留给你根据你的操作系统在你的机器上执行)。不需要其他先决条件。所以,快点,让我们开始吧!
如何操作...
-
在你的 Linux/Mac 计算机上,转到终端并运行以下命令:
pip install -U requests只有在没有权限访问 Python 站点包时,你才需要使用
sudo,否则不需要sudo。 -
以下代码帮助你使用 Python 的
requests模块进行 HTTPGET请求:import requests r = requests.get('http://ip.jsontest.com/') print("Response object:", r) print("Response Text:", r.text) -
你将看到以下输出:
![如何操作...]()
-
使用 requests 创建带有数据负载的 HTTP
GET请求也很简单。以下代码帮助你实现这一点。这就是你如何检查将要发送的 URL 请求:payload = {'q': 'chetan'} r = requests.get('https://github.com/search', params=payload) print("Request URL:", r.url)![如何操作...]()
-
现在我们使用
requests模块进行 HTTPPOST调用。这类似于在网站上填写并提交登录或注册表单:payload = {'key1': 'value1'} r = requests.post("http://httpbin.org/post", data=payload) print("Response text:", r.json())![如何操作...]()
-
使用 requests 处理错误和异常也非常方便。以下代码片段展示了错误处理的示例。如果你在没有网络连接的情况下运行此代码,它将引发异常。异常处理程序捕获异常并指出它未能建立新的连接,正如预期的那样:
try: r = requests.get("http://www.google.com/") except requests.exceptions.RequestException as e: print("Error Response:", e.message)
工作原理...
在这个菜谱中,我们探讨了如何使用 Python 的requests模块进行不同类型的 HTTP 请求。让我们看看这段代码是如何工作的:
-
在第一个示例中,我们向
ip.jsontest.com发起了GET请求并获得了响应代码和响应文本。它返回我们计算机在互联网上的当前 IP 地址。 -
在第二个例子中,我们使用带有有效载荷数据的 HTTP
GET请求。看看请求 URL 中包含?q=chetan,它通过 GitHub 搜索所有名为 Chetan 的仓库。 -
接下来,我们使用有效载荷数据
{'key1', 'value1'}进行了POST请求。这就像我们在如何做部分观察到的在线表单提交。 -
requests模块有一个Response对象r,它包含各种方法。这些方法有助于在处理网络时提取响应、状态码和其他所需信息:-
r.status_code- 返回响应代码 -
r.json()- 将响应转换为.json格式 -
r.text- 返回查询的响应数据 -
r.content- 包含响应内容中的 HTML 和 XML 标签 -
r.url- 定义请求的 Web URL
-
-
我们还探讨了使用
requests模块的异常处理,如果没有互联网,则会发生异常,而requests模块可以轻松捕获这个异常。这是通过requests模块的requests.exceptions类实现的。
还有更多...
太棒了,这很简洁!在网络上进行 HTTP 请求只是开始。我们还可以在网络上做更多的事情,比如处理页面内容。那么,让我们看看接下来是什么。
网络抓取的简要概述
在我们学习如何进行网络抓取之前,让我们先了解什么是抓取。在互联网世界中,抓取是一种通过计算机程序筛选网站页面,以提取所需信息的方式,这些信息以特定的格式呈现。例如,如果我想获取一个博客上所有发布的文章的标题和日期,我可以编写一个程序来抓取博客,获取所需数据,并根据需要将其存储在数据库或平面文件中。
网络抓取经常与网络爬虫混淆。网络爬虫是一个机器人,它系统地浏览网络,目的是进行网页索引,并被搜索引擎用于索引网页,以便用户更有效地搜索网络。
但抓取并不容易。对我们来说有趣的数据可能以特定的格式存在于一个博客或网站上,比如说 XML 标签或嵌入在 HTML 标签中。因此,在我们开始提取所需数据之前,了解格式是很重要的。此外,网络抓取器应该知道提取的数据需要存储的格式,以便以后可以对其采取行动。还重要的是要理解,如果 HTML 或 XML 格式发生变化,即使浏览器显示相同,抓取代码也会失败。
网络抓取的合法性
从法律角度来看,网络抓取一直备受关注。你能进行网络抓取吗?这是否合法或道德?我们能从抓取中获得的数据中获利吗?
这个主题已经引起了大量讨论,但总的来说,如果你在爬取版权信息、违反计算机欺诈和滥用法或违反网站服务条款时进行网络爬虫,你可能会遇到问题。例如,如果你在爬取公共数据,你应该没问题。然而,这非常具体,你需要小心你正在爬取的内容以及你如何使用这些数据。
这里有一些关于网络数据抓取的要点:
准备工作
我们以 GitHub 网站的定价数据为例,用 Python 演示网络爬虫。这是一个非常简单的例子,但能让我们熟悉爬虫。让我们开始,用这个 Python 食谱抓取一些有趣的数据。
如何操作...
-
在你的电脑上打开 Google Chrome 浏览器并打开
github.com/pricing/网页。在这个页面上,你会注意到多个定价计划,即个人、组织和企业。 -
现在,在你的浏览器上,右键点击个人计划的定价,然后点击检查元素,如下截图所示:
![如何操作...]()
-
一旦你点击检查,Chrome 浏览器的控制台日志就会打开,这将帮助你理解 GitHub 定价页面的 HTML 结构,如下所示:
![如何操作...]()
-
如果你查看高亮的 HTML
span-<span class="default-currency">$7</span>,你会知道这个网页使用default-currency类来列出计划的定价。现在我们将使用这个属性来提取多个 GitHub 计划的定价。 -
在此之前,让我们安装 Python 模块
lxml,它将用于从前面的 HTML 文档中提取内容。安装lxml和requests模块:pip install lxml pip install requests -
现在,打开你喜欢的编辑器并输入此代码片段:
from lxml import html import requests page = requests.get('https://github.com/pricing/') tree = html.fromstring(page.content) print("Page Object:", tree) plans = tree.xpath('//h2[@class="pricing-card-name alt-h3"]/text()') pricing = tree.xpath('//span[@class="default- currency"]/text()') print("Plans:", plans, "\nPricing:", pricing) -
如果你查看前面的代码,我们使用了
default-currency类和pricing-card-name display-heading-3来获取定价和定价计划。如果你运行代码片段,程序的输出将如下所示:![如何操作...]()
注意
使用网络爬虫时,你会看到当网页内容的 HTML 标签发生变化时会出现问题。例如,如果 CSS 类名被更改或锚点被按钮替换,爬虫代码可能无法获取你所需的数据。因此,请确保你相应地更改你的 Python 代码。
它是如何工作的...
正如我们之前讨论的,我们需要找到一种合适的方法来提取信息。因此,在这个例子中,我们首先获取了github.com/pricing/页面的 HTML 树。我们通过fromstring()方法获得了树,该方法将页面内容(字符串格式)转换为 HTML 格式。
然后,使用lxml模块和tree_xpath()方法,我们查找了default-currency类和pricing-card-name display-heading-3以获取定价和定价计划。
看看我们如何使用完整的 XPath,h3[@class='class-name'],来定位定价计划,以及//span[@class="default-currency"] XPath 来选择实际定价数据。一旦选择了元素,我们就打印出返回给我们的 Python 列表中的文本数据。
就这样;我们刮取了 GitHub 页面上的所需数据。简单又直接。
还有更多...
你学习了什么是网络爬虫,以及它们如何继续从网络中提取有趣的信息。你也理解了它们与网络爬虫的不同之处。但总有更多的事情!
网络爬取涉及提取,但在我们从网页解析 HTML 内容以获取对我们感兴趣的数据之前,这是不可能发生的。在下一个菜谱中,我们将详细了解如何解析 HTML 和 XML 内容。
解析和提取网络内容
好吧,现在我们对向多个 URL 发起 HTTP 请求有了信心。我们还查看了一个简单的网络爬取示例。
但万维网由多种数据格式的页面组成。如果我们想刮取网络并理解数据,我们还应该知道如何解析网络上的数据所采用的不同格式。
在这个菜谱中,我们将讨论如何...
准备工作
网络上的数据大多以 HTML 或 XML 格式存在。为了理解如何解析网络内容,我们将以一个 HTML 文件为例。我们将学习如何选择特定的 HTML 元素并提取所需的数据。为此菜谱,你需要安装 Python 的BeautifulSoup模块。BeautifulSoup模块是 Python 中最全面的模块之一,它将很好地解析 HTML 内容。所以,让我们开始吧。
如何做到这一点...
-
我们首先在我们的 Python 实例上安装
BeautifulSoup。以下命令将帮助我们安装模块。我们安装最新版本,即beautifulsoup4:pip install beautifulsoup4 -
现在,让我们看一下以下 HTML 文件,它将帮助我们了解如何解析 HTML 内容:
<html > <head> <title>Enjoy Facebook!</title> </head> <body> <p> <span>You know it's easy to get intouch with your <strong>Friends</strong> on web!<br></span> Click here <a href="https://facebook.com">here</a> to sign up and enjoy<br> </p> <p class="wow"> Your gateway to social web! </p> <div id="inventor">Mark Zuckerberg</div> Facebook, a webapp used by millions </body> </html> -
让我们把这个文件命名为
python.html。我们的 HTML 文件是手工制作的,这样我们就可以学习多种解析它的方法来获取所需的数据。Python.html提供了以下典型的 HTML 标签:-
<head>- 它是所有头元素(如<title>)的容器。 -
<body>- 它定义了 HTML 文档的主体。 -
<p>- 此元素在 HTML 中定义一个段落。 -
<span>- 它用于在文档中分组内联元素。 -
<strong>- 它用于给此标签下出现的文本应用粗体样式。 -
<a>- 它代表一个超链接或锚点,并包含指向超链接的<href>。 -
<class>- 它是一个指向样式表中类的属性。 -
<div id>- 它是一个封装其他页面元素并将内容划分为不同部分的容器。每个部分都可以通过属性id来识别。
-
-
如果我们在浏览器中打开这个 HTML,它将看起来像这样:
![如何做...]()
-
现在我们来编写一些 Python 代码来解析这个 HTML 文件。我们首先创建一个
BeautifulSoup对象。提示
我们总是需要定义解析器。在这种情况下,我们使用了
lxml作为解析器。解析器帮助我们以指定的格式读取文件,这样查询数据就变得容易了。import bs4 myfile = open('python.html') soup = bs4.BeautifulSoup(myfile, "lxml") #Making the soup print "BeautifulSoup Object:", type(soup)前面代码的输出可以在以下屏幕截图看到:
![如何做...]()
-
好的,这很整洁,但我们如何检索数据?在我们尝试检索数据之前,我们需要选择包含所需数据的 HTML 元素。
-
我们可以通过不同的方式选择或查找 HTML 元素。我们可以通过 ID、CSS 或标签来选择元素。以下代码使用
python.html来演示这个概念:#Find Elements By tags print soup.find_all('a') print soup.find_all('strong') #Find Elements By id print soup.find('div', {"id":"inventor"}) print soup.select('#inventor') #Find Elements by css print soup.select('.wow')前面代码的输出可以在以下屏幕截图查看:
![如何做...]()
-
现在我们继续前进,从 HTML 文件中获取实际内容。以下是我们可以提取感兴趣数据的一些方法:
print "Facebook URL:", soup.find_all('a')[0]['href']
print "Inventor:", soup.find('div', {"id":"inventor"}).text
print "Span content:", soup.select('span')[0].getText()
前面代码片段的输出如下:

哇!看看我们是如何从 HTML 元素中获取所有我们想要的文本的。
它是如何工作的...
在这个菜谱中,你学会了根据 ID、CSS 或标签查找或选择不同 HTML 元素的技术。
在这个菜谱的第二个代码示例中,我们使用了find_all(**'**a**'**)来从 HTML 文件中获取所有锚元素。当我们使用find_all()方法时,我们得到了多个匹配项作为数组。select()方法可以帮助你直接到达元素。
我们还使用了find(**'**div**'**, <divId>)或select(<divId>)通过div Id选择 HTML 元素。注意我们如何使用find()和select()方法以两种方式选择了具有div ID #inventor的inventor元素。实际上,选择方法也可以用作select(<class**-**name>)来选择具有 CSS 类名的 HTML 元素。我们在示例中使用了这种方法来选择元素wow。
在第三个代码示例中,我们搜索了 HTML 页面中的所有锚元素,并使用soup.find_all(**'**a**'**) [0]查看第一个索引。请注意,由于我们只有一个锚标签,我们使用了索引 0 来选择该元素,但如果我们有多个锚标签,我们可以通过索引 1 来访问。像getText()这样的方法和像text这样的属性(如前例所示)有助于从元素中提取实际内容。
更多...
好的,所以我们理解了如何使用 Python 解析网页(或 HTML 页面)。你也学习了如何通过 ID、CSS 或标签选择或查找 HTML 元素。我们还看了如何从 HTML 中提取所需内容的示例。如果我们想从网络上下载页面或文件的内容呢?让我们看看我们是否能在下一个菜谱中实现这一点。
从网络下载内容
因此,在先前的菜谱中,我们看到了如何进行 HTTP 请求,你也学习了如何解析网络响应。现在是时候继续前进,从网络上下载内容了。你知道万维网不仅仅是 HTML 页面。它还包含其他资源,如文本文件、文档和图片,以及其他许多格式。在这个菜谱中,你将学习如何使用示例在 Python 中下载图片。
准备工作
要下载图片,我们需要两个 Python 模块,即BeautifulSoup和urllib2。我们可以使用requests模块代替urllib2,但这样做将帮助你了解urllib2作为 HTTP 请求的替代方案,这样你就可以炫耀一下了。
如何操作...
-
在开始这个菜谱之前,我们需要回答两个问题。我们想下载什么类型的图片?我从网络上的哪个位置下载图片?在这个菜谱中,我们从谷歌(
google.com)图片搜索下载《阿凡达》电影图片。我们下载符合搜索条件的顶部五张图片。为了做到这一点,让我们导入所需的 Python 模块并定义我们将需要的变量:from bs4 import BeautifulSoup import re import urllib2 import os ## Download paramters image_type = "Project" movie = "Avatar" url = "https://www.google.com/search?q="+movie+"&source=lnms&tbm=isch" -
好的,那么我们现在就创建一个带有 URL 参数和适当头部的
BeautifulSoup对象。看看在使用 Python 的urllib模块进行 HTTP 调用时User-Agent的使用情况。requests模块在执行HTTP调用时会使用它自己的User-Agent:header = {'User-Agent': 'Mozilla/5.0'} soup = BeautifulSoup(urllib2.urlopen (urllib2.Request(url,headers=header))) -
谷歌图片托管在域名
http://www.gstatic.com/下的静态内容中。因此,使用BeautifulSoup对象,我们现在尝试找到所有源 URL 包含http://www.gstatic.com/的图片。以下代码做了完全相同的事情:images = [a['src'] for a in soup.find_all("img", {"src": re.compile("gstatic.com")})][:5] for img in images: print "Image Source:", img前面的代码片段的输出可以在下面的屏幕截图中看到。注意我们是如何获取网络上顶部五张图片的源 URL:
![如何操作...]()
-
现在我们已经得到了所有图片的源 URL,让我们开始下载它们。下面的 Python 代码使用
urlopen()方法来read()图片并将其下载到本地文件系统:for img in images: raw_img = urllib2.urlopen(img).read() cntr = len([i for i in os.listdir(".") if image_type in i]) + 1 f = open(image_type + "_"+ str(cntr)+".jpg", 'wb') f.write(raw_img) f.close() -
当图片下载完成后,我们可以在我们的编辑器中看到它们。以下快照显示了我们所下载的顶部五张图片以及
Project_3.jpg的外观:![如何操作...]()
工作原理...
因此,在这个菜谱中,我们探讨了从网络下载内容。首先,我们定义了下载的参数。参数就像配置,定义了可下载资源的位置以及要下载的内容类型。在我们的例子中,我们定义了必须下载阿凡达电影图片,而且是从Google下载。
然后,我们创建了BeautifulSoup对象,它将使用urllib2模块进行 URL 请求。实际上,urllib2.Request()使用配置准备请求,例如头部和 URL 本身,而urllib2.urlopen()实际上执行请求。我们包装了urlopen()方法的 HTML 响应,并创建了一个BeautifulSoup对象,这样我们就可以解析 HTML 响应。
接下来,我们使用 soup 对象搜索 HTML 响应中存在的顶级五张图片。我们根据img标签使用find_all()方法搜索图片。正如我们所知,find_all()返回一个包含图片 URL 的列表,其中图片在Google上可用。
最后,我们遍历了所有 URL,并在 URL 上再次使用urlopen()方法来read()图片。read()以原始格式返回图片的二进制数据。然后我们使用这个原始图片将它们写入到我们的本地文件系统中。我们还添加了逻辑来命名图片(它们实际上是自动递增的),以便在本地文件系统中唯一标识它们。
真好!这正是我们想要达到的效果!现在让我们提高一下难度,看看在下一个菜谱中我们还能探索些什么。
与第三方 REST API 一起工作
现在我们已经涵盖了抓取、爬取和解析,是时候做另一件有趣的事情了,那就是使用 Python 与第三方 API 一起工作。我假设我们中的许多人可能已经了解并且可能对REST API有基本的了解。那么,让我们开始吧!
准备工作
为了展示理解,我们以 GitHub gists 为例。GitHub 中的 gists 是分享你工作的最佳方式,一个小代码片段可以帮助你的同事,或者一个包含多个文件的小应用程序,可以让人理解一个概念。GitHub 允许创建、列出、删除和更新 gists,并且它是使用 GitHub REST API 的一个经典案例。
因此,在本节中,我们使用我们自己的requests模块向 GitHub REST API 发送 HTTP 请求以创建、更新、列出或删除 gists。
以下步骤将向您展示如何使用 Python 操作 GitHub REST API。
如何操作...
-
要使用 GitHub REST API,我们需要创建一个个人访问令牌。为此,请登录到
github.com/并浏览到github.com/settings/tokens,然后点击生成新令牌:![如何操作...]()
-
你现在将被带到新的个人访问令牌页面。在页面顶部输入描述,并在提供的范围中选择gists选项。请注意,范围代表令牌的访问权限。例如,如果你只选择gists,你可以使用 GitHub API 来处理gists资源,但不能处理其他资源,如repo或用户。对于这个菜谱,gists范围正是我们所需要的:
![如何操作...]()
-
一旦你点击生成令牌,你将看到一个包含你的个人访问令牌的屏幕。请将此令牌保密保存。
-
在获得访问令牌后,让我们开始使用 API 并创建一个新的摘要。在创建过程中,我们添加一个新的资源,为此,我们在 GitHub API 上执行 HTTP
POST请求,如下所示:import requests import json BASE_URL = 'https://api.github.com' Link_URL = 'https://gist.github.com' username = '<username>' ## Fill in your github username api_token = '<api_token>' ## Fill in your token header = { 'X-Github-Username': '%s' % username, 'Content-Type': 'application/json', 'Authorization': 'token %s' % api_token, } url = "/gists" data ={ "description": "the description for this gist", "public": True, "files": { "file1.txt": { "content": "String file contents" } } } r = requests.post('%s%s' % (BASE_URL, url), headers=header, data=json.dumps(data)) print r.json()['url'] -
如果我现在去 GitHub 上的
gists页面,我应该能看到新创建的摘要。哇,它可用!!![如何操作...]()
-
嘿,我们使用 GitHub API 成功创建了摘要。这很酷,但我们现在可以查看这个
gist吗?在上面的例子中,我们也打印了新创建的摘要的 URL。它将是以下格式:https://gist.github.com/<username>/<gist_id>。我们现在使用这个gist_id来获取摘要的详细信息,这意味着我们在gist_id上执行 HTTPGET请求:import requests import json BASE_URL = 'https://api.github.com' Link_URL = 'https://gist.github.com' username = '<username>' api_token = '<api_token>' gist_id = '<gist id>' header = { 'X-Github-Username': '%s' % username, 'Content-Type': 'application/json', 'Authorization': 'token %s' % api_token, } url = "/gists/%s" % gist_id r = requests.get('%s%s' % (BASE_URL, url), headers=header) print r.json() -
我们使用 HTTP
POST请求创建了一个新的摘要,并在前面的步骤中用 HTTPGET请求获取了摘要的详细信息。现在,让我们使用 HTTPPATCH请求更新这个摘要。注意
许多第三方库选择使用
PUT请求来更新资源,但 HTTPPATCH也可以用于此操作,正如 GitHub 所选择的那样。 -
以下代码演示了更新摘要的过程:
import requests import json BASE_URL = 'https://api.github.com' Link_URL = 'https://gist.github.com' username = '<username>' api_token = '<api_token>' gist_id = '<gist_id>' header = { 'X-Github-Username': '%s' % username, 'Content-Type': 'application/json', 'Authorization': 'token %s' % api_token, } data = { "description": "Updating the description for this gist", "files": { "file1.txt": { "content": "Updating file contents.." } } } url = "/gists/%s" % gist_id r = requests.patch('%s%s' %(BASE_URL, url), headers=header, data=json.dumps(data)) print r.json() -
现在,如果我查看我的 GitHub 登录并浏览到这个摘要,摘要的内容已经更新了。太棒了!别忘了查看截图中的修订版本--它已经更新到修订版本2:
![如何操作...]()
-
现在是最具破坏性的 API 操作--是的,删除摘要。GitHub 通过在其
/gists/<gist_id>资源上使用 HTTPDELETE操作提供删除摘要的 API。以下代码帮助我们删除gist:import requests import json BASE_URL = 'https://api.github.com' Link_URL = 'https://gist.github.com' username = '<username>' api_token = '<api_token>' gist_id = '<gist_id>' header = { 'X-Github-Username': '%s' % username, 'Content-Type': 'application/json', 'Authorization': 'token %s' % api_token, } url = "/gists/%s" % gist_id r = requests.delete('%s%s' %(BASE_URL, url), headers=header, ) -
让我们快速查看摘要现在是否可在 GitHub 网站上找到?我们可以通过在任何网页浏览器上浏览摘要 URL 来完成此操作。浏览器说了什么?它说404资源未找到,所以我们已成功删除了摘要!请参考以下截图:
![如何操作...]()
-
最后,让我们列出你账户中的所有摘要。为此,我们在
/users/<username>/gists资源上执行 HTTPGETAPI 调用:
import requests
BASE_URL = 'https://api.github.com'
Link_URL = 'https://gist.github.com'
username = '<username>' ## Fill in your github username
api_token = '<api_token>' ## Fill in your token
header = { 'X-Github-Username': '%s' % username,
'Content-Type': 'application/json',
'Authorization': 'token %s' % api_token,
}
url = "/users/%s/gists" % username
r = requests.get('%s%s' % (BASE_URL, url),
headers=header)
gists = r.json()
for gist in gists:
data = gist['files'].values()[0]
print data['filename'],
data['raw_url'], data['language']
以下代码对我的账户的输出如下:

它是如何工作的...
Python 的requests模块有助于在 GitHub 资源上执行 HTTP GET/POST/PUT/PATCH和DELETE API 调用。这些操作,在 REST 术语中也称为 HTTP 动词,负责对 URL 资源执行某些操作。
正如我们在示例中所看到的,HTTP GET 请求有助于列出 gists,POST 创建新的 gists,PATCH 更新 gists,而DELETE 完全删除 gists。因此,在这个菜谱中,你学习了如何使用 Python 与第三方 REST APIs(今天 WWW 的必要部分)进行交互。
相关内容
有许多第三方应用程序被编写为 REST APIs。你可能想尝试它们,就像我们为 GitHub 所做的那样。例如,Twitter 和 Facebook 都有很好的 API,文档也易于理解和使用。当然,它们也有 Python 绑定。
Python 中的异步 HTTP 服务器
如果你意识到,我们与之交互的许多 Web 应用程序默认是同步的。对于客户端发出的每个请求,都会建立客户端连接,并在服务器端调用一个可调用方法。服务器执行业务操作并将响应体写入客户端套接字。一旦响应耗尽,客户端连接就会关闭。所有这些操作都是按顺序一个接一个发生的——因此,是同步的。
但我们今天所看到的网络,不能仅仅依赖于同步操作模式。考虑这样一个网站,它从网络中查询数据并为您检索信息的情况。(例如,您的网站允许与Facebook集成,每次用户访问您网站上的某个页面时,您都会从他的Facebook账户中拉取数据。)现在,如果我们以同步方式开发这个网络应用程序,对于客户端发出的每个请求,服务器都会对数据库或网络进行 I/O 调用以检索信息,然后将信息呈现给客户端。如果这些 I/O 请求响应时间较长,服务器会阻塞等待响应。通常,Web 服务器维护一个线程池来处理来自客户端的多个请求。如果服务器等待足够长的时间来处理请求,线程池可能会很快耗尽,服务器将停滞不前。
解决方案?异步处理方式登场!
准备工作
对于这个菜谱,我们将使用 Tornado,这是一个在 Python 中开发的异步框架。它支持 Python 2 和 Python 3,最初是在 FriendFeed 开发的(blog.friendfeed.com/)。Tornado 使用非阻塞网络 I/O,解决了扩展到数万个实时连接的问题(C10K 问题)。我喜欢这个框架,并享受用它来编写代码。我希望你也会!在我们进入“如何做”部分之前,让我们首先通过执行以下命令来安装 tornado:
pip install -U tornado
如何做...
-
我们现在准备好开发自己的基于异步哲学的 HTTP 服务器了。以下代码展示了在
tornadoWeb 框架中开发的异步服务器:import tornado.ioloop import tornado.web import httplib2 class AsyncHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def get(self): http = httplib2.Http() self.response, self.content = http.request("http://ip.jsontest.com/", "GET") self._async_callback(self.response, self.content) def _async_callback(self, response, content): print "Content:", content print "Response:\nStatusCode: %s Location: %s" %(response['status'], response['content-location']) self.finish() tornado.ioloop.IOLoop.instance().stop() application = tornado.web.Application([ (r"/", AsyncHandler)], debug=True) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start() -
以以下方式运行服务器:
python tornado_async.py -
服务器现在正在 8888 端口上运行,准备接收请求。
-
现在,启动您选择的任何浏览器,并浏览到
http://localhost:8888/。在服务器上,您将看到以下输出:![如何操作...]()
它是如何工作的...
我们异步 Web 服务器现在已启动并运行,正在 8888 端口上接受请求。但这是什么异步的呢?实际上,Tornado 基于单线程事件循环的哲学。这个事件循环持续轮询事件,并将其传递给相应的事件处理器。
在前面的例子中,当应用程序运行时,它首先运行ioloop。ioloop是一个单线程事件循环,负责接收来自客户端的请求。我们定义了get()方法,它被@tornado.web.asynchronous装饰,使其异步。当用户在http://localhost:8888/上发起 HTTP GET 请求时,会触发get()方法,它内部会对ip.jsontest.com发起 I/O 调用。
现在,一个典型的同步 Web 服务器会等待这个 I/O 调用的响应并阻塞请求线程。但 Tornado 作为一个异步框架,它会触发一个任务,将其添加到队列中,发起 I/O 调用,并将执行线程返回到事件循环。
事件循环现在持续监控任务队列,并轮询 I/O 调用的响应。当事件可用时,它执行事件处理器async*_*callback(),打印内容及其响应,然后停止事件循环。
还有更多...
事件驱动的 Web 服务器,如 tornado,使用内核级库来监控事件。这些库是kqueue、epoll等。如果你真的感兴趣,你应该做更多阅读。以下是一些资源:
使用 selenium 绑定的 Web 自动化
在迄今为止的所有食谱中,我们都有一个专门的 URL 来发起 HTTP 请求,无论是调用 REST API 还是从网络下载内容。但是,有些服务没有定义 API 资源,或者需要登录到网络才能执行操作。在这种情况下,你对请求的控制很少,因为同一个 URL 根据用户会话或 cookie 提供多种不同的内容。那么我们该怎么办呢?
那么,控制浏览器本身以在这样场景下完成任务怎么样?控制浏览器本身?有趣,不是吗?
准备工作
对于这个菜谱,我们将使用 Python 的 selenium 模块。Selenium (www.seleniumhq.org/) 是一个便携式软件框架,用于 Web 应用程序,并自动化浏览器操作。你可以使用 selenium 自动化一些日常任务。Selenium 会启动一个浏览器,并帮助你执行任务,就像有人在做一样。Selenium 支持一些最常用的浏览器,如 Firefox、Chrome、Safari 和 Internet Explorer 等。在这个菜谱中,我们将使用 Python 的 selenium 来举例说明如何登录 Facebook。
如何操作...
-
我们首先安装 Python 的 selenium 绑定。可以使用以下命令安装 selenium:
pip install selenium -
让我们先创建一个浏览器对象。我们使用 Firefox 浏览器来生成浏览器实例:
from selenium import webdriver browser = webdriver.Firefox() print "WebDriver Object", browser -
以下截图显示了如何创建一个 selenium WebDriver 对象。它还有一个唯一的会话 ID:
![如何操作...]()
-
接下来,我们要求浏览器浏览到 Facebook 主页。以下代码帮助我们实现这一点:
browser.maximize_window() browser.get('https://facebook.com') -
一旦运行前面的代码,你将看到一个 Firefox 浏览器被打开,并且它连接到了 Facebook 登录页面,如下截图所示:
![如何操作...]()
-
对于下一步,我们定位电子邮件和密码元素并输入适当的数据:
email = browser.find_element_by_name('email') password = browser.find_element_by_name('pass') print "Html elements:" print "Email:", email, "\nPassword:", password前面代码的输出如下:
![如何操作...]()
-
一旦我们选择了电子邮件和密码文本输入框,我们现在用正确的电子邮件和密码填充它们。以下代码将启用输入电子邮件和密码:
email.send_keys('abc@gmail.com') *#Enter correct email address*password.send_keys('pass123') *#Enter correct password*![如何操作...]()
-
现在我们已经输入了电子邮件和密码,最后一步是提交表单并点击登录按钮。我们通过通过 ID 找到元素并点击元素来完成这个操作:
browser.find_element_by_id('loginbutton').click()如果你已经输入了正确的电子邮件 ID 和密码,你将已经登录到 Facebook!
它是如何工作的...
对于这个菜谱,我们使用了 selenium WebDriver Python API。WebDriver是 selenium API 的最新加入,它能够像用户一样原生地驱动浏览器。它可以在本地或远程机器上使用 selenium 服务器进行驱动。在这个例子中,我们在本地机器上运行它。基本上,selenium 服务器在本地机器上默认端口 4444 上运行,selenium WebDriver API 与 selenium 服务器交互,在浏览器上执行操作。
在这个菜谱中,我们首先使用 Firefox 浏览器创建了一个 WebDriver 实例。然后我们使用 WebDriver API 浏览到 Facebook 主页。然后我们解析 HTML 页面并定位到电子邮件和密码输入元素。我们是如何找到这些元素的?是的,类似于我们在网络爬取示例中所做的。因为我们有 Chrome 的开发者控制台,我们可以在 Firefox 中安装 firebug 插件。使用这个插件,我们可以获取电子邮件和密码的 HTML 元素。请看以下截图:

一旦我们确定了 HTML 元素名称,我们就使用 WebDriver 的find_element_by_name()方法程序性地创建了一个 HTML 元素对象。WebDriver API 有一个可以作用于元素对象并输入所需文本(在这种情况下是email和password)的send_keys()方法。最后一个操作是提交表单,我们通过找到登录对象并点击它来完成这个操作。
还有更多...
我们使用 selenium WebDriver Python 绑定查看了一个非常基础的例子。现在,就取决于你的想象力了,看看你可以用 selenium 实现什么,自动化日常任务。
使用网络爬取自动化线索生成
莱恩是 Dely Inc 公司的市场营销经理。Dely 是一家食品配送初创公司,正在努力在伦敦市建立自己的品牌。Dely 擅长物流,并希望在其平台上聚合餐厅,这样当消费者从这些餐厅订购食物时,Dely 将负责实际的配送。Dely 希望随着每一次配送,他们都能从餐厅那里获得一定的提成。作为回报,餐厅只需考虑他们的厨房,而不必考虑物流方面。如果你仔细思考,几乎每个大小餐厅都是他们的潜在客户。Dely 希望接触这些餐厅,并希望将它们添加到他们的平台上,以满足他们的配送需求。
莱恩负责与餐厅取得联系,并希望在所有目标餐厅上开展营销活动。但在他能够这样做之前,他需要创建一个包含伦敦所有餐厅的数据库。他需要诸如餐厅名称、街道地址和联系电话等详细信息,以便他能联系到这些餐厅。莱恩知道他的所有线索都列在 Yelp 上,但他不知道从哪里开始。此外,如果他手动查看所有餐厅,这将花费他大量的时间。凭借你在本章中获得的知识,你能帮助莱恩进行线索生成吗?
网络爬取的合法性
我们在本章的前部分讨论了网络爬取的法律方面。我想再次提醒大家注意这一点。本章中涵盖的例子,再次,是为了让您了解如何进行网络爬取。此外,在这里我们正在爬取 Yelp 的公共数据,这些数据通常都是公开的,就像在这个例子中,这些数据本身就可在餐厅的网站上找到。
准备工作
现在,如果你看看莱恩的问题,他需要一个自动化的方式来收集伦敦所有列出餐厅的数据库。是的,你猜对了。网络爬取可以帮助莱恩建立这个数据库。这能这么简单吗?让我们在本食谱中看看。
对于这个食谱,我们不需要任何额外的模块。我们将使用在本章之前食谱中使用的BeautifulSoup和urllibPython 模块。
如何做到这一点...
-
我们首先访问 Yelp 网站(
yelp.com/),并搜索伦敦市的所有餐厅。当你这样做时,你会得到伦敦市所有餐厅的列表。观察显示搜索条件的 URL。它是www.yelp.com/search?find_desc=Restaurants&find_loc=London。参见以下截图以供参考:![如何操作...]()
-
现在,如果你点击搜索结果中出现的任何餐厅链接,我们应该能够获取 Ryan 需要的详细信息。参见以下截图,其中我们获取了Ffiona's Restaurant的详细信息。注意每个餐厅都有一个专属的 URL;在这种情况下,它是
www.yelp.com/biz/ffionas-restaurant-london?osq=Restaurants。此外,注意在这个页面上,我们有餐厅的名称、街道地址,甚至还有联系电话。Ryan 为其活动所需的全部详细信息;这真酷!!如何操作... -
好的,那么我们现在知道了如何获取餐厅列表,并且也能获取餐厅的相关详细信息。但是,我们如何以自动化的方式实现这一点呢?正如我们在网络爬虫示例中所见,我们需要在网页上查找 HTML 元素,以便收集这些数据。
-
让我们从搜索页面开始。在你的 Chrome 浏览器上打开搜索页面(
www.yelp.com/search?find_desc=Restaurants&find_loc=London)。现在,右键点击第一个餐厅的 URL,并点击检查以获取 HTML 元素。如果你注意到,在以下截图中的搜索页面列出的所有餐厅都有一个共同的 CSS 类名,biz-name,这表示餐厅的名称。它还包含href标签,该标签指向餐厅的专属 URL。在我们的截图中,我们获取了名称,Ffiona's Restaurant,而href指向餐厅的 URL,yelp.com/biz/ffionas-restaurant-london?osq=Resturants。![如何操作...]()
-
现在,让我们看看餐厅的专属页面,看看我们如何使用 HTML 元素收集餐厅的街道地址和联系电话。我们执行相同的操作,右键点击,并检查以获取街道地址和联系电话的 HTML 元素。参见以下截图以供参考。注意,对于街道地址,我们有一个单独的 CSS 类,
street-address,而联系电话则在一个名为biz-phone的 span 标签下可用。![如何操作...]()
-
太棒了!因此,我们现在拥有了所有可以用来自动抓取数据的 HTML 元素。现在让我们看看实现方法。以下 Python 代码以自动化的方式执行这些操作:
from bs4 import BeautifulSoup from threading import Thread import urllib #Location of restaurants home_url = "https://www.yelp.com" find_what = "Restaurants" location = "London" #Get all restaurants that match the search criteria search_url = "https://www.yelp.com/search?find_desc=" + find_what + "&find_loc=" + location s_html = urllib.urlopen(search_url).read() soup_s = BeautifulSoup(s_html, "lxml") #Get URLs of top 10 Restaurants in London s_urls = soup_s.select('.biz-name')[:10] url = [] for u in range(len(s_urls)): url.append(home_url + s_urls[u]['href']) #Function that will do actual scraping job def scrape(ur): html = urllib.urlopen(ur).read() soup = BeautifulSoup(html, "lxml") title = soup.select('.biz-page-title') saddress = soup.select('.street-address') phone = soup.select('.biz-phone') if title: print "Title: ", title[0].getText().strip() if saddress: print "Street Address: ", saddress[0].getText().strip() if phone: print "Phone Number: ", phone[0].getText().strip() print "-------------------" threadlist = [] i=0 #Making threads to perform scraping while i<len(url): t = Thread(target=scrape,args=(url[i],)) t.start() threadlist.append(t) i=i+1 for t in threadlist: t.join() -
好的,太棒了!现在,如果我们运行前面的 Python 代码,我们将得到伦敦前 10 家餐厅的详细信息,包括它们的名称、街道地址和联系电话。请参考以下截图:
![如何操作...]()
-
在前面的截图中,我们获取了由 Yelp 提供的伦敦 10 家餐厅的记录。"标题"是餐厅的名称,街道地址和电话号码是显而易见的。太棒了!我们为 Ryan 完成了这个任务。
它是如何工作的...
在前面的代码片段中,我们构建了搜索条件。我们在yelp.com上搜索,并查找伦敦的餐厅。有了这些详细信息,我们在 Yelp 上获取了搜索 URL。
我们首先创建了一个urllib对象,并使用该对象的urlopen()方法对搜索 URL 进行read()操作,以获取 Yelp 提供的符合搜索条件的所有餐厅列表。所有餐厅的列表存储为一个 HTML 页面,该页面存储在变量s_html中。
使用BeautifulSoup模块,我们在 HTML 内容上创建了一个 soup 实例,这样我们就可以开始使用 CSS 元素提取所需的数据。
初始时,我们在 Yelp 上浏览了搜索结果的前 10 条,并获取了餐厅的 URL。我们将这些 URL 存储在 Python 的 URL 列表中。为了获取 URL,我们使用代码soup_s.select(.biz-name)[:10]选择了 CSS 类名biz-name。
我们还定义了一个方法scrape(),它接受餐厅 URL 作为参数。在这个方法中,我们使用 CSS 类名biz-page-title、street-address和biz-phone分别读取餐厅的详细信息,如名称、街道地址和联系电话。为了获取确切的数据,我们使用title=soup.select(.biz-page-title)选择 HTML 元素,并使用title[0].getText().strip()获取数据。请注意,select()方法返回找到的元素作为数组,因此我们需要查找索引0以获取实际的文本。
我们使用while循环遍历所有餐厅的 URL,并使用scrape()方法抓取每个餐厅的详细信息。它将在你的控制台上打印出每个餐厅的名称、街道地址和联系电话,正如我们在前面的截图中所看到的。
为了提高我们抓取程序的性能,我们对每个餐厅的数据提取都使用了独立的线程。我们使用t = Thread(target=scrape,args=(url[i],))创建了一个新线程,并通过t.join()调用从每个线程中获取结果。
就这样,各位!瑞恩对这个努力感到非常满意。在这个例子中,我们帮助瑞恩自动化了一个关键的业务任务。在这本书中,我们将探讨各种用例,展示 Python 如何被用来自动化业务流程并提高效率。想要了解更多?那么,我们下一章见。
第二章. 使用 CSV 和 Excel 工作表
想象一个世界,你的重要文件存储在文件中,并在办公桌上进行管理。多亏了计算机和像 Excel 表这样的软件的出现,我们可以以有组织的方式管理我们的数据。实际上,你甚至可以用 Python 以自动化的方式管理工作表。
本章将涵盖以下食谱:
-
使用读取器对象读取 CSV 文件
-
将数据写入 CSV 文件
-
开发自己的 CSV 方言
-
以自动化的方式管理员工信息
-
读取 Excel 表
-
将数据写入工作表
-
格式化 Excel 单元格
-
玩转 Excel 公式
-
在 Excel 表中构建图表
-
自动比较公司财务
简介
在计算机成为我们日常生活的一部分之前,办公记录是在纸上创建并存储在柜子里的。今天,多亏了不断增长的计算领域,我们使用计算机应用程序在文本文件中存储这些记录。文本(.txt)文件非常适合保存大量数据;在文本文件中搜索信息也很容易,但数据从未以有组织的方式存储。随着时间的推移,信息量的增长也增加了存储信息的需求,从而导致了 CSV 和 Excel 表的诞生,其中数据不仅可以以结构化格式存储,而且可以轻松读取和处理。
CSV 文件包含由逗号分隔的数据;因此,它们被称为逗号分隔值(CSV)文件。CSV 允许以表格格式存储数据。CSV 文件在任何存储系统上导入都更容易,不受所使用的软件的影响。由于 CSV 文件是纯文本文件,它们可以轻松修改,因此用于数据的快速交换。
另一方面,Excel 表包含由制表符或其他分隔符分隔的数据。Excel 表以行列网格格式存储和检索数据。它们允许格式化数据,使用公式进行操作,并且能够在文件中托管多个工作表。Excel 非常适合输入、计算和分析公司数据,如销售额或佣金。
当 CSV 文件是用于存储和检索数据的文本文件时,Excel 文件是二进制文件,用于更高级的操作,如图表、计算,以及通常用于存储报告。
Python 有一套有用的模块来处理 CSV 和 Excel 文件。你可以读取/写入 CSV 和 Excel 文件,格式化 Excel 单元格,准备图表,并使用公式对数据进行计算。
本章中的食谱将专注于帮助我们执行 CSV 和 Excel 表前述操作的 Python 模块。具体来说,本章将重点关注以下 Python 模块:
-
openpyxl(pypi.python.org/pypi/openpyxl) -
XlsxWriter(pypi.python.org/pypi/XlsxWriter)
使用 reader 对象读取 CSV 文件
此配方将向您展示如何读取 CSV 文件,特别是如何创建和使用 reader 对象。
准备工作
要逐步执行此配方,您需要安装 Python v2.7。为了处理 CSV 文件,我们有一个很好的模块,csv,它是默认 Python 安装的一部分。因此,让我们开始吧!
如何做...
-
在您的 Linux/Mac 计算机上,转到终端并使用 Vim,或选择您喜欢的编辑器。
-
我们首先创建一个 CSV 文件。正如我们所知,CSV 文件具有结构化格式,其中数据由逗号分隔,因此创建一个应该很简单。以下截图是一个包含国家不同地区联系人详细信息的 CSV 文件。我们将其命名为
mylist.csv:![如何做...]()
-
现在,让我们编写 Python 代码来读取这个 CSV 文件并打印其中的数据:
import csv fh = open("mylist.csv", 'rt') try: reader = csv.reader(fh) print "Data from the CSV:", list(reader) except Exception as e: print "Exception is:", e finally: fh.close()前面代码片段的输出如下:
![如何做...]()
-
哦!发生了什么?我们似乎遇到了一个错误。错误提示 CSV 读取器找不到新行字符。这发生在 Mac 平台上的 CSV 文件中。这是因为 Mac OS 使用回车符(CR)作为行结束字符。
-
Python 有一个简单的解决方案来解决这个问题;我们以rU模式(即通用换行模式)打开文件。以下程序运行得很好,我们可以适当地读取文件内容:
try: reader = csv.reader(open("mylist.csv", 'rU'), dialect=csv.excel_tab) print "Data from the CSV:" d = list(reader) print "\n".join("%-20s %s"%(d[i],d[i+len(d)/2]) for i in range(len(d)/2)) except Exception as e: print "Exception is:", e finally: fh.close()前面程序的输出如下:
![如何做...]()
-
非常好!对于我们在前面代码片段中观察到的那个问题,还有一个简单的解决方案。我们可以做的是,简单地将文件格式从 Mac CSV 更改为 Windows CSV。我们可以通过执行打开和另存为操作来完成此操作。在以下示例中,我已经将
mylist.csv保存为mylist_wincsv.csv(Windows CSV 格式),并且读取文件内容不再有问题:fh = open("mylist_wincsv.csv", 'rt') reader = csv.reader(fh) data = list(reader) print "Data cells from CSV:" print data[0][1], data[1][1] print data[0][2], data[1][2] print data[0][3], data[1][3]在前面的代码示例中,我们打印了 CSV 文件的一部分数据。如果您意识到,CSV 文件也可以在 Python 中以 2D 列表的形式读取,其中第一个索引是行,第二个索引是列。在这里,我们打印了
row1和row2的第二、第三和第四列:![如何做...]()
-
使用 Python,使用有帮助的
DictReader(f)方法在字典中读取 CSV 文件的内容也非常方便:import csv f = open("mylist_wincsv.csv", 'rt') print "File Contents" try: reader = csv.DictReader(f) for row in reader: print row['first_name'], row['last_name'], row['email'] finally: f.close()在前面的代码片段中,我们使用文件句柄
f打开文件。然后,这个文件句柄被用作DictReader()函数的参数,该函数将第一行的值视为列名。这些列名作为字典中的键,数据被存储在其中。因此,在前面的程序中,我们可以选择性地打印三个列的数据:first_name、last_name和e-mail,并像以下截图所示打印它们:![如何做...]()
-
csv模块的DictReader()有几个辅助方法和属性,使得读取 CSV 文件变得容易。这些也被称为读取对象:import csv f = open("mylist_wincsv.csv", 'rt') reader = csv.DictReader(f) print "Columns in CSV file:", reader.fieldnames print "Dialect used in CSV file:", reader.dialect print "Current line number in CSV file:", reader.line_num print "Moving the reader to next line with reader.next()" reader.next() print "Reading line number:", reader.line_num f.close()
在此代码示例中,我们使用了以下属性和方法:
-
fieldnames:提供列名列表 -
dialect:CSV 文件格式(我们将在后面了解更多) -
line_num:当前读取的行号 -
next():带你到下一行
在下面的屏幕截图中,第一行包含我们 CSV 文件中的所有列名。在第二行中,我们打印了用于读取 CSV 文件的方言。在第三行中打印了当前正在读取的行号,屏幕截图的最后一行描述了读取对象在读取时将移动到的下一行:

还有更多...
Python 模块csv是一个辅助模块,完全可以通过使用open()方法打开文件,并用readline()方法读取文件内容来处理 CSV 文件。然后,你可以对文件的每一行执行split()操作,以获取文件内容。
阅读是件好事,但只有当有数据写入 CSV 文件时,你才会去阅读,对吧? 😃 让我们看看下一道菜谱中可用的将数据写入 CSV 文件的方法。
将数据写入 CSV 文件
再次强调,在本节中的菜谱,我们不需要除 Python 安装捆绑的模块之外的新模块,即csv模块。
如何操作...
-
首先,以写入模式和文本格式打开一个文件。我们创建两个 Python 列表,包含要写入 CSV 文件的数据。以下代码将执行这些操作:
import csv names = ["John", "Eve", "Fate", "Jadon"] grades = ["C", "A+", "A", "B-"] f = open("newlist.csv", 'wt') -
现在让我们使用
write()方法将数据添加到 CSV 文件中,如下所示:try: writer = csv.writer(f) writer.writerow( ('Sr.', 'Names', 'Grades') ) for i in range(4): writer.writerow( (i+1, names[i], grades[i]) ) finally: f.close()在前面的代码中,我们使用标题初始化了 CSV 文件;使用的列名是:Sr.、Names和Grades。接下来,我们启动一个 Python
for循环,运行四次,并将四行数据写入 CSV 文件。记住,我们有一个包含在 Python 列表Names和Grades中的数据。writerow()方法实际上是在for循环中逐行添加 CSV 文件中的内容。前面的代码片段的输出可以在下面的屏幕截图中看到:
![如何操作...]()
-
很酷,这很简单直接。值得注意的是,默认情况下,当我们写入 CSV 文件时,行中的文件内容是用逗号分隔的。但如果我们想改变行为,使其用制表符(
\t)分隔怎么办?writer()方法具有这种改变不仅分隔符,还有行终止符的功能。(注意:分隔符是用于在 CSV 文件中分隔行内数据的字符。终止字符用于标记 CSV 文件中行的结束。你将在下面的例子中理解这一点):import csv f=open("write.csv", 'wt') csvWriter = csv.writer(f, delimiter='\t', lineterminator='\n\n') csvWriter.writerow(['abc', 'pqr', 'xyz']) csvWriter.writerow(['123', '456', '789']) f.close()当运行前面的代码片段时,在文件系统中会创建一个新的文件
write.csv。文件内容可以在下面的屏幕截图中查看。如果你查看给定行中的内容,你会看到它们通过制表符分隔,而不是逗号。新行分隔符是回车键(按两次),这在下面的屏幕截图中也很明显。注意,两行之间有一个额外的换行符:![如何做...]()
开发自己的 CSV 方言
为了更容易地读取和写入 CSV 文件,我们可以指定csv模块的Dialect类中的格式化参数。在这里,我们看看一些可用的方言,并学习如何编写我们自己的。
准备工作
对于这个食谱,我们将使用 Python 默认安装中存在的相同csv模块,因此不需要显式安装任何东西。
如何做...
-
让我们先看看
Dialect类中存在的一些属性:-
Dialect.delimeter:我们在之前的食谱中使用过这个属性,用来改变 CSV 文件行中内容写入的方式。它用于分隔两个字段。 -
Dialect.lineterminator:这个属性用于表示 CSV 文件中行的终止。我们也在前面的部分中使用过它。 -
Dialect.skipinitialspace:这将跳过分隔符之后的所有前导空格。它有助于避免人为错误。
我们可以使用以下代码获取可用方言的列表:
print "Available Dialects:", csv.list_dialects()可用的两个主要方言是
excel和excel-tab。excel方言用于处理 Microsoft Excel 的默认导出格式中的数据,并且也适用于 OpenOffice 或 NeoOffice。 -
-
现在我们创建一个我们选择的方言。例如,我们选择
-符号来分隔 CSV 文件中的列:import csv csv.register_dialect('pipes', delimiter='-') with open('pipes.csv', 'r') as f: reader = csv.reader(f, dialect='pipes') for row in reader: print row -
我们创建了一个名为
pipes.csv的文件,如下所示:![如何做...]()
如果我们在
pipes.csv文件上运行前面的 Python 代码,它将返回每行作为一个数组,所有元素通过-字符分割。下面的屏幕截图显示了程序输出:![如何做...]()
它是如何工作的...
在第二个代码片段中,我们使用register_dialect()方法注册了我们自己的方言。我们将其命名为pipes,与pipes关联的分隔符是符号-,正如我们打算做的。
我们现在使用我们自己的read()方法读取pipes.csv文件,并使用读取器对象获取 CSV 文件的内容。但是,你看到了dialect='pipes'的使用吗?这将确保读取器期望列通过-分隔,并相应地读取数据。
如果你观察,reader对象根据方言pipes定义的-分割行。
你学习了如何将你自己的数据读取和写入 CSV 文件。你也理解了方言的用法。现在是时候感受一下如何使用前面的概念与实际用例相结合了。
以自动化的方式管理员工信息
迈克是他的组织的 HR 经理,他正在尝试收集加利福尼亚州所有员工的联系信息。他希望将这些信息分类,以便他对加利福尼亚州的员工进行问卷调查。他不仅想要收集这些信息,还希望将其持久化到另一个 CSV 文件中,以便在以后的时间更容易地处理。
我们能帮助迈克吗?你如何应用你到目前为止学到的概念?在帮助迈克的过程中,你会学到更多吗?让我们看看实现方法。
准备工作
对于这个例子,我们不需要任何特殊的模块。之前作为食谱的一部分安装的所有模块对我们来说都足够了。对于这个例子,我们使用包含员工信息的相同mylist.csv文件。
如何做到这一点...
-
让我们直接进入代码,打开这两个文件。一个文件句柄用于读取文件内容(读取员工数据),另一个用于写入
CA_Employees.csv文件。注意文件打开模式的不同('rt'和'wt')。当然,员工 CSV 文件是以读取模式打开的,而CA_Employees.csv文件是以写入模式打开的。import csv f = open("mylist.csv", 'rt') fw = open("CA_Employees.csv", 'wt') -
接下来,我们使用
DictReader()方法从 CSV 文件中以字典的形式读取员工信息。我们还创建了一个csvWriter对象,我们将使用它将数据写入CA_Employees.csv文件。 -
你可能会想象,当我们开始读取 CSV 文件的行时,我们也会读取第一行。我们应该跳过这一行,因为它只包含列名,对吧?是的,我们使用
reader对象的line_num属性来跳过标题行(记住,我们之前在本章中学习了属性)。一旦跳过了标题行,我们就遍历所有行,筛选出属于CA州的员工,并获取这些员工的电子邮件和电话信息。然后,使用csvWriter对象将这些筛选后的数据写入CA_Employees.csv文件。请注意,一旦文件操作完成,关闭文件句柄非常重要,因为这可能会导致内存泄漏或数据不一致:try: reader = csv.DictReader(f) csvWriter = csv.writer(fw) for row in reader: if reader.line_num == 1: continue if row['state'] == 'CA': csvWriter.writerow([row['email'], row['phone']]) finally: f.close() fw.close()
如何工作...
当我们运行前面的程序时,我们将得到一个CA_Employees.csv文件,其外观如下所示:

如果你查看代码实现,我们使用line_num属性来跳过标题行,即mylist.csv文件的第一行。我们还使用writerow()方法将筛选后的数据写入新创建的CA_Employees.csv文件。做得好,我认为迈克已经对你很满意了。他的问题已经解决了。😃
我们结束了关于处理 CSV 文件的这一部分。CSV 文件本质上以纯文本格式存储数据。我们无法用这些文件做很多事情,因此 Excel 工作表的出现。在下一个菜谱中,我们将开始处理 Excel 工作表,并欣赏它们能提供什么!
读取 Excel 工作表
如你所知,Microsoft Office 已经开始为 Microsoft Excel 工作表提供一个新的扩展名.xlsx,从 Office 2007 开始。随着这一变化,Excel 工作表转向基于 XML 的文件格式(Office Open XML)并使用 ZIP 压缩。当商业社区要求一个可以帮助在不同应用程序之间传输数据的开放文件格式时,Microsoft 做出了这一改变。让我们开始并看看我们如何使用 Python 处理 Excel 工作表!
准备工作
在这个菜谱中,我们使用openpyxl模块来读取 Excel 工作表。openpyxl模块是一个全面的模块,它对 Excel 工作表执行读取和写入操作。openpyxl的另一个替代模块是xlrd。虽然xlrd自 1995 年以来一直擅长支持 Excel 格式,但该模块只能用于从 Excel 工作表中读取数据。openpyxl模块有助于执行更多操作,如修改数据、将数据写入文件以及复制,这些对于处理 Excel 文件至关重要。
让我们使用我们最喜欢的工具pip安装openpyxl模块:
pip install openpyxl
如何做...
-
我们首先创建自己的 Excel 工作表,内容如以下截图所示。你必须知道,Excel 文件被称为工作簿,包含一个或多个工作表,因此 Excel 文件也被称为电子表格。我们将文件保存为
myxlsx.xlsx,在两个工作表人员和物品中:让我们看看人员工作表中的数据:
![如何做...]()
现在,让我们看看物品工作表中的数据:
![如何做...]()
-
现在让我们继续前进并读取 XLSX 文件。以下代码将帮助我们获取 Excel 工作簿中所有工作表的名称:
import openpyxl workbook = openpyxl.load_workbook('myxls.xlsx') print "Workbook Object:", workbook.get_sheet_names() -
现在,如果你想处理一个特定的工作表,你如何访问该对象?下面的代码片段将我们带到人员工作表:
people = workbook.get_sheet_by_name('People') print "People sheet object:", people哇,这太酷了!
-
现在让我们继续前进,阅读单元格对象。我们可以通过名称或基于行/列位置来读取单元格。下面的代码片段展示了这一点:
import openpyxl workbook = openpyxl.load_workbook('myxls.xlsx') people = workbook.get_sheet_by_name('People') print "First cell Object:", people['A1'] print "Other Cell Object:", people.cell(row=3, column=2) -
但我如何获取单元格中的值呢?很简单,
object.value返回单元格中存在的值:print "First Name:", people['B2'].value, people['C2'].value如果我们运行 Python 代码片段,我们将得到以下输出,如本截图所示:
![如何做...]()
它是如何工作的...
在前面的例子中,我们导入了 openpyxl 模块。这个模块有一个方法,可以用来访问工作表对象及其中的单元格。load_workbook() 方法将整个 Excel 表格加载到内存中。get_sheet_names() 和 get_sheet_by_name() 方法有助于选择给定工作簿的工作表。因此,我们已经准备好了工作簿和工作表对象。
单元格对象可以通过 cell() 方法访问,而 cell().value 返回工作表单元格中实际存在的值。不错,看看用 Python 从 Excel 表格中读取数据是多么简单。但再次强调,读取数据只有在我们知道如何将数据写入 Excel 表格时才有用。所以,我们还在等什么呢?让我们继续学习如何在下一菜谱中做到这一点。
将数据写入工作表
使用 openpyxl 模块读取文件非常简单。现在,让我们将注意力转向写入 Excel 文件。在本节中,我们将对 Excel 文件执行多个操作。
准备工作
对于这个菜谱,我们将使用另一个非常棒的 Python 模块,即 xlsxwriter。正如其名所示,这个模块帮助我们执行 Excel 表格上的多个操作。有趣的是,xlsxwriter 不支持在 Excel 表格上执行读取操作(在撰写本书时)。我们使用以下方式安装 xlsxwrite 模块:
pip install xlsxwriter
如何操作...
-
我们从一个非常基础的创建 XLSX 文件并添加新工作表的操作开始。以下代码执行了这个操作:
import xlsxwriter workbook = xlsxwriter.Workbook('add_sheet.xlsx') worksheet = workbook.add_worksheet(name='New Sheet 2') workbook.close() -
让我们继续前进,并在工作表上执行
write操作,存储一些有用的信息:import xlsxwriter workbook = xlsxwriter.Workbook('Expenses01.xlsx') worksheet = workbook.add_worksheet() expenses = ( ['Rent', 1000], ['Gas', 100], ['Food', 300], ['Gym', 50], ) row = 0 col = 0 for item, cost in (expenses): worksheet.write(row, col, item) worksheet.write(row, col + 1, cost) row += 1 worksheet.write(row, 0, 'Total') worksheet.write(row, 1, '=SUM(B1:B4)') workbook.close()
它是如何工作的...
本菜谱的第一个代码片段使用 Workbook() 方法在新的 Excel 文件 add_sheet.xlsx 下创建了一个 workbook 对象。然后,它继续使用 add_worksheet() 方法创建了一个 worksheet 对象。创建了一个名为 New Sheet 2 的新工作表。
在第二个代码示例中,我们创建了一个名为 Expenses01.xlsx 的 XLSX 文件。我们从 expenses 字典中添加了费用数据。为此,我们遍历字典,使用键作为 Excel 表格中的一列,而值作为另一列。最后,我们添加了一行,汇总了所有费用。Expenses01.xlsx 的内容如下截图所示:

在前面的代码片段中,我们使用 xlsxwrite 模块在 Excel 表格上执行了简单的写入操作。我们首先使用 Workbook() 方法创建了一个工作簿,然后使用 add_worksheet() 方法向该工作簿添加了一个新的 sheet 对象。通过在 worksheet 对象上使用 write() 方法,我们将数据添加到 Excel 表格中。我们还执行了一个小公式操作,使用 =SUM(B1:B4) 获取所有费用的总和。
我们所看到的是一个非常基础的编写 Excel 文件的例子。我们可以像在 Excel 表格上手动操作那样,通过编程执行更多的操作。现在,让我们学习如何在下一组菜谱中格式化 Excel 单元格。
格式化 Excel 单元格
单元格格式化有多种原因。在商业世界中,它们用于根据主题分组数据,或者在软件开发过程中,单元格被着色以指示功能是否完成或错误是否已修复。
准备工作
对于这个菜谱,我们将使用相同的 xlsxwriter 模块并格式化单元格。我们将学习如何添加和应用于单元格格式。
如何做...
-
我们继续使用支出示例来演示单元格的格式化。但首先让我们了解如何创建格式。格式是通过
add_format()方法添加的。以下代码示例显示了如何创建一个格式:format = workbook.add_format() format.set_bold() format.set_font_color('green')在前面的例子中,我们创建了一个单元格格式,其中单元格(应用了该格式的单元格)中的数据是加粗的,颜色设置为
绿色。 -
回到支出表格的例子,如何突出显示超过 150 的单元格?是的,我们可以通过创建一个格式来突出显示红色单元格的程序化方式来实现。但让我们按顺序来做。首先,我们创建一个表格并向其中添加数据,如下面的代码所示:
import xlsxwriter workbook = xlsxwriter.Workbook('cell_format.xlsx') worksheet = workbook.add_worksheet() expenses = ( ['Rent', 1000], ['Gas', 100], ['Food', 300], ['Gym', 50], ) row = 0 col = 0 for item, cost in (expenses): worksheet.write(row, col, item) worksheet.write(row, col + 1, cost) row += 1前面的代码将创建一个名为
cell_format.xlsx的 Excel 表格,并向其中添加支出。 -
现在,让我们创建一个格式,其中单元格被着色为蓝色,单元格值将显示为红色。我们可以使用
set_font_color()方法设置格式,但在以下示例中,我们通过'bg_color'和'font_color'等选项设置格式:format1 = workbook.add_format({'bg_color': 'blue', 'font_color': 'red'}) -
现在,唯一剩下的步骤是将此格式应用于超过 150 的支出。以下代码应用了格式并遵守了条件:
worksheet.conditional_format('B1:KB5', {'type': 'cell', 'criteria': '>=', 'value': 150, 'format': format1} ) workbook.close()当我们运行这个程序时,
cell_format.xlsx文件的内容看起来如下面的截图所示:![如何做...]()
还有更多...
太棒了,现在我们已经完成了单元格格式化,接下来我们如何继续在 Excel 表格中处理公式?
玩转 Excel 公式
我们用一个非常简单的例子来演示在 Excel 表格中使用公式。
准备工作
对于这个菜谱,我们将使用相同的 xlsxwriter 模块,并在单元格中添加公式。Excel 表格支持许多操作,例如获取数据的标准差、对数、获取趋势等,因此花时间了解大多数可用操作是值得的。
如何做...
你需要执行以下步骤:
我们使用一个简单的例子,其中我们使用 SUM() 公式添加一个数字列表,并将总和存储在单元格 A1 中:
import xlsxwriter
workbook = xlsxwriter.Workbook('formula.xlsx')
worksheet = workbook.add_worksheet()
worksheet.write_formula('A1', '=SUM(1, 2, 3)')
workbook.close()
工作原理...
当我们运行前面的代码时,会创建一个新的 Excel 文件,名为 formula.xlsx,其中单元格 A1 包含数字 6(1、2 和 3 的和)。
如前所述,我们可以使用 Excel 公式执行更复杂的数学运算。例如,你可以在 Excel 表格中规划你团队的年度 IT 预算。
还有更多...
如果我们不讨论图表并完成关于 Excel 工作表的章节,那就没有乐趣了。是的,在下一节中,我们将讨论如何使用 Excel 图表。
在 Excel 工作表中构建图表
Excel 工作表能够构建各种图表,包括折线图、柱状图和饼图等,帮助我们描绘趋势和可视化数据。
准备工作
对于这个菜谱,我们将使用相同的xlsxwriter模块,并使用模块中定义的方法来构建图表。
如何操作...
-
在这个例子中,我们将在 Excel 文件中写入一个列,其中填充了数字。我们可以取所有单元格的值并构建一个折线图:
import xlsxwriter workbook = xlsxwriter.Workbook('chart_line.xlsx') worksheet = workbook.add_worksheet() data = [10, 40, 50, 20, 10, 50] worksheet.write_column('A1', data) chart = workbook.add_chart({'type': 'line'}) chart.add_series({'values': '=Sheet1!$A$1:$A$6'}) worksheet.insert_chart('C1', chart) workbook.close()在前面的代码片段中,我们有一个整数值从
10到50的数据列表。像往常一样,我们使用Workbook()方法创建一个工作簿,并添加一个默认的Sheet1工作表。然后我们写入一个新的列,其中包含列表数据中的所有数字。 -
add_chart()方法随后定义了图表的类型。在这种情况下,它是一个折线图。add_chart()方法返回一个图表类型的对象。但仅仅创建一个对象是没有帮助的。图表将如何知道要绘制的数据点?这是通过add_series()方法实现的,该方法接受用于绘制图表的单元格值。在这个例子中,单元格范围从A1到A6(记住我们已将data列表中的所有数字添加到从A1开始的列A中)。 -
一旦图表准备就绪,它也应该添加到 Excel 工作表中。这是通过
insert_chart()方法实现的,该方法接受单元格名称和图表对象作为参数。在这个例子中,图表被插入到单元格C1。 -
当我们运行这个程序时,会创建一个新的文件
chart_line.xlsx,其中插入了折线图。以下截图显示了折线图和绘制的数据:![如何操作...]()
自动比较公司财务
我们在 Excel 工作表上的菜谱涵盖了多个方面,如读取/写入文件、格式化单元格、处理公式和图表。让我们用本章获得的知识解决一个美好的商业案例。
Monica 是 Xtel Inc 的财务经理,负责公司的收益。Xtel Inc 正在寻找资金,Monica 的任务是根据过去三年的收入报表比较公司的财务状况。这些数据将提交给投资者,以便他们可以就投资 Xtel Inc 做出适当的决定。获取三年的数据将很容易,但 Xtel 的 CFO 要求 Monica 获取过去 5 年的月度数据。Monica 担心手动比较 60 个月的财务数据!在本章中获得的知识,你认为你能帮助 Monica 吗?
准备工作
让我们用 Python 菜谱来解决 Monica 的问题。为此菜谱,我们将比较 Xtel Inc 过去三年的财务状况,并使用 Python 在 Excel 工作表中绘制比较图。我们将借助影响公司利润表的因素,即收入、成本和毛利润来完成这项工作。
如何做到这一点...
-
在以下代码中,我们首先在工作表中添加公司财务信息,例如收入、销售成本和毛利润。假设我们有一个包含这些数据的 Python 列表
data。 -
然后,我们使用柱状图绘制这些值,并使用 Excel 公式计算净增益百分比。以下代码片段正是 Monica 需要的:
import xlsxwriter workbook = xlsxwriter.Workbook('chart_column.xlsx') worksheet = workbook.add_worksheet() chart = workbook.add_chart({'type': 'column'}) data = [ ['Year', '2013', '2014', '2015'], ['Revenue', 100, 120, 125], ['COGS', 80, 90, 70], ['Profit', 20, 30, 55], ] worksheet.write_row('A1', data[0]) worksheet.write_row('A2', data[1]) worksheet.write_row('A3', data[2]) worksheet.write_row('A4', data[3]) chart.add_series({'values': '=Sheet1!$B$2:$B$4', 'name':'2013'}) chart.add_series({'values': '=Sheet1!$C$2:$C$4', 'name':'2014'}) chart.add_series({'values': '=Sheet1!$D$2:$D$4', 'name':'2015'}) worksheet.insert_chart('G1', chart) worksheet.write(5, 0, '% Gain') worksheet.write(5, 1, '=(B4/B2)*100') worksheet.write(5, 2, '=(C4/C2)*100') worksheet.write(5, 3, '=(D4/D2)*100') workbook.close() -
当我们运行这个 Python 程序时,会生成一个新的 Excel 工作表,比较公司三年来的财务表现,如下面的截图所示。这正是 Monica 想要的! 😃
![如何做到这一点...]()
它是如何工作的...
在前面的代码片段中,我们将公司财务数据收集到一个 Python 列表 data 中。
使用 xlsxwriter 模块,我们创建一个工作簿对象,然后使用 add_worksheet() 方法向其中添加一个工作表。
一旦我们有了工作表和数据,我们就开始使用 write_row() 方法将数据写入工作表。
我们还在工作表中添加了一个图表对象。这将帮助我们轻松地添加比较过去三年公司财务状况的条形图。我们使用 add_chart() 方法添加图表对象。
由于我们已经将数据填充到我们的工作表中,我们使用这些数据通过 add_series() 方法创建所有三年的条形图。add_series() 方法接受 Excel 单元格作为参数,并绘制这些单元格中的数据条形图。最后,我们使用 insert_chart() 方法将图表对象(以及条形图)插入到工作表中。
最后,我们使用 Excel 公式和 write() 方法添加所有年份的增益数据。
太棒了!这很简单,你为 Monica 做到了!她可以修改这个 Python 代码片段来比较她需要的所有数据点的公司财务状况,而且还能在非常短的时间内完成。确实,Xtel Inc 的 CEO 会非常高兴她的工作的!
还有更多...
好了,伙计们,这就是本章的全部内容。与 CSV 和 Excel 文件一起的乐趣永远不会停止。你可以用这些文件执行许多其他操作,它们可以在商业和软件开发世界中以不同的方式使用。因此,我强烈建议你尝试本章中讨论的模块,并根据自己的用例构建它们。下章再见!
第三章. 用 PDF 文件和文档发挥创意
Word 文档和 PDF 文件是商业专业人士最常用的文件格式之一。你想向客户发送发票或向供应商发送一组需求,企业通常会使用 PDF 文件和文档来满足他们的需求。让我们看看如何在 Python 中处理这些文件格式。
在本章中,我们将涵盖以下食谱:
-
从 PDF 文件中提取数据
-
创建和复制 PDF 文档
-
操作 PDF(添加页眉/页脚,合并,拆分,删除)
-
自动生成财务部门的工资条
-
读取 Word 文档
-
将数据写入 Word 文档(添加标题,图片,表格)
-
以自动化的方式为 HR 团队生成个性化的新员工入职培训
简介
在前几章中,我们研究了如何处理 CSV 文件,然后扩展了我们的范围来学习如何处理 Excel 工作表。虽然 CSV 文件是简单的文本格式,但 Excel 文件是二进制格式。
在本章中,我们将讨论另外两种二进制文件格式:.pdf和.docx。你将建立关于生成和读取 PDF 文件、复制它们甚至操作它们以构建自己的页眉和页脚格式的知识。你知道你可以通过简单的 Python 食谱合并多个 PDF 文件吗?
本章还将带你了解如何处理 Word 文档。它帮助你建立关于读取和将数据写入 Word 文件的知识。添加表格、图片、图表,你想要的这里都有。听起来很有趣?那么这一章绝对适合你!
具体来说,在本章中,我们将重点关注以下 Python 模块:
-
PyPDF2(pythonhosted.org/PyPDF2/) -
fpdf(pyfpdf.readthedocs.io/) -
python-docx(python-docx.readthedocs.io/en/latest/)
注意
尽管在本章中你将学习到.pdf和.docx文件所支持的多数操作,但我们无法全面涵盖它们。我建议你尝试本章讨论的库中剩余的 API。
从 PDF 文件中提取数据
PDF(便携式文档格式)是一种用于在文档中存储数据,与应用程序软件、硬件和操作系统无关的文件格式(因此得名,便携)。PDF 文档是固定布局的平面文件,包含文本和图形,并包含显示内容所需的信息。这个食谱将向你展示如何从 PDF 文件中提取信息并使用阅读器对象。
准备工作
要逐步执行此食谱,你需要安装 Python v2.7。要处理 PDF 文件,我们有PyPDF2,这是一个很好的模块,可以使用以下命令安装:
sudo pip install PyPDF2
已经安装了模块?那么,让我们开始吧!
如何操作...
-
在你的 Linux/Mac 计算机上,前往终端并使用 Vim 或选择你喜欢的编辑器。
-
我们首先从互联网上下载一个现有的 PDF 文件。让我们下载
diveintopython.pdf文件。注意
你可以在互联网上搜索这个文件并轻松获取它。如果你下载了这本书的代码示例,你也会得到这个文件。
-
现在,让我们编写创建 PDF 文件读取对象的 Python 代码:
import PyPDF2 from PyPDF2 import PdfFileReader pdf = open("diveintopython.pdf", 'rb') readerObj = PdfFileReader(pdf) print "PDF Reader Object is:", readerObj上述代码片段的输出如下:
![如何操作...]()
-
这很好;我们现在有了 PDF 文件的读者对象。让我们继续看看我们可以用这个对象做什么,基于以下 Python 代码:
print "Details of diveintopython book" print "Number of pages:", readerObj.getNumPages() print "Title:", readerObj.getDocumentInfo().title print "Author:", readerObj.getDocumentInfo().author上述代码片段的输出如下所示。看看我们是如何使用
PdfFileReader对象来获取文件元数据的:![如何操作...]()
-
好的,这很整洁!但我们都想提取文件的内容,不是吗?让我们继续看看如何通过一个简单的代码片段来实现这一点:
print "Reading Page 1" page = readerObj.getPage(1) print page.extractText()那么,我们在前面的代码中做了什么?我猜,
print语句很明显。是的,我们读取了diveintopython书的首页。以下屏幕截图显示了diveintopython书的第一页内容:![如何操作...]()
内容是部分性的(因为我无法将整个页面放入截图),但正如你所见,内容格式与 PDF 文件中的格式不同。这是 PDF 文件文本摘录的一个缺点。尽管不是 100%,但我们仍然可以以相当高的准确性获取 PDF 文件的内容。
-
让我们用
PdfFileReader对象做另一个有趣的操作。用它来获取书籍大纲怎么样?是的,这在 Python 中很容易实现:print "Book Outline" for heading in readerObj.getOutlines(): if type(heading) is not list: print dict(heading).get('/Title')
上述代码示例的输出可以在以下屏幕截图中看到。正如你所见,我们得到了书的完整大纲。一开始,我们看到Dive Into Python的介绍和目录。然后我们得到了从第一章到第十八章的所有章节名称,以及从附录 A到附录 H的附录:

它是如何工作的...
在第一个代码片段中,我们使用了PyPDF2模块中的PdfFileReader类来生成一个对象。这个对象打开了从 PDF 文件中读取和提取信息的大门。
在下一个代码片段中,我们使用了PdfFileReader对象来获取文件元数据。我们得到了书籍的详细信息,例如书的页数、书的标题以及作者的名字。
在第三个例子中,我们使用了从PdfFileReader类创建的读者对象,并指向diveintopython书的首页。这创建了一个由page变量表示的page对象。然后我们使用了page对象,并通过extractText()方法读取页面的内容。
最后,在这个菜谱的最后一段代码中,我们使用了 getOutlines() 方法来检索书籍的大纲作为一个数组。大纲不仅返回主题的标题,还返回主主题下的子主题。在我们的例子中,我们过滤了子主题,只打印了如图所示的书籍主大纲。
还有更多...
很酷,所以我们已经查看了一些可以使用 PdfFileReader 实现的功能。你学习了如何读取文件元数据,读取大纲,浏览 PDF 文件中的指定页面,以及提取文本信息。所有这些都很棒,但是嘿,我们还想创建新的 PDF 文件,对吧?
创建和复制 PDF 文档
使用 PDFs 添加更多价值,当你能够从零开始以编程方式创建它们时。让我们看看在本节中我们如何创建自己的 PDF 文件。
准备工作
我们将继续使用 PyPDF2 模块来完成这个菜谱,并将处理其 PdfFileWriter 和 PdfFileMerger 类。我们还将使用另一个模块 fpdf 来演示向 PDF 文件中添加内容。我们将在菜谱的后面讨论这个问题。
如何做到这一点...
-
我们可以通过多种方式创建 PDF 文件;在这个例子中,我们复制旧文件的内容来生成新的 PDF 文件。我们首先取一个现有的 PDF 文件--
Exercise.pdf。下面的截图显示了该文件的内容。它包含两页;第一页是一个技术练习,第二页给出了练习解决方案的可能提示,如图所示:![如何做到这一点...]()
-
我们将通过读取
Exercise.pdf并将练习的第一页内容写入新文件来创建一个新的 PDF 文件。我们还将向新创建的 PDF 文件中添加一个空白页。让我们先写一些代码:from PyPDF2 import PdfFileReader, PdfFileWriter infile = PdfFileReader(open('Exercise.pdf', 'rb')) outfile = PdfFileWriter()在前面的代码中,我们从
PyPDF2模块中导入了适当的类。由于我们需要读取Exercise.pdf文件并将内容写入新的 PDF 文件,我们需要PdfFileReader和PdfFileWriter类。然后我们使用open()方法以读取模式打开练习文件,并创建一个名为infile的读取对象。稍后,我们实例化PdfFileWriter并创建一个名为outfile的对象,该对象将用于将内容写入新文件。 -
让我们继续前进,并使用
addBlankPage()方法向outfile对象中添加一个空白页。页面的尺寸通常是 8.5 x 11 英寸,但在这个例子中,我们需要将它们转换为点单位,即 612 x 792 点。小贴士
点 是桌面出版点,也称为 PostScript 点。100 点 = 1.38 英寸。
-
接下来,我们使用
getPage()方法读取Exercise.pdf的第一页内容。一旦我们有了页面对象p,我们就将这个对象传递给写入对象。写入对象使用addPage()方法将内容添加到新文件中:outfile.addBlankPage(612, 792) p = infile.getPage(0) outfile.addPage(p)注意
到目前为止,我们已经创建了一个输出 PDF 文件对象
outfile,但还没有创建文件。 -
好的,太棒了!现在我们有了写入对象和要写入新 PDF 文件的内容。因此,我们使用
open()方法创建一个新的 PDF 文件,并使用写入对象写入内容,生成新的 PDF 文件myPdf.pdf(这是 PDF 文件在文件系统上的可用位置,我们可以查看)。以下代码实现了这一点。在这里,f是新创建的 PDF 文件的文件句柄:with open('myPdf.pdf', 'wb') as f: outfile.write(f) f.close()以下截图显示了新创建的 PDF 文件的内容。正如你所见,第一页是空白页,第二页包含了
Exercise.pdf文件的第一页内容。太棒了,不是吗!![如何操作...]()
-
但是,嘿,我们总是需要从头开始创建一个 PDF 文件!是的,还有另一种创建 PDF 文件的方法。为此,我们将使用以下命令安装一个新的模块
fpdf:pip install fpdf -
让我们看看以下代码片段中给出的一个非常基本的例子:
import fpdf from fpdf import FPDF pdf = FPDF(format='letter')在这个例子中,我们从
fpdf模块实例化FPDF()类并创建一个对象,pdf,它本质上代表了 PDF 文件。在创建对象时,我们还定义了 PDF 文件的默认格式,即letter。fpdf模块支持多种格式,例如A3、A4、A5、Letter和Legal。 -
接下来,我们开始将内容插入到文件中。但是,嘿,文件仍然是空的,所以在我们写入内容之前,我们使用
add_page()方法插入一个新页面,并使用set_font()方法设置字体。我们将字体设置为Arial,大小为12:pdf.add_page() pdf.set_font("Arial", size=12) -
现在,我们实际上开始使用
cell()方法将内容写入文件。单元格是一个包含一些文本的矩形区域。所以,正如你在以下代码中所见,我们添加了一行新内容欢迎来到自动化!,然后紧接着又添加了一行由 Chetan 创建。你必须注意一些事情。200 x 10 是单元格的高度和宽度。ln=1指定了新的一行,align=C将文本对齐到页面中心。当你向单元格添加长文本时可能会遇到问题,但fpdf模块有一个multi_cell()方法,它可以自动使用可用的有效页面宽度断开长文本行。你总是可以计算出页面宽度:pdf.cell(200, 10, txt="Welcome to Automate It!", ln=1, align="C") pdf.cell(200,10,'Created by Chetan',0,1,'C') pdf.output("automateit.pdf")上述代码的输出是一个包含以下截图所示内容的 PDF 文件:
![如何操作...]()
操作 PDF(添加页眉/页脚、合并、拆分、删除)
你是否想过能否在几秒钟内以编程方式合并 PDF 文件?或者能否迅速更新许多 PDF 文件的头和尾?在这个菜谱中,让我们继续做一些有趣且最常执行的操作,即对 PDF 文件进行操作。
准备工作
对于这个菜谱,我们将使用为早期菜谱安装的PyPDF2和fpdf模块。
如何操作...
-
让我们从使用
PyPDF2的PdfFileMerge类开始工作。我们使用这个类来合并多个 PDF 文件。以下代码示例做了完全相同的事情:from PyPDF2 import PdfFileReader, PdfFileMerger import os merger = PdfFileMerger() files = [x for x in os.listdir('.') if x.endswith('.pdf')] for fname in sorted(files): merger.append(PdfFileReader(open( os.path.join('.', fname), 'rb'))) merger.write("output.pdf") -
如果你运行前面的代码片段,它将生成一个新的文件,
output.pdf,该文件将合并多个 PDF 文件。打开output.pdf文件并亲自查看。 -
那真酷!现在,我们来看看如何给 PDF 文件添加页眉和页脚。让我们调用前面菜谱中使用的
fpdf模块来生成 PDF 文件(automateit.pdf)。现在,如果我们需要创建一个带有页眉和页脚信息的类似文件,怎么办?下面的代码正是这样做的:from fpdf import FPDF class PDF(FPDF): def footer(self): self.set_y(-15) self.set_font('Arial', 'I', 8) self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C') def header(self): self.set_font('Arial', 'B', 15) self.cell(80) self.cell(30, 10, 'Automate It!', 1, 0, 'C') self.ln(20) pdf = PDF(format='A5') pdf.add_page() pdf.set_font("Times", size=12) for i in range(1, 50): pdf.cell(0, 10, "This my new line. line number is: %s" % i, ln=1, align='C') pdf.output("header_footer.pdf")前面代码片段的输出可以在下面的屏幕截图中查看。看看我们如何能够操纵我们的 PDF 文档的页眉和页脚:
![如何操作...]()
-
哇!真不错;现在,让我们快速覆盖一些其他操作。记得我们在前面的菜谱中向
myPdf.pdf文件添加了一个空白页?如果我想从 PDF 文件中移除空白页怎么办?infile = PdfFileReader('myPdf.pdf', 'rb') output = PdfFileWriter() for i in xrange(infile.getNumPages()): p = infile.getPage(i) if p.getContents(): output.addPage(p) with open('myPdf_wo_blank.pdf', 'wb') as f: output.write(f)如果你运行 Python 代码并查看
myPdf_wo_blank.pdf的内容,你将只会看到一页,空白页将被移除。 -
现在,如果我们想向我们的文件添加特定的元信息怎么办?我们应该能够轻松地使用以下 Python 代码编辑 PDF 文件的元数据:
from PyPDF2 import PdfFileMerger, PdfFileReader mergerObj = PdfFileMerger() fp = open('myPdf.pdf', 'wb') metadata = {u'/edited':u'ByPdfFileMerger',} mergerObj.addMetadata(metadata) mergerObj.write(fp) fp.close() pdf = open("myPdf.pdf", 'rb') readerObj = PdfFileReader(pdf) print "Document Info:", readerObj.getDocumentInfo() pdf.close()前面代码的输出可以在下面的屏幕截图中看到。看看我们如何成功地将编辑后的元数据添加到我们的 PDF 文件中。
![如何操作...]()
-
从开发角度来看,另一个很好的选项是能够在 PDF 文件中旋转页面。是的,我们也可以使用
PyPDF2模块做到这一点。以下代码将Exercise.pdf的第一页逆时针旋转90度:from PyPDF2 import PdfFileReader fp = open('Exercise.pdf', 'rb') readerObj = PdfFileReader(fp) page = readerObj.getPage(0) page.rotateCounterClockwise(90) writer = PdfFileWriter() writer.addPage(page) fw = open('RotatedExercise.pdf', 'wb') writer.write(fw) fw.close() fp.close()以下屏幕截图显示了文件逆时针旋转后的样子:
![如何操作...]()
它是如何工作的...
在第一个代码片段中,我们创建了PdfFileMerger类的对象,命名为merger。然后我们遍历当前工作目录中的所有文件,并使用 Python 的列表推导式选择所有扩展名为.pdf的文件。
我们首先对文件进行了排序,并运行了一个循环,一次选择一个文件,读取它,并将其追加到merger对象中。
一旦所有文件都合并完成,我们就使用了merger对象的write()方法来生成一个单独的合并文件:output.pdf。
注意
在这个例子中,我们不需要为output.pdf文件创建文件句柄。合并器内部处理它,并生成一个漂亮的 PDF 文件。
在第二个代码片段中,我们执行了多个操作:
-
我们继承了标准的
FPDF类,并编写了自己的类,PDF。 -
我们重写了两个方法--
header()和footer()--来定义当我们使用 PDF 类创建新的 PDF 文件时,页眉和页脚应该看起来是什么样子。 -
在
footer()方法中,我们为每一页添加了页码。页码以斜体形式显示,字体大小为8,使用Arial字体。我们还将其居中,并设置为在页面底部上方 15 毫米处显示。 -
在
header()方法中,我们创建了标题单元格并将其定位到最右侧。标题为Automate It,字体为Arial和加粗,字号为15。标题也在单元格的上下文中居中。最后,我们在标题下方添加了 20 像素的换行。 -
然后,我们创建了自己的 PDF 文件,页面格式设置为
A5。 -
PDF 的内容将是
This is my new line. Line number is <line_no>,字体设置为Times,字号为12。 -
生成的 PDF 看起来如下截图所示。注意,页面大小为
A5,因此页面只能添加 15 行。如果它是信纸大小,那么它至少可以容纳一页上的 20 行。
在本菜谱的第三个代码示例中,getContents()执行了检查给定页面是否有内容的临界任务。因此,当我们开始读取旧的 PDF 文件时,我们会检查页面的内容。如果没有内容,该页面将被忽略,不会添加到新的 PDF 文件中。
在第四个代码片段中,我们使用addMetadata()方法将元数据信息添加到我们的 PDF 文件中。addMetadata()方法接受一个键值对作为参数,其中我们可以传递需要修改的 PDF 文件属性。在我们的例子中,我们使用该方法将/edited元数据字段添加到 PDF 文件中。
对于最后的例子,我认为代码的其他部分都是不言自明的,除了rotateCounterClockwise()的使用,它实际上会旋转页面。我们也可以使用rotateClockwise()将页面顺时针旋转。
更多...
你已经学习了如何读取和写入 PDF 文件,并且了解了多种操作 PDF 文件的方法。现在是时候用一个现实生活中的例子来将这些知识应用到实践中了。
自动化财务部门的工资单生成
让我们以一个组织用例为例,其中公司的财务经理希望使工资单生成过程更快。他意识到这项任务不仅单调乏味,而且耗时。随着更多员工预期加入公司,这将变得更加困难。他选择自动化这个过程,并找到你。你该如何帮助?
嗯,通过本章所学的内容,我敢打赌这对你们来说将是一件轻而易举的事情!让我们着手解决这个问题。
准备工作
对于这个例子,我们不需要任何特殊的模块。之前菜谱中安装的所有模块对我们来说已经足够了,你不这么认为吗?
如何实现...
让我们首先考虑一个工资单模板。工资单包含什么内容?
-
员工信息
-
公司的支付
-
扣除(支付给政府的税款)
-
总支付金额
因此,我们需要获取员工信息,添加支付和扣除的表格,并添加一个月支付的总工资条目。
这个场景的代码实现可能如下所示:
from datetime import datetime
employee_data = [
{ 'id': 123, 'name': 'John Sally', 'payment': 10000,
'tax': 3000, 'total': 7000 },
{ 'id': 245, 'name': 'Robert Langford', 'payment': 12000,
'tax': 4000, 'total': 8000 },
]
from fpdf import FPDF, HTMLMixin
class PaySlip(FPDF, HTMLMixin):
def footer(self):
self.set_y(-15)
self.set_font('Arial', 'I', 8)
self.cell(0, 10, 'Page %s' % self.page_no(), 0, 0, 'C')
def header(self):
self.set_font('Arial', 'B', 15)
self.cell(80)
self.cell(30, 10, 'Google', 1, 0, 'C')
self.ln(20)
def generate_payslip(data):
month = datetime.now().strftime("%B")
year = datetime.now().strftime("%Y")
pdf = PaySlip(format='letter')
pdf.add_page()
pdf.set_font("Times", size=12)
pdf.cell(200, 10, txt="Pay Slip for %s, %s" %
(month, year), ln=3, align="C")
pdf.cell(50)
pdf.cell(100, 10, txt="Employeed Id: %s" % data['id'],
ln=1, align='L')
pdf.cell(50)
pdf.cell(100, 10, txt="Employeed Name: %s" %
data['name'], ln=3, align='L')
html = """
<table border="0" align="center" width="50%">
<thead><tr><th align="left" width="50%">
Pay Slip Details</th><th align="right" width="50%">
Amount in USD</th></tr></thead>
<tbody>
<tr><td>Payments</td><td align="right">""" +
str(data['payment']) + """</td></tr>
<tr><td>Tax</td><td align="right">""" +
str(data['tax']) + """</td></tr>
<tr><td>Total</td><td align="right">""" +
str(data['total']) + """</td></tr>
</tbody>
</table>
"""
pdf.write_html(html)
pdf.output('payslip_%s.pdf' % data['id'])
for emp in employee_data:
generate_payslip(emp)
这是带有标题、页脚和工资单详情的工资单的外观:

它是如何工作的...
我们首先在字典employee_data中获取了员工数据。在现实场景中,它可能来自员工表,并且将通过 SQL 查询检索。我们编写了自己的PaySlip类,它继承自FPDF类,并定义了自己的页眉和页脚。
然后,我们编写了自己的方法来生成工资条。这包括顶部的页眉,包含公司名称(在这个例子中,比如说谷歌)和工资条适用的期间。我们还添加了员工 ID和员工姓名。
现在,这很有趣。我们创建了一个 HTML 文档,使用add_html()方法生成一个表格,并将支付、税费和总工资信息添加到工资条中。
最后,我们使用output()方法将所有这些信息添加到 PDF 文件中,并将工资条命名为payslip_<employee_id>。
还有更多...
尽管我们编写了示例代码,但您认为有什么遗漏吗?是的,我们没有加密 PDF。用密码保护工资条总是一个好主意,这样除了员工外,没有人能够查看它。以下代码将帮助我们加密文件。在这个例子中,我们加密了Exercise.pdf,用密码P@$$w0rd保护,并将其重命名为EncryptExercise.pdf:
from PyPDF2 import PdfFileWriter, PdfFileReader
fp = open('Exercise.pdf', 'rb')
readerObj = PdfFileReader(fp)
writer = PdfFileWriter()
for page in range(readerObj.numPages):
writer.addPage(readerObj.getPage(page))
writer.encrypt('P@$$w0rd')
newfp = open('EncryptExercise.pdf', 'wb')
writer.write(newfp)
newfp.close()
fp.close()
如果我们打开受保护的文件,它将要求您输入密码:

嗯,这是一个很棒的解决方案!我相信您的财务经理会很高兴!想知道如何解密受保护的文件吗?我将把它留给你;这相当直接。阅读文档说明。
我们已经到达了关于处理 PDF 文件的部分的结尾。PDF 文件本质上以二进制格式存储数据,并支持我们讨论的多个操作。在下一节中,我们将开始处理文档(.docx)并欣赏它们能提供什么!
阅读 Word 文档
如您所知,从 Office 2007 开始,Microsoft Office 开始为 Word 文档提供一个新的扩展名,即.docx。随着这一变化,文档转移到基于 XML 的文件格式(Office Open XML)并使用 ZIP 压缩。当商业社区要求一个开放文件格式以帮助在不同应用程序之间传输数据时,Microsoft 做出了这一改变。因此,让我们从 DOCX 文件开始我们的旅程!
准备工作
在这个菜谱中,我们将使用python-docx模块来读取 Word 文档。python-docx是一个综合模块,它可以在 Word 文档上执行读取和写入操作。让我们用我们最喜欢的工具pip安装这个模块:
pip install python-docx
如何操作...
-
我们首先创建了自己的 Word 文档。这与我们在处理 PDF 文件时看到的上一个部分中的练习相同。除了我们向其中添加了一个表格并将其存储为
WExercise.docx之外。它看起来如下:![如何操作...]()
-
现在我们继续读取
WExercise.docx文件。以下代码将帮助我们获取指向WExercise.docx文件的对象:import docx doc = docx.Document('WExercise.docx') print "Document Object:", doc上述代码的输出在下面的屏幕截图中显示。阅读 Word 文档在概念上与在 Python 中读取文件非常相似。就像我们使用
open()方法创建文件句柄一样,在这个代码片段中我们创建了一个文档句柄:![如何做...]()
-
现在,如果我们想获取文档的基本信息,我们可以使用前面代码中的文档对象
doc。例如,如果我们想检索文档的标题,我们可以使用以下代码。如果你仔细查看代码,我们会使用paragraphs对象来获取文本。段落是文档中的行。假设文档的标题是文档的第一行,我们获取文档中段落的0索引并调用text属性来获取标题的文本:import docx doc = docx.Document('WExercise.docx') print "Document Object:", doc print "Title of the document:" print doc.paragraphs[0].text注意以下屏幕截图中的输出,我们如何打印练习文档的标题:
![如何做...]()
-
哇,这太酷了!让我们继续并读取 Word 文档中我们关心的其他属性。让我们使用相同的
doc对象:print "Attributes of the document" print "Author:", doc.core_properties.author print "Date Created:", doc.core_properties.created print "Document Revision:", doc.core_properties.revision上述代码的输出在下面的屏幕截图中显示。文档的作者是
Chetan Giridhar。正如你可能已经观察到的,它是在 7 月 2 日早上 4:24 创建的。此外,请注意文档已被修改了五次,这是文档的第五次修订:![如何做...]()
-
好吧,我现在将变得更加大胆,并读取文档中的表格。
python-docx模块非常适合读取表格。看看下面的代码片段:table = doc.tables[0] print "Column 1:" for i in range(len(table.rows)): print table.rows[i].cells[0].paragraphs[0].text print "Column 2:" for i in range(len(table.rows)): print table.rows[i].cells[1].paragraphs[0].text print "Column 3:" for i in range(len(table.rows)): print table.rows[i].cells[2].paragraphs[0].text -
在前面的例子中,我们使用
tables对象来读取文档中的表格。由于整个文档中只有一个表格,我们使用tables[0]获取第一个索引并将对象存储在table变量中。 -
每个表格都包含行和列,并且可以使用
table.rows或table.columns访问它们。我们使用table.rows来获取表格中的行数。 -
接下来,我们遍历所有行,并使用
table.rows[index].cells[index].paragraphs[0].text读取单元格中的文本。我们需要paragraphs对象,因为它包含单元格的实际文本。(我们再次使用了第 0 个索引,因为假设每个单元格只有一行数据。) -
从第一个
for循环中,你可以识别出我们在读取所有三行,但读取每行的第一个单元格。本质上,我们正在读取列值。 -
上述代码片段的输出显示了所有列及其值:
![如何做...]()
-
太棒了!所以,我们现在已经成为阅读 Word 文档的专家。但如果我们不能将数据写入 Word 文档,那又有什么用呢?让我们看看如何在下一个菜谱中写入或创建
.docx文档。
将数据写入 Word 文档(添加标题、图片、表格)
使用python-docx模块读取文件非常简单。现在,让我们将注意力转向编写 Word 文档。在本节中,我们将对文档执行多个操作。
准备工作
对于这个菜谱,我们将使用相同的出色的 Python 模块,python-docx。我们不需要在设置上花费太多时间。让我们开始吧!
如何做到这一点...
-
我们从创建一个
.docx文件并添加一个标题到它开始。以下代码执行了这个操作:from docx import Document document = Document() document.add_heading('Test Document from Docx', 0) document.save('testDoc.docx')文档看起来就是这样:
![如何做到这一点...]()
-
如果你查看截图,你会看到一个包含字符串的新文档正在创建。观察截图是如何指示它被格式化为标题文本的。我们是如何做到这一点的?你在我们的 Python 代码的第三行看到
0了吗?它谈论的是标题类型,并相应地格式化文本。0表示标题;1和2表示带有标题 1或标题 2的文本。 -
让我们继续前进,并在文档中添加一行新内容。我们用一些粗体字和一些斜体字装饰了这个字符串:
document = Document('testDoc.docx') p = document.add_paragraph('A plain paragraph having some ') p.add_run('bold words').bold = True p.add_run(' and italics.').italic = True document.save('testDoc.docx') -
文档现在看起来如下所示。观察添加的正常样式的行。文本中的一些词是粗体的,少数是斜体的:
![如何做到这一点...]()
-
好的,很好。让我们给我们的文档添加另一个子主题。看看下面的代码实现。在这里,我们创建了一个带有标题 1样式的子主题,并在该主题下添加了一行新内容:
document = Document('testDoc.docx') document.add_heading('Lets talk about Python language', level=1) document.add_paragraph('First lets see the Python logo', style='ListBullet') document.save('testDoc.docx') -
文档现在看起来如下所示。在截图时,我点击了截图中的标题 1行。注意子主题是如何被格式化为项目符号的:
![如何做到这一点...]()
-
经常需要在文档中包含图片。现在这真的非常简单。查看以下代码来完成这一步:
from docx.shared import Inches document = Document('testDoc.docx') document.add_picture('python.png', width=Inches(1.25)) document.save('testDoc.docx')如果你在你自己的解释器上运行 Python 代码,你会看到文档现在包含了一个漂亮的 Python 标志。请注意,我在截图之前点击了图片以吸引你的注意,所以这不是由库完成的:
![如何做到这一点...]()
-
最后但同样重要的是,我们可能还想在我们的文档中添加表格,对吧?让我们这么做。以下代码演示了如何向 DOCX 文件添加表格:
document = Document('testDoc.docx') table = document.add_table(rows=1, cols=3) table.style = 'TableGrid' data = {'id':1, 'items':'apple', 'price':50} headings = table.rows[0].cells headings[0].text = 'Id' headings[1].text = 'Items' headings[2].text = 'Price' row = table.add_row().cells row[0].text = str(data.get('id')) row[1].text = data.get('items') row[2].text = str(data.get('price')) document.save('testDoc.docx')
以下截图显示了完整的文档以及表格的外观。真不错!

它是如何工作的...
在第一个代码片段中,我们从Document类创建了document对象。然后我们使用这个对象添加了一个新的标题,其中包含文本Hi this is a nice text document。我知道这并不是一个文本文档,而只是一个字符串。
在第二个例子中,添加新行是通过add_paragraph()方法完成的(记住,在上一节中使用了paragraphs来从 Word 文档中读取行)。那么我们是如何得到样式的呢?可以通过设置add_run()方法的属性bold和italic为true来实现。
在第四个例子中,我们只是使用了add_image()方法将图片添加到文档中。我们还可以设置图片的高度和宽度为英寸。为此,我们导入了一个新的类Inches,并将图片的宽度设置为 1.25 英寸。简单又整洁!
在最后的例子中,我们通过以下步骤将表格添加到文档中:
-
我们首先使用
add_table()方法创建了一个表格对象。我们配置了表格包含一行和三列。我们还对表格进行了样式设置,使其成为一个网格表格。 -
正如我们在上一节中看到的,
table对象有rows和columns对象。我们使用这些对象来填充表格中的字典data。 -
然后我们在表格中添加了一个标题。标题是表格的第一行,因此我们使用了
table.rows[0]来填充其中的数据。我们通过Id填充了第一列,通过Items填充了第二列,通过Price填充了第三列。 -
在标题之后,我们添加了一行新行,并从数据字典中填充了这一行的单元格。
-
如果你看看截图,文档现在添加了一个表格,其中 ID 为
1,项目为apple,价格为50。
还有更多...
在上一节中你所学到的都是直接、经常做的、日常的将数据写入 DOCX 文件的操作。我们可以以编程方式执行更多操作,就像我们习惯在 Word 文档上手动操作一样。现在让我们将所学知识结合到一个商业案例中。
以自动化的方式为 HR 团队生成个性化的新员工入职培训
作为你们公司的 HR 经理,你负责新员工的入职培训。你看到每个月至少有 15-20 名新员工加入你们组织。一旦他们在公司完成一个月的工作,你就必须通过入职培训向他们介绍公司的政策。
为了这个目的,你需要给他们发送一份包含新员工入职培训详细信息的个性化文档。从数据库中逐个获取员工的详细信息是繁琐的;更不用说,你还得根据不同的部门筛选出即将进行入职培训的员工。
所有这些都很耗时,你觉得这个过程可以很容易地自动化。让我们看看我们如何利用本章学到的知识来自动化这个过程。
准备工作
对于这个菜谱,我们将使用python-docx,这在我们的前一个菜谱中非常有帮助。因此,我们不需要安装任何新的模块。
如何做到这一点...
-
让我们先分解问题。首先,我们需要收集需要参加入职培训的员工。接下来,我们需要知道他们的部门并查看基于部门的日程模板。一旦有了这些细节,我们需要将这些信息整理成文档。
-
查看此场景的代码实现:
from docx import Document employee_data = [ {'id': 123, 'name': 'John Sally', 'department': 'Operations', 'isDue': True}, {'id': 245, 'name': 'Robert Langford', 'department': 'Software', 'isDue': False}, ] agenda = { "Operations": ["SAP Overview", "Inventory Management"], "Software": ["C/C++ Overview", "Computer Architecture"], "Hardware": ["Computer Aided Tools", "Hardware Design"] } def generate_document(employee_data, agenda): document = Document() for emp in employee_data: if emp['isDue']: name = emp['name'] document.add_heading('Your New Hire Orientationn', level=1) document.add_paragraph('Dear %s,' % name) document.add_paragraph('Welcome to Google Inc. You have been selected for our new hire orientation.') document.add_paragraph('Based on your department you will go through below sessions:') department = emp['department'] for session in agenda[department]: document.add_paragraph( session , style='ListBullet' ) document.add_paragraph('Thanks,n HR Manager') document.save('orientation_%s.docx' % emp['id']) generate_document(employee_data, agenda) -
如果您运行此代码片段,您的文档将呈现有关入职的所有相关细节。酷!但它是如何工作的?我们将在如何工作部分中看到。
![如何操作...]()
它是如何工作的...
在前面的代码中,我们有一个预填充的字典employee_data,其中包含员工信息。此字典还包含有关员工是否需要参加入职培训的信息。我们还有一个agenda字典,它根据部门作为不同会议的模板。在这个例子中,我们手动将这些数据添加到 Python 字典中,但在现实世界中,需要从您组织的数据库中提取这些数据。
接下来,我们编写一个generate_document()方法,该方法接受employee_data和agenda。它遍历所有员工并检查是否有员工需要参加入职培训,然后开始编写文档。首先添加标题,然后是针对员工的个性化致辞,然后根据员工的部门调整到需要参加的会议。
最后,所有文本都保存为名为orientation_<emp_id>.docx的文档文件。
那真是太酷了!想象一下您节省的时间。作为人力资源经理,您有多高兴?您获得了一些新技能,并迅速将其应用到团队的利益中。太棒了!
我们已经完成了关于读取、编写和操作 PDF 文件和文档的章节。希望您喜欢它,并学到了许多可以应用到您在办公室或学校工作中的应用新知识!当然,您可以做得更多;我强烈鼓励您尝试这些模块,并享受其中的乐趣。
第四章. 玩转短信和语音通知
云电话是将您的电话系统迁移到云的技术。这确保了我们现在可以探索使用短信和语音通知自动化的可能性。本章从云电话的介绍开始,并涵盖使用 Python 在文本和语音消息中自动化业务用例。
本章将涵盖以下食谱:
-
在云电话服务提供商处注册
-
发送短信
-
接收短信消息
-
Domino's 的短信工作流程
-
发送语音消息
-
接收语音通话
-
构建自己的客户服务软件
简介
在前几章中,我们探讨了处理纯文本和逗号分隔值(CSV)文件的方法,然后我们扩展了我们的范围,学习如何处理 Excel 工作表、Word 文档和 PDF 文件。Excel、Word 和 PDF 文件以二进制格式提供,并支持数学运算、表格、图表和其他许多操作。我们还探讨了可以用 Python 自动化的有趣业务用例。
在本章中,我们将探讨一个有趣的云电话世界。随着互联网的出现,企业也将他们的通信系统迁移到了云端。基于互联网的托管电话已经取代了传统的电话设备,如 PBX。这为使用云电话解决业务需求打开了可能性,而且还是在 Python 中。使用云电话为您的业务提供让您能够同时进行多个通话和短信的服务。呼叫转移、录音、批量短信是云电话可以利用的一些令人惊叹的功能。云电话确保在不影响质量、成本的情况下管理业务需求,而且无需投资任何额外的基础设施。
本章中的食谱将专注于帮助我们发送/接收短信和语音通话的 Python 模块。我们将学习如何注册云电话服务提供商,使用 Python API,并自动化有趣的业务流程。具体来说,我们将与Twilio电话服务提供商合作,并在本章中使用以下 Python 模块:
-
Flask(flask.pocoo.org/) -
twilio(pypi.python.org/pypi/twilio)
注意
虽然我们将学习 Twilio 云电话服务提供商,但还有其他提供商。每个提供商都有一套优秀的 API,并且与 Python 库兼容。如果您选择实现自己的解决方案,您可以在www.capterra.com/telephony-software/查看其中的一些。
在云电话服务提供商处注册
要使用基于云的电话服务,我们需要在电话服务提供商处注册一个账户。你可以在网上搜索时找到一些流行的云提供商。对于这一章,我们使用 Twilio (www.twilio.com/)。让我们看看如何注册一个账户。
准备工作
为了使用云电话 API,我们需要在 Twilio 注册一个账户,以便我们能够获取AccountSID和AuthToken。我们还需要为短信和语音部分的食谱租用号码。让我们在这个食谱中学习如何使用 Twilio API。
如何操作...
-
在你的电脑上,打开你喜欢的浏览器并浏览到
www.twilio.com/try-twilio:![如何操作...]()
-
一旦你创建了一个账户,登录并从你的账户仪表板上的下拉菜单中的账单页面添加一些资金。如果你已经登录,也可以直接浏览到
www.twilio.com/user/billing的账单页面:![如何操作...]()
-
要进行 Twilio API 调用,我们需要AccountSID和AuthToken。我们可以通过在下拉菜单中点击账户部分或直接浏览到
www.twilio.com/user/account/settings来获取这些详细信息。在这个页面上,你会看到以下截图所示的 API 凭证。现在不用担心双因素认证,但请确保你的账户启用了SSL 证书验证:![如何操作...]()
-
好的,那很好。现在,让我们通过直接浏览到
www.twilio.com/user/account/phone-numbers/search来租用一个电话号码。 -
一旦你点击购买号码,你将看到一个页面,你可以根据国家、前缀或区号来租用一个号码。在这里,我选择了国家为美国,前缀为
510。我还在租用一个能够处理短信和语音通话的号码:![如何操作...]()
-
现在,点击页面底部的高级搜索选项以获取所有选项,如下截图所示。你可以使用默认设置全部或选择租用本地或免费电话号码。根据地区或国家的当地规则,租用号码可能需要你提供地址证明,但你不一定需要所有这些,可以选择任何选项。测试号码是从特定国家新添加到 Twilio 的号码,用于支持一系列国家。在这个食谱中,我们不需要关心这个选项,所以保持空白:
![如何操作...]()
-
当您点击 搜索 时,此页面将带您到结果屏幕,您可以选择购买任何可用的号码。请确保您购买了一个同时启用了 语音 和 短信 的号码。免费电话号码成本较高,因此最好为此练习购买一个 本地 号码:
![如何操作...]()
-
太棒了!如果您已经完成了这些,那么恭喜您!现在您已经准备好开始学习如何使用 Python API 来使用短信和语音通话了。
工作原理...
如前所述,要使用 Twilio API,我们需要注册一个账户。Twilio 会为我们创建一个账户,并为我们提供一个唯一的 AccountSID 和 AuthToken,这些信息将验证我们的请求,并确保我们的账户为所进行的 API 调用付费。
Twilio 中的电话号码用作主叫 ID 来发送短信或语音通话。主叫 ID(也称为来电号码识别)是显示在被叫方设备(固定电话或手机)上的来电号码。在这种情况下,我们将使用我们从 Twilio 租用的号码作为主叫 ID。
更多内容...
我们已经了解了如何创建账户、获取 AccountSID 和 AuthToken 以及使用 Twilio 生成电话号码。现在,让我们在下一个菜谱中使用这些信息。
发送短信
让我们看看我们关于处理短信的第一个菜谱。在这个菜谱中,我们将通过 SMS 向收件人发送消息。请注意,您可能现在还需要为执行下一组操作向您的账户收费。
准备工作
我们首先使用 Twilio API 发送短信。让我们在本节中看看如何操作。在这样做之前,让我们按照以下步骤创建一个 Python 虚拟环境,并使用 pip 安装 flask 和 twilio 模块。
注意,我们将使用 flask 来托管一个简单的 Web 服务,该服务将由电话服务提供商 Twilio 调用。然后,flask 应用程序将根据 Twilio 的回调执行所需的企业操作。当我们查看菜谱时,我们将了解更多关于此的信息。
设置虚拟环境并安装模块需要在您的计算机命令行中完成。我们使用 Python pip 来安装 flask 和 twilio 模块:
virtualenv ~/book/ch05/
source ~/book/ch05/
pip install flask
Collecting flask==0.10.1
Downloading Flask-0.10.1.tar.gz (544kB)
100% |████████████████████████████████| 544kB 774kB/s
Collecting Werkzeug>=0.7 (from flask==0.10.1)
Downloading Werkzeug-0.11.10-py2.py3-none-any.whl (306kB)
100% |████████████████████████████████| 307kB 1.5MB/s
Collecting Jinja2>=2.4 (from flask==0.10.1)
Downloading Jinja2-2.8-py2.py3-none-any.whl (263kB)
100% |████████████████████████████████| 266kB 2.4MB/s
Collecting itsdangerous>=0.21 (from flask==0.10.1)
Downloading itsdangerous-0.24.tar.gz (46kB)
100% |████████████████████████████████| 49kB 6.2MB/s
Collecting MarkupSafe (from Jinja2>=2.4->flask==0.10.1)
Downloading MarkupSafe-0.23.tar.gz
Building wheels for collected packages: flask, itsdangerous, MarkupSafe
Running setup.py bdist_wheel for flask
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/b6/09/65/5fcf16f74f334a215447c26769e291c41883862fe0dc7c1430
Running setup.py bdist_wheel for itsdangerous
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/fc/a8/66/24d655233c757e178d45dea2de22a04c6d92766abfb741129a
Running setup.py bdist_wheel for MarkupSafe
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/a3/fa/dc/0198eed9ad95489b8a4f45d14dd5d2aee3f8984e46862c5748
Successfully built flask itsdangerous MarkupSafe
Installing collected packages: Werkzeug, MarkupSafe, Jinja2, itsdangerous, flask
Successfully installed Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.10 flask-0.10.1 itsdangerous-0.24
接下来,使用以下命令安装 twilio:
pip install Twilio
Collecting twilio
Downloading twilio-5.4.0.tar.gz (193kB)
100% |████████████████████████████████| 196kB 2.2MB/s
Collecting httplib2>=0.7 (from twilio)
Downloading httplib2-0.9.2.zip (210kB)
100% |████████████████████████████████| 212kB 2.0MB/s
Collecting six (from twilio)
Downloading six-1.10.0-py2.py3-none-any.whl
Collecting pytz (from twilio)
Downloading pytz-2016.6.1-py2.py3-none-any.whl (481kB)
100% |████████████████████████████████| 483kB 1.0MB/s
Building wheels for collected packages: twilio, httplib2
Running setup.py bdist_wheel for twilio
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/91/16/85/2ea21326cf1aad3e32f88d9e81723088e1e43ceb9eac935a9b
Running setup.py bdist_wheel for httplib2
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/c7/67/60/e0be8ccfc1e08f8ff1f50d99ea5378e204580ea77b0169fb55
Successfully built twilio httplib2
Installing collected packages: httplib2, six, pytz, twilio
Successfully installed httplib2-0.9.2 pytz-2016.6.1 six-1.10.0 twilio-5.4.0
如何操作...
-
让我们从创建一个包含我们的 Twilio AccountSID 和 AuthToken 的配置文件开始。我们还将我们的租用号码作为主叫 ID 添加到配置中,并添加要发送消息的定制号码。您可以在该字段中添加自己的号码以查看它是否适合您。
-
我们的配置文件如下所示,我们将其存储为
config.py:TWILIO_ACCOUNT_SID = 'Account SID' TWILIO_AUTH_TOKEN = 'Auth Token' CALLERID = '+Rented Number' MYNUMBER = '+Your Number' -
现在,让我们编写我们的应用程序,它将实际发送这条消息。我们将它存储为
send_sms.py并从config.py中导入配置:import config from flask import Flask from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN) message = client.messages.create( to=config.MYNUMBER, from_=config.CALLERID, body="Hey, this is cool!") -
我们通过进入终端或从你喜欢的编辑器运行此代码来运行此代码。检查你的手机,你应该会收到一条来自你租用号码的消息,嘿,这很酷! 确实,这很酷!你的消息可能需要 2-3 分钟才能到达,具体取决于你的运营商和网络拥堵,所以请耐心等待。查看以下截图以查看收到的消息:
![如何操作...]()
它是如何工作的...
首先,我们创建了一个配置文件,并填充了所有必需的字段。在 send_sms.py 文件中,我们通过导入 config.py 来导入这个配置文件,同时也导入了所需的模块,如 flask 和 twilio Python 模块。
我们随后通过使用从 twilio.rest 模块导入的类 TwilioRestClient 创建了 twilio 对象,名为 client。使用 AccountSID 和 AuthToken 创建了一个对象。
然后,我们使用 client.messages 类的 create() 方法发送一条消息。在这里,to 是接收消息的电话号码,from_ 是主叫 ID,而 body 是要发送的文本。为什么是 from_ 而不是 from?嗯,那是因为 from 会与 Python 的 from 关键字冲突,该关键字用于导入模块。
还有更多...
简单吧?!向我们的号码发送短信轻而易举。现在,你能编写你自己的应用程序吗?让我们看看你能想到什么。可能的话,给我发条消息邀请我参加你的生日派对!
但是,如果我们不知道如何接收传入的消息,那还有什么乐趣呢?让我们在下一节学习如何做到这一点。
接收短信消息
当你在思考发送短信的各种用例时,你肯定觉得接收短信的能力同样重要。所以,让我们通过一个自动响应消息应用来看看这一点。
准备中
对于这个菜谱,我们将使用 ngrok 软件。ngrok (ngrok.com/) 软件可以帮助你将本地机器连接到互联网。这意味着你可以将位于 NAT 或防火墙后面的本地服务器暴露给互联网。这是一个真正强大的实用工具!对于下一个菜谱,下载 ngrok (ngrok.com/download) 并在终端中使用命令在端口 5000 上运行它。如果你连接到互联网,你应该会看到一个实例正在为你运行:
./ngrok http 5000
以下截图显示了 ngrok 如何使你的应用对互联网可见,并在 URL https://<uuid>.ngrok.io/ 上:

注意
还不要启动 ngrok,我们将在我们的菜谱中启动 ngrok。
如何操作...
-
让我们从编写我们的服务器以接收短信开始。我们将称之为
recv_sms.py。服务器的代码如下所示:from flask import Flask import twilio.twiml app = Flask(__name__) @app.route("/insms", methods=['GET', 'POST']) def respond_sms(): resp = twilio.twiml.Response() resp.message("Thanks for your query. We will get back to you soon") return str(resp) if __name__ == "__main__": app.run(debug=True) -
使用以下命令从你的终端使用 Python 运行服务器:
python recv_sms.py -
使用以下命令启动 ngrok:
./ngrok http 5000 -
很好,我们的服务器已经运行起来了。让我们通过添加 请求 URL 来配置我们的 Twilio 号码。为此,请登录到 Twilio,转到 电话号码 部分,然后点击你的租用号码。在这里,转到消息部分,并输入如以下截图所示的 请求 URL。确保 URL 指向在上一步骤中启动的 ngrok 实例:
![如何操作...]()
我们完成了!所以,当有人向你的租用号码发送消息时,他们将收到一个自动回复,说 感谢您的查询。我们将尽快回复您。
太棒了!这太好了,但嘿,它是如何工作的?
它是如何工作的...
接收传入消息的服务器是用 Flask 编写的,并运行在端口 5000 上。这使得服务器在本地机器上运行。为了使其在互联网上可用,我们启动 ngrok 并使其在 Flask 服务器相同的端口(5000)上运行。
我们配置 Twilio 电话号码,将传入的消息路由到我们的 Flask 服务器。为了实现这一点,我们将请求 URL 添加到 Twilio。因此,每当有人向租用的号码发送消息时,它将通过 ngrok 路由到我们的 Flask 服务器。在我们的应用程序中,我们将其路由到 https://<ngrokId>.ngrok.io/insms。
如果你看看我们的 Flask 服务器,我们已经有了一个配置了 URL 的路由,/insms。这个路由从 Twilio 服务器(多亏了 请求 URL 设置)接收 POST 请求,然后回复消息,感谢您的查询。我们将尽快回复您。
还有更多...
你已经学习了如何使用 Twilio 发送和接收短信。我知道你已经在思考你的用例以及如何利用短信来解决这些问题。让我们看看食品零售行业的一个例子。
Domino's 的 SMS 工作流程
美国的多米诺比萨店老板约翰正在寻找提高比萨饼销售的方法。提高销售的一种方法是将重点放在简化订购比萨饼的过程上。他还希望自动化工作流程,以便他可以随时更新顾客的订单和配送状态。他还觉得虽然互联网很棒,但顾客可能也希望在网络信号弱的地方订购比萨饼。你认为他必须做什么?
准备就绪
让我们考虑一下用例,并写下我们需要的所有内容?以下是我能想到的一些事情:
-
接收传入消息的能力
-
维护和查询订单状态
-
发送出站状态消息
如何操作...
让我们看看解决方案,然后了解它是如何为多米诺比萨店工作的。
以下代码片段分为三个主要方面:使用 flask 路由接收传入的消息、维护查询订单的顾客的订单状态,以及从 Flask 应用程序发送出站消息的能力:
from flask import Flask, request
import twilio.twiml
class Pizza:
def __init__(self):
self.status = None
def setStatus(self, status):
self.status = status
def getStatus(self):
return self.status
app = Flask(__name__)
@app.route("/insms", methods=['GET', 'POST'])
def respond_sms():
content = request.POST['Body']
resp = twilio.twiml.Response()
if content == 'ORDER':
resp.message("Thanks! We're already on your order!")
pizza = Pizza()
pizza.setStatus('complete')
return str(resp)
if content == 'STATUS':
pizza = Pizza()
status = pizza.getStatus()
if status == 'complete':
resp.message("Your order is ready!")
return str(resp)
else:
resp.message("Sorry! could not locate your order!")
return str(resp)
else:
resp.message("Sorry! Wrong selection")
return str(resp)
if __name__ == "__main__":
app.run(debug=True)
它是如何工作的...
我们已经有了接收传入消息的应用程序。因此,我们使用这个应用程序并扩展它以满足我们的需求。我们租用的号码变成了多米诺比萨的号码,它被闪现在他们的广告牌上:
-
在我们的用例中,我们决定使用两个关键词,
ORDER和STATUS,多米诺比萨的客户可以使用。 -
当客户发送
ORDER消息到多米诺比萨时,他们可以为他们的选择订购披萨。披萨店积极回应订单,表示他们已经在处理了。 -
当客户想要了解他们订单的状态时,他们可以通过发送
STATUS文本来检查。在我们的案例中,当客户查询他们的订单时,他们从披萨店得到一个回复,说他们的订单已经准备好了。想象一下客户在阅读这个回复时的满意度。 -
客户觉得下订单和了解其状态非常简单。他们肯定会开始订购更多!嗯,至少我会。
-
约翰非常高兴,他决定给他的 IT 经理加薪,而巧合的是,那个人就是你!酷,不是吗?
还有更多...
现在,如果你注意到的消息是由或发送到比萨店的,它们是发送到租用的号码。把消息发送到像DOMP(多米诺比萨的缩写)这样的自定义代码怎么样?是的,你可以使用短信短码来实现这一点;它们不是免费的,你需要以高昂的价格购买。
参见
-
我们已经完成了所有短信消息的菜谱。希望您学到了一些东西,并将其中的一些应用到您的实际中。你能尝试发送 MMS 消息,比如带有多米诺比萨标志的优惠信息吗?这是你可以探索的事情。
-
在下一个菜谱中,我们将开始处理语音通话,并欣赏它们能提供的内容。我们能用语音信息做一些酷炫的事情吗?让我们在下一组菜谱中看看。
发送语音消息
你听说过 VoIP 吗?是的,互联网协议语音。VoIP(缩写)是一组用于在互联网协议网络上(如互联网本身)传输语音和多媒体的技术,例如 Skype 和 Google Talk 等提供消费者和企业领域的通信解决方案的产品。VoIP 为互联网上的通信开辟了一个全新的世界。
电信 API 提供商,如 Twilio,也使用 VoIP 协议发送语音消息。在本节中,你将学习如何使用 Twilio API 进行或接收语音通话。所以,让我们跳进去,开始使用 API 吧!
准备工作
在这个菜谱中,我们使用twilio和flask模块,就像我们在之前的短信菜谱中使用的那样。所以,本节不需要新的安装。
如何做...
-
我们首先创建我们的配置文件。发送语音消息就像发送短信一样简单。我们在这里也需要 Twilio 的AccountSID和AuthToken。我们还需要呼叫 ID 和发送语音消息的号码。这是我们的配置看起来像:
TWILIO_ACCOUNT_SID = '<Account SID>' TWILIO_AUTH_TOKEN = '<Auth Token>' CALLERID = '<Rented Number>' MYNUMBER = '<Number to call>' -
让我们现在开始编写我们的 Flask 服务器代码。以下代码帮助我们使用 Twilio Python API 进行语音通话。我们将文件保存为
voice_outbound.py:import config from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN) @app.route('/call', methods=['POST']) def outbound_call(): response = twiml.Response() call = client.calls.create( to=config.MYNUMBER, from_=config.CALLERID, record='true', ) return Response(str(response), 200, mimetype="application/xml") if __name__ == '__main__': app.run(debug=True) -
使用以下命令运行 Flask 服务器。这将使我们的 Flask 服务器在默认端口 5000 上运行:
python voice_outbound.py -
使用以下命令在端口 5000 上启动 ngrok。这将确保我们的服务器通过 ngrok 提供的隧道设施在互联网上可用。复制 ngrok 正在运行的 URL。它将是以下格式,
https://<ngrokid>.ngrok.io/,就像我们在前面的章节中看到的那样:./ngrok http 5000我们的服务器现在已准备好拨打电话,所以请继续向
https://<ngrokid>.ngrok.io/call发送POST请求,你应该会接到配置文件中添加的电话。哇,这太酷了!但是当你接起电话时会发生什么?你的电话会被断开。为什么?那是因为在 Twilio 中,每个语音电话都伴随着一个回调 URL,一旦电话被接起,就会执行下一组指令。这在我们代码中没有定义,因此电话会因为错误而断开。让我们来修复这个问题。
-
那么,让我们添加回答回调 URL 并完成我们的服务器代码:
import config from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN) @app.route('/call', methods=['POST']) def outbound_call(): response = twiml.Response() call = client.calls.create( to=config.MYNUMBER, from_=config.CALLERID, record='true', url=config.BASE_URL + '/answer_url', ) return Response(str(response), 200, mimetype="application/xml") @app.route('/answer_url', methods=['POST']) def answer_url(): response = twiml.Response() response.addSay("Hey! You are awesome. Thanks for answering.") return Response(str(response), 200, mimetype="application/xml") if __name__ == '__main__': app.run(debug=True) -
如果你查看
outbound_call()方法中的url参数,它指向BASE_URL。这与 ngrok URL 后缀为/answer_url的 URL 相同。现在,如果你向https://<ngrokid>.ngrok.io/call发送POST请求,你的号码将收到电话。一旦你接起电话,就会向https://<ngrokid>.ngrok.io/answer_url发送一个回调POST请求,你将听到消息“嘿!你太棒了。感谢你接听”。哇! -
这里是服务器日志的示例:
* Detected change in '/Users/chetan/book/ch05/app.py', reloading * Restarting with stat * Debugger is active! * Debugger pin code: 608-057-122 127.0.0.1 - - [16/Jul/2016 21:35:14] "POST /call HTTP/1.1" 200 - 127.0.0.1 - - [16/Jul/2016 21:35:22] "POST /answer_url HTTP/1.1" 200 -
它是如何工作的...
那么,它是如何工作的?在完成短信部分后,这部分应该很容易理解。我们将一步一步地通过代码来讲解:
-
我们首先使用
twilio.rest模块中的TwilioRestClient类创建一个名为client的twilio对象。 -
我们在 Flask 应用中定义了一个路由,
/call,它接受POST方法调用。这个路由会给我们自己的号码拨打电话。 -
实际的电话是通过
outbound_call()路由方法,利用client.calls类的create()方法来拨打的。 -
在
create()调用中,我们定义了参数,例如:-
to:这是被拨打的手机/固定电话号码 -
from_:这是从 Twilio 租用的号码 -
record:这将决定电话是否应该被录音 -
url:这是当语音电话被接听时被调用的回调url
-
-
在 Flask 应用中,我们还定义了一个新的路由,
/answer_url,当电话被接起时会被调用。现在,这很有趣,需要理解。Twilio 基于 TwiML(也称为 Twilio 标记语言)的哲学。如果你查看标记,它基本上是一个 XML 标记。TwiML 是一组指令,可以用来告诉 Twilio 如何处理传入的短信或语音电话。所以,addSay()方法与以下相同:<?xml version="1.0" encoding="UTF-8"?> <Response> <Say>Hey! You are awesome. Thanks for answering</Say> </Response>
还有更多...
因此,我们学习了如何向特定号码发送语音电话,以及当电话被接听时,答案回调 URL 如何被调用。现在,让我们学习如何处理来电。
接收语音电话
接收语音电话是使用云电话开发应用程序的一个重要方面。许多业务案例(正如你所想象的)都依赖于来电。让我们看看如何使用 Twilio API 处理来电。
准备工作
在这个菜谱中,我们使用 twilio 和 flask 模块,就像我们在之前的 SMS 菜谱中使用的那样。因此,本节不需要进行新的安装。
如何操作...
-
我们首先创建配置文件。我们在这里也需要 Twilio 的 AccountSID 和 AuthToken。在这种情况下,我们不需要任何来电者 ID,因为租用的号码本身就是来电者 ID。
-
现在,让我们看看我们的 Flask 服务器,其代码如下所示。我们将其称为
voice_inbound.py:from flask import Flask, Response, request from twilio import twiml from twilio.rest import TwilioRestClient app = Flask(__name__) client = TwilioRestClient(config.TWILIO_ACCOUNT_SID, config.TWILIO_AUTH_TOKEN) @app.route('/incall', methods=['POST']) def inbound_call(): response = twiml.Response() response.addSay("Thanks for calling our customer service." "Please hold while we connect you to our advisors.") return Response(str(response), 200, mimetype="application/xml") if __name__ == '__main__': app.run(debug=True) -
使用以下命令运行 Flask 服务器。这将使我们的 Flask 服务器在默认端口 5000 上运行:
python voice_outbound.py -
使用以下命令在端口 5000 上启动 ngrok。这将确保我们的服务器通过 ngrok 提供的隧道设施在互联网上可用。复制 ngrok 运行的 URL。它将是
https://<ngrokid>.ngrok.io/的格式,就像我们在上一节中看到的那样:./ngrok http 5000 -
现在,登录到 Twilio 并为来电配置租用的号码。我们将 请求 URL 配置为指向 ngrok URL。查看以下截图了解如何将 请求 URL 添加到你的租用号码:
![如何操作...]()
-
一旦你在 Twilio 上启动了服务器并配置了设置,就可以通过 Skype 或 Google Talk 向你租用的号码打电话。这将向我们的 Flask 服务器发送一个
POST请求,然后服务器会以 TwiML 响应回复,内容为:“感谢您拨打我们的客服电话。请稍候,我们将为您连接顾问。”
如何工作...
接收传入消息的服务器是用 Flask 编写的,并在端口 5000 上运行。这是本地于我们的机器上,为了使其在互联网上可用,我们使用 ngrok 创建隧道。
现在,当任何人拨打租用的号码时,Twilio 会查找 请求 URL 并向该 URL 发送请求,表明租用号码有来电。
我们的 Flask 服务器定义了一个路由 /incall(用于匹配 请求 URL),当我们的租用号码收到来电时会被调用。/incall 路由反过来创建一个 TwiML 响应,向 <Response> 标记添加 <Say>,通话者会听到 <Say> XML 中添加的消息。
以下截图显示了 Twilio 中的 TwiML 响应外观。顺便说一下,接收或发送的每个电话或短信都可以在 Twilio 界面中看到:

构建自己的客户服务软件
保罗负责他公司的客户服务。公司有一个看起来很漂亮的网站,它有一个通过聊天接收客户投诉或问题的功能。保罗经常收到客户的反馈,说聊天系统对他们来说没有用,因为他们希望在遇到产品问题时能够联系到公司的人,并希望这些问题能够迅速得到解决。你能让保罗的生活变得更轻松吗?
准备就绪
让我们考虑一下用例,并写下我们需要的所有内容?以下是我能想到的一些事情:
-
接收来电的能力
-
将电话转接到客户支持工程师
如何做到...
让我们看看解决方案,然后理解它对保罗是如何工作的。
在这个代码片段中,我们将添加接收租用号码来电的能力,并添加通话转接的功能:
import config
from flask import Flask, Response, request
from twilio import twiml
from twilio.rest import TwilioRestClient
app = Flask(__name__)
client = TwilioRestClient(config.TWILIO_ACCOUNT_SID,
config.TWILIO_AUTH_TOKEN)
@app.route('/call', methods=['POST'])
def inbound_call():
call_sid = request.form['CallSid']
response = twiml.Response()
response.dial().conference(call_sid)
call = client.calls.create(to=config.MYNUMBER,
from_=config.CALLERID,
url=config.BASE_URL +
'/conference/' + call_sid)
return Response(str(response), 200,
mimetype="application/xml")
@app.route('/conference/<conference_name>',
methods=['GET', 'POST'])
def conference_line(conference_name):
response = twiml.Response()
response.dial(hangupOnStar=True).conference(
conference_name)
return Response(str(response), 200,
mimetype="application/xml")
if __name__ == '__main__':
app.run(debug=True)
如何工作...
我们已经创建了一个接收来电的应用程序。我们将此应用程序扩展到我们的用例中,以便当客户拨打租用的号码时,会向/call路由(由inbound_call()方法定义)发起一个POST调用。
我们的flask路由通过 TwiML 指令集将入站电话添加到会议中。正如你所知,会议是一组彼此桥接的通话。
response.dial().conference(conference_name)方法是帮助我们向会议添加通话腿的方法。这就是 TwiML 的样子;你可以看到在<Response>标签下我们有<Dial>和<Conference>标签:

flask路由确保它向客户支持工程师(通过MYNUMBER识别)发起一个出站电话。出站电话到客户支持工程师配置了url参数(就像我们在出站语音电话部分看到的答案 URL)。因此,当支持工程师接起电话时,回调答案 URL 会被调用,工程师通话腿也会被添加到与入站通话腿相同的会议中。
两个通话腿,来自客户的来电和打给支持工程师的出站电话,现在都在一个会议中,可以进行对话。客户的问题很快得到解决,保罗也很高兴。太酷了!
还有更多...
你学习了如何通过使用云电话 API 构建自己的短信和语音应用程序。然而,如果你真的对利用已经构建的解决方案来满足你的需求感兴趣,你可以查找一些标准软件应用程序,例如 CallHub(callhub.io/),这可以帮助你以合理的成本高效地自动化你的用例。你也可以使用他们的 API 构建自己的呼叫中心解决方案。那么,你接下来要构建什么呢?
我相信你们喜欢这一章;让我们在下一章中继续享受乐趣!让我们看看我们准备了些什么!
第五章。电子邮件的乐趣
在过去几十年中,电子邮件已成为信息交换的主要模式。您每天都会处理电子邮件,出于多种原因。但您是否曾想过可以用 Python 操作您的收件箱?
在本章中,我们将介绍以下食谱:
-
发送电子邮件消息
-
电子邮件加密
-
使用 MIME 消息美化电子邮件消息
-
带附件的电子邮件消息
-
连接到您的收件箱
-
获取和阅读电子邮件消息
-
标记电子邮件消息
-
清除收件箱中的电子邮件消息
-
使用电子邮件响应自动化客户支持流程
简介
嗨,朋友们!希望你们今天过得愉快。在本章中,我们将讨论电子邮件以及我们可以使用 Python 实现的众多电子邮件操作。我们还将通过实际业务用例了解如何利用电子邮件自动化业务流程。
那么,我们还在等什么呢?让我们开始,了解一下电子邮件的历史及其技术实现。
实际上,电子邮件无需介绍;当然,它们是用户之间交换数字消息的方法。电子邮件在互联网上的计算机网络上进行信息交换。您可以登录您喜欢的电子邮件客户端,开始处理存储在电子邮件服务器上的消息。最广泛使用的网络客户端是 Gmail。
电子邮件有着非常有趣的历史。在过去,电子邮件需要发送者和接收者同时在线才能成功进行通信。这难道没有意义吗?随着时间的推移,电子邮件服务器变得智能,开始采用存储和转发哲学。今天,电子邮件消息异步存储在服务器上,以便接收者可以在方便的时候查看。因此,电子邮件服务器能够提供接受、转发和标记消息等服务。
电子邮件最初只使用 ASCII 字符,后来通过多用途互联网邮件扩展(MIME)扩展为支持富文本和附件。从协议的角度来看,电子邮件最初使用文件传输协议(FTP)在计算机之间发送消息,但如您所知,简单邮件传输协议(SMTP)是目前最广泛使用的电子邮件处理协议。
备注
请注意,本书不涵盖电子邮件服务器的设置。如果你在网上搜索,你会找到更多可以帮助你入门的资源。本章的目的是让你了解使用 Python 程序可以做什么。我们使用 Gmail 网络客户端作为例子,这样你可以快速尝试代码示例,并欣赏使用 Python 自动化电子邮件任务而不必设置自己的电子邮件服务器的强大功能。虽然我们以 Gmail 为例,但这些代码片段也可以适用于任何其他支持 SMTP 发送电子邮件和 IMAP 检索电子邮件的电子邮件服务器。
在本章中,我们将学习如何使用 Python 处理电子邮件。我们还将使用以下列出的多个 Python 模块来对电子邮件消息执行各种操作:
-
smtplib(docs.python.org/2/library/smtplib.html) -
imaplib(docs.python.org/2/library/imaplib.html) -
gmail(github.com/charlierguo/gmail)
注意
当涉及到使用 Python 处理电子邮件时,你需要的是一个帮助你构建消息的模块,一个可以发送电子邮件的模块,以及一个帮助你检索和更新消息的模块。
发送电子邮件消息
你可能首先想要通过电子邮件客户端实现的是向你的朋友或同事的电子邮件地址发送消息。让我们继续看看在 Python 中我们如何实现这一点。
准备工作
为了发送电子邮件,我们需要拥有 Python 的smtplib模块。正如其名所示,这个库使用 SMTP 协议来发送电子邮件。我们可以使用我们喜欢的pip工具通过以下命令安装smtplib。但 Python 的默认安装应该已经包含了这个模块:
pip install smtplib
如何操作...
-
在你的电脑上,打开你最喜欢的编辑器并添加以下代码片段。让我们称这个为
config.py。配置文件包含登录详细信息,如电子邮件地址、密码以及需要发送电子邮件的电子邮件地址:fromaddr = "abc@gmail.com" password = "xyz@123" toaddr = "abc@gmail.com" -
现在,让我们编写代码来使用这个配置文件发送电子邮件:
import smtplib import config server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login(config.fromaddr, config.password) msg = "Some nice msg" server.sendmail(config.fromaddr, config.toaddr, msg) server.quit() -
将前面的代码保存为
basic_email.py,并使用以下命令运行代码:python basic_email.py -
如果你运行前面的代码,你会看到带有
SMTPAuthenticationError的异常,并且你的程序将因为退出代码1而失败。你的异常将如下所示:![如何操作...]()
-
好吧,这很糟糕,但相反,这也很好!异常表明服务器登录是正常的,但 Gmail 阻止了您发送消息。现在,如果您登录 Gmail,您应该会看到一封电子邮件建议检测到来自不安全应用的注册。真的吗?!是的,这是因为我们尝试从我们的 Python 程序访问 Gmail 账户。这就是为什么我们收到了来自 Google 的电子邮件,建议如果我们的账户有恶意活动,可能存在安全漏洞。Google 的电子邮件消息可以在以下截图中查看:
![如何操作...]()
-
但显然,这是一个合法尝试使用 Gmail 账户,所以让我们向 Google 确认这一点。打开来自 Google 的电子邮件消息,点击允许访问。您将被带到不安全应用页面,在那里您可以开启此设置,如下面的截图所示:
![如何操作...]()
-
现在,请从 Gmail 网页客户端登出并再次登录,以便让设置对您的账户生效。如果一切顺利,您将收到来自 Google 的电子邮件,告知不安全应用的访问已开启。Google 的确认电子邮件将类似于以下截图:
![如何操作...]()
现在,如果您再次运行 Python 程序,它应该会成功运行,并且您将在收件箱中收到一封电子邮件:
![如何操作...]()
-
太棒了!注意,消息内容与我们添加到代码片段中的内容相同。此外,由于发件人和收件人地址相同,邮件是从您那里发出的,但它没有任何主题,这并不理想。我们将在下一个菜谱中解决这个问题。
它是如何工作的...
如前所述,SMTP 用于发送电子邮件消息。我们使用 Python 模块smtplib来完成这个目的。
如果您查看前面的代码片段,我们使用构造函数smtplib.SMTP()来配置 Gmail 的 SMTP 设置并获取对电子邮件服务器的访问权限。Gmail 的 SMTP 服务器运行在smtp.gmail.com上,端口为 587。
一旦我们有了服务器对象server,我们就使用它来用我们的用户名和密码登录 Gmail。请注意,在前面代码中我们还有另一行:server.starttls();我们将在本章后面讨论这个问题。
我们创建了一个测试消息并将其存储在变量msg中,然后使用sendmail方法('fromaddr', 'toddr', msg)发送。
最后,我们使用server.quit()关闭与电子邮件服务器的连接。
还有更多...
我们探讨了如何使用 SMTP 协议和 Python 的smptlib库登录 Gmail 并发送基本电子邮件。虽然这个菜谱让我们开始了,但还有一些细节我们将在下一个菜谱中深入探讨。让我们来看看它们。
电子邮件加密
电子邮件容易泄露信息。目前大多数电子邮件都是以明文格式传输的。电子邮件加密涉及加密或伪装电子邮件的内容,以便只有预期的收件人可以阅读。始终记住,在处理电子邮件时,安全性是最重要的。让我们看看如何使用 Python 加密电子邮件。
准备工作
在之前的菜谱中,我们看到了如何发送基本的电子邮件,但starttls()方法是什么?电子邮件加密是如何工作的?我们将在这部分得到这些问题的答案。
如何做...
-
让我们先打开我们最喜欢的编辑器,并输入以下代码片段:
import smtplib server = smtplib.SMTP('smtp.gmail.com', 587) try: server.set_debuglevel(True) print "Sending ehlo" server.ehlo() if server.has_extn('STARTTLS'): print "Starting TLS Session" server.starttls() print "Sending ehlo again" server.ehlo() finally: server.quit() -
现在,让我们运行 Python 代码并查看它打印的内容。我们有三个不同的输出段。第一个是我们向电子邮件服务器发送
ehlo()消息时:![如何做...]()
-
第二种情况是我们对服务器对象调用
starttls()方法。查看以下截图:![如何做...]()
-
第三个是我们再次使用
ehlo()连接到电子邮件服务器时:![如何做...]()
它是如何工作的...
让我们从基础知识开始。电子邮件加密意味着保护电子邮件消息不被除预期当事人之外的其他人阅读。电子邮件通常以明文发送,可以被第三方嗅探。为了避免这种情况,我们在协议层加密电子邮件;这可能包括身份验证。
SMTP 服务器通常使用 SSL/TLS 协议在端口 25 上发送电子邮件。然而,随着 STARTTLS(位于 SMTP 之上的一层)的出现以及使用端口 587 进行消息提交,像 Gmail 这样的电子邮件客户端使用 STARTTLS 和端口 587 来发送电子邮件。Gmail 还实现了身份验证;记住我们使用server.login (username, password)登录到 Gmail 服务器。
为了让 STARTTLS 在服务器和客户端之间使用,客户端首先需要知道服务器是否支持此协议。当我们发出server.ehlo()时,程序会向 SMTP 服务器发送一个EHLO消息以建立通信。服务器会以消息和允许的扩展响应,如第一张截图所示。
现在,从代码中,我们使用server.has_extn('STARTTLS')检查服务器是否支持STARTTLS扩展。正如我们在第一张截图中所见,SMTP 服务器响应了STARTTLS扩展;这证实了 Gmail 支持STARTTLS协议层,这真是太棒了。
现在,我们使用server.starttls()与服务器通信。服务器通过发送消息Ready to start TLS来响应。这样,我们就加密了我们的会话。如果你现在查看第三张截图,当我们发送server.ehlo()时,我们通过 TLS 会话重新向服务器确认身份。这也表明服务器实现了身份验证扩展。
最后,我们使用 server.quit() 退出 SMTP 会话,服务器响应为 closing connection,如第三张截图所示。
还有更多...
好吧,这相当详细。花点时间理解它。实际上,了解简单电子邮件发送背后的过程很有趣。但不用担心太多;让我们开始有趣的部分,并探索更多示例。
使用 MIME 美化电子邮件消息
在前几个菜谱中,我们以简单的纯文本格式发送了电子邮件消息。MIME 互联网标准帮助我们构建包含非 ASCII 字符、multipart 消息和图像的消息。它还帮助处理附件和其他许多任务。这样,我们可以构建丰富的电子邮件消息。让我们看看 MIME 格式在这个菜谱中的应用。
准备工作
对于这个菜谱,我们将使用相同的模块 smtplib 来发送电子邮件消息。我们还将介绍另一个模块 email,它将帮助我们使用 MIME 格式构建更好的电子邮件消息。email 模块随 Python 安装而来;因此,我们不需要进行任何新的模块或安装。在本节中,我们将探讨如何使用 MIME 属性发送外观更好的电子邮件。
如何做到这一点...
-
让我们先导入我们需要的所有模块:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import config import email.utils -
现在,让我们使用 MIME 模块构建我们的电子邮件消息。以下代码创建了消息:
fromaddr = config.fromaddr toaddr = config.toaddr msg = MIMEMultipart() msg['Subject'] = "Hello from the Author of Automate It!" msg['To'] = email.utils.formataddr(('Recipient', toaddr)) msg['From'] = email.utils.formataddr(('Author', fromaddr)) body = "What a wonderful world!" msgBody = MIMEText(body, 'plain') msg.attach(msgBody) -
因此,现在我们有了要发送电子邮件消息的收件人详细信息。我们已使用 MIME 格式构建了电子邮件消息。我们还在等什么呢?让我们使用以下代码发送它:
server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login(fromaddr, config.password) text = msg.as_string() print "Text is:", text server.sendmail(fromaddr, toaddr, text) server.quit()收到的电子邮件如下所示:
![如何做到这一点...]()
太棒了!太好了,但是...它是怎么工作的?
工作原理...
在前面的例子中,我们导入配置文件,从中我们获得了 fromaddress 和 password 以登录 SMTP 服务器,以及 toaddress 以发送电子邮件消息。
现在,在发送消息之前,我们使用 email.mime.multipart 模块中的 MIMEMultipart() 类构建一个新的 MIME 消息对象。对于那些不知道的人来说,MIME multipart 消息意味着在单个电子邮件中既有 HTML 内容又有文本内容。因此,在这段代码中,我们创建了一个新的 multipart MIME 消息,并将其文本内容添加到其中。
文本内容,即电子邮件的正文,是通过 email.mime.text 模块中的 MIMEText() 构造函数创建的,然后使用 attach() 方法将其附加到 multipart 消息中。
构建的 MIME 消息如下截图所示,其中内容类型为 multipart,MIME 版本为 1.0,主题、收件人和发件人的详细信息符合预期,电子邮件正文包含预期的文本:

一旦我们有了消息和收件人详细信息,我们就可以像往常一样使用 SMTP.sendmail() 方法发送电子邮件。
带有附件的电子邮件消息
电子邮件中最常用且简单的用例之一是能够向你的电子邮件消息中添加附件。在本节中,我们将学习如何在 Python 中给我们的电子邮件添加附件。
准备工作
我们在这个例子中使用了相同的 smtplib 和 email 模块。所以,不必担心要安装的模块。让我们继续进行。
如何操作...
-
让我们先快速创建一个小的文本文件。我们将称之为
attach.txt,其内容如下截图所示:![如何操作...]()
-
让我们看看将附件添加到我们的电子邮件中的代码:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders import config fromaddr = config.fromaddr toaddr = config.toaddr msg = MIMEMultipart() msg['From'] = fromaddr msg['To'] = toaddr msg['Subject'] = "Email with an attachment" body = "Click to open the attachment" msg.attach(MIMEText(body, 'plain')) filename = "attach.txt" attachment = open(filename, "rb") part = MIMEBase('application', 'octet-stream') part.set_payload((attachment).read()) encoders.encode_base64(part) part.add_header('Content-Disposition', "attachment; filename= %s" % filename) msg.attach(part) server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login(fromaddr, config.password) text = msg.as_string() server.sendmail(fromaddr, toaddr, text) server.quit() -
当你运行前面的代码时,你将在你的收件箱中收到电子邮件,其外观类似于以下截图:
![如何操作...]()
工作原理...
我们已经熟悉了创建 MIME 消息对象。因此,在这段代码中,我们创建了一个多部分消息对象 msg。然后我们使用 msg.attach() 向其中添加一条文本消息。文本正文说“点击打开附件”。
在这个食谱中,我们使用了来自 email 模块的新类 MIMEBase,它将被用来附加文本文件。记住,我们已经创建了 attach.txt 文件,我们使用 Python 的 open() 方法打开文件,并获取文件句柄 attachment。然后我们创建一个 MIMEBase 对象 part,并将文件的 内容作为有效载荷分配给此对象。文件内容通过 attachment.read() 获取,有效载荷通过 set_payload() 方法设置。
为了附加文件,MIMEBase 对象必须被编码为 base64,并且需要将 Content-Disposition 标头添加到 part 对象中。现在我们有了 part 对象,它可以像我们为正文文本所做的那样,使用 attach() 方法附加到多部分对象 msg 上。
很酷,所以我们已经有了完整的 MIME 消息以及发送消息给谁的具体信息。那么,我们就继续发送带有附件的电子邮件。这正是我们想要达成的目标。
连接到你的收件箱
在本章中,我们一直在谈论使用 Python 发送电子邮件。然而,在某些时候,你也可能想要扫描你的收件箱并阅读收到的消息。那么,你该如何做呢?让我们在本食谱中理解和学习它。
准备工作
在这个食谱中,我们使用了一个新的 Python 模块,它将帮助我们从收件箱中检索消息。我们使用的是 Python 模块 imaplib,它是默认 Python 安装的一部分。酷,那么让我们开始吧。
如何操作...
-
我们首先使用配置文件,这是我们之前创建的用于存储我们的电子邮件和密码的文件,来登录服务器。然后我们添加我们的代码来创建一个处理或对象来处理我们的收件箱。代码如下所示:
import config, imaplib M = imaplib.IMAP4_SSL("imap.gmail.com", 993) M.login(config.fromaddr, config.password) print "Inbox:", M M.logout()如果你运行前面的代码片段,你将得到以下输出:
![如何操作...]()
-
默认情况下,当我们登录到 Gmail 时,默认收件箱会被选中,但如果我们还创建了其他收件箱,我们可以通过添加一小段代码来获取列表。现在,从所有标签中,如果我们特别想选择
Inbox,这也可以实现。请看以下代码示例:import config, imaplib M = imaplib.IMAP4_SSL("imap.gmail.com", 993) M.login(config.fromaddr, config.password) print M.list() M.select('INBOX') print "Inbox:", M M.logout()上述代码片段的输出显示在以下屏幕截图中。尽管我已经创建了多个标签,但我使用较少标签的屏幕截图:
![如何做...]()
它是如何工作的...
如本章开头所述,我们有三项主要协议用于处理电子邮件。我们大量使用 SMTP 发送电子邮件,但在阅读电子邮件时,我们可以使用 POP 或 IMAP 从电子邮件服务器检索消息。我们将逐步分析代码。
Python 的 imaplib 库帮助我们使用 互联网消息访问协议(IMAP)连接到我们的邮箱。Gmail 服务器配置为 IMAP,服务器运行在 imap.gmail.com 上,端口为 '993'。
在我们的代码示例中,我们使用构造函数 IMAP4_SSL("imap.gmail.com", 993) 创建了一个类型为 imaplib 的对象;我们称这个对象为 M。
关于加密,我们使用 IMAP4_SSL 连接到服务器,因为它在 SSL 套接字上使用加密通信。我们避免使用 IMAP4 类,它内部使用明文套接字。
现在,使用对象 M,我们可以使用我们的用户名和密码登录到 Gmail 并连接到我们的收件箱。
当我们在对象 M 上调用 list() 方法时,它会返回您已经创建的所有标签。现在,在我的情况下,我已经创建了 ACM 标签(用于我的与 ACM 的工作),因此它出现在我的标签列表中。
如果你看代码示例,我们可以使用 select() 方法明确连接到 INBOX。一旦连接到收件箱,我们就可以开始从收件箱获取电子邮件消息。
最后,我们使用 M.logout() 方法关闭与收件箱的连接。酷!这很简单。
还有更多...
因此,我们在这个食谱中学习了如何连接到我们的收件箱,但我们可能还想阅读消息、标记它们并在它们上执行有趣的操作。让我们看看如何在下一个食谱中执行消息操作。
获取和阅读电子邮件消息
使用 imaplib 获取电子邮件消息也很容易实现。在本食谱中,我们将学习如何使用 Python 代码来完成这项操作。在本食谱中,我们将搜索具有特定主题行的电子邮件,并从符合预定义标准的收件箱中获取最新消息。
准备工作
我们继续使用 imaplib 模块来读取电子邮件消息,因此不需要为本食谱进行新安装。
如何做...
-
我们利用配置文件并导入
fromaddress、password和toaddress来登录到服务器。一旦登录,我们选择默认收件箱,获取电子邮件消息并阅读它们。让我们看看完整的代码:import config, imaplib M = imaplib.IMAP4_SSL("imap.gmail.com", 993) M.login(config.fromaddr, config.password) M.select("INBOX") typ, data = M.search(None, 'SUBJECT', "Email with an attachment") typs, msg = M.fetch(data[0].split()[-1], '(RFC822)') print "Message is ", msg[0][1] M.close() M.logout() -
将前面的文件保存为
inbox_search.py并使用以下命令运行代码:python inbox_search.py -
前面代码片段的输出显示在以下截图:
![如何操作...]()
如何工作...
在前面的代码片段中,我们首先使用适当的 IMAP 设置创建了一个 IMAP_SSL4() 对象。然后,我们借助 IMAP 对象,使用配置文件中的凭据登录到客户端。接着,我们选择我们的 INBOX 以便在其上执行搜索操作。
在 IMAP 对象上调用 M.search() 方法可以帮助我们搜索主题为 带附件的电子邮件 的电子邮件。search() 方法返回一个与搜索标准匹配的消息数组。
现在,如果我们需要获取特定的消息,并且由于我们必须读取符合我们标准的最新的电子邮件,我们使用 M.fetch() 方法。fetch() 方法需要一个给定的消息对象和您想要获取的消息部分。因此,在这个代码示例中,我们传递符合标准的最新电子邮件对象,并传递 RFC822,这表示我们想要以 RFC 822 格式获取电子邮件正文。
当我们打印从 fetch() 获取的消息时,我们得到与搜索匹配的最新电子邮件的电子邮件正文内容。
现在,你还记得看到电子邮件的内容吗?嗯,这正是我们在之前的菜谱中发送的同一封电子邮件,当时我们用它来演示电子邮件附件。
还有更多...
好的!所以,现在我们可以搜索一条消息并获取它。在我们的收件箱中还有许多更细粒度的操作,比如标记我们想要执行的操作。让我们在下一个菜谱中看看它们。
标记电子邮件消息
在之前的菜谱中,我们看了获取和读取消息。这不是太复杂了吗?在进行像搜索或读取这样的简单操作时,我们必须注意这么多细节吗?在本节中,我们将看看另一个可以帮助我们不仅更好地搜索或读取,还可以在我们的电子邮件上执行各种操作的库。
准备中
对于本节,我们将安装 gmail 模块。您可以通过终端使用 pip 命令安装此模块,如下所示:
pip install gmail
让我们看看如何使用 gmail API 搜索和读取电子邮件。这将使我们开始使用 gmail 模块。以下代码片段搜索在 2016 年 7 月 22 日之后收到的电子邮件。然后我们取最新的消息并获取它。一旦我们获取了消息,我们就可以继续读取电子邮件的正文:
import gmail, config
from datetime import date
g = gmail.login(config.fromaddr, config.password)
mails = g.inbox().mail(after=date(2016, 7, 22))
mails[-1].fetch()
print "Email Body:\n", mails[-1].body
g.logout()
前面代码的输出显示在以下截图。看起来我可能收到了来自 Quora 的电子邮件摘要!

这里是我的收件箱的截图:

这不是太简单了吗?顺便说一下,gmail模块是在imaplib之上编写的,但提供了更好的 API,所以让我们利用这个模块做一些惊人的操作。
如何操作...
-
让我们打开收件箱,寻找符合特定标准的未读邮件,并将其标记为已读。以下代码可以轻松完成此操作:
import gmail, config g = gmail.login(config.fromaddr, config.password) mails = g.inbox().mail(unread=True, sender='noreply@glassdoor.com') mails[-1].fetch() mails[-1].read() g.logout()在运行此程序之前,我在收件箱中有一封来自
glassdoor.com的未读电子邮件。在我的收件箱中它看起来是这样的:![如何操作...]()
运行代码片段后,它将这封电子邮件识别为符合来自
noreply@glassdoor.com的未读邮件标准,并将我的邮件标记为已读。所以现在在我的收件箱中看起来是这样的。Gmail 会取消粗体显示已读邮件,这就是我在收件箱中看到的情况:![如何操作...]()
很好!
-
让我们看看另一个例子。从 2016 年 1 月开始,我收到了来自 Amazon Now 的大量促销电子邮件。我的邮箱看起来是这样的:
![如何操作...]()
-
现在,我想将它们全部标记为
read,并将它们分配到单个标签AMAZON下。我该如何操作?以下代码执行了这个操作:import gmail, config from datetime import date g = gmail.login(config.fromaddr, config.password) mails = g.inbox().mail(unread=True, sender='store-news@amazon.in', after=date(2016, 01, 01)) for email in mails: email.read() email.add_label("AMAZON") g.logout() -
运行此代码后,您的收件箱中会出现一个新的标签,名为
AMAZON。现在,如果您在收件箱中搜索所有带有标签AMAZON的电子邮件,您会看到所有这些电子邮件都已标记为已读。请看以下截图,我正在搜索带有标签AMAZON的电子邮件:![如何操作...]()
工作原理...
在第一步中,我们通过登录 Gmail 服务器创建了一个名为g的对象。请注意,我们没有传递任何参数(如 IMAP 设置或端口)来创建对象。gmail模块内部处理这些。
现在,使用这个对象,我们开始搜索收件箱中未读且由noreply@glassdoor.in发送的电子邮件,所有符合这个标准的邮件对象都存储在mails中。
之后,我们使用fetch()方法获取最新记录,并使用read()方法将此邮件标记为已读。
同样,在第二个菜谱中,我们遍历所有未读的电子邮件,这些电子邮件是由store-news@amazon.in发送的,并且是在今年发送给我的。
每封邮件随后都会使用read()方法标记为已读,并添加到标签AMAZON中。操作起来就像微风一样,太棒了!
还有更多...
我们查看了一些可以在我们的电子邮件上执行的操作。还有很多。使用gmail模块,您可以标记邮件为未读,甚至用星号标记它们为重要。让我们看看一个可以清理我们收件箱的例子。
清除收件箱中的电子邮件
最后但同样重要的是,这个菜谱将向您展示如何从收件箱中删除电子邮件。正如您所期望的,通过编程删除电子邮件非常直接。
注意
即使你从电子邮件客户端删除了消息,电子邮件服务器仍然可以选择存储它们。所以,当你删除你的消息时,你只是在标记它们以隐藏在你的收件箱中,而它们可以根据服务器实现继续留在你的电子邮件服务器上。
准备工作
我们继续使用 imaplib 模块来删除电子邮件消息,因此不需要为这个配方安装任何新软件。
如何操作...
-
让我们利用配置文件并导入
fromaddress、password和toaddress来登录到服务器。 -
这就是完整的代码看起来像什么:
import gmail, config g = gmail.login(config.fromaddr, config.password) emails = g.inbox().mail(sender='junk@xyz.com') if emails: for mail in emails: mail.delete() -
将前面的文件保存为
inbox_delete.py并使用以下命令运行代码:python inbox_delete.py
它是如何工作的...
与我们之前看到的例子类似,我们首先使用配置文件中的登录凭证登录到 Gmail。
我们然后连接到我们的收件箱并搜索来自 junk@xyz.com 的电子邮件。如果我们找到符合这个标准的任何电子邮件,我们希望删除它们。
因此,我们遍历邮件对象并对它们执行 delete() 操作,就是这样!我们的收件箱现在已经没有我们认为的垃圾邮件了。 😃
还有更多...
太好了!所以,现在我们知道了如何发送电子邮件消息,添加附件,以及获取和读取它们。我们还学会了如何将我们的消息标记为已读,添加适当的标签,并在需要时删除这些消息。掌握了这些知识,我们能为凯利做些什么吗?
使用电子邮件响应自动化客户支持流程
客户支持部经理凯利手头有一个问题。她的大部分支持工程师最终都在处理一级支持请求,这些请求是客户在网站上已经可以找到信息的。客户只是简单地发送电子邮件给支持部门,而没有尝试自己搜索。
这一系列事件对客户和支持工程师来说都是低效的。客户只是等待信息,而不是直接在网站上获取信息,支持工程师手动将网站上的常见问题解答(FAQ)部分的指针发送给客户。凯利认为这是一个改进的机会,并希望通过自动化这个流程来减少在支持上花费的时间。我们能做些什么来帮助她?
准备工作
当然,这是一个更大的问题,但至少我们可以做一些事情来自动化流程。每当支持团队通过电子邮件收到新的工单时,我们可以自动回复工单,确认已收到工单,并从公司网站上发送 FAQ 部分的链接。这样,客户就可以浏览并从 FAQ 部分查找他们所需的信息。这也减轻了支持工程师的负担,因为自动回复的电子邮件已经迅速解决了客户的查询。
那么,我们现在到底需要什么呢?我们需要监控我们的支持收件箱,查看任何新的客户查询,然后使用我们的模板电子邮件自动回复。
如何操作...
-
让我们直接跳到我们的解决方案。创建一个 Python 文件,并将以下代码片段复制到其中。它正好完成了我们需要自动化支持流程的工作:
from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage import config, time, gmail def send_email(strTo): strFrom = config.fromaddr msgRoot = MIMEMultipart('related') msgRoot['Subject'] = 'Thanks for your ticket' msgRoot['From'] = strFrom msgRoot['To'] = strTo msgRoot.preamble = 'This is a multi-part message in MIME format.' msgAlternative = MIMEMultipart('alternative') msgRoot.attach(msgAlternative) msgText = MIMEText('This is the alternative plain text message.') msgAlternative.attach(msgText) msgText = MIMEText('Hi there, <br><br>Thanks for your query with us today.' ' You can look at our <a href="https://google.com">FAQs</a>' ' and we shall get back to you soon.<br><br>' 'Thanks,<br>Support Team<br><br> <img src="img/cid:image1">', 'html') msgAlternative.attach(msgText) fp = open('google.png', 'rb') msgImage = MIMEImage(fp.read()) fp.close() msgImage.add_header('Content-ID', '<image1>') msgRoot.attach(msgImage) import smtplib server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login(config.fromaddr, config.password) server.sendmail(config.fromaddr, config.toaddr, msgRoot.as_string()) server.quit() while True: g = gmail.login(config.fromaddr, config.password) mails = g.inbox().mail(unread=True) mails[-1].fetch() from_ = mails[-1].fr send_email(from_) time.sleep(60) -
使用 Python 运行前面的代码,你会观察到程序仍在运行。实际上,它正在等待新的电子邮件,在这种情况下,是客户对支持工程师的请求。
-
如果你现在给客户支持发送电子邮件,你将收到我们 Python 程序的自动回复。
注意
在这种情况下,支持收件箱是我的电子邮件地址,但你可以轻松地为你的公司设置一个电子邮件账户,以便客户请求被导向这个账户。
这就是自动回复电子邮件的样子:
![如何操作...]()
它是如何工作的...
我们从一个每分钟运行一次(60 秒)的while循环开始。每次迭代都会读取支持收件箱并搜索未读电子邮件。
如果while循环发现任何未读电子邮件,它将获取该电子邮件并获取fr属性。fr属性让你获取电子邮件消息的from字段。from字段是请求支持工程师信息的客户电子邮件地址。
一旦我们有了客户的电子邮件地址,我们就会从我们的收件箱向客户发送自动回复。前面的截图显示了自动回复的确切样子。太酷了,所以现在当客户通过发送电子邮件向客户支持工程师提问时,他们将会收到一个带有常见问题解答部分链接的自动电子邮件回复。
这样,客户可以快速地从常见问题解答链接中获取所需信息。此外,由于支持工程师不需要手动回复日常支持请求,因此他们的工作负担也得到了减轻。
我相信凯莉会对这个改进感到高兴。她明白客户支持流程已经自动化到一定程度,并希望很快看到生产力的提升!
还有更多...
太棒了!你还可以用电子邮件做很多事情。尝试下载电子邮件附件了吗?想试试看?我们会让你亲自尝试。下章再见!
第六章. 演示文稿及其他
哦,今天还要给老板做一个演示文稿!我能不能简单地运行一个程序生成演示文稿,而不是手动从头再来?别再担心了;这一章可能正是解决您所有担忧的方法。我们将探讨您可以使用 Python 以自动化方式创建自己演示文稿的各种方法。
在本章中,我们将涵盖以下菜谱:
-
读取 PowerPoint 演示文稿
-
创建和更新演示文稿,以及添加幻灯片
-
玩转布局、占位符和文本框
-
使用不同形状和添加表格
-
图片和图表的视觉盛宴
-
自动化每周销售报告
简介
当涉及到报告数据或工作管理状态或展示一个想法时,PowerPoint 演示文稿是您最好的选择之一。PowerPoint 允许您制作交互式多媒体幻灯片来展示信息。问问您商业界的几位朋友;专业人士几乎都是在演示文稿中思考,这意味着思维过程是围绕PowerPoint演示文稿的幻灯片结构化的(简称为PPT)。
在本章中,我们讨论使用 Python 生成自定义 PowerPoint 演示文稿。您将了解如何读取、写入和操作 PPT 文件。PPT 提供了添加表格、托管图像和展示图表等功能;我们将学习如何使用 Python 来操作所有这些有趣的功能。
在本章中,我们将使用二进制文件格式:.pptx。与 PPT 版本不同,PPTX 演示文稿使用的是自 Microsoft Office 2007 引入的 Microsoft Open XML 格式。
本章中的菜谱将专注于帮助我们执行多个 PPTX 文件操作的 Python 模块;具体来说,我们将在本章中关注以下 Python 模块:
-
python-pptx(python-pptx.readthedocs.io/en/latest/index.html) -
pandas(pandas.pydata.org/)
注意
注意,尽管我们试图涵盖与 PowerPoint 演示文稿相关的所有主要操作,但总是有可能做更多。因此,我强烈建议您尝试多种技巧,并用 Python 进行实践。
您也可以使用 Windows 的 Win32 COM API 在 Windows 操作系统中处理 PPT,而不是使用python-pptx模块(我们将在本章中使用)。
读取 PowerPoint 演示文稿
基于我们对 PowerPoint 演示文稿的经验,我们知道 PPT 文件包含幻灯片,每张幻灯片都包含需要向观众展示的详细信息。这个菜谱将向您展示如何使用python-pptx模块从 PPTX 文件中提取信息。
准备工作
要逐步完成这个菜谱,我们需要安装python-pptx模块。让我们使用 Python 的pip来安装这个模块:
chetans-MacBookPro:ch08 Chetan$ sudo pip install python-pptx
Password:
Downloading/unpacking python-pptx
Downloading python-pptx-0.6.0.tar.gz (6.3MB): 6.3MB downloaded
Running setup.py (path:/private/tmp/pip_build_root/python-pptx/setup.py) egg_info for package python-pptx
Requirement already satisfied (use --upgrade to upgrade): lxml>=3.1.0 in /Library/Python/2.7/site-packages (from python-pptx)
Requirement already satisfied (use --upgrade to upgrade): Pillow>=2.6.1 in /Library/Python/2.7/site-packages (from python-pptx)
Requirement already satisfied (use --upgrade to upgrade): XlsxWriter>=0.5.7 in /Library/Python/2.7/site-packages (from python-pptx)
Installing collected packages: python-pptx
Running setup.py install for python-pptx
Successfully installed python-pptx
Cleaning up...
已经安装了模块吗?让我们开始吧!
如何做到这一点...
-
我们首先使用 Microsoft PowerPoint 2013 创建一个 PPTX 文件。我们将使用这个文件作为样本来学习如何从演示文稿中读取和提取数据。如果你下载了这本书的代码示例,你也会得到这个文件。我们称这个文件为
myprofile.pptx;它包含有关本书作者的信息,其中包含两张幻灯片。以下截图显示了文件内容:![如何做...]()
-
如果你查看演示文稿,第一张幻灯片有两个文本项:标题项是Chetan Giridhar,副标题是世界充满知识.... 第二张幻灯片有更多数据;它有不同的布局,幻灯片的标题是他希望,包含作者的所有四个愿望和一个圆形内容,这是生命的循环。很有趣!
-
在你的电脑上,转到终端并使用
vim或选择你喜欢的编辑器。为了读取 PPT 文件,让我们首先使用 Python 代码为myprofile.pptx创建一个演示文稿对象:from pptx import Presentation path_to_presentation = 'myprofile.pptx' prs = Presentation(path_to_presentation) print "Presentation object for myprofile file: ", prs上述代码片段的输出如下:
Presentation object for myprofile file: <pptx.presentation.Presentation object at 0x10e56e550> -
好的;现在我们有了演示文稿对象,让我们使用它来获取幻灯片对象。我们知道演示文稿有两张幻灯片。以下代码获取了幻灯片对象。幻灯片在
slide对象中以 Python 列表的形式表示,并且可以在 for 循环中迭代:print "Slides are:" for slide in prs.slides: print "Slide object:", slide -
上述代码片段使用演示文稿对象
prs来检索幻灯片对象。代码片段的输出如下:Slides are: Slide object: <pptx.slide.Slide object at 0x10e59f500> Slide object: <pptx.slide.Slide object at 0x10e59f460> -
好的,这很整洁!让我们再深入一层,看看
slides对象的一些属性。以下代码打印了slide对象的一些属性:print "Slide has following objects:" slide1, slide2 = prs.slides[0], prs.slides[1] print "Slide Ids: \n", slide1.slide_id, ",", slide2.slide_id print "Slide Open XML elements: \n", slide1.element, ",", slide2.element print "Slide layouts: \n", slide1.slide_layout.name, ",", slide2.slide_layout.name -
注意到第一张幻灯片的布局是标题幻灯片,下一张幻灯片是标题和内容,这确实是如此。我们还打印了
幻灯片 ID和Open XML 元素:Slide has following objects: Slide Ids: 256, 257 Slide Open XML elements: <Element {http://schemas.openxmlformats.org/ presentationml/2006/main}sld at 0x109fc2d60>, <Element {http://schemas.openxmlformats.org/ presentationml/2006/main}sld at 0x109fc23c0> Slide layouts: Title Slide, Title and Content -
现在,每个幻灯片都包含一些形状。例如,第一张幻灯片有两个文本占位符,标题和副标题。在第二张幻灯片中,我们有两个占位符,但还有一个圆形形状。以下代码打印了这些信息:
print "Shapes in the slides" i=1 for slide in prs.slides: print 'Slide', i for shape in slide.shapes: print "Shape: ", shape.shape_type i +=1上述代码片段的输出如下。你可以观察到第一张幻灯片包含文本框,但第二张幻灯片也有一个自动形状:
Shapes in the slides Slide 1 Shape: PLACEHOLDER (14) Shape: PLACEHOLDER (14) Slide 2 Shape: PLACEHOLDER (14) Shape: PLACEHOLDER (14) Shape: AUTO_SHAPE (1) -
好的,所以现在我们有了幻灯片、幻灯片布局和幻灯片形状。让我们尝试从两张幻灯片和所有形状中获取文本内容。以下代码正好做到了这一点:
text_runs = [] for slide in prs.slides: for shape in slide.shapes: if not shape.has_text_frame: continue for paragraph in shape.text_frame.paragraphs: for run in paragraph.runs: text_runs.append(run.text) print "Text is: ", text_runs
代码示例的输出如下。它包含了两张幻灯片上的所有文本。在python-pptx的世界里,这些被称为文本运行:
Text is: [u'Chetan Giridhar', u'World is full of knowledge..', u'He wishes to', u'Travel round the world', u'Build apps', u'Have fun', u'Die peacefully', u'This is circle of life']
它是如何工作的...
在这个菜谱中,我们读取了整个演示文稿,并获取了两张幻灯片的内容。
我们首先使用 Microsoft PowerPoint 2013 手动创建了一个 PPTX 文件,并使用 PPTX 模块的 Presentation 类创建了一个 myprofilepptx 文件的 prs 对象。使用此对象,我们通过 presentation 对象的 prs.slides 方法获得了对幻灯片的访问权限。
接下来,我们使用了 slides 对象来获取所有可用的形状,包括 slides.shapes 中的幻灯片。遍历此对象帮助我们获取幻灯片中的形状,如 PLACEHOLDER 和 AUTO_SHAPE。我们将在本章的后续部分学习更多关于幻灯片和形状的内容。
然后,我们使用了 shape.has_text_frame 属性来检查形状是否有文本框架,如果有,则从文本框架中获取 paragraphs 对象。段落对象的 runs 属性包含了实际的文本数据列表,然后这些数据被存储在数组 text_runs 中。
还有更多...
太酷了!所以,我们在一个菜谱中学习了很多东西:演示文稿对象、幻灯片、布局、形状、文本框架和段落。有了这些,我们处于一个很好的位置来读取 PPTX 文件。
所有这些都很棒,但是嘿,我们想要创建新的 PPTX 文件,对吧?并且希望自动化创建演示文稿?那么,让我们继续看看如何在 Python 中实现这一点。
创建和更新演示文稿,以及添加幻灯片
在本节中,我们继续使用 python-pptx 模块。因此,我们不需要安装任何新的模块。我们将学习如何创建一个空白演示文稿并将其添加到幻灯片中,当然,还有一些内容。
如何操作...
-
让我们从创建一个带有Yo! Python字样的非常简单的示例 PPT 开始。以下代码帮助我们创建演示文稿:
from pptx import Presentation prs = Presentation() slide = prs.slides.add_slide(prs.slide_layouts[0]) slide.shapes.title.text = "Yo, Python!" slide.placeholders[1].text = "Yes it is really awesome" prs.save('yoPython.pptx')如果我们运行前面的代码片段,它将创建一个标题为Yo, Python!,副标题为是的,它真的很棒的 PPTX 文件。以下截图显示了幻灯片的内容:
![如何操作...]()
此外,请注意幻灯片布局是标题幻灯片的布局。
-
我们也可以从一个现有的演示文稿中创建一个新的演示文稿。在下一个代码示例中,我们使用 PowerPoint 模板创建一个新的 PPT,并向其中添加了一个带有文本内容的幻灯片。我们使用以下模板进行此示例:
![如何操作...]()
如果我们在模板演示文稿上运行以下程序,我们将得到一个新的 PPT,如下一张截图所示:
from pptx import Presentation prs = Presentation('sample_ppt.pptx') first_slide = prs.slides[0] first_slide.shapes[0].text_frame.paragraphs[0] .text = "Hello!" slide = prs.slides.add_slide(prs.slide_layouts[1]) text_frame = slide.shapes[0].text_frame p = text_frame.paragraphs[0] p.text = "This is a paragraph" prs.save('new_ppt.pptx') -
第一张幻灯片更新了标题文本Hello!,并添加了一个带有布局、标题和内容的新的幻灯片,文本为这是一个段落。以下截图显示了新创建的演示文稿
new_ppt.pptx:![如何操作...]()
它是如何工作的...
在本节中,我们学习了如何使用 Python 创建演示文稿。在前面的代码片段中,我们实现了三个不同的功能。
首先,我们使用默认模板创建了一个标题幻灯片,并向其中添加了标题文本和副标题文本。我们通过以下步骤实现了这一点:
-
使用
pptx模块的Presentation类创建了一个演示文稿对象prs。 -
然后,使用
prs对象通过add_slide()方法添加了一个新的幻灯片。将布局0作为参数传递给add_slide(),这表示新幻灯片是类型Title,并由变量slide引用。 -
标题布局通常包含一个标题和副标题。标题文本的内容是通过
slide.shape.title.text属性添加的,而副标题的内容是通过slide.placeholders.text属性添加的。
接下来,我们从一个现有的 PPT 模板中创建了一个新的演示文稿。该模板存储在 sample_ppt.pptx 文件中,并且已经包含了一个空白布局幻灯片。这是我们在这个菜谱中实现的内容:
-
我们从模板 PPT 中创建了一个演示文稿对象
prs。然后我们使用演示文稿对象来引用第一个幻灯片,prs.slides[0],它存储在变量first_slide中。 -
然后使用
first_slide对象访问第一个形状,即标题文本。标题文本随后更新为内容 Hello! -
之后,我们添加了一个新的幻灯片,其布局为 Layout 一个(标题和内容),并用
slide变量引用它。新创建的幻灯片的第一个形状是一个文本框,其中添加了内容 这是一个段落。 -
最后,我们将新创建的演示文稿以
new_ppt.pptx的名称保存。
还有更多...
太棒了!所以,我们学习了如何从头创建新的演示文稿,更新现有的模板,向其中添加新内容并创建演示文稿,最后,创建具有不同类型布局和项目符号数据的演示文稿。在下一个菜谱中,让我们看看我们可以使用 Python 做些什么其他的事情。
玩转布局、占位符和文本框
现在,让我们继续做一些有趣的 PPT 操作。重要的是,我们将讨论最常用的操作。
准备工作
此菜谱不需要特定的模块;我们将使用为早期菜谱安装的 python-pptx 模块。在这个菜谱中,我们将使用不同的幻灯片布局,并玩转形状和文本。
如何操作...
-
让我们更进一步,使用不同类型的幻灯片布局,并在其中添加项目符号内容。以下代码片段完成了我们所需的工作:
from pptx import Presentation prs = Presentation() two_content_slide_layout = prs.slide_layouts[3] slide = prs.slides.add_slide(two_content_slide_layout) shapes = slide.shapes title_shape = shapes.title title_shape.text = 'Adding a Two Content Slide' body_shape = shapes.placeholders[1] tf = body_shape.text_frame tf.text = 'This is line 1.' p = tf.add_paragraph() p.text = 'Again a Line 2..' p.level = 1 p = tf.add_paragraph() p.text = 'And this is line 3...' p.level = 2 prs.save('two_content.pptx')当我们运行前面的 Python 代码时,我们得到一个新的 PPT,它包含了一个带有添加的项目符号内容的 两内容 幻灯片。以下截图显示了创建的新演示文稿的输出。不错,对吧?我们可以将项目符号内容添加到幻灯片左侧的占位符中:
![如何操作...]()
-
现在,让我们发挥创意,在我们的幻灯片中添加另一种类型的形状,即文本框。以下代码片段向我们的幻灯片中添加了一个文本框:
from pptx import Presentation from pptx.util import Inches, Pt prs = Presentation() blank_slide_layout = prs.slide_layouts[6] slide = prs.slides.add_slide(blank_slide_layout) txBox = slide.shapes.add_textbox(Inches(2), Inches(2), Inches(5), Inches(1)) tf = txBox.text_frame tf.text = "Wow! I'm inside a textbox" p = tf.add_paragraph() p.text = "Adding a new text" p.font.bold = True p.font.italic = True p.font.size = Pt(30) prs.save('textBox.pptx')以下截图显示了新创建的 PPTX 文件的外观。如果你仔细观察,你会注意到我们添加了一个文本框,并且文本框内的第二行文本是粗体、斜体,字体大小为 30。
![如何做到这一点...]()
它是如何工作的...
在这个示例中,我们使用了空白模板来添加一个双内容布局幻灯片。让我们看看我们还做了些什么:
-
在代码中,我们使用
prs.slide_layouts[3]将演示文稿对象prs传递给添加具有双内容布局的幻灯片。双内容布局还有一个标题文本,它通过使用shapes.title属性更新为添加双内容幻灯片。 -
接下来,我们查看占位符。占位符是一个预先格式化的容器,可以向其中添加内容。占位符是形状的类别,这意味着多个形状可以有占位符。例如,一个自动形状(如第一个示例中的圆形)是一个占位符;图片或图形框架也可以是占位符。在一个双内容幻灯片中,我们有两个占位符,一个在左侧,一个在右侧。我们使用
shapes.placeholders[1]定位左侧的占位符,并将第一行文本这是第一行。添加到由shapes.placeholders[1].text_frame引用的文本框架中。 -
然后,我们通过向
text_frame添加一个段落并使用add_paragraph()方法添加文本再次是第二行...在一级和这是第三行...在二级。 -
基本上,并非所有形状都包含文本,但可以通过使用
shape.has_text_frame属性来检查一个形状是否支持文本。在这个示例中,我们知道我们的形状包含一个可以处理文本内容的占位符。因此,我们使用text_frame属性添加了第一行文本。同样,我们使用add_paragraph()方法添加后续的文本,使用level属性以项目符号的形式添加。 -
最后,我们将新的演示文稿以
two_content.pptx为名保存。如果你查看截图,你会看到文本被添加到幻灯片左侧文本框上的项目符号样式。
接下来,我们在演示文稿中添加了一个文本框。文本框在演示文稿中非常常用。人们使用文本框来突出重点,并利用文本框的调整大小和移动功能。以下是我们示例中做了什么:
-
我们首先创建了一个布局为六的空白幻灯片,并使用
add_slide()方法将其添加到演示文稿中。 -
接下来,我们创建了一个具有适当尺寸的文本框。我们使用
Inches(2)作为左上角坐标,然后分别使用Inches(5)和Inches(1)来管理宽度和高度。在这里,英寸映射到相同的现实世界实体,即1 英寸=2.54 厘米。我们使用add_textbox()方法将这个文本框添加到幻灯片中。 -
使用文本框对象,我们通过
text_frame属性添加了一个文本框架对象tf。 -
如前一个示例所示,我们在文本框架中添加了文本哇!我就在文本框里面。
-
我们通过使用
add_paragraph()方法添加一个段落,并将文本添加新文本添加到这个段落中,使文本加粗、斜体,并将字体大小增加到30。 -
最后,我们将文件保存为
textBox.pptx。
你学习了关于占位符和文本框的内容。你学习了如何使用文本框和段落向我们的幻灯片添加文本。你还学习了如何向我们的幻灯片添加所需尺寸的文本框。
使用不同形状和添加表格
好的,让我们继续前进,通过添加不同的形状、表格甚至图片来使我们的演示文稿更有趣!为什么等待?让我们迅速采取行动。
准备工作
对于这个食谱,不需要特定的模块;我们将使用为早期食谱安装的python-pptx模块。
如何做到这一点...
-
在这个食谱中,我们将向我们的演示文稿添加一些形状。形状在演示文稿中非常有用,因为它们可以代表现实世界中的对象,可以指示关系,并为听众(正在听演示的人)提供很好的视觉反馈。在这个食谱中,我们添加了一个主页按钮,然后添加了一个矩形标注来显示我们的主页按钮的位置。我们还将自定义颜色填充到我们的
Callout元素中。以下代码将形状添加到演示文稿中:from pptx import Presentation from pptx.enum.shapes import MSO_SHAPE from pptx.util import Inches from pptx.dml.color import RGBColor prs = Presentation() title_only_slide_layout = prs.slide_layouts[5] slide = prs.slides.add_slide(title_only_slide_layout) shapes = slide.shapes shapes.title.text = 'Adding Shapes' shape1 = shapes.add_shape(MSO_SHAPE.RECTANGULAR_CALLOUT, Inches(3.5), Inches(2), Inches(2), Inches(2)) shape1.fill.solid() shape1.fill.fore_color.rgb = RGBColor(0x1E, 0x90, 0xFF) shape1.fill.fore_color.brightness = 0.4 shape1.text = 'See! There is home!' shape2 = shapes.add_shape(MSO_SHAPE.ACTION_BUTTON_HOME, Inches(3.5), Inches(5), Inches(2), Inches(2)) shape2.text = 'Home' prs.save('shapes.pptx')在运行前面的代码后,我们得到了一个新的演示文稿,
shapes.pptx,其外观如下所示:![如何做到这一点...]()
-
真是 neat!现在让我们看看我们是否可以向我们的演示文稿添加一个表格。同样,表格在演示文稿中用于展示数据和做出明智的决策。演讲者(负责制作演示的人)经常通过表格展示某些项目的相关事实,并从观众那里征求讨论或反馈。使用 Python 在演示文稿中添加表格是微不足道的;请参考以下代码。在这个例子中,我们添加了一个包含三名学生信息的表格:
from pptx import Presentation from pptx.util import Inches prs = Presentation() title_only_slide_layout = prs.slide_layouts[5] slide = prs.slides.add_slide(title_only_slide_layout) shapes = slide.shapes shapes.title.text = 'Students Data' rows = 4; cols = 3 left = top = Inches(2.0) width = Inches(6.0) height = Inches(1.2) table = shapes.add_table(rows, cols, left, top, width, height).table table.columns[0].width = Inches(2.0) table.columns[1].width = Inches(2.0) table.columns[2].width = Inches(2.0) table.cell(0, 0).text = 'Sr. No.' table.cell(0, 1).text = 'Student Name' table.cell(0, 2).text = 'Student Id' students = { 1: ["John", 115], 2: ["Mary", 119], 3: ["Alice", 101] } for i in range(len(students)): table.cell(i+1, 0).text = str(i+1) table.cell(i+1, 1).text = str(students[i+1][0]) table.cell(i+1, 2).text = str(students[i+1][1]) prs.save('table.pptx')如果我们运行这个代码片段,你将在演示文稿中看到一个包含所有学生数据的表格。请参考以下截图:
![如何做到这一点...]()
它是如何工作的...
在第一个代码片段中,我们通过以下操作向我们的演示文稿添加了一些形状:
-
我们通过使用
add_shapes()方法添加形状,该方法接受形状类型作为输入。 -
在我们的代码中,我们使用了
MSO_SHAPE枚举(其中列出了所有形状)并选择了两个形状,即MSO_SHAPE.RECTANGULAR_CALLOUT和MSO_SHAPE.ACTION_BUTTON_HOME。就像在文本框的情况下,add_shapes()方法也需要使用Inches()方法定义形状的大小。 -
我们还成功地使用
SlideShape类的fill方法定义了调用形状的自定义颜色。我们使用shape.fill.fore_color.rgb属性设置了调用形状的颜色。使用的 RGB 颜色是1E90FF,这是一种浅蓝色,如截图所示。我们还使用shape.fill.fore_color.brightness属性设置了颜色亮度。 -
当然,我们通过设置
shape.text属性在形状中添加了文本。最后,我们将文件保存为shapes.pptx。
在第二个例子中,我们使用 Python 代码的帮助在演示文稿中添加了一个漂亮的表格。这是我们的做法:
-
我们使用
add_slide()方法创建了一个仅包含标题布局的演示文稿,并添加了一个幻灯片。我们还定义了幻灯片的标题为学生数据。 -
添加表格就像添加形状一样简单。我们使用
add_table()方法将表格添加到演示文稿中。正如预期的那样,add_table()方法期望输入行数和列数,同时期望表格的大小。在我们的例子中,我们将行数设置为4,列数设置为3,表格的大小为坐标Inches(2)、Inches(2)、Inches(6)和Inches(8),这意味着表格距离左侧 2 英寸,距离幻灯片顶部 2 英寸以下,表格宽度为 6 英寸,高度为 1.2 英寸(15.3cm x 3.1cm)。 -
我们定义了表格具有三个列;每个列的宽度设置为 2 英寸。我们使用
table.columns.width属性来设置这一点。我们还使用table.cell(row, column).text属性设置了列标题的文本为序号、学生姓名和学生 ID。请注意,在这里,行值始终为0,表示第一行或标题行,而列从0到2变化,表示三个列。 -
为了本例的目的,我们使用了一个预定义的字典
students,其中包含学生姓名和学生 ID 等信息。我们遍历所有学生信息,并更新表格的单元格以填充适当的信息,因此表格中包含了所有学生数据,如截图所示。 -
最后,我们将演示文稿保存为
table.pptx。
还有更多...
太棒了!使用 Python 制作演示文稿我们还能做什么呢?或者你们中的一些人已经期待我谈论图表或图片了,不是吗?哦,是的,我们也会涵盖这一点。让我们来点图形的,换句话说!
视觉盛宴:图片和图表
是时候了。在本节中,我们将探讨如何向您的演示文稿添加图片和图表。他们说“一图胜千言”,确实,您一定见过包含大量图片和图表的演示文稿。它们的存在是有原因的。您可以在一张幻灯片中传达尽可能多的信息。图表和图片都具有这种力量,如果不了解它们,本章就不完整。所以,让我们开始吧!
准备工作
此菜谱不需要特定的模块;我们将使用为早期菜谱安装的python-pptx模块。
如何操作...
我们将这个菜谱分为两部分。首先,我们将介绍如何向幻灯片添加图片,在下一部分,我们将处理图表。
-
以下代码片段帮助我们向幻灯片添加图片:
from pptx import Presentation from pptx.util import Inches img_path = 'python.png' img_path2 = 'learn_python.jpeg' prs = Presentation() blank_slide_layout = prs.slide_layouts[6] slide = prs.slides.add_slide(blank_slide_layout) left = top = Inches(2) pic = slide.shapes.add_picture(img_path, left, top, height=Inches(2), width=Inches(3)) left = Inches(2) top = Inches(5) height = Inches(2) pic = slide.shapes.add_picture(img_path2, left, top, height=height) prs.save('picture.pptx')当我们运行上述程序时,我们得到一个包含两张图片的幻灯片,如下面的截图所示:
![如何操作...]()
-
现在,让我们看看我们是否可以用 Python 代码向幻灯片添加图表。
python-pptx模块支持多种类型的图表,如折线图、柱状图和气泡图,但我的最爱一直是饼图。在我们的菜谱中添加一个如何?是的,以下代码向演示文稿添加了一个饼图:from pptx import Presentation from pptx.chart.data import ChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.enum.chart import XL_LABEL_POSITION, XL_LEGEND_POSITION from pptx.util import Inches prs = Presentation() slide = prs.slides.add_slide(prs.slide_layouts[5]) slide.shapes.title.text = 'Data based on regions' chart_data = ChartData() chart_data.categories = ['West', 'East', 'North', 'South'] chart_data.add_series('Series 1', (0.35, 0.25, 0.25, 0.15)) x, y, cx, cy = Inches(2), Inches(2), Inches(6), Inches(4.5) chart = slide.shapes.add_chart( XL_CHART_TYPE.PIE, x, y, cx, cy, chart_data ).chart chart.has_legend = True chart.legend.position = XL_LEGEND_POSITION.BOTTOM chart.legend.include_in_layout = False chart.plots[0].has_data_labels = True data_labels = chart.plots[0].data_labels data_labels.number_format = '0%' data_labels.position = XL_LABEL_POSITION.OUTSIDE_END prs.save('chart.pptx')上述程序输出的结果如下:
![如何操作...]()
它是如何工作的...
在第一个代码片段中,我们向幻灯片添加了两张图片。我们是如何做到的?听起来就像逻辑一样,我们使用了add_picture()方法。这个库不是很好吗?使用add_textbox()添加文本框,使用add_slide()添加幻灯片,现在使用add_picture()添加图片。让我们更深入地看看我们在菜谱的第一部分中做了什么:
-
如预期的那样,
add_picture()期望从图像需要添加到演示文稿的路径,就像其他方法一样,图片的坐标和大小。在我们的例子中,我们添加了两张图片。第一张图片是python.org,我们将其配置为从左侧 2 英寸和从顶部 2 英寸处显示。我们还配置了图片的大小,使其宽度为 3 英寸,高度为 2 英寸。 -
我们添加的第二张图片是
learn_python.jpeg;它被配置为从左侧 2 英寸,从顶部 5 英寸处显示,高度为 2 英寸,宽度与图片宽度相同。 -
在我们的例子中,我们创建了一个新的幻灯片,使用了空白幻灯片布局,并添加了这两张图片,最后将文件保存为
picture.pptx。
在第二部分,我们在幻灯片演示文稿中添加了一个饼图。我们这样做:
-
我们添加了一个幻灯片,并将标题文本设置为基于区域的数据。
-
然后,我们创建了一个
ChartData()类的对象,并将其命名为chart_data。我们使用chart_data.categories属性定义了饼图的类别,并将其设置为区域数组['西', '东', '北', '南']。我们还使用add_series()方法为所有区域配置了chart_data对象的数据。 -
我们是如何在演示文稿幻灯片上添加这个图表的?是的,你猜对了:
add_chart()方法为我们做到了这一点。add_chart()方法期望图表类型作为其中一个参数,并且像其他方法一样,它期望维度。在我们的代码中,我们还设置了has_legend、number_format和数据标签的属性,使饼图看起来很棒!
更多内容...
太棒了!所以我们在这个章节中学到了很多有趣的东西。但将知识应用于解决现实世界的用例不是更有趣吗?你听说过亚历克斯在每周销售报告中遇到的问题吗?
自动化每周销售报告
亚历克斯是 Innova 8 Inc 公司的销售总监,该公司销售笔记本电脑和商业软件。他有一群销售经理向他汇报。他负责衡量下属的成功,并每周向销售副总裁汇报。亚历克斯的老板主要对两件事感兴趣:来自商业账户产生的收入以及销售经理的业绩。亚历克斯需要在每周的员工会议上报告这些数字。他使用 PowerPoint 作为工具,每周将数据汇总并展示给销售副总裁。
然而,亚历克斯有一些问题。他从销售经理那里得到的数据通常在 Excel 表格中。此外,数据非常动态,根据客户是否在会议前付款,数据会一直变化到最后一刻。由于这种变化,亚历克斯很难提前为会议制作演示文稿。此外,亚历克斯分析数据并生成图表的过程非常繁琐——这是一个完全手动的过程。
你能帮助亚历克斯用你在本章中学到的知识吗?
准备工作
如果分析这些问题,我们可以为亚历克斯自动化整个流程。亚历克斯的数据在 Excel 表格中;我们可以使用 Python 的pandas模块轻松读取这些数据。此外,我们可以使用python-pptx模块创建一个新的演示文稿。
以下步骤可以帮助解决亚历克斯的问题:
-
读取 Excel 表格的内容并获取所需数据。
-
创建一个新的 PowerPoint 演示文稿,并向其中添加两张幻灯片。
-
在第一张幻灯片上,创建一个饼图来展示不同账户的收入数据,并根据百分比进行比较。
-
在第二张幻灯片上,添加一个柱状图来比较销售经理基于收入的业绩。
对于这个菜谱,让我们安装pandas模块。我们使用我们最喜欢的 Python 实用工具pip来安装pandas。我们使用以下命令来安装pandas:
pip install pandas
注意
这只是一个使用pandas处理 Excel 表格的简单示例。pandas模块有一套全面的 API,可用于数据分析、过滤和聚合。我们在处理数据分析和可视化的章节中讨论了所有这些以及更多内容。
现在我们准备好了,让我们看看帮助 Alex 自动化这个过程的代码。
如何操作...
-
让我们先看看包含每周销售数据的 Excel 表格。我们称这个文件为
Sales_Data.xlsx,它看起来如下截图所示:![如何操作...]()
-
现在,让我们看看代码片段,这将帮助 Alex 读取这些数据并生成他需要的精确演示文稿:
from pptx import Presentation from pptx.chart.data import ChartData from pptx.enum.chart import XL_CHART_TYPE from pptx.enum.chart import XL_LABEL_POSITION, XL_LEGEND_POSITION from pptx.util import Inches from datetime import datetime import pandas as pd xls_file = pd.ExcelFile('Sales_Data.xlsx') prs = Presentation('sample_ppt.pptx') first_slide = prs.slides[0] first_slide.shapes[0].text_frame.paragraphs[0] .text = "Weekly Sales Report %s" \ % datetime.now().strftime('%D') first_slide.placeholders[1].text = "Author: Alex, alex@innova8" blank_slide_layout = prs.slide_layouts[6] slide = prs.slides.add_slide(blank_slide_layout) slide.shapes.title.text = '% Revenue for Accounts' df = xls_file.parse('Sales') df['total'] = df['Items'] * df['UnitPrice'] plot = df.groupby('Account')['total'] .sum().plot(kind='pie', \ autopct='%.2f', fontsize=20) f=plot.get_figure() f.savefig("result.png", bbox_inches='tight', dpi=400) left = Inches(2.5); top = Inches(3) pic = slide.shapes.add_picture("result.png", left, top, height=Inches(4), width=Inches(5)) slide = prs.slides.add_slide(prs.slide_layouts[6]) slide.shapes.title.text = 'Sales Manager Performance' df = xls_file.parse('Sales') df['total'] = df['Items'] * df['UnitPrice'] mgr_data = df.groupby(['Manager'])['total'].sum() managers = mgr_data.index.values.tolist() sales = [] for mgr in managers: sales.append(mgr_data.loc[mgr]) chart_data = ChartData() chart_data.categories = managers chart_data.add_series('Series 1', tuple(sales)) x, y, cx, cy = Inches(2), Inches(3), Inches(6), Inches(4) chart = slide.shapes.add_chart( XL_CHART_TYPE.COLUMN_CLUSTERED, x, y, cx, cy, chart_data ).chart chart.has_legend = True chart.legend.position = XL_LEGEND_POSITION.BOTTOM chart.legend.include_in_layout = False prs.save('sales.pptx') -
现在,如果我们运行前面的代码片段,我们将得到一个包含所有必要图表和数据点的 PowerPoint 演示文稿,这正是 Alex 每周销售报告中需要的。看看演示文稿的所有幻灯片的截图。这正是 Alex 所需要的!第一张幻灯片是演示文稿的标题幻灯片,标题为每周销售报告 <日期>。它还提到了 Alex 的名字和电子邮件地址,作为本演示文稿的作者:
![如何操作...]()
第二张幻灯片展示了通过饼图帮助展示账户间的收入分布:
![如何操作...]()
最后,最后一张幻灯片通过柱状图比较了所有销售经理的表现。酷吧?
![如何操作...]()
它是如何工作的...
在前面的例子中,我们首先为包含所有销售数据的 Excel 表格Sales_Data.xlsx创建了一个读取对象。我们通过使用pandas模块的ExcelFile()方法实现了这一点。
接下来,我们通过使用sample_ppt.pptx文件创建了一个新的演示文稿。如果你还记得,我们的示例演示文稿有一个没有任何文本的标题幻灯片。因此,在我们的代码片段中,我们通过设置标题为每周状态报告 <YYYY - MM - YY>来更新了这个标题幻灯片。我们还添加了一个包含作者名字的副标题,在这个例子中是作者:Alex alex@innova8。我们通过文本框中的占位符来设置这些标题。
接下来,我们在新的演示文稿中添加了一个新的空白幻灯片,布局为六。我们使用这个幻灯片来添加账户的营收数据。我们通过prs.slides.add_slide()方法实现了这一点。然而,我们的数据存储在 Excel 表格中,因此我们使用 Excel 表格的reader对象来读取销售工作表。销售工作表包含了亚历克斯用于分析的所有数据。pandas 模块以数据框的形式读取 Excel 数据(以矩阵格式存储的数据)。如果你查看 Excel 表格的截图,我们可以看到两列,价格和数量,这表示了销售的笔记本电脑或软件许可证的数量以及每单位的价格。因此,在我们的代码中,我们首先将这些值相乘以获取 Excel 记录中每个条目的收入,并将其存储在以total为列名的数据框中。现在,我们不仅需要总收入数字;还需要根据账户对其进行分类。使用数据框,获取这些信息非常简单;就像运行一个 SQL 查询一样。如果你查看代码,我们已经通过Account对数据框进行了分组,并汇总了所有的total数据(通过数量乘以价格获得)并使用这些数据点绘制饼图,这样亚历克斯就可以更容易地比较每个账户的收入占总收入的比例。对于数据分组,我们使用了groupby()方法,然后使用sum()方法汇总了收入,并使用plot()方法绘制饼图。再次强调,如果图表在pandas中可用但不在 PowerPoint 中,那么它就没有用了,所以我们使用pandas的savefig()方法将图表保存为result.png文件;我们就是这样做的。最后,我们使用add_picture()方法将这张图片文件添加到我们的演示文稿中,并管理了图片的坐标和大小,以确保其可见且看起来很棒。
亚历克斯还需要绘制所有销售经理的表现。为此,我们使用了相同的方法来读取 Excel 表格数据,并以数据框的形式存储。对于这个问题,我们通过销售经理对数据进行了分组,并获取了每个销售经理分配的总收入。在这里,我们也使用了groupby()方法,但是在 Excel 数据的Manager列上。我们将所有销售经理的姓名存储在数组managers中,遍历每个销售经理的所有记录,获取他们的销售数据,并将其添加到列表sales中。然后我们将这个列表转换为元组以供以后使用。就像我们在前面的菜谱中看到的那样,我们使用ChartData()方法创建了一个图表数据对象,使用sales元组作为输入创建了一个簇状柱形图,并使用add_chart()方法将其添加到演示文稿的第二张幻灯片中。
最后,我们将这个新创建的演示文稿保存为sales.pptx,这作为亚历克斯的每周销售报告。就这样了!
我希望您喜欢本章中的食谱、我们讨论的示例和用例。我相信您也迫不及待地想要自动化您的演示文稿。我什么时候去您的办公桌欣赏您的工作呢?
第七章. API 的力量
本章将带你进入有趣的 API 世界。API 是当今商业世界的一个关键部分。查询数据、在服务之间交换信息等任务都依赖于 Web API 和 Webhooks。
在本章中,我们将涵盖以下主题:
-
设计你自己的 REST API
-
使用 Twitter API 自动化社交媒体营销
-
Webhooks 简介
-
实现 Webhooks
-
使用 Webhooks 自动化潜在客户管理
简介
在由互联网驱动的世界中,API 变得绝对不可或缺。你必须与之交互的每个网络应用程序都在其后端使用 API 来实现其核心功能——亚马逊、谷歌、推特,等等!更重要的是,你看到所有这些应用程序都是基于 API 繁荣起来的。亚马逊用它来驱动其支付交易,谷歌用它来显示所有这些花哨的地图。API 对商业如此重要,以至于从 CEO 到经理,再到软件开发者,你都能听到 API 这个词。总的来说,使用 API 是使不同的软件相互通信的基本方式。操作系统操作也是通过 API 执行的。它们从一开始就是至关重要的。
但 API 是什么?它们有什么用?我们如何开发自己的 API?它们是如何进入业务流程自动化的?在本章中,我们将找到所有这些问题的答案。
我们从一个更熟悉且更古老的网络术语开始:网络服务。网络服务本质上是在不同平台和不同语言上使用独立系统托管的不同应用程序之间的集成关键点。网络服务通过 WWW 相互通信,通常涉及两方:一方暴露一组 API,也称为服务器,另一方调用或消费服务器 API,也称为消费者或客户端。网络服务独立于客户端实现,因此与浏览器、手机或任何可以发起 API 调用的软件都很好地协同工作。
它们使用不同的协议进行通信,有不同的消息和 URI 合约。最常见网络服务的实现包括:
-
基于 HTTP 的REST(表示状态转移)网络服务
-
基于 SOAP 的(简单对象访问协议)网络服务
-
XML RPC(远程过程调用)
这些服务常用的消息格式包括:
-
JSON(JavaScript 对象表示法)
-
XML(可扩展标记语言)
今天的网络应用程序的核心是网络服务,因此需要提供良好的性能;它们需要可扩展和可靠。
对了,所以在本章中,我们将介绍基于 HTTP 的 REST API。你将详细了解如何使用 Python 开发 RESTful 网络服务。你还将学习客户端如何使用 RESTful 网络服务自动化他们的业务流程。
注意
注意,有不同术语可用于引用 API,例如 HTTP API、Web API 等。我建议您阅读有关它们的资料以获得更好的清晰度。然而,本质上,它们的核心理念是应用程序中两个服务或跨应用程序的多个服务器/服务之间的集成点。
在本章中,我们将查看以下列表中提到的多个 Python 模块:
-
flask(flask.pocoo.org/) -
twython(twython.readthedocs.io/en/latest/) -
pytz(pypi.python.org/pypi/pytz) -
django(www.djangoproject.com/) -
django-rest-hooks(github.com/zapier/django-rest-hooks)
设计自己的 REST API
表征状态转移(REST)在社区中获得了许多偏好和流行,几乎成为设计实现 RESTful 网络服务的默认架构风格。
注意
注意,还有其他可能的网络服务实现方式,例如遵循 SOAP 和 XML-RPC 方式,这些内容不在本章范围内。
在本食谱中,我们将学习如何使用 Python flask 微框架实现一个简单的 RESTful 网络服务。我们将实现一个用户服务用于用户管理,这是任何网络应用的一个强制方面。
REST 架构旨在与 HTTP 协议兼容,并具有资源概念,即统一资源标识符(URIs)。客户端通过不同的 HTTP 请求方法向这些 URI 发送请求,并作为响应返回受影响资源的状态。
那么,我们还在等什么呢?让我们设计和实现用户网络服务。
如何实现...
-
让我们从定义我们的模型——用户开始。我们的用户资源通常由以下属性标识:
-
id: 用于识别用户的唯一标识符 -
username: 在应用中使用的用户名 -
email: 用于电子邮件通知的用户电子邮件地址 -
status: 检查用户是否活跃或已验证
-
-
设计 REST API 涉及到识别资源(URIs)和动词(HTTP 方法),这些动词对用户模型进行操作。我们需要执行创建新用户、更新用户某些属性、获取用户或用户列表,或必要时删除用户等操作。我们还需要将我们的操作与 HTTP 动词相关联,并为我们服务定义 CRUD 操作。CRUD 表示在用户模型上执行创建、读取、更新和删除操作。以下表格显示了我们需要的内容:
URI 方法 操作 http://v1/users/GET获取可用用户列表 http://v1/users/POST创建新用户 http://v1/users/1/GET获取 ID 等于 1 的现有用户详细信息 http:///v1/users/1/PUT/DELETE更新或删除 ID 等于 1 的用户 -
现在让我们编写代码来实现 RESTful 用户服务。我们首先创建一个虚拟环境。我希望我们所有人都知道
virtualenv,但对于初学者来说,虚拟环境是一个隔离 Python 模块的工具。这有助于解决与权限相关的问题;它还有助于避免污染全局 Python 安装,并管理跨应用使用的同一模块的版本。如果你系统上没有virtualenv,你可以使用 Python 的pip安装它,或者从pypi.python.org/pypi/virtualenv下载它。chetans-MacBookPro:ch07 Chetan$ pip install virtualenv chetans-MacBookPro:ch07 Chetan$ virtualenv user New python executable in user/bin/python2.7 Also creating executable in user/bin/python Installing setuptools, pip, wheel...done. -
一旦我们安装了
virtualenv,我们需要使用简单的命令来激活它。正如你在下面的第二行中看到的,virtualenv用户已经被激活:chetans-MacBookPro:ch07 Chetan$ source user/bin/activate (user)chetans-MacBookPro:ch07 Chetan$ -
让我们继续在虚拟环境中安装
flask。我们使用 Python 的pip通过pip install flask命令来完成此操作:(user)chetans-MacBookPro:ch07 Chetan$ pip install flask Collecting flask Using cached Flask-0.11.1-py2.py3-none-any.whl Collecting click>=2.0 (from flask) Using cached click-6.6.tar.gz Collecting its dangerous>=0.21 (from flask) Collecting Werkzeug>=0.7 (from flask) Using cached Werkzeug-0.11.10-py2.py3-none-any.whl Collecting Jinja2>=2.4 (from flask) Using cached Jinja2-2.8-py2.py3-none-any.whl Collecting MarkupSafe (from Jinja2>=2.4->flask) Building wheels for collected packages: click Running setup.py bdist_wheel for click Stored in directory: /Users/chetan/Library/ Caches/pip/wheels/b0/6d/8c/ cf5ca1146e48bc7914748bfb1dbf3a40a440b8b4f4f0d952dd Successfully built click Installing collected packages: click, itsdangerous, Werkzeug, MarkupSafe, Jinja2, flask Successfully installed Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.10 click-6.6 flask-0.11.1 itsdangerous-0.24 -
如果你查看安装日志,我们会发现我们似乎已经安装了
flask以及模板引擎Jinja2和Werkzeug,这是一个支持多种操作的 WSGI 实用工具,例如处理 cookie、文件上传和请求/响应对象。 -
好的!我们已经安装了
flask,环境也设置得很好。让我们编写一个简约的 Web 应用程序,命名为app.py。我们的 Web 服务代码如下:from flask import Flask app = Flask(__name__) @app.route('/') def index(): return "Hello, Python!" if __name__ == '__main__': app.run(debug=True) -
如果你运行应用程序,你会看到 Flask 服务器正在 5000 端口上运行:
(user)chetans-MacBookPro:ch07 Chetan$ python app.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger pin code: 272-029-183 -
如果你尝试访问 5000 端口的服务器,你会看到我们预期的效果:
![如何操作...]()
-
太好了!现在让我们改进这个来实现用户 REST API。我们首先在
/v1/users/资源上实现 HTTP 的GET。在以下代码中,我们实现了一个flask路由,名为get_users()API,以返回所有用户的 JSON 格式:from flask import Flask, jsonify app = Flask(__name__) users = [ { 'id': 1, 'username': u'cjgiridhar', 'email': u'abc@xyz.com', 'active': True }, { 'id': 2, 'username': u'python', 'email': u'py@py.org', 'active': False } ] @app.route('/v1/users/', methods=['GET']) def get_users(): return jsonify({'users': users}) if __name__ == '__main__': app.run(debug=True) -
如果我们重新运行应用程序(如果你使用的是 PyCharm 等编辑器,每次你保存代码时,它都会自动重新加载应用程序),我们的
flask路由就会被加载;我们现在可以在/v1/users/API 上发出 HTTPGET请求。请求的输出将产生响应,如下一张截图所示。嘿,太酷了!我们为 RESTful 用户服务编写了第一个资源。 -
注意响应的头部部分:
-
Content-Type是 application/JSON(我们将在本章后面讨论消息格式) -
服务器是基于 Werkzeug 的 Flask
-
日期指的是服务器响应请求的时间
-
响应体包含以下内容:
-
包含所有用户信息的
users键输出 -
关于用户的信息包括所需的属性,如 ID、用户名、电子邮件和账户状态
响应格式是 JSON,如头部所示
注意
注意,我们使用 Firefox 的 RESTED 插件来发送这些请求。
![如何操作...]()
-
-
太好了!我们已经将第一个 URI 作为用户服务的一部分实现了。现在让我们快速继续实现下一个资源。在这里,我们需要根据 ID 获取用户。以下
flask路由将为我们完成这项工作:@app.errorhandler(404) def not_found(error): return make_response(jsonify({'error': 'Not found'}), 404) @app.route('/v1/users/<int:id>/', methods=['GET']) def get_user(id): for user in users: if user.get("id") == id: return jsonify({'users': user}) abort(404) -
在前面的代码中,我们定义了一个
flask路由,其 API 为get_user(id),它接受用户 ID 作为参数。当我们对这个 URI 发起 HTTPGET请求时,get_user()API 被调用;它内部查找所有可用的用户以定位到具有所需 ID 的用户。如果找到用户,则将用户记录以 JSON 格式返回;如果没有找到,服务器发送 HTTP 404 响应。以下是一个说明此过程的截图:![如何操作...]()
-
您希望用户在您的网络应用程序上注册,对吧?所以让我们编写一个
flask路由,它将帮助创建一个新的用户。以下代码执行此操作:@app.route('/v1/users/', methods=['POST']) def create_user(): if not request.json or not 'email' in request.json: abort(404) user_id = users[-1].get("id") + 1 username = request.json.get('username') email = request.json.get('email') status = False user = {"id": user_id, "email": email, "username": username, "active": status} users.append(user) return jsonify({'user':user}), 201 -
现在如果您在
/v1/users/资源上发起一个 HTTPPOST请求并将用户信息传递到请求体中,您将能够创建一个新的用户。默认情况下,用户的状 态将是未激活('active': False);当用户验证她的/他的电子邮件地址时,您可以将其设置为'active': False:![如何操作...]()
-
好吧!现在让我们快速看一下将编辑用户详情并在需要时删除用户的 REST API。以下 Flask 路由将编辑用户详情:
@app.route('/v1/users/<int:id>/', methods=['PUT']) def update_user(id): user = [user for user in users if user['id'] == id] user[0]['username'] = request.json.get( 'username', user[0]['username']) user[0]['email'] = request.json.get( 'email', user[0]['email']) user[0]['active'] = request.json.get( 'active', user[0]['active']) return jsonify({'users': user[0]}) -
现在如果我们对
/v1/users/:id/资源执行 HTTPPUT操作并传递更改后的数据,我们应该能够更新用户信息。在以下截图中,请求体包含需要更新为用户 ID 等于1的新电子邮件地址。当我们发起 HTTPPUT请求时,信息得到更新,我们有了用户的新电子邮件地址:![如何操作...]()
-
现在唯一待定的操作是实现
DELETE操作。我们可以使用这个操作来删除一个用户。但您可能会问,“我为什么要删除用户?”所以您可以为DELETE操作实现自己的实现;可能的话,您可以通过将active属性设置为False来使用户未激活。但为了这次讨论,让我们随意删除用户。 -
以下代码根据用户 ID 删除用户:
@app.route('/v1/users/<int:id>/', methods=['DELETE']) def delete_user(id): user = [user for user in users if user['id'] == id] users.remove(user[0]) return jsonify({}), 204 -
DELETE操作通常返回状态码 204 NO CONTENT,如下截图所示:![如何操作...]()
-
太棒了!所以我们已经完全实现了 RESTful 用户服务并使其运行。太好了!
它是如何工作的...
REST 是万维网的一种架构风格。它由一组协调的组件组成,其中重点在于组件角色和数据元素之间的交互,而不是实现。其目的是使网络更具可扩展性、可移植性和可靠性,并提高性能。
REST 架构根据以下约束工作:
-
客户端-服务器:统一资源定位符(URL)将 REST API 与客户端分开。服务器不关心用户界面或状态;因此,REST API 更具可扩展性。
-
无状态:这意味着每个请求都是独立的,并且与之前的请求或客户端没有关联。客户端必须包含完成请求所需的所有信息,会话状态保持在客户端,因此不会存储在服务器上。
-
可缓存:RESTful 网络服务可以缓存或不缓存响应。服务必须让客户端知道响应是否被缓存。这有助于提高系统的性能,因为某些请求可能不再需要,基于缓存过期时间。
-
分层系统:客户端可能直接与服务器交互,也可能不直接交互;它们始终可以拥有中介服务器,如缓存或负载均衡器。
-
统一资源:每个 REST 资源应该是独立的;这允许你有一个关注点的分离,并且解耦了架构。
-
按需代码:服务器可以为客户端提供在其上下文中执行的代码。这是一个可选的要求。
GET和HEAD方法是安全方法的例子,因为它们不会改变资源的状态。PUT/DELETE方法是幂等的。这意味着客户端可以对资源进行多次类似的调用,资源将以完全相同的方式表现;当然,响应本身也会发生变化。
太棒了!现在我们处于创建自己的 RESTful API 的位置。我们可以在互联网上托管这些 API,供我们的客户使用,或在我们的网络应用程序中实现功能。做得好!
还有更多...
我们研究了 REST 架构的基础,并学习了如何设计 RESTful 网络服务。我们得到了 Flask 微框架的帮助,并学习了如何编写我们自己的 REST API。
在下一个菜谱中,我们将看到客户端如何根据其需求消费 REST API。我们还将学习如何使用 REST API 来自动化业务流程。
使用 Twitter API 自动化社交媒体营销
乔伊是一家世界知名消费品牌的营销经理。她负责公司的内容营销组合,并严重依赖博客和社交媒体来展示公司的产品线并在市场上制造轰动。
毫无疑问,她有几个问题!她所营销的一些产品针对不同的市场设计不同,因此她必须跨越时区工作,以确保她的内容在正确的时间发布。她还觉得有必要重复发布帖子,以确保她能够接触到大多数客户;这也有助于提高品牌知名度。
准备中
如果你仔细分析她的情况,Joy 有两个问题。一是,她必须确保她的社交媒体内容在正确的时间发布,基于她的客户市场。所以如果她的产品在澳大利亚销售,她需要确保她的推文在澳大利亚时间发布,那时她的客户最有可能查看。二是,为了使她的产品公告(如周末优惠)获得更多关注,她可能希望在稍后的时间重复几条推文。
好的!现在我们了解了她的问题,让我们尝试制定一个解决方案。看起来我们需要注意以下要点:
-
我们应该提供她自动发布推文的 capability
-
她的推文应该在期望的时间发布,即使 Joy 睡着了
-
我们还应该提供安排重复推文的 capability
如何做...
REST APIs 来拯救!Twitter 提供了一套令人惊叹的 REST APIs,用户可以使用这些 API 来玩转 Twitter 数据、用户信息,当然还有发布推文。你还可以执行多个操作,如上传图片、查询时间线、发送私信。哇!太酷了!但让我们不要分心,而是继续解决手头的这个问题:
-
首先,让我们看看我们如何使用 Python 发布推文,即使不登录到 Twitter。要发布推文,我们将使用一个名为
twython的 Python 库。所以让我们使用我们的朋友 Pythonpip安装twython:(user)chetans-MacBookPro:ch07 Chetan$ pip install twython Collecting twython Downloading twython-3.4.0.tar.gz Collecting requests>=2.1.0 (from twython) Downloading requests-2.11.1-py2.py3-none-any.whl (514kB) 100% |████████████████████████████████| 516kB 495kB/s Collecting requests-oauthlib>=0.4.0 (from twython) Downloading requests_oauthlib-0.6.2-py2.py3-none-any.whl Collecting oauthlib>=0.6.2 (from requests-oauthlib>=0.4.0- >twython) Downloading oauthlib-1.1.2.tar.gz (111kB) 100% |████████████████████████████████| 114kB 80kB/s Building wheels for collected packages: twython, oauthlib Running setup.py bdist_wheel for twython Stored in directory: /Users/chetan/Library/Caches/pip/ wheels/48/e9/f5/a4c968725948c73f71df51a3c6 759425358c1eda2dcf2031f4 Running setup.py bdist_wheel for oauthlib Stored in directory: /Users/chetan/Library/Caches/pip/ wheels/e6/be/43/e4a2ca8cb9c78fbd9b5b14b9 6cb7a5cc43f36bc11af5dfac5b Successfully built twython oauthlib Installing collected packages: requests, oauthlib, requests-oauthlib, twython Successfully installed oauthlib-1.1.2 requests-2.11.1 requests-oauthlib-0.6.2 twython-3.4.0 (user)chetans-MacBookPro:ch07 Chetan$ -
但在我们开始玩转我们的 Twitter 账户之前,我们需要在 Twitter 上注册一个应用程序。这确保了 Twitter 了解我们的 API 调用,并认为它们是合法的。我们可以通过导航到
apps.twitter.com/并点击 Create New App 来注册一个应用程序。你可以填写以下截图所示的详细信息并创建你的应用程序。请注意你需要填写的一些细节:
-
应用程序名称在 Twitter 的所有用户中是唯一的,所以尽量让它对你来说非常独特,但同时也要保持简单
-
制作一个精确定义你的 use case 的描述,这样你以后就能记住它
-
填写你的网站名称;保持简短
-
只有当你想让 Twitter 发送有关你的 authentication 的数据给你时,才需要回调 URL,而这在这个练习中不是必需的
![如何做...]()
-
-
你还需要获取你的 App Key 和 App Secret,为此你需要 OAuth Token 和 OAuth Token Secret。这些基本上是用于验证你的 API 调用与 Twitter,否则 Twitter 会拒绝你的 REST API 调用,认为它是恶意行为。你可以通过点击你新创建的应用程序并浏览到页面顶部的 Keys and Access Tokens 选项卡来获取这些详细信息:
![如何做...]()
-
好的!让我们写一些代码,检查我们是否可以与 Twitter 的 REST API 一起工作。以下代码调用 Twitter 时间轴 REST API,并获取您时间轴上最顶部推文的详细信息。在这里,我们对
dev.twitter.com/rest/reference/get/statuses/home_timelineREST API 执行 HTTPGET操作:from twython import Twython APP_KEY = '' APP_SECRET = '' OAUTH_TOKEN ='' OAUTH_TOKEN_SECRET = '' twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) tweet = twitter.get_home_timeline()[1] print "Tweet text: ", tweet["text"] print "Tweet created at: ", tweet["created_at"] print "Tweeted by: ", tweet["entities"]["user_mentions"][0]["name"] print "Re Tweeted?: ", tweet["retweet_count"]前面代码片段的输出如下:
![如何操作...]()
这很酷!我们从时间轴上的帖子中获取所有必要的信息。看起来在 Twitter 应用和密钥方面我们已经准备好了。
-
现在,让我们尝试通过向它发送数据来使用状态 REST API 进行推文。这里使用的 REST API 是
dev.twitter.com/rest/reference/post/statuses/update。以下 Python 代码将对这个 REST 资源发起POST请求,并在我的推特账户上创建一条推文:from twython import Twython APP_KEY = '' APP_SECRET = '' OAUTH_TOKEN ='' OAUTH_TOKEN_SECRET = '' twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) twitter.update_status(status='Python import antigravity https://xkcd.com/353/')在运行前面的代码后,我查看了推特,哇!我的名字下自动出现了一条推文。以下是这条推文的截图:
![如何操作...]()
因此,我们已经为乔伊解决了第一个问题。即使她不在或无法登录互联网,也可以代表她发布推文。这可以通过前面的 Python 代码片段完成。但她还不能按照澳大利亚时区安排她的推文。嗯,让我们现在看看如何解决调度问题。
-
在我们查看如何安排推文之前,我们将安装一个对下一个菜谱非常有用的模块。我们将安装
pytz。它帮助我们处理时区,并将有助于解决乔伊的问题:(user)chetans-MacBookPro:ch07 Chetan$ pip install pytz Collecting pytz Using cached pytz-2016.6.1-py2.py3-none-any.whl Installing collected packages: pytz Successfully installed pytz-2016.6.1 -
为了解决调度问题,我们需要两样东西。首先,我们需要一个配置,可以用来决定推文的内容、时间和时区。其次,我们需要一个运行程序,将使用这个配置在推特上发布推文。现在让我们看看以下代码,它正好是我们需要的:
scheduled_tweets.py from twython import Twython APP_KEY = '' APP_SECRET = '' OAUTH_TOKEN ='' OAUTH_TOKEN_SECRET = '' twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) from datetime import datetime import pytz, time from pytz import timezone import tweet_config as config while True: for msg in config.scheduled_messages: print msg["timezone"] tz = timezone(msg["timezone"]) utc = pytz.utc utc_dt = datetime.utcnow().replace(tzinfo=utc) au_dt = utc_dt.astimezone(tz) sday = au_dt.strftime('%Y-%m-%d') stime = au_dt.strftime('%H:%M') print "Current Day:Time", sday, stime if sday == msg["day"]: if stime == msg["time"]: print "Time", stime print "Content", msg["content"] twitter.update_status(status='%s' % msg["content"] ) print "Running.. Will try in another min" time.sleep(60) tweet_config.py offers_sydney = { "content":"Weekend Offers, avail 30% discount today!", "day":"2016-08-27", "time":"13:25", "timezone":"Australia/Sydney" } post_newyork = { "content":"Introducing sun glasses at your favorite stores in NY!", "day":"2016-08-27", "time":"12:41", "timezone":"America/New_York" } scheduled_messages = [offers_sydney, post_newyork]前面代码的输出如下。这是第一次迭代:
![如何操作...]()
这是第二次迭代:
![如何操作...]()
这是实际的推文:
![如何操作...]()
-
太酷了!所以我们有了乔伊需要的东西。一条自动推文,在正确的时间,向全球的正确受众推送正确的内容。哇哦!
它是如何工作的...
在前面的代码片段中,我们有两个文件。我们有tweet_config.py,它包含一个配置字典,用于指定推文的内容和安排。它还提到了需要发布推文的时区。
第二个文件scheduled_tweets.py是一个运行程序。它每分钟检查一次配置,看看当天是否有安排的推文。
当运行程序scheduled_tweets.py运行时,它会检查是否有任何安排的消息。在迭代 1 中,运行程序没有找到需要处理的内容;它只是返回当前时区的日期和时间。
在迭代 2 中,它确实发现有一个在澳大利亚时区,即悉尼,8 月 27 日 13:25 安排的推文;^,因为时间匹配,它发布了一条推文。当然,这里采用的例子非常简单。我们可能想要安排 Cron 作业而不是无休止的 while 循环。但是嘿,这是一个为了说明自动安排推文这一点的例子。
在本节中,我们自动化了 Joy 的营销过程。现在她不仅可以在睡觉时发推文,还可以为不同的时区和不同的内容安排推文。这就是自动化的力量。
“但是嘿,这只是一个社交媒体平台;那 Facebook 呢?”你可能想知道。是的,我们这里有一个小技巧。Twitter 提供了连接你到多个服务的应用程序,包括 Facebook。所以为你的账户配置一个应用程序,以便你发布的每条推文也会被发布在 Facebook 上。这是配置看起来像什么。它发布你的原始推文,并在你的 Facebook 个人资料上转发它们:

记得我们最初发布的关于 Python 反重力(antigravity)的消息吗?是的,它实际上也被发布在了 Facebook 墙上。看看推文日期和时间旁边的源代码;是的,那是 Twitter!也许,Twitter 使用 Facebook API 来自动化这个过程:

Webhooks 简介
在上一节中,我们了解了如何设计和开发 REST API,以及如何通过 Twitter/Facebook 自动化的例子来利用 REST API 为我们带来好处。让我们看看另一个令人惊叹的部分:Webhooks。Webhook 是一种 HTTP 回调——当发生有利事件时,向用户定义的 URL(实现为 HTTP API)发送的 HTTP POST请求。Webhooks 通常被称为反向 API,用于跨服务的实时通信或集成。但在我们深入之前,让我们了解一下轮询。
你可能见过应用程序长时间轮询以检查是否发生了某个事件,以便它们可以对该事件执行一些后续操作。以一个现实世界的例子来说明。你去了自助餐厅,为午餐点了一份你最喜欢的披萨。柜台上的那个人给你一个订单号,并告诉你去观察那个标记机,以便你可以领取你的披萨。当周围的人都忙着吃饭时,你饿了,每隔 5 秒钟就会看一次这个标记机,希望看到你的订单号在上面闪烁。现在这就是轮询。你正在轮询这个标记机。在 API 世界中,客户端会轮询披萨店的 API 来检查订单的状态。
服务台的人当订单准备好时大声喊出订单号不是足够简单吗?所以在你下单后,你可以忙于检查你的官方电子邮件。当服务人员叫出你的订单号时,你可以从配送柜台取走你的披萨。这确保了你的时间得到更好的利用。现在这就是一个 Webhook。当发生有利的事件(你的订单准备好了)时,你会在你的 URL(在这种情况下,你的耳朵)上收到回调(服务人员大声喊出你的订单号),这个 URL 实际上是在监听并响应回调。在 API 世界中,你会注册你的 URL(HTTP API),当你的订单准备好时,披萨店会调用这个 URL。
Webhooks 可以用于三个主要目的:
-
实时接收数据
-
接收数据并将其推送到另一个服务
-
接收数据然后处理并返回它
你可以思考在前面三个场景中如何使用 Webhooks 的许多不同方式。
如果你想到轮询和 Webhooks,它们都使用 API 来满足集成需求。虽然轮询是一种客户端驱动的集成技术,但 Webhooks 是服务器驱动的。轮询在效率上非常低,因为客户端不断地通过时间戳调用服务器 API 来检查资源的状态(在我们的例子中,是一个订单资源)。轮询可以每 x 分钟、x 小时甚至 x 秒进行,以变得更加实时,但我认为你已经了解了轮询的低效率。另一方面,Webhooks 在有利事件发生时将数据发送回回调 URI。这比持续的轮询更有效率,但不利的一面是你最终需要在客户端开发 API,所以你的客户端倾向于表现得像服务器本身。
实现 Webhooks
带着这些知识,让我们开始并在这个菜谱中实现 Webhooks。
准备工作
对于这个菜谱,我们将使用一个著名的 Python 网络框架,称为 Django。它允许你使用多个插件,这些插件可以简单地插入。在这里,我们将使用 Zapier 开发的 django-rest-hooks 插件来实现 Webhooks。
所以让我们开始并安装所需的包。我们使用我们最喜欢的工具 Python pip 安装 Django==1.10 和 django-rest-hooks==1.3.1:
(user)chetans-MacBookPro:ch07 Chetan$ pip install Django==1.10
Collecting Django==1.10
Downloading Django-1.10-py2.py3-none-any.whl (6.8MB)
100% |████████████████████████████████| 6.8MB 71kB/s
Installing collected packages: Django
Successfully installed Django-1.10
(user)chetans-MacBookPro:ch07 Chetan$ pip install django-rest-hooks
Collecting django-rest-hooks
Downloading django-rest-hooks-1.3.1.tar.gz
Requirement already satisfied (use --upgrade to upgrade): Django>=1.4 in ./user/lib/python2.7/site-packages (from django-rest-hooks)
Requirement already satisfied (use --upgrade to upgrade): requests in ./user/lib/python2.7/site-packages (from django-rest-hooks)
Building wheels for collected packages: django-rest-hooks
Running setup.py bdist_wheel for django-rest-hooks
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/96/93/12/3ec10693ee2b394a7d8594e8939f7506d7231fab69c8e69550
Successfully built django-rest-hooks
Installing collected packages: django-rest-hooks
Successfully installed django-rest-hooks-1.3.1
如何做到这一点...
-
好的,让我们创建一个 Django 应用。我们通过以下命令来完成这个操作:
python manage.py startproject bookstore cd bookstore python manage.py startapp book -
接下来,让我们配置 Django 以在应用中使用
rest_hooks模块。我们通过将rest_hooks添加到bookstore/settings.py中的INSTALLED_APPS来完成这个操作。我们还把我们的应用 book 添加到这个列表中。通过使用HOOK_EVENTS常量,将一个事件user.signup添加到settings.py中。在这里,我们没有将事件user.signup绑定到任何操作,所以它是空的。这就是settings.py应该看起来像的:![如何做到这一点...]()
-
现在,让我们将此事件注册到回调 URL 上。但在我们继续之前,导航到你的项目根目录并运行以下命令以初始化你的 Django 模型:
(user)chetans-MacBookPro:bookstore Chetan$ python manage.py migrate Operations to perform: Apply all migrations: admin, contenttypes, rest_hooks, auth, sessions Running migrations: Rendering model states... DONE Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying rest_hooks.0001_initial... OK Applying sessions.0001_initial... OK -
一旦初始化了模型,转到数据库 shell 并运行以下 Python 代码片段。它们将在 Django 用户表中创建一个用户并为该用户注册一个 Webhook:
>>> from django.contrib.auth.models import User >>> from rest_hooks.models import Hook >>> usr=User.objects.create(username='chetan') >>> hook = Hook(user=usr, event='user.signup', target='http://localhost:8000/hook/') >>> hook.save() >>> hook <Hook: user.signup => http://localhost:8000/hook/> -
现在将一个名为
urls.py的文件添加到 Book 应用程序中,并添加以下代码:from django.conf.urls import url from . import views urlpatterns = [ url(r'event/$', views.event), url(r'hook/$', views.webhook),] -
将以下方法添加到
book/views.py中以创建 Django 视图:from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from rest_hooks.signals import raw_hook_event from django.contrib.auth.models import User import datetime from django.http.response import HttpResponse # Create your views here. @csrf_exempt def webhook(request): print request.body return HttpResponse() def event(request): user = User.objects.get(username='chetan') raw_hook_event.send( sender=None, event_name='user.signup', payload={ 'username': user.username, 'email': user.email, 'when': datetime.datetime.now().isoformat() }, user=user # required: used to filter Hooks ) return HttpResponse() -
此外,将以下 URL 包含在项目中的
bookstore.urls.py文件中,如下所示:from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^', include('book.urls')) ] -
现在按照以下方式运行 Django 服务器:
(user)chetans-MacBookPro:bookstore Chetan$ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). August 27, 2016 - 11:14:52 Django version 1.9, using settings 'bookstore.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. -
从您的浏览器中,访问
http://localhost:8000/event/并查看您的服务器日志。您将看到已注册的 Webhook 被调用,这意味着向目标 URLhttp://localhost:8000/hook/发送了一个包含我们配置在视图中的所有信息的 HTTPPOST请求。服务器日志如下所示:[27/Aug/2016 10:53:29] "GET /event/ HTTP/1.1" 200 0 {"hook": {"target": "http://localhost:8000/hook/", "id": 1, "event": "user.signup"}, "data": {"username": "chetan", "when": "2016-08-27T10:53:29.301317", "email": ""}} [27/Aug/2016 10:53:29] "POST /hook/ HTTP/1.1" 200 0
太棒了!你看了吗?我们调用了 /event URL,它反过来将所需信息发布到目标 URL,该 URL 已在我们的 Webhook 中注册为 user.signup 事件。
与自定义 Webhook 类似,也可以开发 RESTful Webhook。RESTful Webhook 通过 RESTful 接口支持订阅、通知和发布操作。RESTful Webhook 必须支持四种事件类型,即 ACCESSED、CREATED、UPDATED 和 DELETED,它们对应于四个 HTTP 动词;对于应用于资源的操作,应发送通知。例如,当资源被创建时,会生成一个事件;因此,Webhook 必须被触发,并且目标 URL 应该被发布。在我们的示例中,我们可以定义两个额外的钩子事件,即 book.added 和 book.deleted,以及如 book.Book.added 或 book.Book.deleted 的操作。当我们对模型执行 book.save() 操作时,book.added 事件将被触发,并且如果我们为该用户定义了此事件的钩子,则将在目标 URL 上调用 HTTP POST 请求。
它是如何工作的...
在前面的代码片段中,我们首先在 settings.py 文件中定义了一个事件。该事件名为 user.signup。由于它是一个自定义事件,因此没有定义任何操作。
我们随后使用默认的 Django 用户模型在 auth_user 表中创建了一个新的用户 chetan。
之后,我们为用户 chetan 定义了一个 Webhook。此 Webhook 被配置为 user.signup 事件,目标 URL 设置为 http://localhost:8000/hook/。
我们还在我们的 Django 应用程序中定义了两个视图。第一个视图负责触发对应用户和事件的 Webhook,并发送有效负载信息。第二个视图 Webhook 定义为目标 URL。
我们随后启动了 Django 开发服务器,并导航到 http://localhost:8000/event/,该地址将有效负载信息发布到目标 URL,即 http://localhost:8000/hook/。目标 URL 接收了所有有效负载数据,例如用户名、电子邮件以及注册发生的时间。
还有更多...
在本节中,我们探讨了轮询和 Webhooks,它们是 Web 上使用 API 的另一种集成形式。我们学习了轮询的效率低下,以及 Webhooks 如何更加有用。在前面的菜谱中,我们介绍了一个对用户注册有用的自定义事件,因为我希望以通用的方式解释这个概念。覆盖范围很简短,所以我希望你能更多地研究 RESTful Webhooks,因为它们展示了自动化强大的用例。有了这个理解,让我们看看 Oliver 遇到了什么问题,以及我们如何帮助他。
使用 Webhooks 自动化潜在客户管理
Oliver 是 Joy 的同事,在市场营销部门工作。他负责用户入职流程。他的主要职责包括:
-
向在您的网站上注册的用户发送欢迎邮件
-
将新签约者的记录添加到 CRM 中
以前,由于网站上的注册数量很少,他很容易手动执行这两个任务。但随着网站知名度的增长,他开始看到每天注册数量的激增。毫无疑问,他认为这是一个非常耗时且可以轻松自动化的活动。你能帮助 Oliver 吗?
如果我们仔细分析问题,Oliver 的主要问题是服务之间的集成。他需要集成的两个服务是电子邮件和 CRM。他需要跟踪一个注册事件并对这个事件采取行动。Webhooks 是这个用例的完美解决方案。让我们看看我们如何帮助 Oliver 自动化他的任务。
如何操作...
我们将使用同一个 Django 项目来解决这个问题。我们还将使用外部服务 Zapier,看看它如何帮助使事情变得如此简单。让我们开始吧:
-
从终端进入 Django 项目的根目录,并运行 Python
manage.pyshell 命令以登录到 DB shell。在这里,更新我们的用户chetan的电子邮件地址。这可以通过以下命令集实现:>>> from django.contrib.auth.models import User >>> from rest_hooks.models import Hook >>> usr = User.objects.get(username='chetan') >>> usr.email='chetan@email.io' >>> usr.save() -
现在通过导航到
zapier.com/创建 Zapier 应用的账户。一旦创建了账户,点击创建 ZAP进入选择应用,然后在内置应用部分下点击Webhooks:![如何操作...]()
-
一旦你选择了一个 Webhook,你将得到一个屏幕,在左侧面板上创建一个触发器和操作。在右侧,选择捕获钩子选项。点击保存+继续。参考以下截图:
![如何操作...]()
-
接下来,你将获得一个页面,让你提供你想要从有效载荷中选择的一个 JSON 密钥。这是一个可选步骤,可以忽略。点击继续进入下一步。在这里,你会得到一个自定义的 Webhook URL。复制这个 URL;它将作为目标 URL。
-
现在,回到您的 Django 项目并导航到 DB shell。创建一个新的钩子,使用相同的事件
user.signup并将目标 URL 定位到您在早期步骤中收到的 URL。命令如下所示:>>> hook = Hook(user=usr, event='user.signup', target= 'https://hooks.zapier.com/hooks/catch/<Id>/<Webhook_Id>/') >>> hook.save() >>> hook <Hook: user.signup => https://hooks.zapier.com/hooks/catch/<Id>/<Webhook_Id>/> -
使用 Python
manage.py的runserver命令运行 Django 开发服务器。一旦服务器启动,请访问http://localhost:8000/event/;这将向 Zapier 获得的目标 URL 发起回调请求。您可以通过再次访问 Zapier 并在捕获钩子部分左侧的测试此步骤中查看来验证这一点:![如何操作...]()
-
太棒了!我们现在已经设置了触发器。接下来,让我们设置操作。为此,转到您的左侧面板,并在操作下点击设置此步骤。从屏幕右侧显示的应用程序列表中选择 Gmail:
![如何操作...]()
-
一旦点击Gmail,你将获得选择下一步操作的下拉菜单,例如创建草稿或发送电子邮件。点击发送电子邮件,并通过允许 Zapier 访问您的电子邮件账户来激活您的电子邮件账户。以下截图将展示如何执行这些步骤:
![如何操作...]()
在下一个截图中,我们允许 Zapier 访问 Gmail 应用程序:
![如何操作...]()
-
好的!现在唯一要做的就是创建电子邮件模板。我们的模板包含收件人电子邮件地址、主题和正文。Zapier 提供了一个很好的选项来配置您的模板。如果您已经通过向目标 URL 发送数据来测试了触发器,您将在电子邮件模板中每个字段的极端右侧看到一组选项。在接下来的两个截图中,我在收件人字段中输入了数据电子邮件,在主题字段中输入了欢迎数据用户名,并将电子邮件正文设置为您的注册让我们的日子变得美好。
-
以下截图显示了目标 URL 在触发器的测试此步骤部分接收到的有效载荷中的所有可用选项下拉列表。我刚刚展示了用户名。看看模板的字段名收件人如何从有效载荷中选择数据用户名:
![如何操作...]()
在以下截图中,您可以看到配置了所有必要字段的电子邮件模板。我们在 Zapier 中配置了电子邮件的收件人、主题和正文部分:
![如何操作...]()
-
就这样!点击屏幕底部的继续;Zapier 将测试您的操作,您就完成了。以下截图显示了成功的确认!!
![如何操作...]()
-
现在,如果你检查你的电子邮件,你应该已经收到了来自 Zapier 的测试电子邮件,这是用来测试 Zapier 操作的。电子邮件的内容正是我们想要的样子。非常酷!所以现在,当任何人注册奥利弗的产品网站时,视图将
POST注册者的信息作为有效载荷到 Zapier 的 Webhook(目标 URL),Zapier 将自动化电子邮件部分。![如何操作...]()
它是如何工作的...
Zapier 为我们提供了一个创建自定义 Webhooks 的功能。它与几乎所有应用程序都有集成,例如 Gmail、Trello、Slack 等。我们只是创建了一个 Webhook 作为触发器,然后跟随 Gmail 的一个操作。
每当用户注册(新用户创建)时,Django 应用程序会将用户的数据作为有效载荷 POST 到我们在 Zapier 中创建触发器时得到的 Zapier 目标 URL。
一旦 Zapier 收到目标 URL 的有效载荷数据,它会检查操作并发现需要向一个 Gmail 账户发送电子邮件。Zapier 还足够智能,能够从有效载荷中获取数据并将电子邮件发送到用户的电子邮件地址;它还允许配置电子邮件的主题和正文。
太棒了!奥利弗很高兴!那么第二步呢?嗯,它又是另一个 Zapier 触发器,可以是 Salesforce 或 Pipedrive CRM,用于在 CRM 中创建潜在客户记录。轻而易举!
在本节中,我们探讨了使用用户注册事件来自动化用户入职。我们以 Zapier 为例,因为它是最理想的自动化应用程序的方式。如果我们没有这样做,我们可能最终会理解所有这些应用程序提供的 API,并为它们各自编写代码,这可能并不是你的产品或服务的核心。
好了,就是这样,朋友们!希望你们喜欢这篇自动化文章,我确信你们一定会将其应用到你们组织中。
第八章. 与机器人对话
哇,机器人?!真的吗?我会学习如何为娱乐或商业用途构建机器人吗?当然,本章将带你进入使用 Python 的全新机器人世界。
在本章中,我们将涵盖以下内容:
-
构建一个情绪化的 Telegram 机器人
-
不同类型的机器人:无状态、有状态和智能型
-
拥有人工智能的智能机器人
-
使用机器人自动化业务流程
引言
过去的几十年是数字转型和自动化的时代。如今,大多数企业更倾向于选择线上销售模式,而不是传统的实体销售方式。
网站不仅帮助公司扩大了其影响力,还降低了他们销售产品的成本(没有固定的成本,如租金)。一个响应式的图形用户界面(GUI),结合实时技术的力量,使得销售过程变得更加容易;现在高管们可以直接与潜在客户聊天,并引导他们购买产品,从而提高转化率。
随着人工智能(AI)和语言处理技术的进步,企业正在缓慢但稳步地采用对话界面来自动化他们的业务流程。对话用户界面是指具有自由文本的自然语言界面。通过对话界面和自然语言处理技术,企业认为机器可以通过分析上下文来响应某些客户查询。在当今世界,这些机器被称为聊天机器人。
在本章中,你将了解不同类型的机器人,学习如何开发简单的聊天机器人,以及了解机器人如何用于自动化业务流程。此外,请注意,在本章中提到机器人时,我们指的是聊天机器人或基于文本的机器人。
机器人是什么?
好的,让我们来看一个简单的例子。假设你想要在下个周末和朋友们的聚会上订购必胜客的披萨。通常情况下,你会去必胜客的网站,花时间寻找你喜欢的某种披萨或特定的配料,然后下单。很多时候,你其实已经知道你想要订购什么;那么真正的问题其实是,为什么还要在必胜客网站上费劲去找呢?
别再担心了!只需登录Facebook并使用 Facebook Messenger 聊天机器人从必胜客购买你需要的东西。不仅如此,聊天机器人还会告诉你必胜客的最新优惠和更新。所以聊天机器人可以给你在最喜欢的社交网络平台上访问网站的同一种体验。看看blog.pizzahut.com/press-center/pizza-hut-announces-new-social-ordering-platform/上的公告,必胜客宣布与 Facebook Messenger 合作。
你可能会说:“是的,我们理解了使用场景,但聊天机器人到底是什么?”
聊天机器人是由规则和人工智能驱动的服务,作为客户,你可以通过聊天(文本)界面与之互动。机器人执行半智能或日常任务,并以软件应用的形式运行。聊天机器人可以提供多种服务,并且可以在Facebook、Telegram、Slack等社交平台上运行。聊天机器人仍在积极研究中,是一个新兴的计算机科学领域。
机器人是如何工作的?
根据我们迄今为止的讨论,你可能正在想:“这些机器人是如何工作的?它们如何理解人类的语言或情感?它们如何理解上下文?”那么,这就是答案。通常有两种类型的聊天机器人:
-
基于规则引擎的机器人:这种类型的机器人理解某些词语或命令(可以说是这样的)并且行为非常有限。它非常直接:如果x是输入,那么y应该是输出。它们在存在固定问题集或问题作为查询的情况下非常有用。例如,CNN 聊天机器人可以帮助你获取那一刻的头条新闻,而且你还可以选择询问有关特定主题(如政治或商业)的头条新闻。!(太好了!那么我为什么还要去 CNN 网站呢?)看看我从我的 Facebook Messenger 应用中拍摄的关于我与 CNN 聊天机器人交互的一些截图。第一个屏幕要求你点击GET STARTED,当你这样做时,机器人会带你到下一个屏幕,在那里它给你一个查看头条新闻的选项:
![机器人是如何工作的?]()
当你点击TOP STORIES时,它会显示Yahoo新闻,并询问你是否对某些主题感兴趣,例如政治:
![机器人是如何工作的?]()
-
一个基于机器学习的智能机器人:智能机器人利用人工智能和情感分析来理解对话的上下文,并响应语言语义。因此,它们适用于复杂的使用场景,例如购买产品或回答客户支持查询。更重要的是,这些机器人可以从过去的交互中学习。这不是很神奇吗?
注意
情感分析也被称为意见挖掘,旨在从可用的文本中识别和提取主观信息,并确定作者的情绪,同时注意文本的上下文属性。
为什么现在要使用机器人?
你可能会问,“世界已经谈论机器学习有一段时间了,聊天功能也已经存在很长时间了,那么为什么机器人现在变得如此重要?”这是因为以下原因:
-
使用模式:公司已经发现,用户在聊天平台上花费的时间比在社交媒体平台或网站上更多。因此,企业可以通过聊天平台以更好的方式与用户互动。
-
成本效益:不需要人力——听起来似乎没有成本!企业正在利用机器人来自动化流程,如客户服务,而不需要人力资源投资。
-
规模:通过 Facebook 或 Telegram 等作为机器人分发渠道的社交平台,很容易接触到数百万用户。这样,企业可以在不考虑人力成本的情况下,尽可能多地吸引潜在客户。
-
高效技术:人工智能或自然语言处理(NLP)的增长使得将这些算法插入这些机器人变得更加容易。算法会随着时间的推移而成熟,并将更好地服务于客户。
好的,太棒了!既然我们已经更好地理解了机器人和它们的实用性,那就让我们动手开发自己的机器人吧。
构建一个情绪化的 Telegram 机器人
在我们开始开发机器人之前,我们应该清楚我们的目标:我的机器人将要做什么?我们以创建一个根据用户情绪返回表情的机器人为例。它之所以被称为情绪化机器人,是因为它代表了用户的情绪。听起来像是一个有趣的用例?让我们试试吧!
在这个菜谱中,我们将使用python-telegram-bot (github.com/python-telegram-bot/) 库来开发 Telegram 机器人。所以,让我们首先使用我们最喜欢的工具,即 python pip来安装python-telegram-bot模块:
(bots)chetans-MacBookPro:ch09 Chetan$ pip install python-telegram-bot --upgrade
Collecting python-telegram-bot
Downloading python_telegram_bot-5.1.0-py2.py3-none-any.whl (134kB)
100% |████████████████████████████████| 135kB 681kB/s
Collecting certifi (from python-telegram-bot)
Downloading certifi-2016.8.31-py2.py3-none-any.whl (379kB)
100% |████████████████████████████████| 380kB 612kB/s
Collecting future>=0.15.2 (from python-telegram-bot)
Downloading future-0.15.2.tar.gz (1.6MB)
100% |████████████████████████████████| 1.6MB 251kB/s
Collecting urllib3>=1.10 (from python-telegram-bot)
Downloading urllib3-1.17-py2.py3-none-any.whl (101kB)
100% |████████████████████████████████| 102kB 1.2MB/s
Building wheels for collected packages: future
Running setup.py bdist_wheel for future
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/11/c5/d2/ad287de27d0f0d646f119dcffb921f4e63df128f28ab0a1bda
Successfully built future
Installing collected packages: certifi, future, urllib3, python-telegram-bot
Successfully installed certifi-2016.8.31 future-0.15.2 python-telegram-bot-5.1.0 urllib3-1.17
我们还安装了emoji (github.com/carpedm20/emoji) 库来处理表情符号,这样我们就可以根据用户的情绪返回适当的表达:
(bots)chetans-MacBookPro:ch09 Chetan$ pip install emoji --upgrade
Collecting emoji
Downloading emoji-0.3.9.tar.gz
Building wheels for collected packages: emoji
Running setup.py bdist_wheel for emoji
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/94/fc/67/441fb0ca2ed262d6db44d9ac2dfc953e421f57730004dff44d
Successfully built emoji
Installing collected packages: emoji
Successfully installed emoji-0.3.9
你已经安装了模块吗?太酷了!让我们继续前进。
如何操作...
-
要开发自己的机器人,首先在手机上下载 Telegram 应用。注册一个账户并验证你的手机号码。假设你已经这样做,恭喜你!你离创建一个 Telegram 机器人又近了一步。
-
现在,你需要做的下一件事是联系另一个名为BotFather的机器人。在你的 Telegram 应用中搜索BotFather并点击它,开始与之对话(或他?)。它看起来是这样的:
![如何操作...]()
-
一旦你与BotFather开始对话,按照步骤操作,并使用如
/newbot或/enable等命令来配置你的机器人。仔细遵循步骤,你将创建一个新的机器人。以下截图将指导你完成创建新机器人的过程:![如何操作...]()
-
当你创建一个新的机器人时,你会得到一个特定于你的机器人的令牌。请妥善保管并安全地保存它;不要与任何人分享。以下截图显示了BotFather的工作方式和令牌的外观:
![如何操作...]()
-
很好!所以你已经创建了自己的机器人。但机器人目前还没有功能,也没有做什么引人注目的东西。让我们按照菜谱开头计划的那样,让它做一些酷的事情。创建一个名为
bot.py的文件,并将以下代码复制到其中。同时,确保将令牌更改为你的机器人令牌 ID:import logging from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Updater, CommandHandler, CallbackQueryHandler import emoji logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) def start(bot, update): keyboard = [ [InlineKeyboardButton("Happy", callback_data='1'), InlineKeyboardButton("Whatever", callback_data='2')], [InlineKeyboardButton("Sad", callback_data='3')]] reply_markup = InlineKeyboardMarkup(keyboard) update.message.reply_text('Hey there! How do you feel today?', reply_markup=reply_markup) def button(bot, update): query = update.callback_query if query.data == "1": em = emoji.emojize(':smile:', use_aliases=True) bot.editMessageText(text="Oh wow! %s " % em, chat_id=query.message.chat_id, message_id=query.message.message_id) if query.data == "2": em = emoji.emojize(':expressionless:', use_aliases=True) bot.editMessageText(text="Does it matter? %s " % em, chat_id=query.message.chat_id, message_id=query.message.message_id) if query.data == "3": em = emoji.emojize(':disappointed:', use_aliases=True) bot.editMessageText(text="Oh man! %s " % em, chat_id=query.message.chat_id, message_id=query.message.message_id) def help(bot, update): update.message.reply_text("Use /start to test this bot.") def error(bot, update, error): logging.warning('Update "%s" caused error "%s"' % (update, error)) # Create the Updater and pass it your bot's token. updater = Updater('Token') updater.dispatcher.add_handler( CommandHandler('start', start)) updater.dispatcher.add_handler( CallbackQueryHandler(button)) updater.dispatcher.add_handler( CommandHandler('help', help)) updater.dispatcher.add_error_handler(error) # Start the Bot updater.start_polling() # Run the bot until the user presses Ctrl-C or the process receives SIGINT, # SIGTERM or SIGABRT updater.idle() -
好的,太棒了!我们现在已经为我们机器人添加了所需的功能,并期待它能良好运行。但我们如何测试它呢?首先,使用以下命令运行 Python 文件:
python bot.py -
我们接下来搜索我们的机器人,并与之开始对话。在我的情况下,这个机器人叫做Chetbot,我使用标准的
/start命令与之开始了对话:![如何操作...]()
-
在前面的截图中,当我开始与我的机器人对话时,它询问我当天的情绪,并给了我三个选项。这三个选项是快乐、随便和悲伤。
-
真不错!但当我点击这些选项之一时会发生什么?哇!它会返回我当天的情绪表情符号。太棒了!!如何操作...
-
注意,如果我需要再次开始对话,我需要重新输入
/start命令来与机器人交谈。在以下截图中,机器人识别了启动命令,并再次询问我的情绪。不错,对吧?![如何操作...]()
它是如何工作的...
python-telegram-bot模块基于标准的事件驱动哲学。一个机器人可以被视为一个单线程的事件循环,它持续轮询事件。事件循环也与命令处理器(也称为分发器)注册。一旦事件被触发,回调就会处理事件并返回给用户期望的响应。
在前面的代码片段中,我们注册了两个命令处理器:start()和help()。当用户与机器人开始对话(/start命令)或请求帮助(/help命令)时,会调用start()方法。
我们还添加了一个带有button()作为回调方法的回调查询处理器;当用户对机器人的选项做出响应时,它会调用这个方法。
因此,最初,机器人正在运行,等待输入。当用户说/start时,请求会被分发到start()方法,该方法反过来会询问用户嗨,你今天感觉怎么样?并展示一个内联键盘,有三个选项:快乐、随便或悲伤。
当用户选择任何一个选项时,会生成一个事件,该事件由button()回调方法处理。回调方法预先加载了数据,根据选择的选项进行操作。根据用户的选择,机器人会向用户发送正确的情绪。通过包含所有表情的emoji库,表情符号被发送回用户。
还有更多...
太棒了!你已经创建了自己的机器人了吗?你能想到其他简单的例子,说明 Telegram 机器人会有用吗?有很多 Python 模块可以用来开发 Telegram 机器人,例如telepot(github.com/nickoala/telepot)或twx.botapi(github.com/datamachine/twx.botapi);两者都很好。你可以使用其中任何一个来让你的机器人启动并运行。为什么不试试看它们能提供什么?
不同的机器人类型
从我们自己构建机器人的自信中汲取力量,让我们更进一步,看看机器人可以如何分类。
在上一个菜谱中我们开发的机器人可能被标记为不智能。这里的“不智能”是指它会质疑用户,并根据选项以表情符号的形式做出回应。但是当用户再次输入/start时,机器人会提出同样的问题。这有帮助吗?
那么设想一下这样的场景:机器人会记住你的之前的选项,并尝试通过一些美好的文章或你可以在城市中去的地点来激励你?只是为了改变你的心情?或者实际上提高你的幸福感?
为了使前面的讨论更有意义,根据实现方式,机器人可以分为三类:
-
无状态机器人:这些也可以被称为“不记得任何东西”的机器人。它们不持续信息;也就是说,对于它们来说,每一次交互都是一个新会话,并且它们会独立处理每一个问题。例如,一个新闻机器人可以持续提供最新故事的更新,或者总是返回政治类别的头条新闻;然而,如果它不记得对话的状态,它将被视为无状态的,并且不会被认为是有用的。大多数今天构建的机器人都属于这一类别,这也是它们提供的价值非常有限的原因。
-
有状态机器人:我们在前面讨论了新闻机器人。如果新闻机器人能记住用户感兴趣的新闻类别,并据此推荐更多过去的故事,用户可能会觉得有趣,那么我们现在就进入正题了。这样,我们可以让用户与机器人保持更长时间的互动。
这种机器人会跟踪用户的身份,并持续记录当前和之前的会话信息。例如,这些机器人可能会存储今天和过去搜索的新闻类别,然后可以推荐与搜索类别匹配的新闻源。
这样的机器人很有用,但它们并不聪明;它们不理解上下文和语言语义。
-
智能机器人:智能机器人插入了多个电池。它们使用机器学习,理解语言语义,并可以根据它们拥有的数据构建预测算法。
让我们以尿布和啤酒的著名例子为例。据说,如果你分析购买模式,啤酒和尿布的购买之间存在高度相关性,这意味着购买尿布的人或多或少肯定会购买啤酒。智能机器人可以持续数据并找出这样的模式,从而对对话产生有意义的见解。让我们再举一个语言语义的例子。想想看“脏得要命”这个短语;现在,脏意味着肮脏,而“要命”是一个非常积极的词。智能机器人将理解这些短语,并能更好地理解用户的上下文。
根据前面的分类,决定我们为特定用例开发哪种类型的机器人就取决于我们了。在需要远比人类互动更人性化、更复杂的场合,例如客户支持,通常需要智能机器人,但想象一下企业通过使用智能机器人可以获得的效率提升。
拥有人工智能的智能机器人
在上一节中了解了不同类型的机器人之后,让我们尝试编写一个使用 Python 中的人工智能和情感分析的机器人。但在那之前,让我们简要了解这两个领域。
人工智能(AI)是计算机科学的一个领域,强调创建能够像人类一样反应的机器。本质上,人工智能与能够感知其上下文并采取与内容相关的行动以最大化成功机会的机器相关。例如,机器可以根据某些规则和上下文做出决策,以最大化决策的结果。
情感分析,另一方面,是关于识别和分类一段文本,以确定涉及人员的观点或态度是对产品或事件的正面、中性还是负面。它指的是使用自然语言处理算法进行文本分析并提取内容的主观信息,或情感。
我想,到现在为止,你一定已经开始思考如何将人工智能和情感分析用于我们的机器人以满足各种需求了。在这个菜谱中,让我们构建一个拥有这些技术的智能机器人。
注意
智能机器人可以建立在多种技术之上,如预测智能、人工智能、自然语言处理等;然而,完全取决于你决定使用哪种技术来满足你的目标。此外,机器人不需要在网络上或应用中;它们可以是简单的基于命令行的机器人。一个 Web 用户界面、命令行界面或移动应用可以用作机器人的分发渠道,但这并不是构建机器人的必要条件。
准备工作
要在我们的机器人中包含人工智能,我们将使用一个名为 aiml 的知名 Python 模块。AIML 代表 人工智能标记语言,但它本质上是一个 XML 文件。AIML 是一种 XML,它定义了匹配模式和确定响应的规则。因此,让我们开始安装 aiml 模块:
chetans-MacBookPro:ch09 Chetan$ source bots/bin/activate
(bots)chetans-MacBookPro:ch09 Chetan$
(bots)chetans-MacBookPro:ch09 Chetan$ pip install aiml
Collecting aiml
Installing collected packages: aiml
Successfully installed aiml-0.8.6
如何操作...
-
作为第一步,我们首先创建 AIML 文件。前往你喜欢的编辑器并创建一个 AIML 文件,就像一个普通的 XML 文件一样,内容如下:
<aiml version="1.0.1" encoding="UTF-8"> <!-chat.aiml à <category> <pattern>HELLO</pattern> <template> Hi, hello! </template> </category> <category> <pattern>WHO ARE *</pattern> <template> <random> <li>I'm a bot!</li> <li>Bad guy!</li> <li>My name is superman!</li> </random> </template> </category> <category> <pattern>AWESOME *</pattern> <template> You're nice too! J </template> </category> </aiml> -
接下来,我们创建一个启动 XML 文件,该文件将加载 AIML 文件;这也会加载我们添加到前面 AIML 文件中的人工智能。让我们称这个文件为
init.xml:<aiml version="1.0.1" encoding="UTF-8"> <!-- init.xml --> <!-- Category is an atomic AIML unit --> <category> <!-- Pattern to match in user input --> <!-- If user enters "LOAD AIML B" --> <pattern>LOAD AIML B</pattern> <!-- Template is the response to the pattern --> <!-- This learn an aiml file --> <template> <learn>chat.aiml</learn> <!-- You can add more aiml files here --> <!--<learn>more_aiml.aiml</learn>--> </template> </category> </aiml> -
现在,让我们开发运行我们的聊天机器人的 Python 代码。以下代码正好是我们需要的。我们称这个文件为
aibot.py:import aiml # Create the kernel and learn AIML files kernel = aiml.Kernel() kernel.learn("init.xml") kernel.respond("load aiml b") # Press CTRL-C to break this loop while True: print kernel.respond(raw_input("Enter your message >>")) -
如果我们使用
python aibot.py命令运行这个机器人,它会显示一个输入屏幕,等待用户的输入。查看以下截图以了解其工作原理:![如何操作...]()
它是如何工作的...
上述 Python 代码模拟了一个基于人工智能的典型机器人。当我们运行 Python 代码时,amil.Kernel() 将加载 AI 内核。
一旦内核被加载,kernel.learn() 将调用启动 xml 文件。当向内核发送 load aiml b 命令时,AIML 规则引擎被加载。
一旦引擎被加载到内核中,我们就可以自由地与机器人聊天。
在前面的截图中,当我们说 你好 时,机器人理解它(来自 chat.aiml 文件)并以配置在 chat.aiml 中的 嗨,你好 响应。
在第二种情况下,当用户询问 你是谁 时,AI 机器人会匹配 WHO ARE * 模式;该模式再次在 chat.aiml 中定义。
如果你观察,WHO ARE * 模式在 chat.aiml 文件中配置为多个响应,因此,在运行时,机器人会随机选择一个响应并返回 我的名字是超人!。
使用机器人自动化业务流程
到目前为止,在本章中,你已经学习了什么是机器人,它们是如何构建的,以及一些简单的机器人使用案例。让我们看看我们如何利用我们至今为止的知识来解决杰伊的问题,也许还能更多地了解如何构建机器人。
杰伊是一家著名图书出版公司的市场营销经理,在 MyBooks 公司。他的任务是想出图书推广电子邮件。他觉得他发送的推广电子邮件太通用,没有有效地针对读者。例如,关于 Python 学习路径的电子邮件可能不会鼓励 Java 开发者花钱。他认为,如果他了解受众的兴趣并使他的互动更加相关,他可以做得更好;读者更有可能以这种方式购买书籍。他还觉得很多读者(潜在买家)在 Facebook 上,但出版社目前还没有接触到他们。我们能帮杰伊解决这个问题吗?
准备工作
是的,让我们通过开发一个出色的机器人来帮助 Jay。如果你研究 Jay 的问题,他需要了解受众(在这种情况下,对购买书籍感兴趣的读者)并根据他们的兴趣向他们推荐书籍。因此,我们的机器人应该足够智能,能够从读者那里获取相关信息。
此外,由于读者已经在 Facebook 上,我们可以创建一个 MyBooks Facebook 页面并构建一个 Facebook Messenger 机器人,以便联系读者。让我们看看如何操作。
在我们开始构建机器人之前,让我们安装一些将在这个练习中需要的 Python 模块。我们使用 Python 的pip安装flask和requests模块:
(bots)chetans-MacBookPro:ch09 Chetan$ pip install flask
Collecting flask
Using cached Flask-0.11.1-py2.py3-none-any.whl
Collecting click>=2.0 (from flask)
Collecting itsdangerous>=0.21 (from flask)
Collecting Werkzeug>=0.7 (from flask)
Downloading Werkzeug-0.11.11-py2.py3-none-any.whl (306kB)
100% |████████████████████████████████| 307kB 1.4MB/s
Collecting Jinja2>=2.4 (from flask)
Using cached Jinja2-2.8-py2.py3-none-any.whl
Collecting MarkupSafe (from Jinja2>=2.4->flask)
Installing collected packages: click, itsdangerous, Werkzeug, MarkupSafe, Jinja2, flask
Successfully installed Jinja2-2.8 MarkupSafe-0.23 Werkzeug-0.11.11 click-6.6 flask-0.11.1 itsdangerous-0.24
(bots)chetans-MacBookPro:ch09 Chetan$ pip install requests
Collecting requests
Using cached requests-2.11.1-py2.py3-none-any.whl
Installing collected packages: requests
Successfully installed requests-2.11.1
如何操作...
-
要开发一个 Facebook Messenger 机器人,首先创建一个 Facebook 账号(谁没有 Facebook 账号呢?)。登录您的账号,并前往
www.facebook.com/pages/create/创建一个新的页面。 -
在我们的案例中,因为我们正在为 MyBook 公司建立一个页面,我们可以将我们的页面命名为MyBooks,并选择一个合适的组织类型,即媒体/新闻公司。以下是页面的样子:
![如何操作...]()
-
创建 Facebook 页面的第二步是填写 Facebook 请求的其他详细信息,如下面的截图所示。我们为我们的页面提供了一个很好的描述:“获取我们最新书籍的更新”:
![如何操作...]()
-
我们已经为 Jay 填写了所有详细信息,MyBooks 的 Facebook 页面已经准备好,看起来很棒:
![如何操作...]()
现在,这是一个良好的开始。读者们将开始关注这个页面,但我们真的需要添加让我们的读者通过 Facebook 页面进行交流的能力;我们通过 Facebook Messenger 机器人来实现这一点。所以,让我们继续工作,解决我们解决方案的这个方面。
-
要创建一个 Facebook Messenger 机器人,我们需要一个 Facebook 应用。我们将通过导航到
developers.facebook.com/quickstarts/?platform=web并点击跳过并创建 App ID来创建一个应用,如下面的截图所示:![如何操作...]()
-
我们现在填写所需的详细信息,并点击创建 App ID按钮来创建应用。以下截图显示了创建应用时添加的详细信息:
![如何操作...]()
-
一旦我们填写了详细信息并点击创建 App ID,就会为我们创建一个新的应用。这个 Facebook 应用是为我们的机器人准备的。我们在页面的右上角可以看到应用 ID,但要与机器人关联,我们需要向下滚动并点击开始部分中的Get Started:
![如何操作...]()
-
为了让机器人访问 Messenger,我们将生成页面访问令牌,如下面的截图所示。
小贴士
请妥善保管此令牌,不要与任何人分享。
-
此令牌用于响应从MyBooks Facebook 页面与机器人开始对话的读者:
![如何操作...]()
-
好的,还有最后一件事待办。我们还需要接收读者的消息;只有在这种情况下,我们才能回应他们。为此,我们进入Webhooks部分并添加一些设置:
-
回调 URL:这是我们的服务器链接,我们通过 Facebook 页面从读者那里接收消息
-
验证令牌:这里可以使用任何一组字符,比如
token -
订阅字段:我们选择消息作为我们机器人的订阅字段(这可以在以后更改)
如你所见,我们需要有一个回调 URL。这将由 Facebook 用来验证我们的回调 URL 是否设置正确。为此,我们创建一个 Flask 服务器并配置用于回调 URL的路由。以下代码创建了一个名为
/bot的路由,用作回调 URL进行验证:from flask import Flask from flask import request import sys, requests, json, os app = Flask(__name__) @app.route("/bot/", methods=['GET', 'POST']) def hello(): if request.method == 'GET': return request.args.get('hub.challenge')如果我们在 5000 端口运行服务器,并且也使用
ngrok在相同端口运行,我们将得到一个可以放置在Webhook设置中的回调 URL。这是回调 URL 的样式:![如何操作...]()
可以通过点击按钮来验证和保存设置,如下面的截图所示:
![如何操作...]()
当我们验证并保存设置时,会向我们的 Flask 服务器发送一个带有
hub.challenge代码的GET请求。我们从flask路由返回此代码给 Facebook,并验证Webhook设置:/Users/chetan/book/ch09/bots/bin/python /Users/chetan/book/ch09/bookbot.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [01/Oct/2016 10:17:43] "GET /bot/?hub.mode=subscribe&hub .challenge=1742124657&hub.verify_token= token HTTP/1.1" 200 -为了让机器人正常工作,我们还需要确保 Facebook 页面允许某些事件,如阅读或回显消息。我们在Webhooks部分启用这些设置:
![如何操作...]()
-
-
太好了!所以现在我们已经准备好了一个Webhook来接收读者的消息,并且还有一个访问令牌来回应用户。如果你意识到,Webhook将成为我们的机器人服务器!让我们继续让我们的机器人做更智能的事情。以下代码将使我们的机器人完成 Jay 需要的所有伟大事情:
from flask import Flask from flask import request import requests, json app = Flask(__name__) def send_weburl(payload, recipient_id): headers = { "Content-Type": "application/json" } token = { "access_token": "TOKEN" } if payload == 'Python': data = json.dumps({ "recipient": { "id": recipient_id }, "message":{ "attachment":{ "type":"template", "payload":{ "template_type":"generic", "elements":[ { "title":"Learn Python Design Patterns: Chetan Giridhar", "item_url":"https://www.amazon.com/Learning-Python- Design-Patterns-Second/dp/178588803X", "image_url":"https://images-na.ssl-images- amazon.com/images/I/51bNOsKpItL._SX404_BO1, 204,203,200_.jpg", "subtitle":"Python Book for software architects and developers", "buttons":[ { "type":"web_url", "url":"https://www.amazon.com/Learning-Python- Design-Patterns-Second/dp/178588803X", "title":"Buy", "webview_height_ratio":"full" } ] } ] } } } }) if payload == 'Java': data = json.dumps({ "recipient": { "id": recipient_id }, "message":{ "attachment":{ "type":"template", "payload":{ "template_type":"generic", "elements":[ { "title":"RESTful Java Patterns and Best Practices: Bhakti Mehta", "item_url":"https://www.amazon.com/RESTful-Java- Patterns-Best-Practices/dp/1783287969", "image_url":"https://images-na.ssl-images- amazon.com/images/I/51YnSP6uqeL._SX403_BO1, 204,203,200_.jpg", "subtitle":"Python Book for software architects and developers", "buttons":[ { "type":"web_url", "url":"https://www.amazon.com/RESTful-Java- Patterns-Best-Practices/dp/1783287969", "title":"Buy", "webview_height_ratio":"full" } ] } ] } } } }) r = requests.post("https://graph.facebook.com/v2.6/me/messages", params=token, headers=headers, data=data) def send_postback(recipient_id): headers = { "Content-Type": "application/json" } token = { "access_token": "TOKEN" } data = json.dumps({ "recipient": { "id": recipient_id }, "message": { "attachment": { "type": "template", "payload": { "template_type": "button", "text": "Hey there, Welcome to MyBooks. What are you interested in?", "buttons": [ { "type":"postback", "title":"Java", "payload":"Java" }, { "type":"postback", "title":"Python", "payload":"Python" } ] } } } }) r = requests.post("https://graph.facebook.com/v2.6/me/messages", params=token, headers=headers, data=data) @app.route("/bot/", methods=['GET', 'POST']) def hello(): print request.data if request.method == 'GET': return request.args.get('hub.challenge') data = request.get_json() if data["object"] == "page": for entry in data["entry"]: for messaging_event in entry["messaging"]: if messaging_event.get("postback"): sender_id = messaging_event["sender"]["id"] payload = messaging_event["postback"]["payload"] send_weburl(payload, sender_id) if messaging_event.get("message"): # readers send us a message sender_id = messaging_event["sender"]["id"] send_postback(sender_id) return "ok", 200 if __name__ == "__main__": app.run() -
我们运行前面的 Flask 服务器来激活我们的机器人。现在,让我们通过导航到 Facebook 页面来看看机器人是如何工作的。在 Facebook 页面上,如果我们点击消息,我们就可以在MyBooks页面上与机器人开始聊天:
![如何操作...]()
-
让我们用简单的
Hi消息开始与机器人的对话。机器人会向我们提出关于我们是否需要有关 Python 或 Java 书籍信息的问题。太棒了!!如何操作... -
现在,如果我们点击Python,机器人会推荐一本用 Python 编写的架构书籍,并鼓励读者购买。当读者点击Java时,也会发生这种情况。请看以下截图:
![如何操作...]()
以下截图演示了一个 Java 示例,其中当用户选择Java时,推荐了RESTful Java Patterns and Best Practices这本书:
![如何操作...]()
-
太酷了,对吧?这正是 Jay 所需要的。所以,当读者到达MyBooks页面时,他们可以与机器人交谈,机器人根据他们的兴趣推荐一本书。由于机器人提出的建议与读者的相关性比通用促销电子邮件要高得多,读者购买书籍的可能性也更高。太棒了!
它是如何工作的...
我们首先为 Jay 的出版社创建了一个 Facebook 页面:MyBooks。然后我们将一个 Facebook Messenger 机器人与这个页面关联起来,并获得了访问令牌,以便向与机器人聊天的读者发送消息。我们还设置了Webhooks,以便我们的机器人能够接收读者的消息,并使用访问令牌向他们发送消息。在这里,Webhook是机器人的大脑。
当读者到达MyBooks页面时,他们点击消息传递者与机器人开始对话。当他或她说Hi时,HTTP 的POST请求会发送到Webhook https://2d7d823f.ngrok.io/bot/,并带有消息。
机器人读取读者的消息,并向读者发送带有postback选项的通用模板消息。机器人使用 Facebook 的 Graph APIs 发送此消息。
注意
Facebook 有模板消息用于发送postback消息、按钮、图片、URL 和音频/视频媒体文件。
当读者选择Python时,机器人收到这条消息,并根据有效载荷返回书籍的图片以及 URL,以便用户购买。用户然后可以点击购买按钮,进入书籍的 URL 并从那里购买书籍,这正是 Jay 所希望的!
更多...
在本章中,我们基于 CLI、Web UI 和移动应用构建机器人。这些机器人可以驻留在其他聊天系统中,例如拥有良好 API 集的 Slack。你可能想尝试编写一个。如果你写了,请把链接发给我;我很乐意尝试它们。
注意
你可以通过 Twitter 联系我,或者直接给我发消息,我会回复你。
第九章. 想象中的图像
我们几乎每天都在处理图像。上传图像到您的 Facebook 个人资料页面,或者在开发移动或 Web 应用程序时操作图像;有大量的用例。随着计算机视觉领域的众多进步,成像已经成为一个关键领域。使用 Python 处理图像变得非常简单。
在本章中,我们将介绍以下食谱:
-
转换图像
-
调整大小、裁剪和生成缩略图
-
复制粘贴和添加水印的图像
-
图像差异和比较
-
人脸检测
-
成像作为业务流程
简介
在电子世界中,图像是由 0 和 1 组成的位序列。它们是场景或文档的电子快照。即使是绘画或照片也可以数字化成图像。让我们深入了解图像,了解它们的结构。
每张图像都被采样,并由称为像素的点阵表示。这些像素代表屏幕上显示的图片的最小可控元素。图像中可用的像素数量越多,设备屏幕上图像的表示就越准确。
每个像素的强度是可变的。在数字世界中,图像的颜色由三种或四种不同颜色的强度表示:红色、绿色和蓝色(RGB)或青色、品红色、黄色和黑色(CMYK)。计算机程序通常使用 RGBA 格式来表示颜色,其中 A 代表 alpha(或颜色的透明度)。每个像素在 RGBA 格式中以二进制表示,并由计算机作为序列存储。然后计算机读取这个序列进行显示,在某些情况下,将其转换为打印的模拟版本。让我们详细看看一些特定的图像属性。
图像属性
让我们看看一些图像属性:
-
图像大小:如您之前所学的,计算机图像以一系列 0 和 1 存储,并以像素(矩形点)为单位进行测量。图像的文件大小是根据它包含的像素数量和存储的颜色信息量来计算的。实际上,文件大小是图像在计算机硬盘上占用的空间。
-
位深度:这是表示单个像素颜色的位数。这个概念可以定义为每像素位数,表示描述像素所使用的位数。图像的位深度越大,它可以存储的颜色就越多。一个 1 位图像只能存储两种(2¹)颜色--0 和 1--因此是黑白颜色。与之相比,一个 8 位图像可以存储 256(2⁸)种颜色。
-
图像分辨率:分辨率是指图像中的像素数量。分辨率有时通过图像的宽度和高度来识别。例如,分辨率为 1920 x 1024 像素的图像包含 1,966,080 个像素,或是一个 1.9 兆像素的图像。
-
图像质量:它可以根据图像存储的信息进行更改。并非所有图像都需要存储所有像素来表示图像。例如,图片中连续的蓝色海洋区域不需要所有像素,并且图像可以被压缩以减少图像的磁盘空间,而不会影响图像质量。这种磁盘空间的减少被称为压缩。更高的压缩意味着细节的明显损失。在当今世界,典型的压缩类型是 JPG 压缩,它减少了图像的大小,同时也牺牲了图像的质量。
-
图像格式:图像在计算机中以不同的扩展名存储。例如,BMP 或 TIF 这样的格式根本不进行压缩;因此,它们占用更多的磁盘空间。例如,JPG 这样的文件可以进行压缩,您还可以选择压缩级别。因此,TIF 图像是无损的,而 JPG 压缩被称为有损压缩。值得注意的是,有损压缩利用了人类无法区分细微色调差异的能力。多次有损转换会导致图像退化,而多次无损转换将保留图像质量。但通常,在压缩时,是在图像退化和大小之间进行权衡。
好的,这是一个良好的开端。在本章中,我们将探讨更多关于图像的概念,并查看您可以使用 Python 在图像上执行的各种操作。我们将查看多个模块,这些模块将帮助我们以所需的方式操作图像。在本章结束时,我们还将探讨一个可以通过本章中涵盖的食谱所构建的知识自动化的典型业务流程。
在本章的进行过程中,我们将使用以下 Python 模块:
-
Pillow(pypi.python.org/pypi/Pillow) -
scipy(www.scipy.org/) -
opencv(pypi.python.org/pypi/opencv-python)
转换图像
让我们以简单的示例开始我们的图像之旅。但在继续之前,让我们构建我们的虚拟环境。
-
我们将使用
virtualenv命令构建虚拟环境,并激活它:chetans-MacBookPro:~ Chetan$ cd book/ch10/ chetans-MacBookPro:ch10 Chetan$ virtualenv ch10 New python executable in ch10/bin/python2.7 Also creating executable in ch10/bin/python Installing setuptools, pip, wheel...done. chetans-MacBookPro:ch10 Chetan$ source ch10/bin/activate (ch10)chetans-MacBookPro:ch10 Chetan$ -
太好了!因此,我们现在有一个独立的环境来处理本章的食谱。对于第一组示例,我们将使用 Python 的 Pillow 模块。在我们进入食谱之前,让我们先安装这个模块。我们将使用我们喜欢的
python-pip来安装 Pillow 模块:(ch10)chetans-MacBookPro:ch10 Chetan$ pip install pillow You are using pip version 7.1.0, however version 8.1.2 is available. You should consider upgrading via the 'pip install --upgrade pip' command. Collecting pillow Downloading Pillow-3.4.2.tar.gz (10.8MB) 100% |████████████████████████████████| 10.8MB 39kB/s Building wheels for collected packages: pillow Running setup.py bdist_wheel for pillow Installing collected packages: pillow Successfully installed pillow-3.4.2
因此,我们现在有了我们的环境,并且Pillow模块也已经安装。我们现在可以开始使用食谱工作了。
如何操作...
在本节中,我们将处理将图像转换为不同格式的问题。
-
首先,让我们下载一个可以作为示例图像来执行所有操作的图像。我喜欢日落,这也是为什么我在本章的大部分食谱中使用了日落图像。这是它的样子。我把它存储在我的笔记本电脑上,命名为
beach_sunset.png:![如何做...]()
-
现在,让我们继续编写将此图像转换为 JPEG 格式的 Python 代码。以下代码正好符合我们的需求。我们将 Python 代码存储在一个名为
convert.py的文件中:from PIL import Image img = Image.open('beach_sunset.png') img.save('beach-sunset-conv.jpg','jpeg')当你用 Python 的
convert.py命令运行此程序时,它将取原始 PNG 图像,将其转换为 JPG 格式,并存储为beach-susnset-conv.jpg。 -
真棒!现在,让我们对这个图像执行一个额外的操作,并将其转换为灰度图(黑白格式)。人们通常将图像转换为黑白格式,以赋予它们怀旧的外观;这可以通过以下一系列命令行轻松实现:
from PIL import Image img = Image.open('beach_sunset.png').convert('L') img.show() img.save('beach-sunset-gray.png','png')现在,当你运行这个程序时,你将在磁盘上看到另一个生成的图像,其名称为
beach-sunset-gray.png,如下所示:![如何做...]()
-
很酷,让我们更进一步,执行一些旋转和翻转图像的操作。这些动作通常用于有趣的网站,你可以简单地玩弄你的图像。以下代码将帮助你将图像旋转 180 度:
from PIL import Image img = Image.open('sunset.jpg') img.rotate(180).save('sunset180deg.jpg')如果你用我们的基础图像运行此代码,你会看到一个旋转了 180 度的图像,即图像是颠倒的,如下所示:
![如何做...]()
-
虽然旋转图像很酷,但如果我们可以翻转图像,那就更有趣了。PIL 在这里没有让人失望,并提供了水平翻转和垂直翻转图像的选项。以下代码将帮助我们执行翻转操作:
from PIL import Image img = Image.open('sunset.jpg') img.transpose(Image.FLIP_LEFT_RIGHT).save( 'sunset_horizontal_flip.png') img.transpose(Image.FLIP_TOP_BOTTOM).save( 'sunset_vertical_flip.png')现在如果你运行这段代码,它将生成两个图像。以下图像与原始图像相同,但水平翻转(就像在图像的右侧放了一面镜子)。注意山已经移动到图像的右侧:
![如何做...]()
以下截图是原始图像的镜像,它是垂直翻转的。注意,山仍然在图像的左侧,但它现在是颠倒的。落日的命运也是如此。它看起来像日出吗?
![如何做...]()
工作原理...
在本节中,我们处理了两种不同的图像格式:PNG 和 JPEG。可移植网络图形(PNG)文件是非损失性文件,在压缩照片图像时不会降低质量。它是一种优秀的网络图形文件格式;它可以用于多种背景,并支持透明度。对于第一个代码示例中使用的图像,beach_sunset.png,文件大小为 550KB。
联合图像专家小组 (JPEG) 使用有损压缩技术来压缩图像。JPG 通过减少图像的部分到像素或瓦片来压缩图像。JPG 图像可以根据设置以 N:1 的比例压缩。由于图像容易压缩并且可以减少访问互联网上图像的带宽,JPG 已成为互联网上图像的标准。对于转换后的图像,我看到文件大小是 450KB--比 PNG 文件小近 20%。
现在,让我们理解 Python 代码。我们从PIL模块中导入Image类。Image类负责打开、加载和转换图像,以及其他操作,如将图像保存在磁盘上。在我们的示例中,我们使用open()方法打开 PNG 图像,并使用save()方法以 JPEG 格式保存图像。
在第二个示例中,我们将图像转换为黑白格式。正如我们有 RGB 和 CMYK 格式一样,我们也有L格式,它表示黑白。在将图像转换为L格式时,它使用 ITU-R 亮度格式,其中L=R299/1000 + G587/1000 + B114/1000*。
在 Python 代码方面,我们再次使用Image类来open()文件,并使用带有参数L的convert()方法将图像转换为黑白。最后,我们使用save()方法将文件保存在磁盘上。我们保持文件格式为 PNG。
在第三个示例中,我们使用相同的Image类和open()方法打开图像以获取img对象。然后,使用旋转角度作为参数调用rotate()方法。在我们的示例中,我们旋转了图像 180 度,并最终调用save()方法将旋转后的图像以sunset180deg.jpg的名称保存在磁盘上。
在最后一个示例中,我们使用了PIL模块的transpose()方法,并使用Image.FLIP_LEFT_RIGHT和Image.FLIP_TOP_BOTTOM属性将图像左右和上下翻转,然后分别以sunset_horizontal_flip.png和sunset_vertical_flip.png的名称保存翻转后的图像。
还有更多...
Pillow 模块有许多更多的方法可以帮助我们在图像上执行更复杂的操作,例如调整大小、粘贴、裁剪等等。我们将在本章的下一个示例中查看它们。
调整大小、裁剪和生成缩略图
调整图像大小和裁剪以获取图像的选定部分等操作非常常见,但当以编程方式尝试时,这些操作可能会变得繁琐。看看我们如何完成这些任务。
准备工作
在这个示例中,我们将使用Pillow库来调整图像大小和裁剪图像。由于我们已经安装了 Pillow 模块,我们不必担心任何安装。让我们开始操作吧。
如何做到这一点...
-
首先,让我们看看如何将图像调整到给定的尺寸。创建一个 Python 文件,
resize.py,并将以下代码片段粘贴进去:from PIL import Image img = Image.open('sunset.jpg') resized = img.resize((256,256)) resized.save('sunset-resize.jpg', 'jpeg') -
此外,从互联网上下载一张图片,命名为 sunset.jpg。我的图片看起来像这样:
![如何操作...]()
-
现在,使用
python resize.py命令运行 Python 代码,然后查看磁盘上的图像sunset-resize.jpg。你会看到图像被调整大小,看起来类似于以下截图:![如何操作...]()
如预期的那样,图像的尺寸也是 256 像素乘以 256 像素:
![如何操作...]()
-
在编程中经常需要的另一个操作是生成图像的缩略图。缩略图用作原始图像的预览,通常用于电影评论网站或图书出版网站。让我们看看我们是否可以轻松地使用 Pillow 模块生成缩略图。创建一个 Python 文件并添加以下代码:
import os, sys from PIL import Image size = 128, 128 infile = "sunset.jpg" outfile = os.path.splitext(infile)[0] + ".thumbnail.jpg" if infile != outfile: try: im = Image.open(infile) im.thumbnail(size, Image.BICUBIC) im.save(outfile, "JPEG") except IOError: print "cannot create thumbnail for '%s'" % infile现在,如果你运行这段代码,你会得到一个名为
sunset.thumbnail.jpg的图像,它是原始图像的缩略图,看起来如下面的截图所示。如果你查看图像的大小,它不会是 128 x 128(对我来说是 128 x 80 像素)。我们将在稍后解释这个原因。太好了!因此,我们已经生成了图像的缩略图,它可以用作网站上的个人资料缩略图或预览图:
![如何操作...]()
-
在本食谱中,我们将介绍另一种操作,即图像裁剪。以下代码正好实现了我们所需要的功能:
from PIL import Image img = Image.open('sunset.jpg') cropImg = img.crop((965, 700, 1265, 960)) cropImg.save('sunset-crop.jpg')如果你运行前面的 Python 代码片段,你将在磁盘上看到一个名为
sunset-crop.jpg的图像,它从原始日落图像中裁剪了太阳。它看起来是这样的:![如何操作...]()
看起来很棒,我们可以如此轻松且直观地使用 Pillow 对图像执行多个操作。但是这些操作是如何工作的;使用了哪些方法?让我们来看看。
它是如何工作的...
在这个食谱中,我们再次使用了 Pillow 的Image类来调整图像大小、裁剪图像并从原始图像生成缩略图。
在第一个代码片段中,我们使用open()方法打开了 sunset.jpg 图像。然后我们使用带有元组参数的resize()方法,列出了调整大小图像的宽度和高度。然后我们使用save()方法,文件名为sunset-esize.jpg,以 JPEG 格式将文件保存到磁盘上。
在第二个片段中,我们使用open()方法打开图像并获取一个图像对象。然后,我们使用Image类的thumbnail()方法对图像对象进行缩略图生成。thumbnail()方法接受图像的大小(我们使用了 128 x 128),并使用 BICUBIC 图像过滤机制。最后,我们使用save()方法保存图像,目标文件名为sunset.thumbnail.jpg。我们查看缩略图的大小,并发现它并不完全是 128 x 128;实际上,它是 128 x 80。这是因为 PIL 保持图像宽度为 128 像素,然后重新计算高度以保持图像的宽高比。
在第三个示例中,我们使用 Pillow 模块的crop()方法裁剪图像。crop()方法接受从原始图像中需要裁剪出的所有四个坐标。在我们的例子中,我们给出了坐标left = 965,top = 700,right = 1265,bottom = 960来裁剪原始图像,得到的结果就是我们看到的太阳图像。
更多内容...
在缩略图生成示例中,我简要提到了为了提高清晰度而应用于图像的过滤器。在本章中,我不会详细讨论这些内容,但如果您感兴趣,可以在pillow.readthedocs.io/en/3.0.x/releasenotes/2.7.0.html#default-filter-for-thumbnails中详细了解。
复制粘贴和添加水印图像
在这个菜谱中,我们将介绍设计师和营销人员高度使用的操作之一,即图像添加水印。我们还将看到将图像重叠粘贴的有趣用法。让我们继续看看它们。
准备工作
在这个菜谱中,我们将继续使用 Pillow 进行复制粘贴图像,但我们将使用另一个 Python 模块wand进行添加水印。所以,按照常规做法,在我们开始编写任何代码之前,让我们首先安装wand模块。我们使用我们喜欢的工具 Python 的pip安装 wand:
(ch10)chetans-MacBookPro:ch10 Chetan$ pip install wand
You are using pip version 7.1.0, however version 8.1.2 is
available.
You should consider upgrading via the 'pip install --upgrade
pip' command.
Collecting wand
Downloading Wand-0.4.3.tar.gz (65kB)
100% |████████████████████████████████| 65kB 101kB/s
Building wheels for collected packages: wand
Running setup.py bdist_wheel for wand
Stored in directory:
/Users/chetan/Library/Caches/pip/wheels/77/
c2/a3/6cfc4bb3e21c3103df1ce72d7d301b1965657ee6f81cd3738c
Successfully built wand
Installing collected packages: wand
Successfully installed wand-0.4.3
已经安装了模块吗?好的,那么,让我们深入探讨。
如何操作...
-
首先,让我们看看如何使用 Pillow 执行复制粘贴操作。记住,从前面的章节中,我们有两个图像:原始图像,
sunset.jpg,以及从原始图像中裁剪出的太阳图像,sunset-crop.jpg。我们将在下面的 Python 代码中使用这些图像:from PIL import Image img = Image.open('sunset-crop.jpg') pasteImg = Image.open('sunset.jpg') pasteImg.paste(img, (0,0)) pasteImg.save('pasted.jpg') -
让我们将代码保存在名为
copy_paste.py的文件中,并使用 Python 命令copy_paste.py运行代码。一旦运行代码,我们将看到一个新文件被生成,名为pasted.jpg,如下面的截图所示:![如何操作...]()
我们已经成功地将裁剪的图像复制,粘贴到原始图像上,并将粘贴的图像保存为
pasted.jpg。酷吧,不是吗?现在,让我们看看一个有趣的例子,它具有商业用途。在这个例子中,我们将向现有图片添加水印,并以不同的名称存储。但在我们进入 Python 代码之前,让我们看看水印图片看起来是什么样子:
![如何操作...]()
以下 Python 代码帮助我们向我们的原始
sunset.jpg图像文件添加前面的水印:from wand.image import Image with Image(filename='sunset.jpg') as background: with Image(filename='watermark.jpg') as watermark: background.watermark(image=watermark, transparency=0.25, left=560, top=300) background.save(filename='result.jpg') -
运行此代码,你将在你的项目中看到一个
result.jpg文件被生成。它看起来类似于以下截图。看看图片是如何在顶部带有版权图片文本的水印:![如何操作...]()
它是如何工作的...
对于第一个代码片段,我们使用了PIL模块和 Image 类来open()裁剪的图片和原始图片,并获取两个文件的文件句柄,即:img和pasteImg。
如其名所示,我们打开了裁剪的图片img,并使用文件句柄将其粘贴到pasteImg上。
为了粘贴图片,我们使用了 Pillow 的paste()模块,并将img文件句柄作为源图片传递给它。我们还传递了裁剪图片要粘贴到原始图片上的坐标。由于我们选择的坐标是(0,0),所以裁剪的图片被粘贴到了原始图片的左上角。最后,我们将这张图片保存为pasted.jpg。
在第二个例子中,我们打开了原始图片sunset.jpg和水印图片watermark.jpg,并分别创建了文件句柄background和watermark。然后我们使用wand模块的watermark()方法将水印添加到原始图片上。
watermark()方法作用于背景图片对象(在这个例子中,背景,我们的原始图片对象)。它使用image作为keyword参数,表示水印图片的对象。您还可以设置水印图片的透明度,其中0表示水印完全可见,而1表示水印不可见。使用watermark()方法还可以实现的其他有用功能之一是,您可以选择水印在原始图片上的位置。在这个例子中,我们将其选择为从左560像素和从上300像素的位置。
好吧;这个菜谱就到这里。让我们继续看看本章剩余的菜谱中还有什么。
图像差异和比较
你肯定使用过基于文本的搜索,甚至实现过。但你可能不知道,你现在甚至可以进行基于图像的搜索?当然,谷歌做得相当不错。你认为它是如何做到的?如果你必须自己实现,你最好知道如何比较两张图片。根据你的用例,你可能还想要获取两张图片之间的diff或差异。在这个菜谱中,我们将涵盖两个用例:
-
如何获取两张图片之间的差异并将差异存储为图片
-
如何用科学方法客观比较两张图片
准备中
在这个菜谱中,我们将继续使用 Pillow 来比较图片。除了我们的Image类,我们还将使用ImageChops类来获取两张图片之间的差异。我们将使用scipy模块在像素级别比较图片。
-
我们已经安装了 Pillow 模块,所以让我们继续使用 Python 的
pip安装scipy模块。在 Mac OS X 机器上,你需要有一个编译器来安装scipy模块。我们将使用 Mac 的brew命令在 Mac 上安装 GCC 编译器:(ch10)chetans-MacBookPro:ch10 Chetan$ brew install gcc Warning: Building gcc from source: The bottle needs the Xcode CLT to be installed. ==> Using the sandbox ==> Downloading https://ftpmirror.gnu.org/gcc/gcc-6.2.0/gcc- 6.2.0.tar.bz2 Already downloaded: /Users/chetan/Library/Caches/Homebrew/gcc- 6.2.0.tar.bz2 ==> Downloading https://raw.githubusercontent.com/Homebrew/formula- patches/e9e0ee09389a54cc4c8fe1c24ebca3cd765ed0ba/gcc/6.1.0- jit.patch Already downloaded: /Users/chetan/Library/Caches/Homebrew/gcc-- patch- 863957f90a934ee8f89707980473769cff47 ca0663c3906992da6afb242fb220.patch ==> Patching ==> Applying 6.1.0-jit.patch patching file gcc/jit/Make-lang.in ==> ../configure --build=x86_64-apple-darwin15.5.0 -- prefix=/usr/local/Cellar/gcc/6.2.0 -- libdir=/usr/local/Cellar/gcc/6.2.0/lib/gcc/6 --enable- languages=c,c++,objc,obj-c++,fortran ==> make bootstrap ==> make install ==> Caveats GCC has been built with multilib support. Notably, OpenMP may not work: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60670 If you need OpenMP support you may want to brew reinstall gcc --without-multilib ==> Summary /usr/local/Cellar/gcc/6.2.0: 1,436 files, 282.6M, built in 70 minutes 47 seconds (ch10)chetans-MacBookPro:ch10 Chetan$ -
现在我们已经安装了 GCC,让我们使用
python-pip安装scipy。以下是我系统上的安装日志:(ch10)chetans-MacBookPro:ch10 Chetan$ pip install scipy You are using pip version 7.1.0, however version 8.1.2 is available. You should consider upgrading via the 'pip install --upgrade pip' command. Collecting scipy Using cached scipy-0.18.1.tar.gz Building wheels for collected packages: scipy Running setup.py bdist_wheel for scipy Stored in directory: /Users/chetan/Library/Caches/pip/wheels/33/ c4/f5/e00fe242696eba9e5f63cd0f30eaf5780b8c98067eb164707c Successfully built scipy Installing collected packages: scipy Successfully installed scipy-0.18.1
如何操作...
-
现在模块已经安装好了,让我们开始利用它们来满足我们的需求。首先,让我们看看如何获取两张图片之间的差异,并将这个差异存储为一张图片本身。以下代码执行了这个操作:
from PIL import Image, ImageChops def differnce_images(path_one, path_two, diff_save_location): image_one = Image.open(path_one) image_two = Image.open(path_two) diff = ImageChops.difference(image_one, image_two) if diff.getbbox() is None: print "No difference in the images" return else: print diff.getbbox() diff.save(diff_save_location) differnce_images('sunset.jpg','pasted.jpg', 'diff.jpg')在前面的代码示例中,我们计算了原始图像
sunset.jpg和粘贴的图像pasted.jpg(如果你还记得之前的菜谱,pasted.jpg是在将裁剪的太阳图像粘贴到原始日落图像上后获得的)之间的差异。差异图像看起来是这样的:![如何操作...]()
观察一下差异仅是太阳的裁剪图像,因为基础原始图像保持不变。酷!黑色区域表示什么?我们将在如何工作...部分讨论它。
-
现在,让我们继续并看看如何以客观的方式计算图像之间的差异。为此,我们将使用
scipy模块。以下代码示例将帮助我们完成所需的工作:from scipy.misc import imread from scipy.linalg import norm def compare_images(img1, img2): diff = img1 - img2 z_norm = norm(diff.ravel(), 0) return z_norm img1 = imread("sunset.jpg").astype(float) img2 = imread("pasted.jpg").astype(float) z_norm = compare_images(img1, img2) print "Pixel Difference:", z_norm如果我们运行前面的 Python 代码,我们将得到这两张图片像素的差异。我们的示例输出如下:
Pixel Difference: 246660.0
它是如何工作的...
在本节的第一段代码中,我们使用 Pillow 库的ImageChops类计算了两张图片之间的差异。像往常一样,我们使用open()方法打开了这两张图片,分别得到了image_one和image_two这两个图像对象。
我们随后使用了ImageChops类的difference()方法,并将图像对象作为参数传递给这个方法。difference()方法返回一个diff对象,这个对象本质上代表了两个图像之间的差异。
最后,我们将差异作为名为diff.jpg的图像保存在磁盘上。我们还对diff对象使用了getbbox()方法,这个方法计算图像中非零区域的边界框。这里非零区域表示sunset.jpg和pasted.jpg之间的差异为 0 的像素。
现在,如果你查看diff.jpg,它包含一个巨大的黑色区域。这些是差异为 0 的像素,因此颜色为黑色。对于相同的图片,getbbox()方法返回None。
在第二个示例中,我们根据零范数比较了两个图像,这表示不等于零的像素数,换句话说,表示两个图像之间有多少像素不同。为了比较图像,我们首先使用 scipy 模块的 imread() 方法读取两个图像。这两个图像对象都是 img1 和 img2。
然后,我们使用 diff = img1 - img2 计算了两个图像之间的差异。这个返回的差异是 scipy 的 ndarray 类型。当我们把这个差异传递给 norm() 方法时,它返回图像之间不同的像素数。
还有更多...
比较图像有多种方法,我们在这章中没有涉及。如果你对此真的感兴趣,我建议你深入阅读。但就实际用途而言,我认为这一章应该足够了。
面部检测
在前面的章节中,我们讨论了许多图像操作。在这个食谱中,让我们深入探讨并覆盖一个高级操作,例如图像中的面部检测。
准备工作
在这个食谱中,我们将使用 Python 的 opencv 模块,所以让我们首先安装所需的模块。
-
为了使用
opencvPython 绑定,我们首先需要在我们的计算机上安装opencv。在我的 Mac OS X 机器上,我使用brew工具以这种方式安装opencv:(ch10)chetans-MacBookPro:ch10 Chetan$ brew install homebrew/science/opencv ==> Tapping homebrew/science Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew- science'... ... ... ==> Summary /usr/local/Cellar/opencv/2.4.13.1: 277 files, 35.5M -
只在计算机上安装
opencv并没有帮助。你还需要使用以下命令将cv2.so(.so代表共享对象或库)指向虚拟环境,以便将其粘合:cd ch10/lib/python2.7/site-packages/ ln -s /usr/local/Cellar/opencv/2.4.13.1/lib/python2.7/site- packages/cv.py ln -s /usr/local/Cellar/opencv/2.4.13.1/lib/python2.7/site- packages/cv2.so
太棒了!所以,我们现在已经安装了 opencv,这是我们在这个食谱中的示例所必需的。
如何做到这一点...
-
前往你最喜欢的编辑器,创建一个 Python 文件,并将其命名为
face_detection.py。现在,将以下代码复制到 Python 文件中:import cv2 face_cascade = cv2.CascadeClassifier('haarcascade.xml') original_image_path = 'Chetan.jpeg' image = cv2.imread(original_image_path) faces = face_cascade.detectMultiScale( image, scaleFactor=1.1, minNeighbors=3, minSize=(30, 30), flags=cv2.cv.CV_HAAR_SCALE_IMAGE) for (x, y, w, h) in faces: cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite('chetan_face.jpg', image) -
现在,创建一个名为
haarcascade.xml的 XML 文件,并将代码库中的内容复制过来。在我的例子中,我使用的是我自己的一张照片,Chetan.jpeg,但你可以使用你自己的任何照片进行这个示例。以下是Chetan.jpeg的样子:![如何做到这一点...]()
-
现在,让我们运行 Python 代码,看看我们的代码是否能够从图像中识别出我的脸。我们使用命令
python face_detection.py运行代码,并生成一个图像,Chetan_face.jpg,它看起来如下。确实,它检测到了我的脸。![如何做到这一点...]()
它是如何工作的...
在我们的食谱中,我们使用了 opencv 模块,首先使用 haarcascade.xml 文件创建一个级联分类器对象。我们称这个对象为 face_cascade。
使用 Haar 特征的级联分类器进行对象检测是 Paul Viola 和 Michael Jones 在 2001 年提出的一种有效的对象检测方法。它是一种基于机器学习的方法,其中级联函数是从大量的正负图像中训练出来的。然后,它被用来检测其他图像中的对象。
Haar 特征被输入到一个标准的 XML 文件中,这是我们代码示例中使用的。实际上,你可以训练你的分类器来检测你希望的对象。例如,眼检测使用另一个分类器。
接下来,我们使用opencv模块的imread()方法读取原始基础图像Chetan.jpeg,并定义了检测的最小窗口。
事实上,Haar 级联分类器采用滑动窗口方法工作,因此需要最小的窗口进行检测。分类器还需要配置minNeighbors。
这些设置是在级联对象的detectMultiScale()方法中配置的。我们已将minSize=(30,30)和minNeighbors=3设置为。
最后,我们将检测到的图像存储在磁盘上,原始图像有一个绿色矩形作为图像上人脸检测的指示。
还有更多...
我们查看了一个使用opencv进行人脸检测的非常简单的例子,并了解了一些关于分类器的内容。opencv还有更多你可能想了解的内容。
这里有一个链接到一篇你可能觉得有趣的资源:docs.opencv.org/trunk/index.html。
作为一项业务流程的图像化
彼得是 MBI Inc 公司的一名 IT 经理,这是一家大型企业。他的公司已经存在很长时间,大多数合同财务文件、标准操作程序和供应链文件都是基于纸质的。他被赋予了这样一个巨大的责任,即让他的公司实现无纸化。
这意味着他负责消除管理纸质档案的麻烦和成本。根据我们在本章中收集到的图像知识(我们还将学习更多),让我们看看我们是否可以帮助彼得。
如果你仔细分析,彼得需要完成两个重要的任务:
-
扫描文件并将它们以图像格式存储在电子格式中
-
从这些文件生成文本文件,以便它们可以轻松索引
准备就绪
对于这个练习,让我们首先安装所需的模块。我们需要以下模块:
-
scikit-image(scikit-image.org/) -
pyimagesearch(www.pyimagesearch.com/) -
tessaract和pytesseract(pypi.python.org/pypi/pytesseract/)
让我们开始安装模块:
-
让我们先从
scikit-image开始:(ch10)chetans-MacBookPro:ch10 Chetan$ pip install scikit-image You are using pip version 7.1.0, however version 8.1.2 is available. You should consider upgrading via the 'pip install --upgrade pip' command. Collecting scikit-image Downloading scikit-image-0.12.3.tar.gz (20.7MB) 100% |████████████████████████████████| 20.7MB 19kB/s Requirement already satisfied (use --upgrade to upgrade): six>=1.7.3 in ./lib/python2.7/site-packages (from scikit-image) Collecting networkx>=1.8 (from scikit-image) Downloading networkx-1.11-py2.py3-none-any.whl (1.3MB) 100% |████████████████████████████████| 1.3MB 325kB/s Requirement already satisfied (use --upgrade to upgrade): pillow>=2.1.0 in ./lib/python2.7/site-packages (from scikit- image) Collecting dask[array]>=0.5.0 (from scikit-image) Downloading dask-0.11.1-py2.py3-none-any.whl (375kB) 100% |████████████████████████████████| 376kB 946kB/s Collecting decorator>=3.4.0 (from networkx>=1.8->scikit-image) Using cached decorator-4.0.10-py2.py3-none-any.whl Collecting toolz>=0.7.2 (from dask[array]>=0.5.0->scikit-image) Using cached toolz-0.8.0.tar.gz Requirement already satisfied (use --upgrade to upgrade): numpy in ./lib/python2.7/site-packages (from dask[array]>=0.5.0->scikit- image) Building wheels for collected packages: scikit-image, toolz Running setup.py bdist_wheel for scikit-image Stored in directory: /Users/chetan/Library/Caches/pip/wheels/d5/e8/77/925fe026d562a74a0bccf1c7dd47d00f5f6ab2d395f247e674 Running setup.py bdist_wheel for toolz Stored in directory: /Users/chetan/Library/Caches/pip/wheels/b0/84/bf/7089262387e8ea60bdefb1fdb84d2ee99427f6d09c9c7ba37d Successfully built scikit-image toolz Installing collected packages: decorator, networkx, toolz, dask, scikit-image Successfully installed dask-0.11.1 decorator-4.0.10 networkx- 1.11 scikit-image-0.12.3 toolz-0.8.0 -
接下来,让我们安装
pyimagesearch。这是一套由 Adrian Rosebrock 开发的库。他在github.com/jrosebr1上开源了他的工作。实际上,我们在本代码示例中使用了pyimagesearch的扫描仪示例。 -
最后,让我们安装
tesseract和pytesseract。我们需要安装tesseract,这是一个光学字符识别器(OCR)模块,以及pytesseract,这是一个用于与 OCR 模块一起工作的 Python 模块:(ch10)chetans-MacBookPro:ch10 Chetan$ brew install tesseract ==> **Auto-updated Homebrew!** Updated 4 taps (homebrew/core, homebrew/dupes, homebrew/python, homebrew/science). .. .. .. ==> **Installing dependencies for tesseract: leptonica** ==> **Installing tesseract dependency: leptonica** ==> **Downloading https://homebrew.bintray.com/bottles/leptonica- 1.73.el_capitan.bottle.tar.gz** ####################################################### ################ # 100.0% ==> **Pouring leptonica-1.73.el_capitan.bottle.tar.gz** /usr/local/Cellar/leptonica/1.73: 50 files, 5.4M ==> **Installing tesseract** ==> **Downloading https://homebrew.bintray.com/bottles/tesseract- 3.04.01_2.el_capitan.bottle.tar.gz** ######################################################## ############### # 100.0% ==> **Pouring tesseract-3.04.01_2.el_capitan.bottle.tar.gz** /usr/local/Cellar/tesseract/3.04.01_2: 76 files, 39M (ch10)chetans-MacBookPro:ch10 Chetan$ pip install pytesseract Collecting pytesseract Downloading pytesseract-0.1.6.tar.gz (149kB) 100% |████████████████████████████████| 151kB 201kB/s Building wheels for collected packages: pytesseract Running setup.py bdist_wheel for pytesseract Stored in directory: /Users/chetan/Library/Caches/pip/wheels/f2/27/64/ a8fa99a36b38980aaf8d1d2c87f5dd6b5a0a274b8706e3df36 Successfully built pytesseract Installing collected packages: pytesseract Successfully installed pytesseract-0.1.6
好的,太棒了!现在,让我们看看如何做到这一点...部分中的代码。
如何做到这一点...
-
前往你最喜欢的编辑器,创建一个 Python 文件,并将其命名为
scanner.py。对于彼得来说,这将是关于他的财务文件,这些文件是图像格式,但为了这个示例,它将是我在手头上的图像。这是我图像的样子。这是一篇关于安迪·穆雷的报纸文章的照片,我正在尝试将其数字化:![如何做...]()
-
现在,将以下代码复制到
scanner.py中,并使用命令python scanner.py运行代码:**from** pyimagesearch.transform **import** four_point_transform **from** pyimagesearch **import** imutils **from** skimage.filters i**mport** threshold_adaptive import cv2 image = cv2.imread("images/murray.jpg") ratio = image.shape[0] / 500.0 orig = image.copy() image = imutils.resize(image, height = 500) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(gray, 75, 200) cv2.imwrite('scan_edge.jpg', edged) (cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5] for c in cnts: peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) if len(approx) == 4: screenCnt = approx break cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2) cv2.imwrite('scan_contours.jpg', image) warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio) warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) warped = threshold_adaptive(warped, 251, offset = 10) warped = warped.astype("uint8") * 255 cv2.imwrite('scanned.jpg', warped) print "Printing the contents of the image:" from PIL import Image img = Image.open("scanned.jpg") import pytesseract **print**(pytesseract.image_to_string(img))一旦运行 Python 代码,你将在你的硬盘上看到三个图像被创建。第一个是边缘检测图像,在我的情况下,它看起来如下。它被保存为
scan_edge.jpg:![如何做...]()
-
接下来,我们获取另一张图像,它检测到图像中包含文本的整个区域。这被称为轮廓图像,并生成为
scan_contours.jpg。看看它如何突出显示图像中包含文本的部分:![如何做...]()
-
最后,我们得到了关于安迪·穆雷的新闻文章的扫描副本,并保存为
scanned.jpg。看看以下截图中的新闻文章扫描效果如何:![如何做...]()
太酷了,这正是我们一开始想要实现的东西,不是吗?把这个给彼得,他会非常高兴。他可能会想到一些昂贵的咨询公司或扫描仪来做这项工作,而我们可以快速完成,而且还是免费的!
-
似乎这还不够,我们在彼得这里还做了更多。如果你运行程序,你还会得到一个文本输出,它提供了整篇文章的文本。使用这个文本,彼得可以选择对文档进行分类并相应地索引它们:
Printing the contents of the image: Vienna: Andy Murray's pm" suitof the world No. Iranking was severely tested by Slovak lefi-hander Martin Klizan in his Vienna openeron Wednwo day before the British star clinched a 6-3, 64 (57), 6-0 win. The 29-year-old Wimble- don and Olympic champion, who is closing in on Novak , Djokovic's top ranking, took his season record to 66 wins and just ninelosses. fl
太棒了!让我们在“它是如何工作的”部分看看我们程序的内部结构。
它是如何工作的...
在我们的食谱中,我们首先拍摄了我们报纸文章的照片。我们将其命名为murray.jpg。我使用了一部简单的手机摄像头来拍摄这张照片。然后我们继续使用opencv模块的imread()方法读取图像。
我们还计算了原始高度与新高度的比例,克隆了原始图像,并将其调整大小。我们使用copy()方法克隆我们的图像,并使用resize()方法将其调整到新的高度。
我们随后使用cvtColor()方法将图像转换为灰度格式,然后应用高斯滤波器来模糊图像。
我们对模糊的图像使用Canny()方法检测文本的边缘,并将检测到的边缘图像保存为scan_edge.jpg。
接下来,我们使用findContours()方法找到了图像的轮廓,并将图像的轮廓保存为scan_contours.jpg。
然后,我们对图像进行了一些转换。四点转换帮助我们获得原始图像的俯视图。为此,我们使用了four_point_transform()方法。
我们还将图像转换为灰度,然后进行阈值处理,以获得黑白纸张风格的感受。cvtColor()方法将图像转换为灰度,而threshold_adaptive()方法应用适当的阈值。现在我们完成了;图像已经准备好,并且已经扫描并保存为scanned.jpg。
但是,正如我们在上一节中看到的,我们也打印了报纸专栏的文本。我们可以通过首先使用 Pillow 的Image类和pytessaract模块,在图像对象上使用image_to_string()方法来读取扫描的图像来实现这一点。
太棒了,所以我们已经自动化了将纸质文档转换为电子格式的业务流程,并添加了一个索引文件的功能,以便它们可以轻松地输入公司的 ERP 流程。彼得非常高兴!恭喜你!
还有更多...
我们探讨了 OCR 技术从扫描图像中提取文本数据,但还有许多其他可以实现的事情,例如智能字符识别(提取手写文本)和条码识别(识别多种类型的条码),等等。此外,在本章中,我们并没有过多地处理图像滤波。如果你对此真的感兴趣,你可以阅读很多关于这些主题的内容,但这些内容超出了本章的范围。
第十章:数据分析和可视化
你有如此多的数据,它们只是随意地躺在那里?你是否想过如何轻松地分析数据并生成见解?对数据分析过程好奇吗?那么,你就在正确的位置!
在本章中,我们将涵盖以下内容:
-
使用可视化阅读、选择和解释数据
-
使用数据过滤和聚合生成见解
-
为企业自动化社交媒体分析
简介
我们信仰上帝。其他人必须带来数据 - W. Edwards Demming,统计学家
现在,企业严重依赖数据来了解客户的需求,他们将通过哪些渠道购买,等等。这样,企业可以就推出新产品或提出新优惠做出明智的决策。但企业是如何实现这一点的?决策实际上涉及什么?
基于数据的决策制定是指数据检查、清洗或清理、数据转换以及在数据之上生成模型的过程,目的是生成见解、发现有用信息并得出结论。例如,一家电子商务公司会使用这个过程来分析消费者购买模式,并为选定的一组产品提出促销优惠的适当时间。实际上,企业分析静态或实时数据用于多个目的,例如生成趋势、建立预测模型或简单地从原始数据中提取结构化信息。数据分析具有多个方面和方法,可以简要地归类为商业智能、预测分析和文本挖掘。
商业智能(BI)能够处理大量结构化和有时非结构化的数据,以便轻松解释这些大量数据。基于对数据的洞察力识别新机会可以为业务提供竞争优势和稳定性。
预测分析包括应用各种统计模型,如机器学习,来分析历史数据和当前趋势,以便对未来或未知事件做出预测。它涉及生成模型并捕捉数据特征之间的关系,以进行风险评估和决策制定。
文本分析是从结构化或非结构化文本数据中提取质量信息的过程。文本分析涉及语言、统计和上下文技术,以提取和分类信息,为商业提供支持。
然而,基于数据的决策并不容易,也不能一蹴而就。基于数据的决策制定是一个涉及多个操作的逐步过程。让我们在下一节中详细了解整个过程。
基于数据的决策制定步骤
在高层次上,这个过程可以分为以下阶段。当然,你可以根据你的目标定制这个过程:
-
定义假设和数据需求:在开始这个过程之前,你应该对自己的业务目标有清晰的认识——它们应该是具体、可衡量、可接受、相关和及时的(SMART)。你不希望在不清楚要解决的问题的情况下开始收集数据。尽可能明确地提出问题陈述,例如:“过去三个季度消费者领域的移动销售趋势是什么?”或者某种未来性的问题,例如:“这个冬天我能否以 150%的利润率销售电子产品?”你能为你公司提出这样的陈述吗?
-
数据来源:你也应该清楚你的数据来源。你是依赖你自己的公司数据库来进行数据分析吗?你也在依赖任何第三方市场研究或趋势作为分析的基础吗?如果你使用第三方数据,你计划如何从源(可能通过 API)提取数据并将其放入你的数据存储中?
-
数据收集:现在你已经清楚了你想要从中获得洞察力的内容,下一步就是以所需格式收集数据。例如,如果你想了解移动销售的趋势,你应该收集影响移动销售的因素的数据,如新产品推出(产品)、优惠(价格)、支付选项和购买日期/时间,以及其他相关因素。此外,你应该有一个可接受或标准的存储数据的方式;例如,我可能以美元为单位存储每单位的移动销售,而不是欧元,或者我可能以天为单位存储销售,而不是小时。在这种情况下,确定一个代表性的样本非常有用。代表性的样本能够准确反映整个群体,并肯定有助于分析。
-
数据转换:现在你知道了从哪里以及以什么格式收集数据,是时候决定你希望将数据加载到何处了。它可能是一个普通的 CSV 文件或一个 SQL 数据库;你事先需要知道这一点,以便以最佳方式组织数据并准备好分析。这一步可以被称为转换,因为它涉及到从源数据系统提取数据到目标数据系统。在大规模情况下,数据存储在数据仓库系统中。
-
数据清洗:一旦数据被处理和组织,就需要检查数据的合理性。转换后的数据可能不兼容,包含重复项,或者至少包含测量、采样和数据输入错误。数据清洗涉及删除不准确的数据,为缺失数据添加默认值,移除异常值,并解决任何其他数据不一致问题。在移除异常值时,你必须非常小心;你应该决定你想要如何移除它们——是简单地删除记录,还是用其他观察值的平均值/众数来填充?在这种情况下,你是最棒的决策者。
-
数据分析:一旦数据被清洗并准备好使用,就到了进行深入分析的时候了。你可以使用诸如逻辑回归等统计技术来分析数据以生成商业智能,或者生成预测模型。你还可以在上面进行文本分析,以生成洞察并做出决策。
-
数据可视化:一旦完成分析,就可以以多种格式报告,以便有效地将分析传达给受众。数据可视化使用信息展示,如表格和图表,来帮助传达数据中包含的关键信息。可视化还有助于用户解释他们的假设,并从分析中生成有意义的见解。
-
数据解释和反馈:这一阶段帮助你回答三个主要问题。分析是否回答了你最初提出的问题?它是否帮助你验证你的假设,即接受或拒绝你的假设?你是否需要更多数据来改进你的模型或结论?只有当你的结论能够反馈回系统中,这个过程才算完整。反馈循环确保预测模型在未来的使用中得到丰富和良好的训练。
好的,这是一个不错的开始!我想你一定对整个过程有了相当的了解:从数据收集到生成洞察。你会意识到其中的一些步骤,如定义目标、数据收集和数据转换,是特定于市场环境和要解决的问题的。
在本章中,我们将关注一些通用方面,例如收集实时数据、读取数据、执行数据分析以及数据可视化。我们将探讨一些流行的 Python 模块,这些模块将帮助我们高效地读取数据并分析数据以生成洞察。你还将了解帮助解释数据和生成可视化(图表)的 Python 模块。
在本章结束时,我们还将探讨一个典型的业务流程,该流程可以使用本章中涵盖的食谱所构建的知识进行自动化。本章将帮助你开始作为数据科学家的旅程,但并不涵盖广泛的主题,如统计技术或预测建模。
在本章的进行过程中,我们将使用以下 Python 模块:
-
pandas(pandas.pydata.org/pandas-docs/version/0.15.2/tutorials.html) -
numpy(www.numpy.org/) -
matplotlib(matplotlib.org/) -
seaborn(pypi.python.org/pypi/seaborn/)
使用可视化读取、选择和解释数据
在这个菜谱中,我们将使用一个已知的数据集。我们将使用 TechCrunch 的美国大陆 CSV 文件,其中包含 1,460 家公司融资轮次的列表。它看起来是这样的。它包含数据点,例如公司名称、员工人数、融资日期、筹集的资金金额和融资类型(A 轮或天使投资):

-
现在,让我们安装我们将用于从该 CSV 文件读取和选择数据的模块。在这样做之前,我们将设置一个虚拟环境并激活它:
chetans-MacBookPro:ch11 Chetan$ virtualenv analyze New python executable in analyze/bin/python2.7 Also creating executable in analyze/bin/python Installing setuptools, pip, wheel...done. chetans-MacBookPro:ch11 Chetan$ source analyze/bin/activate (analyze)chetans-MacBookPro:ch11 Chetan$ -
好的,太棒了!现在,让我们来安装
pandas。我们将使用pandas来读取我们的 CSV 文件并选择数据进行分析。我们使用我们最喜欢的工具python-pip来安装pandas。以下是我 Mac OSX 上安装pandas的日志:(analyze)chetans-MacBookPro:ch11 Chetan$ pip install pandas Collecting pandas Collecting pytz>=2011k (from pandas) Using cached pytz-2016.7-py2.py3-none-any.whl Collecting python-dateutil (from pandas) Using cached python_dateutil-2.6.0-py2.py3-none-any.whl Collecting numpy>=1.7.0 (from pandas) Collecting six>=1.5 (from python-dateutil->pandas) Using cached six-1.10.0-py2.py3-none-any.whl Installing collected packages: pytz, six, python-dateutil, numpy, pandas Successfully installed numpy-1.11.2 pandas-0.19.1 python-dateutil-2.6.0 pytz-2016.7 six-1.10.0注意
安装
pandas模块也自动安装了numpy模块。实际上,我之前已经在我的机器上安装了这些模块;因此,许多这些模块都是从缓存中获取的。在您的机器上,安装日志可能会有所不同。 -
接下来,让我们安装
matplotlib和seaborn;这些库将用于我们的可视化。以下是我机器上的安装日志,首先是matplotlib:(analyze)chetans-MacBookPro:ch11 Chetan$ pip install matplotlib Collecting matplotlib Requirement already satisfied (use --upgrade to upgrade): numpy>=1.6 in ./analyze/lib/python2.7/site-packages (from matplotlib) Requirement already satisfied (use --upgrade to upgrade): pytz in ./analyze/lib/python2.7/site-packages (from matplotlib) Requirement already satisfied (use --upgrade to upgrade): python-dateutil in ./analyze/lib/python2.7/site-packages (from matplotlib) Collecting cycler (from matplotlib) Using cached cycler-0.10.0-py2.py3-none-any.whl Collecting pyparsing!=2.0.0,!=2.0.4,!=2.1.2,>=1.5.6 (from matplotlib) Using cached pyparsing-2.1.10-py2.py3-none-any.whl Requirement already satisfied (use --upgrade to upgrade): six>=1.5 in ./analyze/lib/python2.7/site-packages (from python-dateutil->matplotlib) Installing collected packages: cycler, pyparsing, matplotlib Successfully installed cycler-0.10.0 matplotlib-1.5.3 pyparsing-2.1.10如您所见,这些模块已安装在我的机器上,因此您在第一次在自己的机器上安装这些模块时,安装日志可能会有所不同。以下是 seaborn 的日志:
(analyze)chetans-MacBookPro:ch11 Chetan$ pip install seaborn Collecting seaborn Collecting scipy (from seaborn) Requirement already satisfied (use --upgrade to upgrade): numpy>=1.7.1 in ./analyze/lib/python2.7/site-packages (from scipy->seaborn) Installing collected packages: scipy, seaborn Successfully installed scipy-0.18.1 seaborn-0.7.1
如何做到这一点...
-
首先,让我们从
support.spatialkey.com/spatialkey-sample-csv-data/下载 CSV 文件。TechCrunch 文件的直接下载链接是samplecsvs.s3.amazonaws.com/TechCrunchcontinentalUSA.csv。您可以使用以下wget命令下载此文件:(analyze)chetans-MacBookPro:ch11 Chetan$ wget http://samplecsvs.s3.amazonaws.com/ TechCrunchcontinentalUSA.csv --2016-11-20 16:01:57-- http://samplecsvs.s3.amazonaws.com/ TechCrunchcontinentalUSA.csv Resolving samplecsvs.s3.amazonaws.com... 54.231.97.224 Connecting to samplecsvs.s3.amazonaws.com |54.231.97.224|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 93536 (91K) [application/x-csv] Saving to: 'TechCrunchcontinentalUSA.csv' TechCrunchcontinentalUSA.csv 100% [======================================================= =========================================>] 91.34K 20.3KB/s in 4.5s 2016-11-20 16:02:03 (20.3 KB/s) - 'TechCrunchcontinentalUSA.csv' saved [93536/93536] -
现在,让我们开始编写我们的第一段 Python 代码来读取 CSV 文件。我们读取 CSV 文件并打印前五行:
import pandas as pd pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) df = pd.read_csv('TechCrunchcontinentalUSA.csv') print "First five rows:\n", df[:5]在前面的代码示例中,我们读取了 CSV 文件的前五条记录:
![如何做到这一点...]()
-
pandas模块读取文件内容并将其转换为行和列的数据框。现在,如果您查看前面代码的输出,您会注意到文件内容中添加了一个索引列。使用pandas,很容易解析日期,判断我们 CSV 文件中的日期是先月后日(英国或美国格式),并将日期列设置为索引列:import pandas as pd pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) df = pd.read_csv('TechCrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True,) print "Top five rows:\n", df[:5]如果你运行前面的代码片段,你应该能够看到索引列 fundedDate,如下面的屏幕截图所示:
![如何做...]()
-
真不错!现在,我们能够读取数据了,但如何选择一些数据以便我们可以在其上进行一些分析呢。让我们选择描述公司筹集资金金额的列(raisedAmt 列):
import pandas as pd pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) df = pd.read_csv('TechCrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True,) raised = df['raisedAmt'][:5] print "Funding Raised by Companies over time:\n", raised注意,在下面的屏幕截图中,我们打印了获得资助的公司的前五条记录:
![如何做...]()
-
好的,酷!所以我们可以选择我们想要的列并获取我们想要分析的数据。让我们看看我们是否可以为它生成一些漂亮的可视化。下面的方法基于筹集的金额(y 轴)为所有年份报告的融资轮次生成一个折线图(x 轴):
import pandas as pd from matplotlib import pyplot as plt import seaborn as sns plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) df = pd.read_csv('TechCrunchcontinentalUSA.csv') print "First five rows:\n", df[:5] df = pd.read_csv('TechCrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True,) print "Top five rows:\n", df[:5] raised = df['raisedAmt'][:5] print "Funding Raised by Companies over time:\n", raised sns.set_style("darkgrid") sns_plot = df['raisedAmt'].plot() plt.ylabel("Amount Raised in USD"); plt.xlabel("Funding Year") plt.savefig('amountRaisedOverTime.pdf')在下面的屏幕截图中,看看资助率(或报告率)是如何增加的,随之,筹集的资金也稳步增加!
![如何做...]()
-
太棒了!我知道你已经开始喜欢我们在这里做的事情了。让我们继续前进,看看我们是否可以从 CSV 文件中选择多个列。在下面的示例中,我们获取了 50 行数据,列名为 company、category 和 fundedDate:
import pandas as pd from matplotlib import pyplot as plt plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) fundings = pd.read_csv('TechcrunchcontinentalUSA.csv') print "Type of funding:\n", fundings[:5]['round'] # Selecting multiple columns print "Selected company, category and date of funding:\n",\ fundings[['company', 'category', 'fundedDate']][600:650]前一个代码片段的输出如下:
![如何做...]()
-
好的,太好了!现在让我们选择这些列中的一个,并在此基础上进行一些分析。在下面的代码示例中,我们选择了 category 列,它给出了所有报告的融资轮次的类别。然后我们处理所选列以获取获得资助的公司最常见的类别:
import pandas as pd from matplotlib import pyplot as plt plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) fundings = pd.read_csv('TechcrunchcontinentalUSA.csv') print "Type of funding:\n", fundings[:5]['round'] # Selecting multiple columns print "Selected company, category and date of funding:\n",\ fundings[['company', 'category', 'fundedDate']][600:650] # Most common category of company that got funded counts = fundings['category'].value_counts() print "Count of common categories of company that raised funds:\n", \ counts前一个代码片段的输出是:
Count of common categories of company that raised funds: web 1208 software 102 mobile 48 hardware 39 other 16 cleantech 14 consulting 5 biotech 4 Name: category, dtype: int64 -
数据和数字提供了很多信息,但实际的影响实际上只能通过可视化来看到。让我们看看我们是否可以将上述数据绘制为水平条形图。下面的方法为我们完成了这项工作:
import pandas as pd from matplotlib import pyplot as plt plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) fundings = pd.read_csv('TechcrunchcontinentalUSA.csv') print "Type of funding:\n", fundings[:5]['round'] # Selecting multiple columns print "Selected company, category and date of funding:\n",\ fundings[['company', 'category', 'fundedDate']][600:650] # Most common category of company that got funded counts = fundings['category'].value_counts() print "Count of common categoris of company that raised funds:\n", \ counts counts.plot(kind='barh') plt.xlabel("Count of categories") plt.savefig('categoriesFunded.pdf')在 y 轴上,我们有获得资助的公司的类别,而 x 轴是给定类别中公司的总数。此外,我们将绘制的图表保存为名为
categoriesFunded.pdf的 PDF 文件:![如何做...]()
哇!这么多网络公司获得了资助?太棒了!我也应该开始创办一家网络公司——这增加了获得资助的机会。
它是如何工作的...
在本节中,我们处理了数据分析的两个主要方面。首先,我们介绍了如何从 CSV 文件中读取数据集并从我们的数据集中选择适当的数据(行或列)。
在第一个代码片段中,我们使用 pandas 模块的帮助来读取 CSV 文件。在 pandas 中,我们有 read_csv(csv_file) 方法,它接受 CSV 文件路径作为参数。pandas 模块读取文件并将文件内容存储为数据框。数据框是一个二维、内存中标记的数据结构,具有可能不同类型的列。它模仿了电子表格、SQL 表或系列对象字典的结构。它有一套很好的方法和属性来选择、索引和过滤数据。在我们的第一个食谱中,我们读取了 CSV 文件并生成了一个 DataFrame 对象 df。使用 df 我们选择了 CSV 文件的头五行,使用 df[:5]。看看使用 pandas 选择 CSV 文件行是多么简单。
我们可以使用 read_csv() 方法做更多的事情。默认情况下,pandas 会为我们的数据集添加另一个索引列,但我们可以指定 CSV 文件中的哪一列应该用于索引我们的数据。我们通过将 index_col 参数传递给 read_csv() 方法实现了这一点。我们还使用 parse_dates 参数将 CSV 文件中 fundedDate 列的字符串日期转换为 datetime 格式,并通过 dayfirst 参数指定日期格式,其中日期的第一部分是日期。
在获取 DataFrame 并使用 fundedDate 作为索引后,我们使用 df['raisedAmt'][:5] 来选择 raisedAmt 列并打印前五行。然后我们使用 seaborn 库通过 sns.set_style("darkgrid") 设置了我们的绘图风格,并使用 plot() 方法生成了条形图。seaborn 库用于生成漂亮的可视化效果,并基于 matplotlib 实现。
使用 matplotlib 库,我们创建了一个对象 plt,然后使用 ylabel() 和 xlabel() 方法来标记我们的图表。plt 对象还用于最终使用 savefig() 方法将生成的图表保存为 PDF 格式。
在第二个示例中,我们使用 fundings[['company', 'category' and 'fundedDate']] 选择了多个列。我们一行代码中从 CSV 文件中选择了三个列。然后我们使用 plot() 方法绘制了一个水平条形图,并使用 kind=barh 指定了图表类型。最后,我们使用 xlabel() 方法通过 matplotlib 库标记了 x 轴,并使用 savefig() 方法保存了图表。正如你所看到的,我们不需要使用 seaborn 库来绘制图表;我们可以简单地使用 matplotlib 来完成。
使用数据过滤和聚合生成洞察
使用 pandas 读取 CSV 文件并选择多个列非常简单。在本节中,我们将探讨如何切片和切块数据,本质上就是使用 pandas 过滤数据。
准备工作
在本节中,我们将使用与之前食谱中相同的库集合(以下列出的库):
-
pandas用于过滤和分析数据 -
使用
matplotlib和seaborn来绘制图表并将数据保存到 PDF 文件中
如何操作...
-
让我们从导入所需的库和读取 CSV 文件使用
read_csv()方法开始。以下代码执行了这些操作:import pandas as pd from matplotlib import pyplot as plt import seaborn as sns plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) fundings = pd.read_csv( 'TechcrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True) -
现在,让我们在数据框上应用筛选,并使用多个列来筛选数据。比如说,我们根据融资类别、州和州内选定的城市来筛选融资记录。这可以通过以下代码片段实现:
import pandas as pd from matplotlib import pyplot as plt import seaborn as sns plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) funding = pd.read_csv( 'TechcrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True) #Web fundings in CA web_funding = funding['category'] == 'web' in_CA = funding['state'] == 'CA' in_city = funding['city'].isin(['Palo Alto', 'San Francisco', 'San Mateo', 'Los Angeles', 'Redwood City'])上述代码返回了加州(CA)州帕洛阿尔托、旧金山、圣马特奥、洛杉矶和雷德伍德城的所有网络公司的所有融资记录。以下是一个输出截图的部分:
![如何操作...]()
-
好极了,现在让我们看看我们是否可以通过城市名称来获取网络类别公司的融资次数。以下代码将为我们提供所需详细信息:
web_funding = funding[web_funding & in_CA & in_city] web_counts = web_funding['city'].value_counts() print "Funding rounds for companies in 'web' category by cities in CA:\n", web_counts上述代码的输出是选定城市中网络类别公司收到的融资轮次数量:
![如何操作...]()
哇!前面的分析非常有用;你了解到旧金山的网络公司已经收到了 195 次(根据我们的数据)的融资。看起来如果你是旧金山的网络公司,你所有的融资担忧都已经结束了。嗯,这听起来合乎逻辑且简单。
-
但是等等,这个信息不是不完整吗?我们为什么不收集所有类别(包括网络)公司的数据,然后以所有类别的百分比来表示网络类别中的公司数据呢?这样我们就可以知道你是否应该将你的公司在“旧金山”或其他任何城市。好的,那么让我们统计一下 CA 州所有类别(包括网络)中所有选定城市的融资轮次。以下代码将为我们提供所需的信息:
total_funding = funding[in_CA & in_city] total_counts = total_funding['city'].value_counts() print "Funding rounds for companies in 'all' categories by cities in CA:\n",total_counts以下是上述代码片段的输出:
![如何操作...]()
-
太好了!现在,让我们获取 CA 州选定城市中网络类别公司占所有类别公司的百分比数据。我们可以通过简单地将网络类别的数据除以所有类别并乘以 100 来表示为百分比。以下代码片段将帮助我们完成这项工作:
sns.set_style("darkgrid") sns_plot = (web_counts*100/total_counts.astype( float)).plot(kind='barh') -
现在,让我们用以下代码将此数据绘制成水平条形图:
plt.xlabel("(Funding Rounds in Web Category) / ( Funding Rounds in All Categories) * (100)") plt.savefig('webFundedByCity.pdf')以下截图帮助我们比较了网络公司与所有其他类别公司和加州各城市融资轮次的情况:
![如何操作...]()
分析之后,你有什么发现?你仍然想在旧金山设立公司吗?如果你是洛杉矶的互联网公司,尽管资金轮次有限,但你获得资金的机会(0.925)比在旧金山(0.855)要高,至少从我们的数据点来看。
现在,让我们看看另一个例子。假设我们想分析我们的数据,看看哪些月份历史上比其他月份筹集的资金更多。此外,我们还能否将此与资金轮次(如 A 轮或天使投资)联系起来?这是一个有趣的思考!但我们如何做到这一点?pandas模块支持对数据进行分组和聚合,这将帮助我们进行这项分析。让我们一步一步解决这个问题:
-
首先,让我们读取 CSV 文件并选择两列:筹集金额和轮次。我们还将添加另一个列,
month作为数据框的索引列。以下代码为后续分析准备好了数据框:import pandas as pd from matplotlib import pyplot as plt import seaborn as sns plt.style.use('default') pd.set_option('display.line_width', 5000) pd.set_option('display.max_columns', 60) df = pd.read_csv('TechCrunchcontinentalUSA.csv', index_col='fundedDate', \ parse_dates=['fundedDate'], dayfirst=True,) funds = df[['raisedAmt', 'round']] funds['month'] = funds.index.month print "Funding Rounds with Month Index:\n", funds -
现在我们需要根据月份获取筹集资金的数据。以下代码正好做了我们需要的:
funding_by_month = funds.groupby('month').aggregate('sum') funding_by_month.index = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', \ 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'] print "Funding Rounds Grouped By Month:\n", funding_by_month -
现在,如果我们绘制用于此分析的数据,我们将看到我们拥有的所有年份数据中,资金在每月是如何波动的:
![如何做...]()
很酷,看起来一月份申请资金会更好。也许,投资者在圣诞节和新年假期之后心情不错;你怎么看?
-
现在,如果你想分析和建立年度月份、筹集金额和资金轮次之间的相关性,我们可以用以下代码片段获取数据:
funds['month'] = funds.index.month funding_by_stage = funds.groupby(['month', 'round']).aggregate('sum') print funding_by_stage前一段代码片段的输出是一个按照以下截图所示的数据框。数据按照资金月份和轮次列进行分组,并且筹集金额相应地汇总:
![如何做...]()
它是如何工作的...
对于第一个问题,我们使用read_csv()方法将数据从 CSV 文件加载为数据框。然后,我们根据多个因素过滤数据,其中州是CA,公司类别是web,城市是Palo Alto、San Francisco、San Mateo、Los Angeles和Redwood City。过滤是一个基于列的操作,相当直接;它应用了标准后,会得到相关的数据框。
然后,我们使用value_counts()方法按城市计算了网络公司资金轮次的数量。我们也对所有类别公司的资金轮次进行了同样的练习,包括网络。
最后,我们简单地划分了数据,并得到了作为所有类别数据百分比的网络公司数据。pandas模块为我们无缝地处理了这个操作。它使用了相同城市的分析数据点,甚至我们都不用担心这一点。
最后,我们使用plot()方法绘制了水平条形图,分别描绘了每个城市的百分比,并获得了我们寻求的洞察。
在第二个示例中,我们首先通过选择多个列:筹集金额和轮次来获取数据框。我们还向月份数据框添加了一个新列,并将其作为索引列。
然后,我们借助groupby()方法根据月份对数据进行分组。然后我们汇总了资金金额,以获取基于月份筹集的资金总额。为了获取总资金,我们使用了聚合()方法并将数据点添加到所需信息中。
此外,为了建立关于月份和轮次筹集资金的关联,我们将数据框按月份和轮次分组,然后再次对筹集金额进行了聚合。
还有更多...
在前面的两个示例中,我们学习了数据分析与可视化,并在我们的示例中广泛使用了pandas Python 模块。pandas模块是一个非常全面的库,具有处理时间序列、高级索引技术、合并和连接对象以及处理不同数据集(如 JSON 和 Excel)等功能。我强烈建议您查阅pandas API 参考,以了解更多关于这个令人惊叹的库的信息。
在下一个示例中,让我们看看我们是否可以应用本章到目前为止所获得的知识,通过帮助 Judy 自动化她的任务。
自动化企业级社交媒体分析
Judy 是伦敦一家领先杂志的专栏作家。作为一名作家,她总是对时事感兴趣。她收集数据并分析它,以产生对读者有吸引力的见解。
目前,Judy 对苹果 iPhone 和三星 Note 之间的竞争感兴趣,并计划在她的杂志上发表文章。她计划通过在街头与人交谈和阅读博客文章来收集数据,但她知道她将从社交媒体中获得大量的信息。她意识到,如今人们倾向于在 Twitter 上表达他们对产品使用时的喜悦或失望,并在社交媒体上向朋友推荐产品。然而,她担心她必须处理如此大量的社交媒体数据来撰写她的文章。
你是一位数据科学家,也是 Judy 的同事。你能帮助 Judy 满足她的需求吗?这是一个展示你数据技能的机会!
让我们从分析 Judy 的问题开始。首先,Judy 需要从不断增长的社会媒体平台,如 Twitter 收集数据。其次,她需要分析这些数据以生成有趣的见解。因此,我们应该能够构建一个满足她这两个问题的系统。此外,你可能想构建一个只解决她当前需求的系统,但她应该能够将其用于未来的任何项目。
准备工作
让我们安装所有我们将需要用于解决这个问题的模块。我们已经有pandas、matplotlib和seaborn安装好了。对于这个问题,我们还将安装tweepy,这是一个用于处理 Twitter 数据的模块。让我们使用我们自己的python-pip来安装 tweepy:
(analyze)chetans-MacBookPro:ch11 Chetan$ pip install tweepy
You are using pip version 7.1.0, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting tweepy
Downloading tweepy-3.5.0-py2.py3-none-any.whl
Collecting requests>=2.4.3 (from tweepy)
Downloading requests-2.12.1-py2.py3-none-any.whl (574kB)
100% |████████████████████████████████| 577kB 161kB/s
Requirement already satisfied (use --upgrade to upgrade): six>=1.7.3 in ./analyze/lib/python2.7/site-packages (from tweepy)
Collecting requests-oauthlib>=0.4.1 (from tweepy)
Downloading requests_oauthlib-0.7.0-py2.py3-none-any.whl
Collecting oauthlib>=0.6.2 (from requests-oauthlib>=0.4.1->tweepy)
Downloading oauthlib-2.0.0.tar.gz (122kB)
100% |████████████████████████████████| 122kB 345kB/s
Building wheels for collected packages: oauthlib
Running setup.py bdist_wheel for oauthlib
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/e4/e1/92/68af4b20ac26182fbd623647af92118fc4cdbdb2c613030a67
Successfully built oauthlib
Installing collected packages: requests, oauthlib, requests-oauthlib, tweepy
Successfully installed oauthlib-2.0.0 requests-2.12.1 requests-oauthlib-0.7.0 tweepy-3.5.0
如何操作...
好的,现在我们已经有了所有模块。那么,让我们开始从 Twitter 收集数据吧。Twitter 有一个惊人的集合,即流式 API,它帮助开发者实时收集推文。我们也将使用这个库来满足我们的数据收集需求。
-
以下代码使用 Twitter 流式 API 收集数据,并将每条推文以 JSON 格式存储在文本文件中。我们寻找包含两个关键词的推文,即
iPhone 7和Note 5。对于这一章,我运行了大约 5 分钟,但对于 Judy 来说,可能需要运行数小时甚至数天来收集尽可能多的数据以生成准确的见解:from tweepy import Stream from tweepy import OAuthHandler from tweepy.streaming import StreamListener import json #consumer key, consumer secret, access token, access secret. ckey="<>" csecret="<>" atoken="<>" asecret="<>" tweets_data_path = 'twitter_data.txt' f = open(tweets_data_path, "w") class listener(StreamListener): def on_data(self, data): print data f.write(data) #all_data = json.loads(data) #tweet = all_data["text"] #lang = all_data["lang"] #username = all_data["user"]["screen_name"] #print "username:%s, tweet:%s, language:%s" %(username, tweet, lang) return True def on_error(self, status): print "Error:", status auth = OAuthHandler(ckey, csecret) auth.set_access_token(atoken, asecret) twitterStream = Stream(auth, listener()) twitterStream.filter(track=["iPhone 7","Note 5"]) f.close()好的,现在我们已经从 Twitter 收集到了数据流,让我们编写代码来分析这些数据,看看我们是否能找到一些有趣的内容可以与 Judy 分享,用于她的文章。
-
苹果 iPhone 和三星 Note 是全球范围内如此受欢迎的产品,以至于人们从世界各地都在谈论这些产品。找到消费者在 Twitter 上讨论这些产品所使用的不同语言将非常有趣。以下代码正是我们想要用 Twitter 数据做的。它遍历存储的推文,确定所有推文的语言,并将它们分组以绘制出前四种语言:
import json import pandas as pd import matplotlib.pyplot as plt import seaborn as sns tweets = [] fh = open("twitter_data.txt", "r") for data in fh: try: tweets.append(json.loads(data)) except: continue tweet_df = pd.DataFrame() tweet_df['lang'] = map(lambda x: x['lang'], tweets) tweets_by_lang = tweet_df['lang'].value_counts() fig, axis = plt.subplots() sns.set_style("darkgrid") axis.set_xlabel('Languages', fontsize=15) axis.set_ylabel('Tweets' , fontsize=15) clrs = ['green', 'blue', 'red', 'black'] sns_plot = tweets_by_lang[:4].plot(ax=axis, kind='bar', color=clrs) plt.savefig('language.pdf')如果我们运行前面的代码片段,它将绘制出人们用于讨论 iPhone 7 和 Note 5 的顶级语言的柱状图。
![如何操作...]()
太棒了!我想 Judy 会喜欢这个分析的。尽管预期中最常用的语言是英语(en),但看到其他三种语言是意大利语(it)、西班牙语(es)和葡萄牙语(pt)也非常有趣。这将是一个很酷的素材供她的文章使用。
你从这个练习中获得的结果将取决于你运行数据收集程序的时间。例如,如果你在格林威治标准时间凌晨 2 点到 8 点之间运行它,你将看到更多来自中国或日本的推文,因为这些国家是白天。顺便说一句,分析人们发推文的最佳时间是否有趣?你可能会发现一些相关性。
-
让我们更进一步,用这些数据做一些更酷的事情。我们是否可以对推文进行基于文本的分析,以获取消费者对这些产品的情感?这里的情感是指,推文是否在诅咒这些产品,赞赏产品的某个特性,或者只是随意的评论?但是等等;我们能否获取这类数据?是的,绝对可以。以下代码使用基于 Python 的 NTLK API 对推文(文本)进行情感分析,以确定其极性:正面、负面或中性情感。然后,它将数据分组以用条形图表示,并将其保存为 PDF 文件:
import json import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import requests tweets = [] fh = open("twitter_data.txt", "r") for data in fh: try: tweets.append(json.loads(data)) except: continue probablities = pd.DataFrame() prob = [] for tweet in tweets: text = tweet['text'] r = requests.post(url="http://text-processing.com/api/sentiment/", data={"text":text},) print r.text if r.status_code == 200: ans = json.loads(r.text) prob.append(ans["label"]) probablities['data'] = map(lambda x: x, prob) p_df = probablities['data'].value_counts() fig, axis = plt.subplots() sns.set_style("darkgrid") axis.set_xlabel('Sentiments', fontsize=15) axis.set_ylabel('Tweets' , fontsize=15) clrs = ['green', 'yellow', 'red'] sns_plot = p_df.plot(ax=axis, kind='bar', color=clrs) plt.savefig('sentiments.pdf')如果你运行前面的代码块,你将得到一个包含所有存储推文的情感数据的条形图。只需看一眼图表,你就能知道消费者普遍对这两个产品持正面评价:
![如何做...]()
关于它们有一些投诉,因此有负面极性的情感,但也有一些中性的评论,可能是产品推荐或对产品的评论。真不错!
虽然这很好,但你认为 Judy 可能想知道有多少负面推文是关于 iPhone 或 Note 的吗?我会把这个留给你;我相信你一定会为 Judy 解决这个问题。
它是如何工作的...
对于第一个问题,我们首先收集了分析所需的数据。Twitter 的流式 API 帮助我们实时收集这些信息。要使用流式 API,你需要注册一个 Twitter 开发者应用并收集你的消费者密钥、消费者密钥、认证令牌和认证密钥。这是一个简单的过程,可以在dev.twitter.com上轻松查找。因此,在数据收集示例(这个菜谱中的第一个示例)中,我们实例化了OAuthHandler类以获取 Twitter 的授权对象auth,然后使用它通过set_access_token()方法设置授权令牌和密钥。Twitter 流式 API 的Stream类绑定到listener类,并返回twitterStream对象。listener类继承自StreamListener类,它将在on_data()方法中监控传入的推文并对到达的推文采取行动。推文的过滤是通过twitterStream.filter()方法完成的。
现在,我们知道传入的推文在on_data()方法中可用;我们将其挂钩以将推文存储在twitter_data.txt文件中。为此,我们以写入(w)模式打开文件,并使用write()方法将推文以 JSON 格式写入文件。这样,我们就完成了第一个菜谱,并收集了 Judy 所需的数据。现在,是时候进行分析了。
对于获取语言的第一个洞察,我们首先以读取(r)模式打开了twitter_data.txt文件。我们阅读了所有的推文(JSON 格式)并将它们追加到tweets数组中。使用pandas,我们创建了一个空的 DataFrame 对象tweet_df,使用pd.DataFrame()。通过 Python 的map()方法,我们对tweets数组进行了操作,并在我们的空 DataFrame 中添加了一个新的列lang。然后使用value_counts()方法获取分析推文下所有语言的计数,并存储在变量tweets_by_lang中。
代码的其他部分与往常一样,我们创建了一个plt对象,并使用plot()方法通过seaborn库生成条形图。我们使用set_xlabel()和set_ylabel()方法设置轴标签,并使用颜色green、blue、red和black来表示不同的语言。最后,我们使用savefig()方法将图表保存为 PDF 文件。
对于涉及情感分析的第二个洞察,我们首先阅读了twitter_data.txt中的所有推文,并将它们存储到tweets数组中。然后我们创建了一个空的数据框,probabilities,对所有的推文进行了情感分析,并将分析结果存储在prob数组中。接着,我们使用prob数组上的map()方法,为我们的空数据框添加了一个名为text的列。
我们的数据框probabilities['text']现在包含了所有我们分析的推文的情感。按照常规的操作步骤,我们得到了分析推文的positive、negative和neutral情感值的集合,并将它们绘制为条形图。
如果你查看这个食谱中的所有示例,我们将数据收集和分析的任务分为单独的程序。如果我们的可视化很大,我们甚至可以将它们分开。这确保了朱迪可以使用数据收集 Python 程序在未来为她文章的关键词集合收集信息。
她还可以通过修改我们的程序中的分析和可视化参数,对她的数据集进行分析。因此,对于她未来的所有文章,我们已经为她自动化了数据收集、分析和可视化过程。
我已经看到朱迪脸上露出了笑容。我希望你喜欢这一章。我坚信你在这一章中获得的知识将帮助你进入数据和可视化的世界。
还有更多...
在前面的食谱中,我们只是触及了基于文本的情感分析表面。情感分析涉及文本分类、分词、语义推理以及更多有趣的内容。我强烈建议你阅读一本关于 NLTK 的书,以了解更多关于在 Python 中使用文本的知识。www.nltk.org/book/。
第十一章:时间在时区中
时间计算既有趣又有趣味性,同时也令人厌烦。当你第一次学会读时间时很有趣,当你了解夏令时时很有趣,当客户抱怨无法通过你的 Web 应用程序安排跨时区的会议时就很烦人。
在本章中,我们将涵盖以下食谱:
-
处理时间、日期和日历
-
比较和组合日期,以及日期算术
-
日期的格式化和解析
-
处理时区计算
-
基于时区自动处理发票
简介
"如果你热爱生命,就不要浪费时间,因为生命就是由时间构成的。" —— 李小龙*
时间是衡量一切的标准。我们生活中有很多事情要做,然而讽刺的是,我们手头的时间却如此之少。如今,我们直观地规划时间:我应该什么时间出行以避开交通,这项任务的截止日期是什么,以及其他许多事情。企业甚至在日历开始之前就计划了全年的活动。
时间计算无处不在。你想和你的澳大利亚同事安排会议吗?确保时区正确,找到一个对你和你的同事都合适的时间,然后安排它。你想编写代码在正确的时间为客户执行任务吗?管理数据库中的时间对象并跟踪所有用户的任务。即使在好莱坞电影《国家宝藏》中,尼古拉斯·凯奇也必须依赖时区计算来找到让他更接近宝藏的下一个线索。
实际上,无论你在哪里做什么,都无法逃避时间计算。在本章中,我们将使用 Python 中的date和time对象。我们还将学习如何在日期上执行算术运算以及处理时区计算。我们还将学习如何根据用户的时区自动处理业务流程。
在本章的整个过程中,我们将主要使用内置的 Python 模块。本章将使用以下内置模块:
-
datetime(docs.python.org/2/library/datetime.html) -
calendar(docs.python.org/2/library/calendar.html)
我们还将使用这个外部模块来处理时区:
pytz(pytz.sourceforge.net/)
pytz 库将 Olson 时区数据库引入 Python。它允许在 Python 2 中进行准确和跨平台的时区计算。它还有助于进行夏令时的计算。
在我们进入食谱之前,让我们检查一下我们的 Python 安装中是否有相关的模块,以及在本章中安装我们需要的模块。我们首先为这一章创建一个虚拟环境并激活它:
chetans-MacBookPro:ch12 Chetan$ virtualenv date
New python executable in date/bin/python2.7
Also creating executable in date/bin/python
Installing setuptools, pip, wheel...done.
chetans-MacBookPro:ch12 Chetan$ source date/bin/activate
(date)chetans-MacBookPro:ch12 Chetan$
现在,让我们使用 Python 的 pip 在我们的虚拟环境中安装 pytz 模块。一旦我们安装了模块,我们将继续进行第一个菜谱,并开始处理 time 和 date 对象:
(date)chetans-MacBookPro:ch12 Chetan$ pip install pytz
Collecting pytz
Using cached pytz-2016.7-py2.py3-none-any.whl
Installing collected packages: pytz
Successfully installed pytz-2016.7
处理时间、日期和日历
在这个菜谱中,我们将使用内置的 Python 模块,我们不需要明确安装任何东西。所以让我们开始吧。
如何做...
-
前往您最喜欢的编辑器,创建一个名为
time_ex.py的文件,并在 Python 文件中编写以下代码:import datetime time_obj = datetime.time(13, 2, 23) print "Time object is:", time_obj print 'Hour :', time_obj.hour print 'Minute:', time_obj.minute print 'Second:', time_obj.second print 'Microsecond:', time_obj.microsecond如果我们运行前面的 Python 代码,我们将看到以下输出。观察我们是如何使用 Python 创建给定的时间对象,并检索给定时间对象的 小时、分钟、秒 和 微秒 详细信息:
![如何做...]()
-
Python 的
Time类有一些可以用于时间计算的属性。例如,在以下代码片段中,我可以获取给定一天的有效时间范围:import datetime print "Time Attributes are:" print "Earliest time of the day :", datetime.time.min print "Latest time of the day :", datetime.time.max前一个代码片段的输出可以在以下截图中看到。观察我们是如何获取一天中最早和最晚可用时间:
![如何做...]()
-
棒极了!现在,让我们看看
date对象。以下 Python 代码为您获取今天的日期。我们还可以使用以下代码检索year、month和day属性:import datetime today = datetime.date.today() print 'Date object:', today print 'Year:', today.year print 'Mon :', today.month print 'Day :', today.day前一个代码片段的输出显示在以下截图:
![如何做...]()
-
好的,酷!我们还可以使用 Python 的
date()和replace()方法创建一个新的date对象或修改现有的一个。以下代码演示了如何使用这些方法:import datetime date_1 = datetime.date(2011, 12, 31) print ' Date is:', date_1 date_2 = date_1.replace(year=2012, month=1) print ' New Date is:', date_2前一个代码片段的输出如下:
![如何做...]()
-
太棒了!让我们继续前进,看看我们是否可以处理月份甚至年份。使用 Python,处理整个日历非常容易。以下代码在控制台上打印出整年的日历。在这种情况下,它返回 2017 年的日历。让我检查一下我的生日...哦,今年我的生日在星期二,我必须去办公室:
import calendar from calendar import TextCalendar cal = TextCalendar() cal.pryear(2017) cal.prmonth(2017, 11)前一个代码片段的输出显示在以下截图。第一个截图返回了 2017 年的完整日历:
![如何做...]()
以下截图仅返回 2017 年的第 11 个月份的日历,即 2017 年 11 月:
![如何做...]()
工作原理...
在这个菜谱中,我们开始处理 time 对象。我们使用 datetime.time() 方法创建了一个 time 对象,该方法接受小时、分钟和秒作为输入参数。我们还通过 hour、minute 和 second 属性读取时间对象,并使用 datetime.time.min 和 datetime.time.max 属性获取一天中的最早和最晚时间。
接下来,我们使用 datetime.date() 方法开始处理日期。我们使用 datetime.date() 方法获取今天的日期,并使用 today.year、today.month 和 today.day 属性打印今天的年、月和日。
我们还使用 datetime.date() 方法通过传递 year、month 和 day 作为参数创建了一个新的日期。一旦有了 date 对象,我们就使用 replace() 方法来获取新的日期。我们将 year 和 month 作为参数传递给 replace() 方法来创建新的 date 对象。
在本节中,我们还处理了 calendar 对象。我们使用 Python 安装中可用的 calendar 模块来完成此目的。首先,我们实例化了 TextCalendar 类来创建一个文本日历对象。然后,我们使用这个对象通过 pryear() 方法在控制台上打印 2017 年的日历。
我们还可以使用 prmonth() 方法仅显示 2017 年 11 月的日历。太棒了!
比较和组合日期和时间对象,以及日期算术
创建 date 对象并使用它们是很好的,但我们需要执行的业务用例中的任务通常与比较或计算日期和时间对象的差异有关。在这个菜谱中,我们将学习如何在 Python 中执行这些操作。然而,需要注意的是,在这个菜谱中我们将看到一个主要的变化。在上一个菜谱中,我们独立地处理时间和日期对象。但 Python 的 datetime 模块提供了很大的便利,从某种意义上说,我们可以处理包含日期和时间属性的多个对象。你将在“如何做”部分看到这个差异。
准备工作
在这个菜谱中,我们将使用 datetime Python 模块,这是我们之前几个示例中使用的。对于这个菜谱,我们不需要进行任何新的安装。
如何做...
-
让我们先获取两个 datetime 对象之间的差异。以下代码执行此操作并计算两个
datetime对象之间的差异。尽管这个操作只告诉你秒数的差异,但你也可以使用这些来获取月份或年份的差异。在下面的屏幕截图中,注意datetime.now()返回一个包含今天日期和当前时间的字符串。重要的是要理解我们正在处理一个包含日期和时间属性的多个对象。如果你这么想,在现实世界中,当我们需要计算两个事件之间的时间差异时,同时处理日期和时间对象将对我们最有用。即使我们独立地处理日期对象或时间对象,我们最终也会执行与使用 datetime 对象相同的计算,所以想象一下这种方法带来的好处:from datetime import datetime import time now_1 = datetime.now() print " Time Now", now_1 time.sleep(5) now_2 = datetime.now() print " Time Now", now_2 print " Difference in the times is:", (now_2 - now_1).seconds前一个代码片段的输出显示在下面的屏幕截图中。看看我们是如何获取两个 datetime 对象之间的秒数的差异:
![如何做...]()
-
这很好,但你可能会问,如果
datetime对象之间的差异是负数会发生什么。在这个例子中,如果我们计算now_1 - now_2,我们会得到一个很大的数字,而不是实际的差异。为此,我们有一个很好的技巧来获取两个datetime对象之间的差异。我们可以使用(now_1 - now_2).total_seconds()来获取负值,即-5秒。 -
好的,现在让我们继续对
datetime对象进行更多计算。例如,从过去或未来获取时间怎么样?以下代码帮助我们执行这些操作;看看我们是如何获取当前日期和时间的,以及如何返回明天的日期和时间:from datetime import datetime, timedelta now = datetime.now() print " Time Now is:", now one_day_later = now + timedelta(days=1) print " Tomorrow is:", one_day_later上一段代码的输出如下:
![如何做...]()
-
如果我们想要获取过去的时间,我们可以用以下代码片段中的方式来做:
from datetime import datetime, timedelta now = datetime.now() print " Time Now is:", now days_in_past = now - timedelta(days=365, hours=1) print " Last year:", days_in_past上一段代码的输出在以下屏幕截图中展示。注意,我们请求的是一个过去的日期,即 365 天前。然而,它显示的是 2015 年 11 月 28 日。为什么?难道不应该显示同一天吗?哦,当然,2016 年是闰年!
![如何做...]()
-
好的,现在我们熟悉了如何获取
date和time对象之间的差异或添加时间。但我们也经常需要比较时间,对吧?让我们通过一个代码片段来学习这一点。以下 Python 代码分别比较了time和date对象:import datetime time_1 = datetime.time(8, 9, 10) print " Time 1:", time_1 time_2 = datetime.time(13, 19, 50) print " Time 2:", time_2 print " Comparing times: time_2 > time_1?", time_2 > time_1 date_1 = datetime.date.today() print " Date 1:", date_1 date_2 = date_1 + datetime.timedelta(days=2) print " Date 2:", date_2 print " Comparing dates: date_1 > date_2?", date_1 > date_2上一段代码的输出可以在以下屏幕截图中看到:
![如何做...]()
-
正如我们在前面的菜谱中看到的,你也觉得需要将你的
time对象与date对象结合起来。例如,你为某个用例开发了你的程序,你想要比较time对象并采取一些行动。但你也可能最终需要进行日期比较,因为你要比较的time对象在不同的日期上。既然你已经知道如何轻松地处理datetime对象,你可能想要将你的time和date对象组合成一个单一的datetime对象,并轻松地处理它们。我们可以很容易地在 Python 中实现这一点;以下代码演示了如何将独立的时间和日期组合到datetime对象中:import datetime time_1 = datetime.time(13, 44, 55) time_2 = datetime.time(13, 44, 55) print " Times:", time_1, time_2 date_1 = datetime.date.today() date_2 = date_1 + datetime.timedelta(days=1) print " Dates:", date_1, date_2 datetime_1 = datetime.datetime.combine(date_1, time_1) datetime_2 = datetime.datetime.combine(date_2, time_2) print " Datetime Difference:", datetime_2 - datetime_1上一段代码的输出如下:
![如何做...]()
它是如何工作的...
在上一节中,我们独立地处理了time、date和calendar对象。在本菜谱中,我们开始处理完整的datetime对象。
在本菜谱的第一个代码示例中,我们计算了datetime对象之间的差异。我们可以用我们熟悉的相同减法(-)运算符轻松做到这一点。这意味着__sub__()方法已被datetime类覆盖。
然后,在第二个和第三个代码片段中,我们使用了timedelta()方法来获取未来的datetime对象或移动到过去。timedelta()方法支持方便命名的属性,如days或hours,可以将当前的datetime对象移动到过去或未来。我们使用- timedelta()到达过去,使用+ timedelta()操作前进。
接下来,我们了解了如何比较datetime对象。这同样像任何其他 Python 对象一样简单完成。在 Python 中,我们使用<和>运算符分别检查一个整数是否小于或大于另一个整数。对于datetime对象也是如此。我们只需使用这些运算符来比较即使是datetime对象。
最后,我们探讨了需要处理date和time对象以获取差异或比较它们的使用场景。为此,我们编写了 Python 代码来组合date和time对象,并使用了datetime.combine()方法。这确保了比较或差异操作可以轻松地在datetime对象上执行,而不是在单独的date或time对象上执行然后合并结果。
日期和时间的格式化与解析
在迄今为止的所有菜谱中,我们对date或time对象执行了多次操作。但对象本身是以特定格式表示的。例如,默认情况下,date()对象以 YYYY-MM-DD 格式表示,而time()对象以 HH:MM:SS 格式表示。虽然这些表示方式很好,但我们不能总是使用这些格式来向用户在网站上表示数据或从网页安排会议。
在本节中,我们快速查看date和time对象可以呈现给用户的不同格式。
准备工作
对于这个菜谱,我们最终使用了与默认 Python 安装捆绑的相同datetime模块。
如何实现...
-
让我们从我们已知的内容开始。以下 Python 代码将以 ISO 格式打印日期和时间。这种格式是全球最常用的格式,并且被普遍接受:
import datetime today = datetime.datetime.today() print " ISO datetime: ", today -
然而,正如你可能已经想象到的,这种格式并不太易读。例如,它以数字形式读取月份(11 代表 11 月)并返回时间,甚至精确到微秒(我认为这并不非常有用)。那么,有没有格式可以解决这些问题,使日期更易读呢?是的,我们可以通过以下代码片段轻松实现这一点。在这个代码中,借助某些格式说明符,例如
'%b',我们成功使月份变得可读:import datetime today = datetime.datetime.today() print " ISO datetime: ", today format = "%a %b %d %H:%M:%S %Y" string_format = today.strftime(format) print " Datetime in String format:", string_format -
你一定见过一些使用 Unix 时间戳或纪元的 Web 应用程序来存储时间。尽管这是一种存储对象的好方法,但你仍然需要以用户理解的方式表示实际的时间或日期。
-
Unix 时间,也称为 POSIX 时间或纪元时间,是一种描述时间的系统,定义为自 1970 年 1 月 1 日星期四 00:00:00 UTC 以来经过的秒数。Unix 时间戳很有用,因为它们代表与时区无关的时间。例如,Unix 时间可以表示伦敦的下午 1:00 和纽约的上午 8:00。
以下代码片段展示了如何将时间戳转换为
datetime对象,反之亦然:import datetime import time time_1 = time.time() print " Datetime from unix timestamp:", datetime.datetime.fromtimestamp(1284101485) date_1 = datetime.datetime(2012,4,1,0,0) print " Unix timestamp", date_1.strftime('%s') -
datetime对象的另一种有趣表示方式是显示从世界开始以来的第 n 天的日期。例如,你能打印出 0001 年 1 月 1 日之后的第 1000 天的日期吗?这是与儒略日历相对应的日期,其中 01/01/01 的序数为 1:import datetime date_1 = datetime.date.fromordinal(1000) print " 1000th day from 1 Jan 0001: ", date_1如果您运行前面的 Python 代码片段,您将能够看到如下截图中的所需对象:
![如何做到这一点...]()
它是如何工作的...
在这个菜谱中,我们探讨了表示 datetime 对象的各种方法。在第一个示例中,我们以 ISO 格式打印了日期和时间。这是最常用的格式,您可以在 en.wikipedia.org/wiki/ISO_8601 上了解更多关于 ISO 格式的信息。如您所见,我们不需要使用新方法来表示这一点;我们只是简单地使用 datetime.today() 来获取 ISO 格式的日期。
在第二个示例中,我们探讨了定义字符串格式中表示日期的自定义格式。我们使用了格式说明符,如 %a、%b 和 %d 来处理日期,以及 %H、%M 和 %S 来处理时间。我们在 format 变量中指定了格式,并使用它将其传递给 strftime() 方法,该方法将 ISO datetime 对象格式化为我们的自定义字符串格式。
下两个示例帮助我们将 Unix 时间戳或纪元转换为 datetime 对象,反之亦然。对于第一个用例,我们使用 datetime.fromtimestamp(<unixtimestamp>) 方法将 Unix 时间戳转换为 datetime 对象,在随后的示例中,我们使用 strftime() 方法将 datetime 对象转换为 Unix 时间戳。本例中使用的 Unix 时间(1284101485)是自 1970 年 1 月 1 日以来的秒数。
在最后一个有趣示例中,我们使用 fromordinal() 方法获取公历日历的日期和时间。您很可能不会使用此方法,但我将其包含在本章中,以便您了解一个有趣的日期格式。
处理时区计算
你在date或time对象上必须执行的最复杂的计算之一就是涉及时区的计算。你的同事在旧金山工作,而你则在悉尼,你打算如何安排电话会议?当你安排会议时,你应该意识到你同事的时区,否则你可能会为悉尼时间晚上 8 点安排会议,而对你同事在旧金山来说,这已经是午夜之后了。时区计算通常很繁琐,在开发商业应用程序时需要谨慎处理。让我们看看 Python 如何在这方面帮助我们。
准备工作
对于这个菜谱,我们将使用我们在本章开头安装的pytz模块。实际上,Python 标准库没有时区库,但我们可以完全依赖 Python 社区贡献的模块来满足我们的需求。
如何做到这一点...
你需要执行以下步骤:
-
让我们从获取 UTC 时间的简单操作开始。UTC代表通用时间转换器,是全球调节时钟和时间测量的标准。UTC 也普遍被称为格林尼治标准时间(GMT)。
from datetime import datetime, timedelta now = datetime.now() print " Local time now is:", now utcnow = datetime.utcnow() print " UTC time now is:", utcnow上一段代码片段的输出显示在下述屏幕截图中。看看我的本地时间比协调世界时(UTC)快 5 小时 30 分钟:
![如何做到这一点...]()
-
好的,这很好。所以,你可以将你的本地时间转换为 UTC,但这并不总是足够的。你的客户(为你的应用程序开发)可能来自世界各地。他们的账户也需要根据他们的时区和本地时间进行管理。让我们看看我们如何确定给定时区的用户本地时间:
from pytz import timezone import pytz utc = pytz.utc print " Selected time zone:", utc eastern = timezone('US/Eastern') print " Switched to time zone:", eastern loc_dt = datetime(2016, 11, 27, 12, 0, 0, tzinfo=pytz.utc) est = loc_dt.astimezone(eastern) fmt = '%Y-%m-%d %H:%M:%S %Z%z' print " Local time in Eastern time zone:", est.strftime(fmt)上一段代码的输出显示在下述屏幕截图中。观察我们是如何通过获取东部时区将本地 UTC 时间转换为东部标准时间(EST)的。实际上,UTC 是跨时区转换时间的最佳方式:
![如何做到这一点...]()
-
在 Python 中对带有时区信息的
datetime对象执行算术计算也是简单的。看看以下代码,看看我们是如何在date对象上执行算术运算的:from datetime import datetime, timedelta au_tz = timezone('Australia/Sydney') local = datetime(2002, 10, 27, 6, 0, 0, tzinfo=au_tz) print " Local time in Sydney:", local past = local - timedelta(minutes=10) print " 10 minutes before time was:", past future = local + timedelta(hours=18) print " 18 hours later it is:", future现在,如果我们在这个 Python 解释器上运行这段代码,我们会得到以下输出:
![如何做到这一点...]()
-
我们在讨论时区时,如果不真正谈论这一点,怎么能算完成呢?对吧?是的,我们如何在时区计算中处理夏令时?感谢本杰明·富兰克林为世界带来的夏令时礼物。让我们通过一个代码示例来理解这一点:
eastern = timezone('US/Eastern') dt = datetime(2016, 11, 06, 1, 30, 0) dt1 = eastern.localize(dt, is_dst=True) print " Date time 1 with day light savings:", dt1.strftime(fmt) dt2 = eastern.localize(dt, is_dst=False) print " Date time 2 without day light savings:", dt2.strftime(fmt)如果你运行代码片段,你会看到两个以字符串格式表示的
datetime对象。第一个考虑了夏令时,而第二个则不考虑。2016 年 11 月 6 日是今年东部时区结束夏令时,时钟往回拨:![如何做到这一点...]()
-
最后,
pytz模块中还有一些有用的辅助方法,例如,根据 ISO 国家代码获取给定国家的时区,或者简单地从 ISO 国家代码中获取国家名称。让我们看看以下示例:tz_au = '\n '.join(pytz.country_timezones['au']) print " Time zones in Australia:", tz_au country_gb, country_fr = pytz.country_names['gb'], pytz.country_names['fr'] print "\n Country names are:\n", " ", country_gb, "\n ", " ", country_gb, "\n ", country_fr上述代码片段的输出可以在以下屏幕截图中进行查看:
![如何做...]()
它是如何工作的...
在这个配方中,我们探讨了处理时区的各种方法,这对于日期时间计算至关重要。在这个配方的第一个代码示例中,我们使用 datetime.now() 计算当前本地时间,然后使用 datetime.utcnow() 获取相同的 UTC 本地时间。utcnow() 方法在需要将日期时间对象存储到数据库以进行进一步处理时(例如安排事件)变得非常有用。
接下来,我们探讨了如何切换到不同的时区并检索该时区的本地时间。pytz 类有一个简单的属性 utc,用于将时区设置为 UTC;我们使用它将当前时区设置为 UTC。稍后,我们使用 pytz 模块的 timezone() 方法通过 timezone('US/Eastern') 切换到东部时区。
在此之前的所有配方中,我们都是使用 datetime() 方法创建 datetime 对象;在这个配方中,我们也使用了 datetime 方法,但这次使用了 tzinfo 参数,如下所示:datetime(YYYY, MM, DD, HH, MM, SS, tzinfo=<timezone>)。tzinfo 参数确保将时区信息添加到 datetime 对象中,这在跨时区进行计算时非常重要。
datetime 类还有一个方便的方法,可以将 datetime 对象表示为我们选择的时区:astimezone() 方法。使用此方法,我们使用以下代码将 UTC datetime 对象转换为东部时间:loc_dt.astimezone(eastern)。
最后,我们使用 strftime(format) 方法创建了一个自定义字符串格式来表示东部时间。
我们还可以在时区计算中添加或删除时间/天数,就像我们在 datetime 对象中所做的那样。在这个配方的第三个代码示例中,我们切换到澳大利亚/悉尼时区并创建了一个该时区的 datetime 对象;这个操作返回了悉尼的本地时间。借助 timedelta() 方法,我们随后从本地时间中减去十分钟,使用 local - timedelta(mins=10),并且通过 local + timedelta(hours=18) 向时间中添加了 18 小时。这样,我们就可以访问过去或未来的时间。把它想象成时间旅行。
在第四个代码片段中,我们了解了如何处理夏令时。为了理解这一点,我们创建了一个没有任何时区信息的datetime对象,并将其分配给dt变量。我们还使用代码eastern = timezone('US/Eastern')创建了一个东部时区的时区对象。然后我们使用时区对象的localize()方法将dt对象转换为东部时间。在这里,我们向localize(is_dst=<True/False>)方法添加了另一个参数is_dst,以返回东部时区的本地时间,考虑或不考虑夏令时。
在 2016 年,11 月 6 日是时钟在凌晨 2 点倒回的那一天。所以,在我们的例子中,当我们用is_dst=True查询东部时间 1:30 am 时,它返回的是东部夏令时(EDT),比协调世界时(UTC-0400 小时)慢四小时。当我们用is_dst=False查询同一时间时,它返回的是 EST,即 UTC-0500 小时。
在这个菜谱的最后一个例子中,我们查看了一些pytz模块提供的有用辅助方法。例如,pytz.country_timezones['au']返回了在澳大利亚(Au)中所有可用的时区,而pytz.country_names['gb']根据 ISO 国家代码gb返回了国家名称,即英国(UK)。当你实际解决一些时区问题时,你会意识到这些库的实用性。
根据用户时区自动化发票
雅各布是北美安齐公司(Anzee Corporation)的财务经理,负责客户发票工作。安齐公司为其客户提供软件即服务(SaaS)平台,并按平台使用情况向客户收费。安齐的客户对错误的月度发票提出了投诉。他们表示,“上个月的发票在次月的 1 号可用,这没问题,但我们的部分使用情况没有被计入。这搞砸了我们的会计工作。”
目前,雅各布通过从平台数据库记录中获取客户支付和平台使用数据来手动生成发票。随着每月客户数量的增加,雅各布意识到手动流程将会非常耗时费力。他还希望有人能关注客户投诉的问题。我们能帮助雅各布吗?
准备工作
在这个菜谱中,我们将使用在之前菜谱中用过的所有内置 Python 模块,以及安装fpdf模块来生成 PDF 发票,以满足雅各布为客户准备发票的自动化需求。我们使用 Python pip来安装该模块:
(date)chetans-MacBookPro:ch12 Chetan$ pip install fpdf
You are using pip version 7.1.0, however version 9.0.1 is
available.
You should consider upgrading via the
'pip install --upgrade pip' command.
Collecting fpdf
Downloading fpdf-1.7.2.tar.gz
Building wheels for collected packages: fpdf
Running setup.py bdist_wheel for fpdf
Stored in directory: /Users/chetan/Library/Caches/pip/wheels/
c9/22/63/16731bdbcccd4a91f5f9e9bea98b1e51855a678f2c6510ae76
Successfully built fpdf
Installing collected packages: fpdf
Successfully installed fpdf-1.7.2
如何操作...
-
让我们从查看数据库记录开始。安齐公司使用 MongoDB 来存储客户支付和月度费用的记录。在这个例子中,假设安齐公司的支付记录以 JSON 格式存储。
-
我们使用一个
users文档,其中包含所有用户的列表,具有id、name、city和timezone等字段,就像任何其他用户表一样。它还在payments中维护用户的所有支付记录,其中包含为平台服务付费的用户 ID、支付金额和支付金额的日期。 -
所有支付的日期时间戳都是 UTC 格式。像支付一样,它还维护了
usage记录,这些记录再次包含使用该平台的人的用户 ID、她因使用而支付的费用以及她被收费的时间:users = [{"id":12, "name":"John", "city":"New York", "timezone":"US/Eastern"}, {"id":13, "name":"Johny", "city":"Indiana", "timezone":"US/Central"}] #All time stamps are in UTC payments = [{"id":12, "amount":12.00, "created_at":"2016-11-29T11:46:07.141Z"}, {"id":13, "amount":22.00, "created_at":"2016-11-30T23:46:07.141Z"}, {"id":12, "amount":5.00, "created_at":"2016-12-01T01:00:00.141Z"}] usage = [{"id":12, "charge":5.00, "created_at":"2016-11-29T11:46:07.141Z"}] -
好的,很酷,现在我们有了所有数据,让我们着手编写生成我们发票的代码。我们首先编写方法来获取给定月份用户的支付和用量。以下代码片段为我们完成这项任务:
user_ids = [] user_names = [] for usr in users: user_ids.append(usr["id"]) user_names.append(usr["name"]) def get_payments(user_id, month): tz = [ x for x in users if x["id"] == user_id] tot_payment = 0.00 for p in payments: dt = datetime.strptime(p["created_at"], '%Y-%m-%dT%H:%M:%S.%fZ') if p["id"] == user_id and dt.month == month: tot_payment += p["amount"] return tot_payment def get_usage(user_id, month): tz = [ x for x in users if x["id"] == user_id] tot_usage = 0.00 for u in usage: dt = datetime.strptime(u["created_at"], '%Y-%m-%dT%H:%M:%S.%fZ') if u["id"] == user_id and dt.month == month: tot_usage += u["charge"] return tot_usage -
接下来,让我们编写代码以自动生成 PDF 发票,正如雅各布为他自己的平台所希望的那样。我们为此目的使用
fpdf模块。以下代码生成了发票:def get_invoice(user_name, user_id, month): html = """ <p>Anzee Corporation</p><br> <b><p>Account Name: """ + user_name + """</p> <p>Invoice for month of: """ + str(calendar.month_name[month]) + """</p></b> <br><br> <p><b>Payments and Usage:</b></p> <table align="center" width="50%"> <thead> <tr> <th align="left" width="50%">Charge Type</th> <th align="right" width="50%">Amount</th> </tr> </thead> <tbody> <tr> <td>Payments Done</td> <td align="right">$""" + str(get_payments (user_id, month)) + """</td> </tr> <tr> <td>Total Usage</td> <td align="right">$""" + str(get_usage (user_id, month)) + """</td> </tr> </tbody> </table> <br><br> """ return html class MyFPDF(FPDF, HTMLMixin): pass html = get_invoice("John", 12, 11) pdf=MyFPDF() pdf.add_page() pdf.write_html(html) pdf.output('invoice.pdf','F') -
如果我们完整地运行前面的代码片段,我们将得到生成的发票,其外观如下所示:
![如何操作...]()
-
好的,这很酷!我们能够像雅各布期望的那样生成发票。我们现在能够通过自动化整个过程为他节省大量时间。但随后他希望我们查看有关发票不包含准确信息的客户投诉。让我们看看可能发生了什么。
-
现在,Anzee 公司存储所有交易和用量时间戳为 UTC,这样他们就可以很容易地检索时间并根据用户的时区向用户展示。因此,当我们查找该月的所有记录以获取发票中的交易时,我们查看的是 UTC 时间戳,而不是用户的时区时间。
-
例如,如果你深入查看 JSON 数据,会提到约翰(John)用户 ID 为 12 的另一次支付,其
created_at时间戳为2016-12-01T01:00:00.141Z。从 UTC 的角度来看,这个时间可能不属于 11 月份,但进行支付的用户属于美国/东部时区。因此,2016 年 12 月 1 日凌晨 1 点 UTC 实际上是 11 月 30 日下午 8 点东部时区。显然,用户没有在他的发票中看到他的支付。 -
以下代码片段通过根据用户时区生成发票来解决该问题:
from datetime import datetime import pytz from pytz import timezone from fpdf import FPDF, HTMLMixin import calendar users = [{"id":12, "name":"John", "city":"New York", "timezone":"US/Eastern"}, {"id":13, "name":"Johny", "city":"Indiana", "timezone":"US/Central"}] #All time stamps are in UTC payments = [{"id":12, "amount":12.00, "created_at":"2016-11-29T11:46:07.141Z"}, {"id":13, "amount":22.00, "created_at":"2016-11-30T23:46:07.141Z"}, {"id":12, "amount":5.00, "created_at":"2016-12-01T01:00:00.141Z"}] usage = [{"id":12, "charge":5.00, "created_at":"2016-11-29T11:46:07.141Z"}] user_ids = [] user_names = [] for usr in users: user_ids.append(usr["id"]) user_names.append(usr["name"]) def get_payments(user_id, month): tz = [ x for x in users if x["id"] == user_id] tot_payment = 0.00 for p in payments: dt = datetime.strptime(p["created_at"], '%Y-%m-%dT%H:%M:%S.%fZ') dt = dt.replace(tzinfo=pytz.UTC) dt = dt.astimezone(timezone(tz[0]["timezone"])) if p["id"] == user_id and dt.month == month: tot_payment += p["amount"] return tot_payment def get_usage(user_id, month): tz = [ x for x in users if x["id"] == user_id] tot_usage = 0.00 for u in usage: dt = datetime.strptime(u["created_at"], '%Y-%m-%dT%H:%M:%S.%fZ') dt = dt.replace(tzinfo=pytz.UTC) dt = dt.astimezone(timezone(tz[0]["timezone"])) if u["id"] == user_id and dt.month == month: tot_usage += u["charge"] return tot_usage def get_invoice(user_name, user_id, month): html = """ <p>Anzee Corporation</p><br> <b><p>Account Name: """ + user_name + """</p> <p>Invoice for month of: """ + str(calendar.month_name[month]) + """</p></b> <br><br> <p><b>Payments and Usage:</b></p> <table align="center" width="50%"> <thead> <tr> <th align="left" width="50%">Charge Type</th> <th align="right" width="50%">Amount</th> </tr> </thead> <tbody> <tr> <td>Payments Done</td> <td align="right">$""" + str(get_payments( user_id, month)) + """</td> </tr> <tr> <td>Total Usage</td> <td align="right">$""" + str(get_usage( user_id, month)) + """</td> </tr> </tbody> </table> <br><br> """ return html class MyFPDF(FPDF, HTMLMixin): pass html = get_invoice("John", 12, 11) pdf=MyFPDF() pdf.add_page() pdf.write_html(html) pdf.output('invoice.pdf','F')前面代码片段的输出显示在以下屏幕截图。看看现在支付列如何反映了正确的数据,并包括 2016 年 11 月 30 日下午 8 点进行的 5 美元支付,总额达到$12 + $5 = $17:
![如何操作...]()
它是如何工作的...
我们首先研究了为雅各布自动化发票生成的方法。我们解析了所有用户的 JSON 数据,并计算了 11 月份所有用户的支付和用量。
我们开发了get_payments(user_id, month)和get_usage(user_id, month)函数,用于遍历payments和usage记录,并选择了 11 月份的记录。我们通过处理created_at JSON 字符串并将它们转换为日期/时间对象来实现这一点,使用dt = datetime.strptime(u["created_at"], '%Y-%m-%dT%H:%M:%S.%fZ')。
但正如我们在上一节中理解的,仅仅将字符串转换为时间戳并没有帮助,因为我们没有考虑到用户的时区。为此,我们使用日期/时间对象dt,通过dt.replace(tzinfo=pytz.UTC)将其转换为 UTC 时区,然后使用dt.astimezone(timezone(<>))方法将dt转换为反映用户时区的时间。这样,我们就可以获取用户时区的支付时间,并且发票数据正确反映了 11 月份的数据。
接下来,我们通过添加适当的用户名、发票时间和存储在html变量中,创建了发票的 HTML 内容。随后,我们创建了一个名为MyFPDF的类,它从fpdf模块继承了FPDF和HTMLMixin类。然后,我们使用MyFPDP类创建了一个pdf对象,它代表了一个空的 PDF 文件对象。我们使用add_page()方法向pdf对象添加了一页,并使用write_html(html)方法用 HTML 内容(我们的发票内容)更新了它。最终,我们使用output(<filename>)方法将包含所有数据的pdf对象输出到磁盘上。
还有更多...
在 Python 中,有许多其他有趣的用例涉及时间和时区操作。如果不正确使用,可能会变得复杂,正如我们在上一个示例中看到的那样。作为一个一般性的指导原则,我建议您:
-
总是使用时区感知的
datetime对象。这种方法永远不会出错。它将始终作为您的提醒。 -
以 ISO 格式返回给定对象的时区信息。
希望您喜欢这一章,并享受了这些示例!请继续关注。









































































































































































































浙公网安备 33010602011771号