mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2026-05-10 19:54:28 +08:00
mod
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<audio id="audio" title="23|怎么用数据透视表更直观地展示汇报成果?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/81/72/8144eddcbc8f4431dbbyye1234709c72.mp3"></audio>
|
||||
|
||||
你好, 我是尹会生。
|
||||
|
||||
从这节课开始,我们将进入课程的最后一个模块“输出模块”的学习了。在这一模块中,我们要把“控制、存储等模块”的数据,以更加智能化的方式展现到你的面前。比如用图表、图形代替数字进行工作成果汇报;利用压缩包、PDF把工作成果定时发送邮件等。这些都是你在输出模块能够学习到的自动化办公技巧。
|
||||
|
||||
通过对这些技巧的掌握,能够为你的工作成果输出带来更高效的处理方式,也能让你把工作成果输出这件事做得更出色。
|
||||
|
||||
那今天这节课,我想和你聊一聊怎样把你的工作成果更好地向领导汇报的问题。
|
||||
|
||||
你在工作中肯定遇到过这样的情况:精心处理了好几天的数据,需要把数据汇报给你的领导时,往往不知道采用什么样的格式更合适。比如只提交了整理过的原始数据,很容易被领导误认为工作只完成了半成品。而提交了最终结果时,领导又可能会提出合理的疑问,比如如果按月汇总、按某项汇总会得到哪些不同的结果等问题。这个时候,你就不得不再进行一次统计结果汇报。
|
||||
|
||||
那么今天这节课,我就要给你介绍一款Excel中的插件,叫做“Power Pivot”,这个插件可以根据需要随时调整数据处理结果。而你可以利用“Power Pivot”生成自己需要的数据模型,还能基于模型生成更灵活的、可随时调整的数据透视表。
|
||||
|
||||
等下次再遇到需要随时调整数据汇总结果的时候,你就可以直接拖拽想要的列,按月、按年、或按照任意你想要的字段进行数据聚合了。
|
||||
|
||||
不过在讲解之前,考虑到你可能初次使用数据透视表,我想先为你介绍一下如何将数据转换为数据透视表的操作步骤,这样你才能更好的理解数据透视表展示的表格和数据之间的对应关系。在掌握了数据透视表的操作基础之上,我再教你怎样使用“Power Pivot”插件来生成支持多表和自定义排序的数据透视表。
|
||||
|
||||
## 利用数据透视表进行灵活的数据分析
|
||||
|
||||
数据透视表能用来做什么呢?一句话来描述它,数据透视表是一种能够随意对数据明细表进行分类汇总,并能随时调整汇总结果的交互式报表。
|
||||
|
||||
可能这样描述它,你仍然觉得不够直观,还不能对数据透视表的作用有具体的感知。那么我就用一个Excel文件,来给你演示一下怎么把数据表生成数据透视表,并进行数据的统计和分析。
|
||||
|
||||
为了让你对数据透视表有个直观的印象,我先把原始的数据表和制作成透视表的表格一起提供给你,原始的数据表记录了5台计算机硬件负载情况的明细数据,透视表按照每台计算机每种硬件在每小时的最大利用率进行展示。两张表格内容如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/95/c6/955d8acb66123d7e344a6083dee15cc6.png" alt="">
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/08/e1/08e7yy1427400227847fc9fdcd2759e1.png" alt="">
|
||||
|
||||
同时,我也把生成数据透视表后的截图,给你放在下方,供你参考:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/1a/99d82fca8fea04506b74e575f2d63f1a.png" alt="">
|
||||
|
||||
数据表中包含了1点-4点,一共5台计算机的CPU、内存、磁盘利用率,如果把数据交给你,你能从中分析出哪些有趣的结果呢?
|
||||
|
||||
那么我在这里用数据透视表提供两种将数据组合起来有意义的结果,它们是每台电脑每小时各种资源利用率的最大值,以及每台电脑每天资源利用率的平均值。
|
||||
|
||||
当然你还能用数据组合出更多有用且有趣的结果。那接下来我们就看看怎样把明细数据转换为数据透视表、怎样组合行和列,才能得到我刚才提到的两个有意义的结果。
|
||||
|
||||
### 使用数据透视表实现数据统计
|
||||
|
||||
使用Excel的数据透视表来实现数据统计,有三个简单的步骤,分别是数据预处理、生成透视表和调整透视表结果。让我们逐个来学习一下它们吧!
|
||||
|
||||
**首先是数据预处理**,它是输出数据的基础。数据预处理的目标就是把每一类数据放在单独的一列,因为数据透视表只能支持这种格式的数据,这样才能输出有意义的透视结果。另外,你可以使用第13讲的“Power Query”对表格进行调整,也可以使用咱们学习过的Python对Excel的数据内容进行调整。
|
||||
|
||||
我给你举一个调整好格式的例子,帮你理解数据透视表能支持的数据明细的格式。比如刚才的计算机利用率的表格,假设“pc-1”这台计算机的利用率明细表中包含“CPU”、“磁盘”和“内存”的利用率数据,如果你需要使用数据透视表,则必须为它建立“类型和利用率”两个列,并把“类型和利用率”作为表头。
|
||||
|
||||
与此同时,在类型列中把“CPU、内存、磁盘”作为数据,在利用率中分别写入每种类型不同时间各自对应的利用率,而不能把“CPU、内存、磁盘”作为三列,把利用率作为每一列的数据。
|
||||
|
||||
如果你还是无法理解需要把数据处理成什么样的格式,才能让数据透视表正常工作,那我再给你提供一个既简便又不会出错的方法:把第一列指定为时间,而其他的列,每一列存放一个类型的数据。把需要透视的数据,按照时间递增依次填入到Excel中。
|
||||
|
||||
这种数据有一个专有的名称,叫做**时序数据**。数据预处理完成后,如果你的透视表不需要时间字段,可以将时间这一列的数据再删掉。
|
||||
|
||||
**接下来是生成透视表**。数据准备好之后,下一步就是在新的工作表中插入数据透视表了。插入数据透视表的命令在“插入”菜单栏的“表格”命令组,你只要找到“数据透视表”按钮,点击后就会打开创建数据透视表的菜单了。
|
||||
|
||||
在这一步骤,有两个选项你需要格外关注一下。
|
||||
|
||||
- 一个是“选择要分析的数据”选项,Excel会自动为你选择当前表中的所有数据,如果数据表中的数据都是你要分析的内容,那你可以不做修改。如果你只希望将表格部分数据制作成数据透视表,可以根据自己的需要选取数据明细表中的具体数据范围。
|
||||
- 另一个是“请选择放置数据透视表的位置”选项,我们可以把透视表放在“新工作表”中。可以保持原始数据不被破坏外,便于你再次利用原始数据做其他的数据处理。
|
||||
|
||||
这两个选项一般保持默认即可。同时,我也把创建透视表的截图放在下方,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/48/c9/480b3c6bbdb7bb56270e624868f2b8c9.png" alt="">
|
||||
|
||||
**最后是调整透视表结果**。创建好默认的数据透视表之后,你会发现它的内容是空的,这时只需要把“报表的字段”**拖动到透视表区域**就行了。同样的,我先给你看一下截图,帮你更直观地感知数据透视表的操作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e4/8a/e4effabd5a9600fa469c224a521d588a.png" alt="">
|
||||
|
||||
在截图的左侧是透视表的结果,而右侧就是透视表的控制区域,你需要根据自己的分析需求,把字段拖入到右下角的“透视表区域”,包括“行、列、值”三个方框。例如我希望得到每台电脑的CPU、内存、磁盘在每小时和每天最大利用率,就可以把“主机、类型”字段拖入“列”,把“时间”字段拖入“行”,把“利用率”字段拖入“值”。
|
||||
|
||||
此外,你还要注意,“值”中的字段默认计算方式是“求和”,如果你像我一样,在这里需要最大值,就可以用鼠标点击“值字段设置”,把“求和”改为最大值即可。当然,你也可以根据自己的需求,设置为其他的。
|
||||
|
||||
这就是利用数据透视表进行数据统计的完整做法。你看,是不是很简单。总结来说,你可以利用数据透视表自由组合数据,得到你想要的统计结果,它的结果也会实时显示在左侧的Excel表中。而且它的最大优势就是你可以随时调整结果,当你需要结果能够行列互换时,直接拖动“透视表区域”的字段就能实现新的数据透视表了。它能解决你工作中大部分常用的数据分析和展示场景。
|
||||
|
||||
不过当你遇到数据在多张数据表中,或者数据量更大(超过百万行)时,你就需要使用“Power Pivot”插件来扩展数据透视表,从而实现多表的数据统计了。
|
||||
|
||||
## 使用Power Pivot插件扩展数据透视表,实现多表统计
|
||||
|
||||
数据透视表的英文叫做“Pivot Table”,把它和“Power Pivot”的名字进行对比,相信你就看得出来,“Power Pivot”插件是数据透视表的增强版本。为了方便你的阅读,在接下来的讲述中,我把它简写为“PP”。
|
||||
|
||||
“PP”插件主要扩展了数据透视表在处理数据的维度、数据的容量限制和自定义汇总的公式这三方面的功能。在一般的办公场景下,默认汇总的公式是能够满足日常需求的,因此我要重点给你讲一下“PP”在维度和容量方面能够支持哪些特性。
|
||||
|
||||
- 在维度上,它在单张数据表的维度上增加了数据表维度,即:能支持多张表格进行数据透视。
|
||||
- 容量上,从默认的100万行数据透视表的默认大小,扩展到能够支持上亿行的数据。
|
||||
|
||||
那么接下来,我就带你看看怎样使用“PP”来加载多张表的数据,来实现扩展的数据透视功能。
|
||||
|
||||
### 使用PowerPivot加载多张数据表
|
||||
|
||||
加载多张表需要使用“PP”插件,因此我们需要在生成数据透视表之前,先打开“PP”插件的窗口,再把多张表加载到“PP”插件中,最后再生成由“PP”插件制作的数据透视表。
|
||||
|
||||
**首先,我们先看看怎么打开“PP”插件。**“PP”在Office2009以后,已经成为Excel的默认插件,在Office最新的Office 365版本中,更是作为独立的菜单栏使用,因此你可以在菜单栏找到“PP”插件的选项卡。
|
||||
|
||||
为了演示“PP”对多张数据表的操作,我把当前的数据表再复制一份出来,形成两张一样的数据表,这样就可以测试它的多表加载功能了。
|
||||
|
||||
“PP”加载多张表的方式是使用“添加到数据模型”按钮。点击按钮后会弹出“创建表”窗口。这个窗口可以选择两张表中的其中一张,先作为数据模型。如图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/64/7e/64a66179c5ee8cafaacc317633a7cd7e.png" alt="">
|
||||
|
||||
图片中“创建表”窗口会默认把当前表全部选中,点击“确定”后,会弹出“PP”的工作窗口。在这一窗口下,你可以对数据进行筛选和处理。
|
||||
|
||||
**接下来我再导入另一个工作表****。****导入更多表的操作方法和导入当前工作表是相同的。**这样,在“PP”工作窗口的左下角,就会出现两张要进行数据透视的表格。需要说明的是,“PP”还能支持从其他数据源导入数据,例如可以从数据库或“Access”导入数据,和Excel中的数据一起进行数据统计。
|
||||
|
||||
**最后,生成由“PP”制作的数据透视表。你在“PP”窗口的任意一张表点击“数据透视表”按钮之后**,就可以创建新的数据透视表了。这时你会发现,数据透视表字段的右上方出现了两张表,你可以把这两张表按照需求拖入到右下方的行、列和值,从而实现多表的数据统计。我把截图放在下方供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f2/9c/f24a28001d02c836bf673ee696f2f69c.png" alt="">
|
||||
|
||||
通过使用“PP”,你可以把多个数据源作为数据透视表的源数据,进行数据的混合输出。而且它没有Excel默认的只能处理10万条数据的限制,能够让你在工作中对较大的数据模型进行处理。
|
||||
|
||||
以上就是如何使用“PP”扩展默认的数据透视表,实现多表数据透视以及更大容量的数据透视表插件。
|
||||
|
||||
另外,我也为你推荐一款Python中能够实现数据透视表的库,叫做“Pandas”(也叫“潘大师”),它也有着和Excel数据透视表功能相同、原理相同的函数“pivot_table()”函数。但是“pivot_table()”函数是通过参数“index、column、values、aggfunc”四个参数来指定行、列、数据和数据处理函数,所以你只能看到结果,无法直观看到中间过程。
|
||||
|
||||
Pandas更适用于你已经掌握数据透视表的原理,并根据参数能在头脑中形成数据透视表的基本样式后,通过编程实现数据透视表,因为Pandas比Excel更复杂,因此Pandas更适用于长期且多次将数据明细处理为数据透视表的场景使用,Excel更适合数据短期使用,形成的透视表也无法以同样的模式应用到另一份数据的场景。相较于办公自动化的场景,Excel比Pandas更适用于数据的快速、灵活输出工作。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,让我来为你总结一下今天的主要内容。今天这一讲我主要围绕着怎么灵活输出统计结果的问题,为你讲解了Excel中的数据透视表,以及它的增强插件“Power Pivot”。
|
||||
|
||||
通过数据透视表展示你的工作结果,既能实时修改,又能实时展示。虽然它操作简单,但是功能还是非常强大的。像是你对工作中,自动化处理的大量数据结果,如果采用邮件的附件形式发送给领导和同事的话,那么数据透视表就是直观的。而且,当面对工作需求变化快的情况,使用数据透视表还能够帮你快速改变数据统计逻辑,快速响应新的工作统计需求。
|
||||
|
||||
## 思考题
|
||||
|
||||
最后,我再来为你留一道思考题,如果你需要使用数据透视表分析一个学校中的10个班级的学生平均成绩,每个班级学生的成绩包括语文、数学、外语。你要怎么设计Excel表格,才能通过数据透视表进行数据分析呢?
|
||||
|
||||
欢迎把你的思考和想法放在留言区,我们一起交流讨论。如果这节课学习的数据透视表对你的工作有帮助,也欢迎你把课程推荐给你的朋友或同事,一起做职场中的效率人。
|
||||
@@ -0,0 +1,174 @@
|
||||
<audio id="audio" title="24|条形、饼状、柱状图最适合用在什么场景下?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/20/f5/20950ae917242932af04012fc9e60cf5.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
提起图表,你一定会想到Excel和PPT中的条形图、饼状图、柱状图,这在我们进行工作汇报的时候会经常用到,是我们最经常打交道的图表了。除此之外,还有很多其他种类的图表,比如折线图、热力图等等。
|
||||
|
||||
但是,不管你通过哪一种图表,它们都是为了让你能够更直观、更简洁地表达自己的想法,也能让我们更好地从一堆杂乱无章的数字中找出规律。
|
||||
|
||||
虽然图表比直接展示数据多了这么多优势,但是也存在一个问题,那就是使用Excel制作一张精美的图表,需要消耗大量的时间。而且这些精美的图表,如果因为临时需要再加载新的数据,又要重复花费时间来制作。别担心,这些问题都可以通过Python中的seaborn库来解决。
|
||||
|
||||
所以在今天这节课当中,我就来教你怎么使用seaborn库实现图表的重复生成,并根据不同的场景使用不同类型的图表。
|
||||
|
||||
## 生成统一风格的图表
|
||||
|
||||
在Python的图表库中,最著名的库叫做matplotlib,它的语法简单,而且支持的图表类型丰富,是数据分析场景中经常用到的图表工具。
|
||||
|
||||
但是如果你直接把它应用到办公自动化场景中,虽然matplotlib的功能是强大的,不过美观程度相对就比较差了。因此,我今天就带你学习一个基于matplotlib库,并且在外观上进行了优化的扩展库,叫做seaborn,它能弥补matplotlib在外观上的不足。
|
||||
|
||||
那么接下来,我就以为鸢尾花分类为例,为你讲解一下seaborn库的安装,以及绘图的基本流程。
|
||||
|
||||
鸢尾花分类是深度学习用于自动分类的经典问题。我们使用它的数据集是因为它的数据量适中,而且包含了必备的花萼和花瓣的长宽数据,以及长宽数据对应的三个品种的鸢尾花。既能通过seaborn观察到分类结果,又能将用于绘图的代码应用到自己的工作场景中。
|
||||
|
||||
### 用seaborn生成图表的基本流程
|
||||
|
||||
seaborn库的安装非常简单,由于它的安装包和软件同名,所以使用pip命令安装即可。安装之后,就可以使用它来生成图表了。你可以按照导入库、设置图表样式、绘制图形三个步骤来实现图表绘制功能。我们来依次学习一下。
|
||||
|
||||
**首先是导入库**。在这一步骤中,你需要格外注意**导入seaborn库的名称,以及导入的方法。**
|
||||
|
||||
由于seaborn的功能是基于matplotlib实现的图表基本绘制功能,所以这两个库必须都要导入,否则就没法生成图表。
|
||||
|
||||
在导入的方法上,我发现导入库的名字很长,这就意味这你在调用库的时候也需要输入比较长的字符。因此我在导入的时候增加了一个“as”关键字,它可以将库的名称简写为更简单的“别名”,以此来简化代码的编写。你需要注意,别名要尽可能有意义,而且不要和保留字或当前代码中的变量重复,以免引发运行时的报错。
|
||||
|
||||
在导入库的代码中,我为名字比较长的两个库分别起了新的名字叫“sns”和“plt”,那么当前代码就可以利用“sns.XXX”和“plt.XXX”的方式导入这两个库的代码了,这样会比使用原始的名字更精简。
|
||||
|
||||
我把导入库的代码写在下方,供你参考。
|
||||
|
||||
```
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
```
|
||||
|
||||
**接下来是设置图表样式**。图表样式是由工作场景确定的,包括背景样式和图表的类型。同时在这一步骤,你还要为图表加载数据。所以设置图表样式是seaborn库绘图最关键的部分。
|
||||
|
||||
图表样式由背景风格和图表类型两部分组成。
|
||||
|
||||
我们先来说风格。风格其实包含了背景色、字体、字形等样式,它们是图表展示时最底层的样子。而这些风格样式通通由seaborn.set()函数的参数控制,所以一旦其中任意一个参数发生了变化,那绘图的效果也会发生变化。
|
||||
|
||||
不过由于设置风格的参数比较多,自由组合并展示到用户面前的话,界面不一定会美观,这也会违背我们使用seaborn生成图表的初衷,因此我们需要经过精心调整样式的搭配。那怎么来搭配它们最合理呢?
|
||||
|
||||
其实你不必纠结,去逐一尝试每个参数,因为在seaborn的set()函数的参数中提供了五种默认风格,这五种风格不说满足特别高的审美要求,但是在一般绘图中,它的美观程度还是可圈可点的。五种默认风格分别是:“darkgrid”“whitegrid”“dark”“white”“ticks”,它们分别代表了“暗黑网格”“白色网格””暗黑无网格”“白色无网格””空白背景”,这五种风格可以通过set()函数的style关键字参数来指定。
|
||||
|
||||
如果你想在这五种默认风格上继续修改,就可以在指定风格后,通过set()函数的其他参数继续进行修改。我把set()函数的参数提供给你,方便你在工作中找到适合你的图表风格。如下:
|
||||
|
||||
```
|
||||
seaborn.set(context='notebook', style=''darkgrid', palette=''deep'', font='sans-serif', font_scale=1, color_codes=True, rc=None)
|
||||
|
||||
```
|
||||
|
||||
我们再来看图表类型。图表类型是由工作场景来决定的。例如:我想根据花瓣的长宽度,以及花萼的长宽度数据,通过图表来区分三种花的类型。因此我需要一种图形来表示花瓣和花萼的长短分布情况。
|
||||
|
||||
显然,我们是希望能根据花瓣和花萼的长短宽窄来得到分布情况,而不是观察变化趋势,因此我会采用散点图,而不是折线图来表达花的数据。那么散点图的绘制,是通过函数seaborn.pairplot()来实现图表类型的设定的。
|
||||
|
||||
seaborn.pairplot()函数不但能够指定图表类型,还能为图表加载数据和设置图表的类型、点样式。主要包括三部分,分别是加载的数据源、指定图表类型以及该类型需要绘制的点的样式。
|
||||
|
||||
第一部分,加载的数据源,数据源可以由二维元组组成类似Excel一样的多行多列的数据,数据中的第一行和第一列会作为标题,被seaborn自动处理。我在代码中使用了示例数据“鸢尾花分类”来为你展示数据的加载。
|
||||
|
||||
它的示例数据是通过seaborn.load_dataset()函数导入的,这个函数会自动访问GitHub下载数据。如果你无法访问GitHub,我就再为你提供一个示例数据的镜像站。除了鸢尾花数据外,镜像站这里还包括房产价格预测等经典示例数据,你可以利用它们来学习不同的图表。我把[示例数据的地址](https://codechina.csdn.net/mirrors/mwaskom/seaborn-data?utm_source=csdn_github_accelerator)放在这里,另外,我把部分示例数据也贴在下方,供你参考:
|
||||
|
||||
```
|
||||
sepal_length sepal_width petal_length petal_width species
|
||||
0 5.1 3.5 1.4 0.2 setosa
|
||||
1 4.9 3.0 1.4 0.2 setosa
|
||||
2 4.7 3.2 1.3 0.2 setosa
|
||||
3 4.6 3.1 1.5 0.2 setosa
|
||||
4 5.0 3.6 1.4 0.2 setosa
|
||||
.. ... ... ... ... ...
|
||||
145 6.7 3.0 5.2 2.3 virginica
|
||||
146 6.3 2.5 5.0 1.9 virginica
|
||||
147 6.5 3.0 5.2 2.0 virginica
|
||||
148 6.2 3.4 5.4 2.3 virginica
|
||||
149 5.9 3.0 5.1 1.8 virginica
|
||||
|
||||
```
|
||||
|
||||
我再来为你解释一下示例数据。它是由五列组成的,分别表示鸢尾花的花萼长度、宽度,鸢尾花的花瓣长度、宽度(你可以通过百度来搜索鸢尾花的图片,来了解什么是花萼的长宽,什么是花瓣的长宽),以及三种鸢尾花品种(setosa 山鸢尾,versicolor 杂色鸢尾,virginica 维吉尼亚鸢尾)。我通过散点图的方式采用不同维度展示花的特性,让你能根据颜色把三种花区分出来。
|
||||
|
||||
第二部分是指定图表类型,它是由“kind = 'scatter'”参数指定的。因为pairplot()函数支持散点图和回归图(kind='reg'),我们需要关注分布情况,所以使用了散点图的方式来展示数据。
|
||||
|
||||
第三部分是点的样式。绘制的散点图中的每个点,也可以单独设置它们的样式。例如我指定了每个点的大小“height=2”,以及指定了色彩样式“palette='husl'”,并为每个列指定不同的颜色“hue = 'species'”。
|
||||
|
||||
以上是如何设置图表的样式的核心代码,为了让你更好地理解设置的参数,我将这一步骤的代码一并写在下方,供你参考。
|
||||
|
||||
```
|
||||
# 设置背景
|
||||
sns.set(style="darkgrid", color_codes=True)
|
||||
# 使用示例数据
|
||||
iris = sns.load_dataset('iris',data_home='seaborn-data',cache=True)
|
||||
# 加载数据,使用散点图,设置点的颜色和样式
|
||||
sns.pairplot(iris,
|
||||
kind = 'scatter', #散点图
|
||||
diag_kind = 'hist', #直方图
|
||||
hue = 'species', #按照某一字段进行分类
|
||||
palette = 'husl', #设置调色板
|
||||
markers = ['o', 's', 'D'], #设置不同系列的点样式
|
||||
height = 2 #图标大小
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
**最后一步是绘制图形,由于seaborn基于matplotlib实现图形,因此需要使用plt.show()函数进行图形的绘制,那么鸢尾花数据的散点图绘制结果如下:**
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4c/e6/4c942198475e24b0f5e7448c39a126e6.png" alt="">
|
||||
|
||||
在截图中,基于花的四个属性,我采用了不同的维度进行绘图。同时你会发现,在某一维度下,其中一种颜色和其他颜色有明显的分界,非常容易把其中一种和另外两种花区分开。
|
||||
|
||||
通过观察散点图,你会得到这样一个结论,使用合理的图形,能够帮你更好地解释某个晦涩难懂的概念,也能更容易从数据中发现规律。那既然不同的图表能带来不同的价值,接下来,我就来为你讲解一下,如何使用seaborn生成其他类型的图表,比如可以通过histplot()函数生成柱状图、heatmmap()生成热力图、kdeplot()生成核密度图等等。
|
||||
|
||||
### 用seaborn生成不同类型的图表
|
||||
|
||||
要想使用seaborn生成其他类型的图表,你需要学会如何使用官方文档。我以折线图为例,为你讲解一下官方文档的正确用法。
|
||||
|
||||
在[seaborn的官方文档地址](https://seaborn.pydata.org/api.html)API页面下,所有的图表都先按照不同的用途进行了分类,折线图在表示关系的分类中,你可以参考如下截图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/65/37/651e97ffd566d55671be4678c8d36437.png" alt="">
|
||||
|
||||
当你需要绘制折线图时,可以**点击“lineplot”,进入折线图的函数解释网页**。它的网页采用了和Python官方文档风格一致的API解释方法,即函数定义、一般场景案例和特殊场景案例。
|
||||
|
||||
如果你是第一次使用折线图,那你可以按照一般场景案例、函数定义和特殊场景案例的顺序来学习这个函数。如果你对折线图已经有了较多的使用经验,可以从函数定义,按照网页顺序阅读官方文档。为什么要按照这样的方式来学习呢?
|
||||
|
||||
在你对某一图表有了初步的使用经验后,会对该图形的样子有一个感性的认识,这时候再通过函数的定义、参数去学习它们,会比通过一般场景案例来学习的效率更高。而且通过学习函数的参数,能够了解哪些技术点会影响图形的展示。
|
||||
|
||||
而对于第一次使用某一图表的话,你没法通过图表的名字想象出这类图形的优缺点,因此我会建议你对初次使用的图形,先按照一般场景案例把图形展示出来,有个直观的印象。
|
||||
|
||||
不只是seaborn的文档,在学习其他库甚至Python语言,或其他任何编程语言,都需要通过阅读官方文档来掌握扩展知识。而阅读官方文档最佳的时机,是当你掌握了该软件的基本应用之后,例如在你掌握seaborn的散点图,以及它的基本运行过程之后,这时你就需要通过官方文档的学习来掌握更多的图表。当你掌握足够多的图表后,用seaborn绘图才能更加得心应手。
|
||||
|
||||
## 为不同的应用场景选择合适的图表
|
||||
|
||||
由于seaborn支持的图表非常丰富,在有经验的开发工程师进行图表选择时,绝不会逐个尝试。他们会根据图表的应用场景来选择适合的种类,再通过适合的种类再细化到图表的具体样式。
|
||||
|
||||
但是你可能并没有使用过seaborn的图表,甚至也不了解图表会有多少种类型、每种类型里包含着哪些具体的图表。因此根据是否有图表的使用经验,你可以按照我给你提供的两种方法来根据工作场景,找到最适合你的图表。这两种解决办法总结来说就是参考图例和参考分类。
|
||||
|
||||
第一种解决办法是参考图例,我把这种情况称作是**“手中有剑、心中无剑”**,“**手中有剑”代表着你能看到图表一共有哪些,但是心中还不清楚哪种更适合你的场景**。在seaborn的官方文档中,列举了各种图例,它的[地址](https://seaborn.pydata.org/examples/index.html)和截图如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b3/79/b38fc214255db346fba37a6f7b15cf79.png" alt="">
|
||||
|
||||
你可以根据截图,找到离你的工作场景最相近的图表,通过点击图表之后,你就可以得到官方网站的演示代码了。演示代码就是你的“宝剑”。通过修改演示代码来完善你的工作场景的图表。
|
||||
|
||||
第二种解决办法是参考分类,我把这种情况称作“心中有剑,手中无剑”,“心中有剑”也就意味着你在心里已经**<strong>把**</strong>应用场景锁定在某一个图表的大类中,但是这一大类里包含了哪些具体的图表,要看seaborn能否支持。
|
||||
|
||||
这时候,你就要根据你的业务场景,分析出它都对应了以下四个分类中的哪一类,再按照分类通过官方文档API页面找到具体的图表函数。四个分类如下。
|
||||
|
||||
1. 关系类,用于展示数据集中多个变量之间的关系,relplot()、scatterplot()、lineplot()都属于关系类。
|
||||
1. 分布类,用于展示数据集中多个变量的分布情况,displot()、kdeplot()是这一类经常使用的图表类型。
|
||||
1. 线性关系类,是把多个变量联系起来,观察每个采样的线性变化趋势。regplot() 和 lmplot()经常用于表示线性关系。
|
||||
1. 结构化多图,用于把多种方式的分析数据放在一起进行展示。例如我们分析鸢尾花就使用了散点图+柱状图的方式,但是散点图更能体现出它的各种属性之间的关系。
|
||||
|
||||
你在心中掌握的图表分类就是“宝剑”,通过分类能够更快找到特定的图表类型。
|
||||
|
||||
这两种方式是基于不同场景,快速选择图表的方法。因为选择图表最核心的思路还是要基于场景,而不能基于个人的喜好或结果的美观性来选择图表,避免以偏概全。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,我来为你总结一下这一讲的主要内容。在本讲中,我通过seaborn生成图表的过程,为你讲解了如何在Python中使用图表。相对于其他软件,Python的图表样式由参数组成,你可以为多次产生图表指定相同的样式、也能为不同的数据重复使用图表来提高绘制图表的效率。
|
||||
|
||||
在为你讲解了散点图之外,我还为你讲解了如何基于场景选择合适的图表,你可以基于目前对图表掌握的深度,选择更适合你的图表深入学习路线。
|
||||
|
||||
同时,我还着重为你强调了文档的重要性,它也是很多专业从事开发的工程师必需掌握的技能之一。如果你希望更加深入学习seaborn以及更加深入学习Python,你应该从现在开始阅读官方文档,它会是你未来编写代码最权威的参考资料。
|
||||
|
||||
## 思考题
|
||||
|
||||
我来为你留一道思考题,如果我的工作场景需要展示当前地区的房价走势,你会选择什么样的图表进行展示呢?你能否用seaborn将这一图表绘制出来呢?
|
||||
|
||||
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。
|
||||
@@ -0,0 +1,243 @@
|
||||
<audio id="audio" title="25|图表库:想要生成动态图表,用Echarts就够了" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9d/90/9d651ed8bbc2dc5ca66dc0f2f4006e90.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
在上一讲中,我们学习了怎么使用Seaborn来生成图片格式的图表。事实上,图片格式的图表也被称作静态图表,它能通过数据来更直观地展示结果。
|
||||
|
||||
不过很多时候,我们不仅要通过图片直观地展示数据,还要让图片容纳更多种类、更丰富的数据信息。这个时候,静态图表能展示的结果就十分有限了。比如你希望能给领导和同事在会议上演示数据的分析结果时,需要通过一张图来容纳更多的数据。
|
||||
|
||||
别担心,这时候我们可以**采用动态图表的方法,来增强图片的表现力**。因为动态图表展示的结果,相当于静态图表和数据这两者的混合,所以容纳的内容信息也就更丰富。
|
||||
|
||||
举个例子,我希望用一张图片来展示全国新冠确诊病例的分布。如果采用动态图,我就可以把鼠标移动到我需要查看的省份上面,显示该地区的确诊人数等相关信息。
|
||||
|
||||
就像下面这张截图一样。这张分布图不但基于颜色深浅显示了确诊人数的变化,还能通过鼠标悬停来显示具体的数据。使用起来是不是很方便?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/60/01/6063f764e3493cb69b2922b1988dcf01.png" alt="">
|
||||
|
||||
这张动态图表是使用HTML网页文件格式来展示的。同时,它也采用了Python的库pyecharts进行了绘制,其中的图形、数据都可以基于你的需要进行调整。最重要的是,绘制这样一张图片,操作起来和seaborn生成静态图表一样简单。
|
||||
|
||||
那接下来,我就为你具体讲解一下**怎么<strong><strong>使用pyecharts来**</strong>绘制疫情实时地图</strong>,并以此为例,让你掌握**怎么通过<strong><strong>pyecharts来**</strong>绘制其他的动态图</strong>。
|
||||
|
||||
要想**使用pyecharts绘制动态图,必须要先对它进行安装,再为pyecharts加载数据,最后才能进行绘制动态图**。所以我们参考静态图表的学习方法,我来先带你从安装开始学习pyecharts。
|
||||
|
||||
## 安装:使用pip安装pyecharts
|
||||
|
||||
pyecharts库和它的安装包同名,因此你依旧可以使用pip命令进行安装。不过在这一步,我们需要验证pyecharts库是否被成功安装。这是非常重要的一步。
|
||||
|
||||
因为和之前安装的软件包最大的不同是,pyecharts库的依赖包非常多。这里我要对依赖包的概念多做些补充。
|
||||
|
||||
依赖包是指软件为了支持某些功能,而这一功能刚好有其他的第三方库已经实现了,那么该软件就不必再次编写该功能。但是你在使用pyecharts库时,它所依赖的库也必须被安装在你的电脑上面,你才能正常使用它。
|
||||
|
||||
这就像你操作Excel在你的脚本使用了“xlwt”库,而其他人使用你的脚本,必须也在他的电脑上安装这个库是相同的道理。因此当你对pyecharts进行安装的时候,会有很多被依赖的安装包一起被安装在你的电脑上。不过不用担心,依赖包并不影响pyecharts库的安装。
|
||||
|
||||
由于安装pyecharts的过程,pip命令需要同时安装很多依赖包,你大概率还会在安装过程看到“Requirement already satisfied”这样的提示,这个提示是指你安装的库已经被安装在你的计算机中,有可能是你之前使用pip命令安装的这些被依赖的包,也有可能是因为其他库的依赖,它们被安装到了你的计算机中,但是这些提示都不影响pyecharts库的安装。这些提示也并非安装错误,你可以直接忽略该提示即可。
|
||||
|
||||
不过依赖包过多,虽然不影响安装,但是会带来一个主要的问题。那就是在安装的终端界面,会显示很多提示信息,如果提示信息超过一个屏幕的长度,会把安装成功或失败的安装结果覆盖掉,导致你很难确认pyecharts是否安装成功,因此我们需要一种可以确认pyecharts乃至任何一个第三方库是否被成功安装在当前计算机的方法。
|
||||
|
||||
想要验证pyecharts库是否被成功安装,可以在命令行执行下面这个命令,来帮助你验证:
|
||||
|
||||
```
|
||||
SHELL$ pip3 freeze | grep pyecharts
|
||||
pyecharts==1.9.0
|
||||
|
||||
```
|
||||
|
||||
在这条命令中,有三个地方需要你格外注意:
|
||||
|
||||
- 命令中的“freeze”,用于查看pip命令在当前计算机安装的所有软件包都有哪些;
|
||||
- “|”叫做管道符,用于连接左右两条命令,并把左边命令的执行结果作为右边命令的输出;
|
||||
- 通过grep命令,过滤只包含“pyecharts”的一行。如果你不使用grep命令,“pip3 freeze”会将当前计算机中所有的第三方库及其版本显示在终端上,如果第三方库非常多,很难手动确认pyecharts是否被成功安装了。
|
||||
|
||||
这就是查看某一个库是否被成功安装在当前计算机,以及查看被安装版本的命令,我经常使用这一命令来确认依赖关系较多的库是否被成功安装。
|
||||
|
||||
不过你肯定会有疑问了,查看库是否安装这一做法是为了避免有些库存在不兼容,导致安装失败。那为什么还要查看安装的版本呢?
|
||||
|
||||
主要是考虑到版本兼容问题。我以pyecharts举例,pyecharts是Python和Echarts的结合体(Echarts是由百度开源的交互式可视化图表工具,基于JavaScript脚本实现)。因此Python提供的接口更新和Echarts工具更新,都会导致使用pyecharts的函数不同。而pyecharts 分为 v0.5.X 和 v1 两个大版本,且v0.5.X 和 v1 不兼容,v1 又是一个全新的版本,这两个版本支持的Python最低版本也不同。简而言之:
|
||||
|
||||
- v0.5.X 版本的pyecharts能支持Python2.7、Python3.4及以上版本;
|
||||
- v1 版本的pyecharts能支持Python3.6及以上版本。
|
||||
|
||||
如果基于公司规定,你必须使用默认的Python3.4版本的话,可以使用如下命令安装0.5版本的pyecharts:
|
||||
|
||||
```
|
||||
pip install pyecharts==0.5
|
||||
|
||||
```
|
||||
|
||||
解决完pyecharts的版本兼容问题后,相信你的pyecharts的正常运行肯定不在话下了,那接下来我就带你学习怎么给pyecharts加载数据。
|
||||
|
||||
## 数据:为pyecharts加载数据
|
||||
|
||||
在给pyecharts加载数据前,我们还要确认**数据的格式和数据来源**。这样做是为了把从网站中得到的数据转换为符合pyecharts绘图的数据。
|
||||
|
||||
- 数据格式,用于传入数据前要把来源数据转换成被pyecharts支持的格式;
|
||||
- 数据来源,决定数据的准确性和详细程度。
|
||||
|
||||
例如网站中的数据包含了省、市、区的确诊人数,以及成功被治愈的人数,而我们只需要每个省被确诊的人数。因此数据格式和来源都需要经过你的精心处理,才能被pyecharts展示给使用者。
|
||||
|
||||
### 确认数据的格式和来源
|
||||
|
||||
pyecharts的**数据格式**,要基于不同的图形类型,使用不同的格式。但是一般情况下,是多行多列组成的类似Excel表格的格式,这种格式在Python中一般使用**嵌套元组**的形式进行保存。
|
||||
|
||||
以绘制疫情地图数据为例,我们需要每个省的名称以及现有确诊人数,那么我们可以把省的名称和人数放在一个元组中,并把多个省的数据再组成一个更大的元组,作为pyecharts的源数据。
|
||||
|
||||
这种并列数据,你可能第一时间想到的不是元组,而是列表,但是我要告诉你的是,列表的查询效率要远远低于元组。为了让你的图形在展示数据时能够更加流畅,我更建议你使用元组,具体方法是在把列表作为源数据使用前,使用“tuple()”函数把列表类型转换为元组。
|
||||
|
||||
确定数据格式之后,我们还需要一个**数据来源**,为了确保数据的准确性和实时性,我们得从腾讯新闻的网站引入外部数据,[数据地址](https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5)的链接我先贴出来,接下来我再给你讲解一下怎么得到数据地址。
|
||||
|
||||
为了得到数据的地址,我分析了网站加载数据的过程,找到了数据接口的地址。这个分析方法你学会以后,也可以应用到其他需要抓取网页数据的工作中。我把抓取的步骤分成四步,分别是开启浏览器调试、请求网页、确认接口和确认返回数据。
|
||||
|
||||
先看第一步,**开启浏览器调试**。这一步骤是为了在请求过程中,能记录网页都请求了哪些数据接口。
|
||||
|
||||
以Chrome浏览器为例,使用快捷键F12可以打开调试模式,把选项卡调整至“Network”,调整后就进入接口的监听状态了。
|
||||
|
||||
第二步,**请求网页**。以腾讯疫情实时网页为例,可以在地址栏输入“[https://news.qq.com/zt2020/page/feiyan.htm#/](https://news.qq.com/zt2020/page/feiyan.htm#/)”,输入后,调试界面会显示该网页都请求了哪些地址。我把调试页面放在下方供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c7/44/c7349yyae70d9bcf369e95644fe4f344.png" alt="">
|
||||
|
||||
截图的左侧就是请求的所有数据接口的地址,从最开始请求的地址向下找,除了JS、JPG等网页图片和样式数据外,其他的请求接口就是我们要重点查看的接口。
|
||||
|
||||
第三步,**确认接****。**由于网页中包含了多次从“getOnsInfo”接口取得数据,所以要逐个查看接口的返回数据。你可以通过鼠标点击接口的链接,然后再点击“Response”按钮,最后根据返回接口的返回内容,查看是否为“疫情实时数据”。
|
||||
|
||||
第四部,**确认返回数据**。当你从接口初步确认了该数据是“疫情实时数据”后,可以把该地址复制到浏览器中,进行访问。访问的具体办法就是通过模拟网页请求接口,这样就可以得到以下数据了。我将数据放在截图中,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/e5/99040018387067600f77ae5d603e4fe5.png" alt="">
|
||||
|
||||
截图中的数据,可以通过查找省份和数据来确认数据的正确性。如果数据不正确,你需要回到第二步,再重新找下一个接口。
|
||||
|
||||
### 调整数据格式
|
||||
|
||||
当你确认了数据格式和数据源之后,接下来就需要把数据源的格式转换为pyecharts需要的嵌套元组格式。
|
||||
|
||||
首先,我先来分析通过网页直接请求数据接口之后的格式,请求后,你会看到网页上面的格式类似于Python的多个字典嵌套在一起,这种格式被称作JSON格式。在Python中你可以通过“json”库去解析这种格式,并把它转换为Python中的字典。解析的方法如下:
|
||||
|
||||
```
|
||||
import requests
|
||||
import json
|
||||
url = 'https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5'
|
||||
data = requests.get(url)
|
||||
alldata = json.loads(data.json()['data']
|
||||
|
||||
```
|
||||
|
||||
在这段代码中,我使用了requests.get()方法获取了接口的内容,并使用json.loads()方法把接口的数据转换成了字典。由于所有的数据都在下标为“data”的字典中,通过“字典['data']”取得字典值的用法,把所有数据存放到alldata变量中。
|
||||
|
||||
接下来,要把alldata变量中的字典转换为嵌套元组形式,这个转换过程需要遍历字典,来取得省份名称和对应的确诊人数。不过为了保存多个省份,我们还需要使用一个新的列表来存储多个省份的数据。最后再把列表转换为元组,作为pyecharts的绘图数据使用。我们依次来看一下:
|
||||
|
||||
```
|
||||
chinadata = []
|
||||
for province in alldata['areaTree'][0]['children']:
|
||||
provincedata = (
|
||||
province['name'],
|
||||
province['total']['nowConfirm']
|
||||
)
|
||||
chinadata.append(provincedata)
|
||||
|
||||
```
|
||||
|
||||
在代码的第2行,我使用了for循环,从alldata字典的多层嵌套结构中取出省份和该省份的确诊、累计、新增等人数信息。
|
||||
|
||||
在代码的第3行,我使用了一个新的元组,provincedata变量只保存每次遍历时得到的省份和确诊人数。
|
||||
|
||||
在代码的第7行,我把每次遍历生成的元组增加到chinadata列表中。这个列表就是最终处理好的数据内容,可以把chinadata数据直接作为pyecharts的源数据来进行绘图。
|
||||
|
||||
这段代码比较简单,不过我还是有两个小的使用建议提供给你。
|
||||
|
||||
第一个是,我在代码的第3行定义了一个provincedata的元组,从代码的正确执行角度来说,这个变量可以不用定义,完全可以把元组直接写入到chinadata.append() 函数的参数中。
|
||||
|
||||
但是从方便阅读代码的角度来看,这样书写代码不利于理解,因为append()函数的参数中包含了较为复杂的类型。因此我建议你在某个函数的参数中,如果传入了多种数据类型时,不妨增加一个临时变量。
|
||||
|
||||
另一个小建议是,在数据量较多的时候,应当尽量把列表转换为元组,加快查询效率。例如:我在代码的第7行先使用了列表对象用来存储变化的数据,然后通过for循环迭代alldata变量修改chinadata列表。最后,直到chindata不再需要改变内容时,我立即使用了tuple()把chinadata列表强制转换为了元组,那么后续的查询操作就都可以使用元组了。
|
||||
|
||||
通过对数据的抓取、分析和处理,源数据的格式和内容就准备完成了。接下来我需要将源数据加载到pyecharts中,并指定图形的类型和样式。
|
||||
|
||||
## 绘图:使用pyecharts绘制动态图表
|
||||
|
||||
当你准备好数据源并处理格式之后,就可以进行绘图了,主要有三个步骤,分别是**确定图表类型**、**加载数据和设置图表样式**。那我们先来看看怎么确定图表类型。
|
||||
|
||||
和我们学习seaborn类似,你可以参考图例,也可以参考分类来学习pyecharts支持的动态图表。与seaborn不同的是,pyecharts的官方文档没有图例,不过不要忘了,pyecharts是基于Echarts编写的,因此图例可以参考[Echarts的官方网站](https://echarts.apache.org/examples/en/index.html)。
|
||||
|
||||
Echarts的图表指定函数和pyecharts相同,找到你需要的图例函数之后,就可以拿到pyecharts中直接使用。
|
||||
|
||||
那针对老手的图表分类和API可以[参考](https://gallery.pyecharts.org/#/README)[这个地址](https://gallery.pyecharts.org/#/README)。以最常用的图表,折线图为例,你可以打开[地址](https://gallery.pyecharts.org/#/Line/temperature_change_line_chart),其中会包括图表的完整调用代码、测试数据和图例,通过参考示例可以让你掌握更多类型的图表。折线图的截图如下:<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/b0/06/b018bf7f0d7b34bfdce2c89fd8c8a006.png" alt=""><img src="https://static001.geekbang.org/resource/image/06/65/06926268b01a96be2aaedaa2720d2d65.png" alt="">
|
||||
|
||||
再让我们回到疫情地图的案例中,由于我们需要绘制中国地图,因此直接使用pyecharts库的Map()类,它是绘制动态地图的类。它的[官方网站](https://gallery.pyecharts.org/#/Map/map_base)链接我贴在这里。
|
||||
|
||||
通过参考官方网站的案例代码,我们可知在将Map()类实例化之后,进行绘图时,调用了add()、set_global_opts()和render()三个方法,它们分别是增加数据、设置样式和渲染。我们来依次学习一下。
|
||||
|
||||
<li>
|
||||
add()方法用来为图表增加图表名称、加载数据、指定地图的区域,其中加载数据的参数我使用了tuple()函数,可以直接把列表转换为元组,这样在查找效率上会比列表更高。
|
||||
</li>
|
||||
<li>
|
||||
set_global_opts()方法用来指定图表的样式和格式,这里我做了两个格式的调整操作,它们是:图表的标题和图表的颜色。图表的标题部分,我将“数据最后更新时间”作为图表的标题,是通过字典“alldata['lastUpdateTime']”实现的。图表的颜色是通过pieces参数实现的,根据不同省份确诊人数在什么数量范围,显示该省份的颜色深浅。颜色越深,表示当前周期采集到的确诊人数越多。
|
||||
</li>
|
||||
<li>
|
||||
render()方法,用来指定图表保存的网页路径和名称。这里需要注意的是,add()和set_global_opts()方法如果出现错误并不会马上报错,只有在调用render()方法时才会出现错误。所以一旦出现错误,你应该从导入数据的格式、add()和set_global_opts()方法参数中检查。
|
||||
</li>
|
||||
|
||||
执行render()方法之后,动态图就会以网页的形式保存至“covid19_map.html”文件中,你可以在浏览器里查看,并通过鼠标移动展示不同省份确诊人数的具体信息。
|
||||
|
||||
最后,我把pyecharts绘制图形的完整代码贴在下方供你参考,你可以直接将代码复制到自己的计算机执行。
|
||||
|
||||
```
|
||||
import requests
|
||||
import json
|
||||
from pyecharts.charts import Map
|
||||
from pyecharts import options as opts
|
||||
|
||||
url = 'https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5'
|
||||
data = requests.get(url)
|
||||
alldata = json.loads(data.json()['data'])
|
||||
|
||||
chinadata = []
|
||||
for province in alldata['areaTree'][0]['children']:
|
||||
provincedata = (
|
||||
province['name'],
|
||||
province['total']['nowConfirm']
|
||||
)
|
||||
chinadata.append(provincedata)
|
||||
|
||||
map_chart = Map()
|
||||
map_chart.add(
|
||||
"全国确诊病例分布图",
|
||||
tuple(chinadata),
|
||||
"china",
|
||||
is_map_symbol_show=False
|
||||
)
|
||||
|
||||
map_chart.set_global_opts(
|
||||
title_opts=opts.TitleOpts(
|
||||
title=f"全国疫情地图( {alldata['lastUpdateTime']} )"),
|
||||
visualmap_opts=opts.VisualMapOpts(
|
||||
is_piecewise=True,
|
||||
pieces=[
|
||||
{"min": 1, "max": 9, "label": "1-9人", "color": "#FFE6BE"},
|
||||
{"min": 10, "max": 99, "label": "10-99人", "color": "#FFB769"},
|
||||
{"min": 100, "max": 499, "label": "100-499人", "color": "#FF8F66"},
|
||||
{"min": 500, "max": 999, "label": "500-999人", "color": "#ED514E"},
|
||||
{"min": 1000, "max": 9999, "label": "1000-9999人", "color": "#CA0D11"},
|
||||
{"min": 10000, "max": 100000, "label": "10000人以上", "color": "#A52A2A"}
|
||||
]))
|
||||
|
||||
map_chart.render('covid19_map.html')
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
今天的主要内容就这么多,我来为你最后总结一下本讲的主要内容。
|
||||
|
||||
在本讲中,我通过pyecharts展示了疫情实时信息的动态图表,并为你介绍了图表的制作方法。对比静态图表,动态图表的制作方法要复杂,但是一张图能容纳的信息也要比静态图表多,你需要根据自己的工作场景合理的选择图表种类。
|
||||
|
||||
同时,你还会发现,图表的绘制难点在数据格式的处理上,通过网络采集数据往往要经过从JSON格式到字典再到元组的嵌套;图表绘制过程可以重复利于的部分就是图表的创建过程,包括数据添加、样式设置和渲染。掌握这些通用的使用原则,可让让你从熟练的操作中,加快自动办公的效率。
|
||||
|
||||
而你掌握的图表越丰富,在进行工作汇报和演示时,能够通过图形表达的信息就越清晰。这是我建议你能够掌握更多图表类型的原因。
|
||||
|
||||
## 思考题
|
||||
|
||||
今天的思考题是个开放的问题,在你使用pyecharts绘制的图表需要每天更新时,如何自动删除上一个生成的文件?那有没有办法让网页自动更新呢?
|
||||
|
||||
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。
|
||||
@@ -0,0 +1,246 @@
|
||||
<audio id="audio" title="26|快速提取图片中的色块,模仿一张大师的照片" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/79/ac/79455c6d804a0bb7513084de8b72feac.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
当你进行工作汇报使用各种图表时,除了要考虑精确的数据、正确的结论外,**合理的配色方案也是精确表达数据的重要部分。**
|
||||
|
||||
正确的配色不但能让用户更容易get到你演示产品的商业用途,还能为你的数据增光添彩。而不合理的配色,不但会降低用户对你演示产品的兴趣,还容易产生误解。
|
||||
|
||||
因此你**要为你的数据搭配上合适的色彩方案,来增加数据的表现力**。那么你要不要找专门的美工或自己分析那些商业模版用了什么配色呢?其实这两点都不需要,有很多设计高手和名画大师已经为我们提供了太多经典的作品了,我们只需要使用相似的配色,将这些配色应用到你的演示图片和文稿中,就能实现非常好的配色方案了。
|
||||
|
||||
我今天就来教你一种利用Pillow库自动分析图片中出现最多的颜色,并自动提取出来的方法。当你再遇到那些为配色发愁的工作场景,就可以找一个你喜欢的商业模版进行模仿了。
|
||||
|
||||
在本节课,我打算使用一幅莫奈的名画《日出·印象》来教你,如何使用Pillow库提取图片中被用到最多的五种颜色并将它们作为色块标注在图片上。下面的截图就是我们要实现的最终效果,接下来我就教你如何来实现它。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/4c/079d7c9459a9cd5770919e2923eab54c.png" alt="">
|
||||
|
||||
## 安装Pillow
|
||||
|
||||
在进行图片的大小、格式以及颜色调整时,我们通常选择Pillow,它是Python用于图片处理的第三方库。
|
||||
|
||||
那我们就看看怎么安装Pillow。它的安装包是同名的包,但是作为库导入的时候,需要使用PIL关键字,这是它和一般第三方库差异最大的地方。我把它的安装和导入命令写在下方,供你参考。
|
||||
|
||||
```
|
||||
SHELL$ pip3 install pillow
|
||||
SHELL$ python3
|
||||
python3> import PIL #注意导入的库的名称
|
||||
|
||||
```
|
||||
|
||||
安装完成后,我们就可以提取图片中使用最多的五种颜色了。不过别心急,在提取色块之前,有一个步骤非常重要,那就是把图片颜色转化为数据。为什么要进行这一步呢?
|
||||
|
||||
这主要是为了方便接下来提取色块的操作。你想啊,我们的目的是提取图片中的颜色,而颜色的“多”和“少”,是需要“数出来”的,所以我们就需要把图片转换为可以量化的数字才行。
|
||||
|
||||
那么接下来,我就教你怎么把图片转化为数据,并统计出一张图片都有哪些颜色,以及每种颜色的数量。
|
||||
|
||||
## 把图片颜色转换为数据
|
||||
|
||||
要想转换图片颜色为数据,并统计它用了多少种颜色,以及哪种颜色用得较多,你要先理解计算机是如何存储一张图片的。
|
||||
|
||||
举个例子你会更好理解。假如说你想知道一个硬盘能够存多少张图片,肯定得先知道磁盘的大小,除此之外还要知道图片的大小。与此同时,为了比较两者的大小,你还需要知道它们的大小单位,进而得知一个硬盘里最多能容纳的图片数量。
|
||||
|
||||
那我们用来表示一张图片内颜色数量的单位,是一种称作位(bits)的单位。每一“位”对应着一个像素的颜色值,而这些像素则按照一定的顺序排列就形成了数字图像。
|
||||
|
||||
计算机可以使用一“位”或者多“位”来组成一个像素,图像的色彩越丰富,每个像素使用的“位”就越多。为了方便表示“位”的数量,我们经常会使用一个叫做“位深度“的概念,来表示当前图像的颜色丰富程度。像是我们常说的8位颜色的图片,那么“位深度”就是8,而每个像素可以用2的8次幂来表示,也就是256种颜色(专业术语叫做256种灰度等级)。
|
||||
|
||||
8“位深度”的图像经常用于图片色彩提取和图片预览。不过我们经常见到的图片都是24“位深度”的,这是因为它能表示2的24位颜色,可以把红、绿、蓝(RGB)三基色以2的8次幂表示出来,这些颜色已经超过了人眼能分辨的颜色数量,所以也被称作真彩色,多用于图片的展示。
|
||||
|
||||
但是24“位深度”的图像不适合统计颜色数量,因为24位和8位图像存储颜色的方式不同,导致前者在计算准确性和计算性能上都没有8位图像处理起来方便:
|
||||
|
||||
<li>
|
||||
在计算准确性上,24位图像保存的数据多,所以在进行像素与像素之间的计算中,会产生浮点数,出现计算数据溢出的情况,并因此导致计算的结果异常。
|
||||
</li>
|
||||
<li>
|
||||
在计算性能上,8位深度图像和24位表示颜色的方式不同。因为在8位深度的图像中,有一个“调色板(palette)”的概念,这一概念只在8位图像才有,16位以上就都把图像颜色记录到图片自身的数据中了。所以**8位图像要想表示一个具体的颜色,需要通过调色板中记录的颜色模版和图片中的模版索引计算之后才能得到**,**<strong>而**</strong>针对**<strong>24位图像**</strong>,就可以**直接<strong><strong>把**</strong>具体的颜色写入到了图片中</strong>。
|
||||
</li>
|
||||
|
||||
我给你举个例子,例如我把调色板保存在一个列表中,那么由于8位图像能保存2的8次幂位深度,并且每个位深度有R、G、B三种颜色,所以调色板中就可以保存在一个包含了768个元素的列表里。
|
||||
|
||||
当你的图片需要使用RGB的某个颜色时,可以在图片文件中只记录该颜色RGB值对应的索引,也就是一个整数数字。而你可以直接通过该数字,找到调色板列表上的R、G、B三个颜色对应的值,这就相当于用一个整数的空间,存储了三个整数。
|
||||
|
||||
这样做既可以节省空间,不用真正把RGB颜色存放在图片里,又方便了我们后续来统计该颜色被使用了多少次。
|
||||
|
||||
相应的24位图像没有调色板,而是把RGB颜色数据之间记录到文件中,要想统计哪些颜色使用的较多时,进行排序的工作也要比8位图像更加复杂。
|
||||
|
||||
这就是一张图片存储颜色数据的单位和不同的存储形式。因此,要想把图片颜色转换为数据,首先要知道当前图片是采用了哪种“位深度”。
|
||||
|
||||
- 如果是8位深度,那么图片转化的数字就是该图片在调色板的索引。而你要想得到8位图片的RGB颜色,就需要使用索引与调色板进行二次计算,从而得到RGB颜色。
|
||||
- 如果是24位深度,那么图片转化为数据的结果就是RGB颜色。
|
||||
|
||||
由于我们的需求是统计一张图片中,哪些是使用次数最高的颜色,所以我们只需要把一张图片转化为8位数据来处理就可以了。那么接下来,我就带你学习一下把图片转化为数据的具体操作步骤,从而提取到色块。
|
||||
|
||||
## 提取色块的四个步骤
|
||||
|
||||
根据对8“位深度”图像存储颜色的原理,我们可以按照这样的步骤来提取色块。
|
||||
|
||||
- 先把24“位深度”的图像转换为8“位深度”;
|
||||
- 再使用调色板的索引,把8位图像按照使用次数的多少进行排序;
|
||||
- 之后再把前五个索引对应的调色板的颜色提取出来;
|
||||
- 最后把色块与图片整合。
|
||||
|
||||
### 转换为8位图像
|
||||
|
||||
把24位图像转换为8位图像,我们可以使用Pillow库的Image包来实现。在Image包中,有一个convert()方法,是图像处理经常用到的方法,它可以把图片转换为24位、8位、灰度图以及黑白图片。
|
||||
|
||||
在一般的场景中,转换为8位图像是为了加快图片的处理速度。我在这里就以《日出·印象》为例,把24位图像的图片转换为8位图像。代码如下:
|
||||
|
||||
```
|
||||
from PIL import Image
|
||||
# pip3 instlal pillow
|
||||
|
||||
# 打开图片文件
|
||||
image = Image.open("./文章26代码/sunrise.jpg")
|
||||
|
||||
# 模式P为8位深度图像,每个像素用8个bit表示
|
||||
image_p = image.convert(
|
||||
"P", palette=Image.ADAPTIVE
|
||||
)
|
||||
image_p.show()
|
||||
|
||||
```
|
||||
|
||||
在这段代码中,首先,我们通过“from import”的形式把PIL库的Image包进行导入。由于Image包是一个类,所以我们需要先使用open()函数打开文件,并实例化为image。
|
||||
|
||||
需要注意的是,这里Image类的open()方法不但能够以2进制打开图片文件,并且打开后,还可以读取图片包含的像素数量、图片的长宽以及位深度。读取之后,我们后续所有对图片的操作就都可以通过image实例进行操作了。
|
||||
|
||||
接着,在代码的第8行,我就对图片进行了位深度转换。代码中的转换为“P”模式,指的就是8“位深度”的图像,palette指的则是转换时指定的调色盘。我们可以通过参数“palette”指定调色盘类型,在统计使用颜色的数量这一场景,使用默认调色盘即可。
|
||||
|
||||
转换之后,你可以通过show()函数直接查看转换后的图片,并且会把操作后的图片原图和对比发现,这张画的主体颜色仍然保持不变,但是会加快接下来的图片处理速度。
|
||||
|
||||
### 对出现最多的五种颜色索引进行排序
|
||||
|
||||
接下来,我们就要**提取图片中被用到最多的5个颜色索引**了。在这一步中,我们需要把8位图像的颜色索引都提取出来,并对每个索引的使用次数进行统计和排序。
|
||||
|
||||
这两项工作我们可以**使用Pillow库的getcolors()方法和sorted()内置函数**来实现,我先把代码贴出来,然后再来解释。
|
||||
|
||||
```
|
||||
# 图像中使用的颜色列表,maxcolors默认256
|
||||
color_counts = sorted(image_p.getcolors(maxcolors=9999), reverse=True)
|
||||
|
||||
```
|
||||
|
||||
这行代码中的“color_counts”保存了从多到少排序之后的颜色索引,其中getcolors()方法会以嵌套元组的形式显示“image_p”的颜色索引使用的次数,以及颜色的索引值。
|
||||
|
||||
每个颜色的次数和值组成一个元组,这些元组被包含在一个更大的元组中。而这个嵌套的元组,刚好可以通过sorted()按照从大到小的顺序排序,就得到了图片中哪个色块被使用的区域最多,也就是图片中的出现最多的颜色了。
|
||||
|
||||
这里有一点需要你特别注意,由于getcolors()方法默认是被拿来处理8位图像的,因此它的参数maxcolors默认为256,即只能对索引小于等于256的索引进行正确处理。如果颜色索引超过了256,则getcolors()方法会返回“None”,如果你在其他的应用场景中,使用到了24位图像操作,需要手动指定maxcolors的值为更大的数量。
|
||||
|
||||
通过对索引从大到小排序,我们就能知道哪些颜色在图片中被使用得多,哪个颜色在图片中被使用得少。最重要的是,我们可以根据索引在调色板中取得颜色真正的RGB值,比如你可以通过RGB值来让PPT模仿图片的颜色。我把PPT中设置RGB值的对话框贴在下面,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ae/64/ae0881eaee48a24322850e7b2b3a6064.png" alt="">
|
||||
|
||||
### 查表取出RGB的颜色
|
||||
|
||||
图片中的颜色索引和RGB值是通过调色板建立对应关系的,这意味着索引不能直接用于PPT等图像软件进行颜色设定,必须转换为RGB颜色,才能对PPT等软件进行颜色设置。所以,接下来我来给你讲一下怎样通过索引取得RGB颜色的思路和代码。
|
||||
|
||||
调色板记录的颜色是按照“RGBRGBRGB......”的顺序记录的,每三个为一组。当你通过索引取得调色板的RGB颜色,就可以使用“下标**3”得到“R”,“下标**3+1”得到“G”,“下标*3+2”得到“B”。这样就可以得到该索引对应的RGB颜色值了。
|
||||
|
||||
在莫奈这幅画中,我将提取图像中被使用最多的前五个索引,并通过调色板提取它们的RGB颜色 。代码如下:
|
||||
|
||||
```
|
||||
# 以列表形式返回图像调色板
|
||||
palette = image_p.getpalette()
|
||||
|
||||
# 通过颜色列表查找到真正的颜色
|
||||
colors = []
|
||||
for i in range(5):
|
||||
palette_index = color_counts[i][1]
|
||||
dominant_color = palette[palette_index * 3 : palette_index * 3 + 3]
|
||||
colors.append(tuple(dominant_color))
|
||||
|
||||
# 输出颜色
|
||||
print(colors)
|
||||
# [(204, 154, 86), (230, 237, 226), (213, 213, 212), (251, 238, 206), (82, 167, 204)]
|
||||
|
||||
```
|
||||
|
||||
这段代码的colors变量,就是用来得到图片中被使用最多像素的颜色的方法。同时,我再来为你解释一下这段代码,是怎么通过下标得到具体颜色的。
|
||||
|
||||
首先,getpalette()是图像调色板的列表形式,我们可以通过下标取得列表中指定的RGB颜色。
|
||||
|
||||
接下来,color_counts[i][1]是下标的索引,i的值从0到4,表示图片中颜色从多到少排序的前五个索引。由于color_counts元组包含两个元素,分别是该索引在图片中的次数和索引的值,所以我就使用color_counts[0][1],来取得当前图片中哪个像素的颜色索引被使用得最多,以及这个颜色对应的具体的值。
|
||||
|
||||
最后,我们通过“palette[索引**3 : 索引**3+3]”的形式,得到该索引对应的RGB值为“(204, 154, 86)”,再把五个索引依次转换为五个RGB颜色之后,加入到colors列表中。那么colors列表的五个元素,就是《日出·印象》这幅画用得最多的五种颜色。
|
||||
|
||||
当我把colors列表中的五个颜色RGB值提取出来后,就可以直接用于颜色设置了。接下来我继续利用Pillow把这五个颜色绘制到图片中。
|
||||
|
||||
### 将色块与图片整合
|
||||
|
||||
在把取出来的五种颜色绘制到图片中这一步,其实我们要做的是把色块与图片进行整合。我来解释一下这个操作。因为我们取出来的是RGB值,这些数字不够直观,那么最好的办法是把数字作为图片,和原画放在一起进行比较,看看提取的颜色是不是原画出现最多的颜色,而利用出现最多的颜色,我们就能模仿大师的配色了
|
||||
|
||||
要想把色块和图片整合,需要使用Pillow的paste()方法,它能在指定的图片位置按照指定的RGB颜色与原图片合并。
|
||||
|
||||
为了让你更直观地看到这幅画中用得最多的五种颜色,我把它们制作成 100*100 像素的正方形,放在原始图像上,方便你和原画进行比较。代码如下:
|
||||
|
||||
```
|
||||
for i, val in enumerate(colors):
|
||||
image.paste(val,(0+i*120, 0 ,100+i*120, 100))
|
||||
|
||||
# 保存并显示图片
|
||||
image.save("./文章26代码/sunrise2.jpg")
|
||||
image.show()
|
||||
|
||||
```
|
||||
|
||||
这段代码展示了合并后的图像,并把图像保存成“sunrise2.jpg”文件。
|
||||
|
||||
在这段代码中,最主要的功能是就是使用“paste()”方法按照颜色和位置进行图像绘制。这幅画使用最多的五个颜色是使用for循环迭代,从colors变量取出的,为了避免每次与图片合并的正方形色块被覆盖,我把它的输出位置每执行一次向右侧移动120个像素。所以我使用了“enumerate(colors)”形式读取colors变量的值。在遍历过程中,i的值是从0开始,每执行一次i的值+1,val的值每次为colors迭代的RGB颜色元组。
|
||||
|
||||
代码最后两行是对新生成的图片进行保持保存,以及在当前运行界面显示图片的操作。通过观察图片,我们发现提取的五个颜色刚好和主色调吻合。
|
||||
|
||||
我把合并好的图片和完整代码放在下面,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/07/4c/079d7c9459a9cd5770919e2923eab54c.png" alt="">
|
||||
|
||||
```
|
||||
from PIL import Image
|
||||
# pip3 instlal pillow
|
||||
|
||||
# 打开图片文件
|
||||
image = Image.open("./文章26代码/sunrise.jpg")
|
||||
|
||||
# 模式P为8位深度图像
|
||||
image_p = image.convert(
|
||||
"P", palette=Image.ADAPTIVE
|
||||
)
|
||||
# image_p.show()
|
||||
|
||||
# 以列表形式返回图像调色板
|
||||
palette = image_p.getpalette()
|
||||
|
||||
# 图像中使用的颜色列表,maxcolors默认256
|
||||
color_counts = sorted(image_p.getcolors(maxcolors=9999), reverse=True)
|
||||
|
||||
# 通过颜色列表查找到真正的颜色
|
||||
colors = []
|
||||
for i in range(5):
|
||||
palette_index = color_counts[i][1]
|
||||
dominant_color = palette[palette_index * 3 : palette_index * 3 + 3]
|
||||
colors.append(tuple(dominant_color))
|
||||
|
||||
# 输出颜色
|
||||
print(colors)
|
||||
# [(204, 154, 86), (230, 237, 226), (213, 213, 212), (251, 238, 206), (82, 167, 204)]
|
||||
for i, val in enumerate(colors):
|
||||
image.paste(val,(0+i*120, 0 ,100+i*120, 100))
|
||||
|
||||
# 保存并显示图片
|
||||
image.save("./文章26代码/sunrise2.jpg")
|
||||
image.show()
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
最后,让我来为你总结一下本讲的主要内容。在本讲中,我通过Pillow带你学习了如何提取图片中出现最多的像素,并将像素的RGB值进行打印的功能。
|
||||
|
||||
通过对提取名画的颜色主题功能,你还可以将提取颜色应用于商业模版,或任何你感兴趣的颜色搭配上。利用更讨喜的颜色搭配,来为你的PPT增色添彩。
|
||||
|
||||
本讲中除了让你掌握如何使用Python对图像操作外,更想让你明白,在计算机中对很多非数字化的元素,也是可以进行计算的,当然计算的前提是将它们转化成可量化的数字,像是深度学习领域中,对语音、文字、图像的处理,都是以将它们数字化为前提进行的。因此当你遇到非数字化的对象,需要进行自动化操作时,也可以使用将非数字化转化为数字化的思路去解决办公难题。
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题,如果有多张图片需要提取其中的主要颜色,你怎样自动化实现它们的颜色提取和文件保存操作呢?
|
||||
|
||||
欢迎把你的思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。
|
||||
@@ -0,0 +1,230 @@
|
||||
<audio id="audio" title="27|zipfile压缩库:如何给数据压缩&加密备份?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d8/08/d82cb048fd6c33a1ce5aa829a524ed08.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
你在日常工作中,肯定和压缩文件打过交道,它们能把文件夹制作成一个体积更小的压缩文件,不仅方便数据备份,还方便作为邮件附件来传输,或者与他人共享。
|
||||
|
||||
但是如果你需要每天都进行数据备份,或者把压缩包作为每天工作的日报发送给领导,你肯定希望它能自动化的压缩。面对这个需求,我们同样可以通过python来解决。我们可以用Python来自动压缩文件夹,并为压缩包设置密码,保证备份数据的安全。
|
||||
|
||||
在Python中,要想实现数据的压缩,一般可以采用基于标准库zipfile的方式来实现,也可以采用命令行方式来实现。
|
||||
|
||||
当我们希望能够用Python自动压缩一个无需密码保护的文件夹时,可以通过zipfile来实现,它的好处是使用简单,而且不用安装任何的软件包,就能制作出“zip”格式的压缩包。不过zipfile没法对压缩文件进行加密,因此当你需要对压缩文件加密时,还需要调用可执行命令。
|
||||
|
||||
这两种实现方式就是我们今天要学习的重点了,接下来我们分别看一下这两种方式的具体操作方法。
|
||||
|
||||
## 使用zipfile实现无密码压缩
|
||||
|
||||
如果我想要把“C:\data\”文件夹压缩为“当前日期.zip”文件,就可以使用**目录遍历、按日期自动生成压缩包的文件名、<strong><strong>把**</strong>文件夹写入压缩文件**<strong>这**</strong>三个步骤来实现。</strong>
|
||||
|
||||
### 目录遍历
|
||||
|
||||
**我们先来学习<strong><strong>怎么实现**</strong>目录遍历功能</strong>。我在第16讲已经为你讲解过它的技术细节了,这里我就继续使用os库来实现目录的遍历。
|
||||
|
||||
由于目录遍历的功能与其他功能之间的调用关系耦合得比较宽松,所以我就把目录遍历功能单独定义成一个getAllFiles()函数,并把要遍历的目录作为函数的参数,把该目录下的所有文件以及所在路径作为函数的返回值。
|
||||
|
||||
我把getAllFiles()函数的代码放在下方,供你参考。
|
||||
|
||||
```
|
||||
import os
|
||||
|
||||
# 遍历目录,得到该目录下所有的子目录和文件
|
||||
def getAllFiles(dir):
|
||||
for root,dirs,files in os.walk(dir):
|
||||
for file in files:
|
||||
yield os.path.join(root, file)
|
||||
|
||||
```
|
||||
|
||||
细心的你一定发现了,在函数getAllFiles()的返回语句中,我使用yield语句代替了之前学习过的return语句返回文件路径和名称。为什么我要使用yield语句呢?
|
||||
|
||||
原因就在于,**<strong>一个函数如果使用yield语句来返回的话,这个函数则被称作生成器**</strong>。yield的返回数据类型以及对类型的访问方式,都和return不同。我来为你解释一下yield和return的具体区别,以及使用yield的好处。
|
||||
|
||||
首先从返回类型来看,yield返回的数据类型叫做生成器类型,这一类型的好处是调用getAllFiles()一次,函数就会返回一个文件路径和文件名。而return返回的是一个列表类型,需要一次性把要备份目录下的所有文件都访问一次,一旦要备份的文件数量非常多,就会导致计算机出现程序不响应的问题。
|
||||
|
||||
除了返回类型,还有调用方式也和return不同。使用yield返回的对象被称作生成器对象,该对象没法像列表一样,一次性获得对象中的所有数据,你必须使用for循环迭代访问,才能依次获取数据。
|
||||
|
||||
此外,当所有的数据访问完成,还会引发一个“StopIteration”异常,告知当前程序,这个生成器对象的内容已经全部被取出来,那么这个生成器将会在最后一次访问完成被计算机回收,这样yield就能够知道对象是否已经全部被读取完。
|
||||
|
||||
从yield和return的行为对比,可以说,yield返回对象最大的好处是可以逐个处理,而不是一次性处理大量的磁盘读写操作,这样就有效减少了程序因等待磁盘IO而出现不响应的情况。这就意味着你不必在调用getAllFiles()函数时,因为需要备份的文件过多,而花费较长的时间等待它执行完成。
|
||||
|
||||
### 按日期自动生成压缩包的文件名
|
||||
|
||||
**接下来我们来学习一下按日期自动生成压缩包的函数**genZipfilename()。按日期生成文件名,在定时备份的场景中经常被用到,我们希望每天产生一个新的备份文件,及时保存计算机每天文件的变化。
|
||||
|
||||
这就要求今天的备份的文件名称不能和昨天的同名,避免覆盖上次备份的文件。
|
||||
|
||||
所以genZipfilename()函数就把程序执行的日期作为文件名来进行备份,例如当前的日期是2021年4月12日,那么备份文件会自动以“20210412.zip”作为文件名称。我把代码贴在下方,供你参考。
|
||||
|
||||
```
|
||||
import datetime
|
||||
|
||||
# 以年月日作为zip文件名
|
||||
def genZipfilename():
|
||||
today = datetime.date.today()
|
||||
basename = today.strftime('%Y%m%d')
|
||||
extname = "zip"
|
||||
return f"{basename}.{extname}"
|
||||
|
||||
```
|
||||
|
||||
在这段代码中,“datetime.date.today()”函数能够以元组格式取得今天的日期,不过它的返回格式是元组,且年、月、日默认采用了三个元素被存放在元组中,这种格式是没法直接作为文件名来使用的。因此你还需要通过strftime()函数把元组里的年、月、日三个元素转换为一个字符串,再把字符串作为文件的名称来使用。
|
||||
|
||||
### **把文件夹写入压缩文件**
|
||||
|
||||
**最后,准备工作都完成之后,你就可以使用zipfile库把要备份的目录写入到zip文件了**。zipfile库是Python的标准库,所以不需要安装软件包,为了让这个完整脚本都不需要安装第三方软件包,我在实现文件遍历的时候同样采用os库代替pathlib库。
|
||||
|
||||
除了不需要安装之外,zipfile库在使用上也比较友好,它创建和写入zip文件的方式就是模仿普通文件的操作流程,使用with关键字打开zip文件,并使用write()函数把要备份的文件写入zip文件。
|
||||
|
||||
所以通过学习一般文件的操作,你会发现Python在对其他格式的文件操作上,都遵循着相同的操作逻辑,这也体现出Python语言相比其他语言更加优雅和简单。
|
||||
|
||||
那么我把使用zipfile库实现创建zip文件的功能写入zipWithoutPassword()函数中,你可以对照一般文件的写入逻辑来学习和理解这段代码,代码如下:
|
||||
|
||||
```
|
||||
from zipfile import ZipFile
|
||||
|
||||
def zipWithoutPassword(files,backupFilename):
|
||||
with ZipFile(backupFilename, 'w') as zf:
|
||||
for f in files:
|
||||
zf.write(f)
|
||||
|
||||
```
|
||||
|
||||
对比一般的文件写入操作,zip文件的打开使用了“ZipFile()函数”,而一般文件的打开使用了open函数。写入方法与一般文件相同,都是调用“write()”函数实现写入。
|
||||
|
||||
这三个函数,也就是函数getAllFiles()、genZipfilename()和zipWithoutPassword(),就是把备份目录到zip文件的核心函数了。我们以备份“C:\data”文件夹为“20210412.zip”压缩文件为例,依次调用三个函数就能实现自动备份目录了,我把调用的代码也写在下方供你参考。
|
||||
|
||||
```
|
||||
if __name__ == '__main__':
|
||||
# 要备份的目录
|
||||
backupDir = r"C:\data"
|
||||
# 要备份的文件
|
||||
backupFiles = getAllFiles(backupDir)
|
||||
# zip文件的名字“年月日.zip”
|
||||
zipFilename = genZipfilename()
|
||||
# 自动将要备份的目录制作成zip文件
|
||||
zipWithoutPassword(backupFiles, zipFilename)
|
||||
|
||||
```
|
||||
|
||||
在执行这段代码后,就会在代码运行的目录下产生“20210412.zip”文件,你通过计算机上的winrar等压缩软件查看,就会发现其中会有“C:\data”文件夹下的所有文件。由于文件名称是以当前日期自动产生的,所以每天执行一次备份脚本,就能实现按天备份指定的文件夹为压缩包了。
|
||||
|
||||
不过在备份时,除了要保证数据的可用性,你还有考虑数据的安全性,最好的办法就是在备份时为压缩包指定密码。接下来我就带你使用命令行调用实现有密码的文件压缩。
|
||||
|
||||
## 使用可执行命令实现有密码压缩
|
||||
|
||||
在制作有密码的压缩包时,我们必须使用命令代替zipfile来压缩文件,因为zipfile默认是不支持密码压缩功能的。当你需要对压缩数据有保密性的要求时,可以使用7zip、winrar这些知名压缩软件的命令行进加密压缩。
|
||||
|
||||
我在本讲中就以7zip压缩工具为例,带你学习一下怎么使用Python通过命令行方式调用7zip实现文件的加密压缩。
|
||||
|
||||
### 执行方式和执行参数
|
||||
|
||||
要想使用7zip实现压缩并被Python直接调用,你除了需要在Windows上安装7zip外,还需要知道它的**执行方式和执行的参数。**
|
||||
|
||||
**我先来带你学习一下执行方式。<strong>7zip软件Windows安装成功后,它的命令行可执行程序叫做“7z.exe”。但是它想要在命令行运行的话,需要指定程序的完整路径。例如:“c:\path\to\installed\7zip\7z.exe”。如果你希望在命令行直接输入“7z.exe”运行,需要你把可执行程序放在命令搜索路径中。我在这里有必要为你解释一下**命令搜索路径</strong>的概念,有助于你以后在各种操作系统上执行命令行工具。
|
||||
|
||||
一条命令要想运行,必须要使用**路径+可执行文件的名称**才可以。例如我Windows中,需要把Python的可执行命令“python.exe”安装到“C:\python3.8\scripts\python.exe”这一位置。
|
||||
|
||||
那么,一般情况下当你需要运行Python解释器时,必须输入很长的路径。这种做法在经常使用命令行参数时没法接受的,一个是你需要记住大量命令的所在路径,另一个是较长的路径也会降低你的执行效率。
|
||||
|
||||
因此在各种操作系统上,都有“命令搜索路径”的概念。在Windows中,命令搜索路径被保存在Path环境变量中,Path变量的参数是由分号分隔开的文件夹,即:当你在命令行输入“python.exe”并回车运行它时,操作系统会遍历Path变量参数中的每个文件夹。如果找到了“python.exe”文件,就可以直接运行它,如果没有找到,则会提示用户该命令不存在。这就避免你每次执行一条命令时都需要输入较长的路径。
|
||||
|
||||
再回到7zip的命令行执行文件“7z.exe”上,我把它安装在“C:\7zip\”文件夹下,如果你希望执行运行7z.exe,且不输入路径,那么根据上面的分析,现在有两种解决办法。
|
||||
|
||||
1. 把7z.exe放到现有的命令搜索路径中,例如“C:\python3.8\scripts\”文件夹。
|
||||
1. 把7z.exe所在的文件夹“C:\7zip\”加入到命令搜索路径Path变量的参数中。加入的方法是在Windows的搜索栏搜索关键字“环境变量,然后在弹出的环境变量菜单,把路径加入到Path变量参数即可。
|
||||
|
||||
设置完成环境变量后,7z.exe就不必在命令行中输入路径,直接运行即可。
|
||||
|
||||
在你掌握了执行方式后,我再来带你学习一下它的参数,要想使用支持密码加密方式的zip压缩包,你需要使用四个参数,它们分别是:
|
||||
|
||||
1. a参数:7z.exe能够把文件夹压缩为压缩包,也能解压一个压缩包。a参数用来指定7z将要对一个目录进行的压缩操作。
|
||||
1. -t参数:用来指定7z.exe制作压缩包的类型和名称。为了制作一个zip压缩包,我将把该参数指定为-tzip,并在该参数后指定zip压缩包的名称。
|
||||
1. -p参数:用来指定制作的压缩包的密码。
|
||||
1. “目录”参数:用来指定要把哪个目录制作为压缩包。
|
||||
|
||||
如果我希望把压缩包“20210412.zip”的密码制作为“password123”,可以把这四个压缩包的参数组合在一起,使用如下命令行:
|
||||
|
||||
```
|
||||
7z.exe a -tzip 20210412.zip -ppassword123 C:\data
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 扩展zipfile
|
||||
|
||||
由于命令的参数较多,且记住它的顺序也比较复杂,所以我们可以利用Python的popen()函数,把“7z.exe”封装在Python代码中,会更容易使用。
|
||||
|
||||
因此我在无密码压缩的代码中,就可以再增加一个函数zipWithPassword(),用来处理要压缩的目录、压缩文件名和密码参数,并通过这个函数,再去调用popen()函数,封装命令行调用7z.exe的代码,从而实现有密码的压缩功能。代码如下:
|
||||
|
||||
```
|
||||
import os
|
||||
def zipWithPassword(dir, backupFilename, password=None):
|
||||
cmd = f"7z.exe a -tzip {backupFilename} -p{password} {dir}"
|
||||
status = os.popen(cmd)
|
||||
return status
|
||||
|
||||
|
||||
```
|
||||
|
||||
我来解释一下这段代码。在实现有密码压缩的函数中,为了调用函数更加方便,我把“压缩的文件夹、zip文件名称、密码”作为该函数的参数,这样当在你调用zipWithPassword()函数时,就能指定所有需要加密的文件和目录了。此外,在执行命令时,我还通过os.popen()函数产生了一个新的子进程(如果你不记得这个概念,可以参考第五讲)用来执行7z.exe,这样7z.exe会按照函数的参数,把文件夹压缩成zip文件并增加密码。
|
||||
|
||||
通过zipWithPassword()函数,你就能够实现zipfile的扩展,实现有密码文件压缩功能了。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,我来为你总结一下今天这节课的主要内容。我通过zipfile库和7zip软件,分别实现了无密码压缩文件和有密码压缩文件。
|
||||
|
||||
无密码压缩文件更加简单方便,而有密码压缩文件更加安全,配合自动根据当前日期改变压缩文件名称,可以作为你进行每日数据自动化备份的主要工具。
|
||||
|
||||
除了备份功能的学习外,我还为你讲解了新的函数返回方式yield,和return不同的是,yield返回的是生成器对象,需要使用for迭代方式访问它的全部数据。yield语句除了可以和zipfile库一起实现数据备份外,还经常被应用于互联网上的图片批量下载压缩场景中。
|
||||
|
||||
以上内容就是怎么实现无密码和有密码压缩的全部内容了,我将完整代码贴在下方中,一起提供给你,你可以直接修改需要备份的目录,完成你自己文件夹的一键备份脚本。
|
||||
|
||||
```
|
||||
from zipfile import ZipFile
|
||||
import os
|
||||
import datetime
|
||||
|
||||
# 以年月日作为zip文件名
|
||||
def genZipfilename():
|
||||
today = datetime.date.today()
|
||||
basename = today.strftime('%Y%m%d')
|
||||
extname = "zip"
|
||||
return f"{basename}.{extname}"
|
||||
|
||||
# 遍历目录,得到该目录下所有的子目录和文件
|
||||
def getAllFiles(dir):
|
||||
for root,dirs,files in os.walk(dir):
|
||||
for file in files:
|
||||
yield os.path.join(root, file)
|
||||
|
||||
# 无密码生成压缩文件
|
||||
def zipWithoutPassword(files,backupFilename):
|
||||
with ZipFile(backupFilename, 'w') as zf:
|
||||
for f in files:
|
||||
zf.write(f)
|
||||
|
||||
def zipWithPassword(dir, backupFilename, password=None):
|
||||
cmd = f"7z.exe a -tzip {backupFilename} -p{password} {dir}"
|
||||
status = os.popen(cmd)
|
||||
return status
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 要备份的目录
|
||||
backupDir = "/data"
|
||||
# 要备份的文件
|
||||
backupFiles = getAllFiles(backupDir)
|
||||
# zip文件的名字“年月日.zip”
|
||||
zipFilename = genZipfilename()
|
||||
# 自动将要备份的目录制作成zip文件
|
||||
zipWithoutPassword(backupFiles, zipFilename)
|
||||
# 使用密码进行备份
|
||||
zipWithPassword(backupDir, zipFilename, "password123")
|
||||
|
||||
```
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题,如果需要备份的是两个甚至更多的目录,你会怎么改造脚本呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
@@ -0,0 +1,336 @@
|
||||
<audio id="audio" title="28|Celery库:让计算机定时执行任务,解放人力" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/41/fe/41a01cbe2a0aed5f07284f334a3007fe.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
上一讲我教你怎么把一个文件夹自动打包成压缩包,学会之后,你肯定会想到可以利用这个功能,把每日工作报告的文件夹制作成压缩包,作为邮件的附件发送给领导。
|
||||
|
||||
那像是每日发送邮件、每周定时提醒、每月填写报表等等,类似这些按照某一周期完成的重复性任务,你肯定希望也能用Python来自动化完成它们,再也不用设置闹钟,提醒自己手动发送邮件和手动提交周报了。
|
||||
|
||||
那么今天我就教你如何使用Windows任务计划和Python的Celery库实现周期任务的自动执行。
|
||||
|
||||
## 定时执行任务的注意事项
|
||||
|
||||
说起计算机执行任务的方式,我们一般分为两种,分别是**手动执行和定时执行**,
|
||||
|
||||
定时执行任务,在自动化上的要求要比手动执行任务更高,所以两者在程序执行方式和输出结果上有较大的区别。为了让你能正确运行定时任务,我先来带你区分两种执行方式上有什么差别。
|
||||
|
||||
首先,执行任务的对象不同。这一点很好理解。手动执行的任务往往是人为操作的,而定时执行的任务启动往往是计算机按照我们设定的时间自动发起的。
|
||||
|
||||
其次,**基于不同的执行对象,执行过程也有不同的要求**。手动执行一个任务时,那么任务弹出的对话框、提示信息等中断任务执行过程的消息,可以人工处理。
|
||||
|
||||
而定时运行的任务,由于是由计算机发起的,因此不会有人工来处理。所以定时执行的任务一定要把代码编写成从任务开始到结束都不需要和“人”进行交互,简而言之,定时任务运行的程序必须是“非交互程序”。
|
||||
|
||||
最后,定时运行的程序是不支持把任务的结果输出到字符终端的,因此如果需要定时执行任务,并输出执行结果的话,需要把输出结果保存在文件中,否则定时任务会执行失败,导致该定时任务不会被计算机执行。
|
||||
|
||||
以上是手动执行任务和定时执行任务的差别,由于我们一般接触的都是手动执行,那么第一次接触定时执行任务时,就要避免把“交互式的程序,需要将执行结果输出到终端的程序”,直接放在定时任务中运行。
|
||||
|
||||
在你了解了定时执行任务的方式和注意事项后,我就来带你学习怎么基于Windows的任务计划和Python的Celery库实现定时任务。
|
||||
|
||||
需要说明的是,Windows的任务计划只能支持在Windows操作系统且在当前计算机定时运行任务,而且采用了图形界面配置定时任务的时间,比其他的任务计划工具操作更简。
|
||||
|
||||
但是如果你要是有多个计算机需要同时配置定时任务,又不想逐个计算机手动配置,那么使用Celery会比Windows任务计划更适合。
|
||||
|
||||
## 使用Windows任务计划,执行定时任务
|
||||
|
||||
首先,我先带你学习如何使用“Windows 任务计划”,实现定时任务。
|
||||
|
||||
当你的定时任务只需要在一台计算机运行,我建议你使用Windows系统自带的“任务计划”实现定时任务,步骤简单、直观,通过三个操作步骤就可以完成。
|
||||
|
||||
第一步,自然是**启动任务计划程序**了。你可以在两个地方找到它的启动入口:
|
||||
|
||||
- 一个是“管理工具” 里的“任务计划程序”图标;
|
||||
- 另一个是“控制面板”的“计划任务”图标。
|
||||
|
||||
启动以后的界面如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f9/22/f9cac554c59659e6ef5a2af1e195dd22.png" alt="">
|
||||
|
||||
界面显示了当前已经在运行的定时任务,你可以点击“名称”来修改它们被执行的时间。如果你需要新建一个定时任务,还可以点击界面右侧的“创建基本任务”按钮。
|
||||
|
||||
第二步,“**创建基本任务**”,指的是设置任务的名称、执行时间,以及要执行的是哪一类程序。
|
||||
|
||||
其中“任务名称”是用来在“任务计划程序”界面区分多个任务的,如上图。而设定任务在什么时间执行的功能,叫做“触发器”,它可以按照你期望程序重复的周期,设置为“每天、每周、每月”以及更详细的时间运行。
|
||||
|
||||
我把“触发器”的设置截图放在下方,帮你更直观地理解定时任务的设置方式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c2/c8/c2624d0955106fd662abc4a68262f2c8.png" alt=""><br>
|
||||
除了“每天、每月、每周”等比较宽泛的设置,你还可以通过点击“下一步”进行更具体的执行时间设置,比如我希望每周一至周五的00:09:01执行任务,就可以按照下图的方式进行设置。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2a/60/2a7f37917dda07e0a2cb1d5293717460.png" alt=""><br>
|
||||
在截图中,我勾选了周一至周五的复选框,并把开始时间设置为0点09分01秒。
|
||||
|
||||
第三步,**设置指定时间运行的任务**。定时执行的任务可以是一个可执行程序,也可以是终端的一条命令。你只需输入可执行文件或命令的完整路径,以及要执行的文件名称就可以了。点击确认后,就可以按你设定的时间运行了。我把截图贴在下面供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c3/32/c38c3c6cd45196dbca26673f4ddc3932.png" alt="">
|
||||
|
||||
由于Windows的“任务计划”使用了图形界面,所以操作简单,容易上手。但是你在学习它的“实现步骤”时,也会发现**跨计算机运行的问题**。
|
||||
|
||||
跨计算机运行分为相同操作系统和不同操作系统两种情况:
|
||||
|
||||
- 在相同操作系统下,要想编写定时任务,你需要给每台计算机编写一次“任务计划”程序,一旦你的定时任务时间基于新的需求发生了改变,你就需要去手动修改逐台计算机;
|
||||
- 针对不同操作系统,你还要掌握Linux和Mac操作系统上定时任务的不同写法。一旦计划任务多起来,修改计划任务也会影响你的工作效率。
|
||||
|
||||
鉴于这些情况,你可以使用Python的Celery库实现分布式的定时任务,你只需配置一次定时任务,就能让它在多台计算机上同时运行,而且当你的任务执行时间发生变化时,只需要修改一次,所有计算机和该时间关联的任务都会同时更新,解决了windows任务计划需要对每台计算机逐个设置定时任务的问题。接下来我就为你讲解如何实现Python的Celery库实现定时任务。
|
||||
|
||||
## 使用Python的Celery执行定时任务
|
||||
|
||||
按照惯例,我还是先来带你学习Celery的安装,再带你学习如何使用它设置定时任务。
|
||||
|
||||
### 安装
|
||||
|
||||
Celery的安装,相较于之前我们学习的第三方库,要稍微复杂些。你需要在安装前,先给Celery安装一个用于处理定时任务队列的数据库软件Redis,因为Celery库本身是没有任务排队功能的,所以就需要借用Redis等数据库来实现任务队列。换言之,Celery的安装包括两个步骤,分别是安装Celery库和安装并启动Redis数据库。
|
||||
|
||||
**安装Celery库**:Celery的安装包和软件同名,使用pip3命令安装即可。这里需要说明的是,Celery库和Redis的安装先后顺序不用作区分。
|
||||
|
||||
**安装并启动Redis数据库**:Redis可以从“[https://github.com/MicrosoftArchive/redis/releases](https://github.com/MicrosoftArchive/redis/releases)”地址下载,根据提示多次执行“下一步”即可安装成功。安装成功后,打开Redis的安装文件夹,找到“redis-server.exe”双击,即可启动Redis服务端,用于Celery存储计划任务。
|
||||
|
||||
这里需要补充的是,在Mac系统下,你可以利用brew工具,使用“brew install redis” 进行安装,并运行“brew services start redis” 来启动Mac版本的Redis服务端。
|
||||
|
||||
安装成功后,我们就可以开始为Celery配置定时任务了。不过为了让你更深刻地理解和掌握Celery定时任务的代码,我先带你学习一下Celery和Redis内部的工作过程,Celery和我们以前学习的库不同的是,它已经替我们实现了定时任务的执行和任务管理功能,你只需要像搭积木一样,把需要运行的任务和任务的时间与Celery组合即可,我们先来看看Celery已经实现的四个组件以及它们的工作过程。
|
||||
|
||||
我把Celery和Redis之间的定时任务工作过程画成一张图,其中包括Celery的 Beat、Broker、Worker和Backend四个组件。<br>
|
||||
<img src="https://static001.geekbang.org/resource/image/27/12/27f5c8802a2741169c40c8c832cf2812.png" alt=""><br>
|
||||
前三个是Celery用于执行任务的组件,由Celery软件自身实现。其中Broker是中间件,用来连接Beat和多个Worker组件,Beat作为任务的生产者、Worker作为任务的消费者,可以通过Broker进行通信,从而定时运行Worker组件的多个任务。
|
||||
|
||||
最后一个是Celery的数据存储组件,基于Redis实现。在定时任务场景中,Redis用于存放用户添加的任务计划到数据库中,并把数据库文件保存到磁盘,这样前三个组件所在的进程即使遇到意外故障,重新运行,也不会造成定时任务的丢失。
|
||||
|
||||
此外,Celery和Redis数据还是通过TCP网络协议连接的,所以你可以给多个定时任务指定一个Redis数据库,以此来保证多个计算机上的定时任务需要进行修改时,只修改一次就可以实现自动数据同步了。
|
||||
|
||||
根据对Celery执行定时任务的工作过程分析,相信你已经对它的四个组件有所了解了。那接下来我就以每周六22点01分定时备份“c:\data”文件夹为例,给你讲解一下怎么通过编写Celery四个组件的代码来定时备份数据。
|
||||
|
||||
### 定时备份文件夹
|
||||
|
||||
**实现文件夹的定时备份功能**,**需要编写代码的组件<strong><strong>主要**</strong>是Worker组件和Beat组件。</strong>
|
||||
|
||||
**Worker组件是真正执行计划任务的组件**,它执行的计划任务对象是一个函数,因此你可以把上一讲我们学习的备份功能封装成函数,由Worker组件按照存储Redis中的时间进行定时备份。
|
||||
|
||||
而**Beat组件编写的代码主要是任务的执行时间和该时间运行的任务名称。**而其他两个组件中的Broker承担的是Beat和Worker之间的调度工作,即接收Beat发送的任务,和等待Worker来取任务,因此它和存储定时任务的Backend组件不需要进行编程。
|
||||
|
||||
那接下来,我们将编写定时运行任务的代码和Worker、Beat两个组件进行整合,来实现完整的Celery定时任务。定时运行任务的代码保护了两部分功能,分别是设置Celery的定时任务日期,以及编写要运行的定时任务函数。我们首先来看一下如何设置定时任务的日期。
|
||||
|
||||
**设置定时任务日期**
|
||||
|
||||
定时任务的日期,是由指定任务名称、任务要执行的函数名称、执行时间和执行函数的参数四个部分组成的嵌套字典类型。任务名称作为字典的“key”,其他三个部分作为字典的值,并分别以“task”、“schedule”、“args”作为内部字典的“key”。
|
||||
|
||||
如果我以每周六22点01分执行test1.py文件的run1()函数编写定时任务,那么嵌套字典应该为如下格式:
|
||||
|
||||
```
|
||||
from celery.schedules import crontab
|
||||
"test1": {
|
||||
"task": "tasks.jobs.test1.run1", #执行的函数
|
||||
"schedule": crontab(minute=1, hour=22, day_of_week=6),
|
||||
"args": ()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
我来详细解释一下三个字典的value值。首先,task字典的值"tasks.jobs.test1.run1"中的“tasks.jobs”是指tasks文件夹下的jobs文件夹,tasks文件夹用来存放定时任务的脚本文件,jobs文件夹用于存放要执行的python脚本文件。
|
||||
|
||||
你肯定会问,为什么要使用这么多不同的文件夹呢?原因就在于,使用不同的文件夹有助于区分不同组件的功能。像“test1.run1”表示jobs文件夹下的test1.py文件中的run1函数,而这个函数就是Celery的Worker组件将要执行的定时任务。
|
||||
|
||||
其次,“schedule”字典的值“value”表示任务“test1”将会在何时运行。它的参数是一个crontab函数。该函数定义如下:
|
||||
|
||||
```
|
||||
crontab(minute='*', hour='*', day_of_week='*',
|
||||
day_of_month='*', month_of_year='*')
|
||||
|
||||
|
||||
```
|
||||
|
||||
crontab()函数的参数,就是用来灵活配置定时任务的执行时间的。我以minute来为你举例,它可以有四种写法:
|
||||
|
||||
1. 如果minute为默认的“*”,表示每分钟执行一次。
|
||||
1. 如果minute为具体的数字,例如10,表示将会在第10分钟运行任务。不过这里要注意的是,当你使用具体数字时需要配合其他参数一起生效。假设小时的参数为“*”,则表示每小时的第10分钟运行任务,假设小时的参数为5,则表示5点10分运行任务。
|
||||
1. 如果minute参数为“*/2” ,表示每2分钟运行一次任务。
|
||||
1. 如果minute参数为“1,3,5”,表示每小时第1、3、5分钟运行一次任务。
|
||||
|
||||
四种写法也同样可以应用于参数hour(小时)、day_of_week(星期)、day_of_month(月)、month_of_year(年),因此其他位置的参数我就不再一一为你进行讲解了。
|
||||
|
||||
最后一个参数是args,它的参数是元组, 用来指定定时运行任务时,为该任务对应的函数传递哪些参数,如果不需要传递参数,则需要保留该值为空元组“()”格式。
|
||||
|
||||
设置好定时任务的日期之后,需要继续编写该日期所需要运行的任务函数。定时运行的任务函数需要按照“task”的字典value保存在“tasks.jobs”目录中,我把目录结构的截图放在下方,帮你更直观地找到每个功能所在的文件目录。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4f/8a/4f1d0de86fb10586548f2456cc13258a.png" alt="">
|
||||
|
||||
在截图中,config.py保存了设置定时任务的代码,jobs文件夹下保存了需要运行的Python脚本文件。
|
||||
|
||||
**编写任务函数**
|
||||
|
||||
接下来,我来继续为你讲解jobs中的test1.py文件中的任务函数run1()函数是如何编写的。
|
||||
|
||||
回到我们备份data目录的例子,run1()函数中就是实现备份目录的全部功能,在定义run1()函数时,为了能够支持被Celery调用,必须在定义时为它增加一个装饰器“@app.task”。
|
||||
|
||||
装饰器就是在不改变函数原有的定义前提下,为函数运行之前和之后增加新的功能。而这里为run1()增加装饰器的目的,就是为了在函数原有功能不变的前提下,让run1()函数增加被Worker组件执行的功能。
|
||||
|
||||
类似某一天天气突然降温,你依然想要穿着昨天帅气的格子衬衫去上班时,就可以在格子衬衫外面再增加一个酷酷的皮衣,既有温度又有风度。
|
||||
|
||||
增加后的代码如下:
|
||||
|
||||
```
|
||||
app = Celery("tasks")
|
||||
|
||||
@app.task
|
||||
def run1():
|
||||
print("这里是备份data文件夹的代码")
|
||||
|
||||
```
|
||||
|
||||
代码中的“app”是Celery的实例,“@app.task”是修饰run1()函数的装饰器,当你需要为Celery增加更多需要运行的函数时,也必须使用“@app.task”对任务函数进行修饰。
|
||||
|
||||
这里还有一个小问题需要你注意,如果你需要运行的任务函数都在同一脚本文件,实例化Celery类可以放在当前脚本文件中,但是当你需要运行多个脚本文件时,Celery会被实例化多次,导致运行出现异常。
|
||||
|
||||
为了避免这一问题,你需要把“app = Celery("tasks")”实例化的代码写入到tasks目录下的“**init**.py”中。当一个目录中如果包含了“**init**.py”文件时,Python会将这个目录当作我们学习过的包来按照Python内置的语法进行处理。Python会在进入tasks包所在的文件夹是,自动运行一次“**init**.py”文件,且第二次进入也不会重复运行,只会在一个程序中执行一次。总结来说,就是包含“**init**.py”的文件夹是Python的包,包在被(导入)使用时,它的“**init**.py”文件会第一时间自动执行一次,且无论导入多少次,“**init**.py”只被Python执行一次。这种特性带来的好处是:由Python解释器保证该实例化只能运行一次,避免了一个类被多次实例带来的运行异常问题。
|
||||
|
||||
调整完成的三个代码文件内容如下:
|
||||
|
||||
```
|
||||
# __init__.py
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# 创建celery应用对象
|
||||
app = Celery("tasks")
|
||||
|
||||
# 导入celery的配置信息
|
||||
app.config_from_object("tasks.config")
|
||||
|
||||
|
||||
```
|
||||
|
||||
在这段代码中,我同样不想让Celery访问Redis数据库会进行多次初始化,因此在“**init**.py”初始化文件中,我还增加了“app.config_from_object("tasks.config")”设置项,确保同一个Celery只和Redis数据库建立一次连接,避免因多次连接带来数据库没有必要的资源开销。
|
||||
|
||||
```
|
||||
# config.py
|
||||
|
||||
from celery.schedules import crontab
|
||||
|
||||
# 指定Redis数据库的地址和端口
|
||||
broker_url = "redis://127.0.0.1:6379/1"
|
||||
|
||||
# 时区
|
||||
timezone = "Asia/Shanghai"
|
||||
|
||||
# 导入任务所在文件
|
||||
imports = [
|
||||
"tasks.jobs.test1",
|
||||
"tasks.jobs.test2",
|
||||
]
|
||||
|
||||
# 需要执行任务的配置
|
||||
beat_schedule = {
|
||||
"test1": {
|
||||
"task": "tasks.jobs.test1.run1", #执行的函数
|
||||
"schedule": crontab(minute=1, hour=22, day_of_week=6),
|
||||
"args": ()
|
||||
},
|
||||
"test2": {
|
||||
"task": "tasks.jobs.test2.run1", #执行的函数
|
||||
"schedule": crontab(minute="*"),
|
||||
"args": ()
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
为了你能更快地看到定时任务执行的结果,我增加了任务test2,实现了每分钟运行一次,因此当你执行Worker组件后,就可以在1分钟内观察到test2任务的输出。
|
||||
|
||||
```
|
||||
# test1.py
|
||||
|
||||
from tasks import app
|
||||
|
||||
@app.task
|
||||
def run1():
|
||||
print("开始备份data文件夹")
|
||||
|
||||
if __name__ == '__main__':
|
||||
tasks()
|
||||
|
||||
# test2.py
|
||||
|
||||
from tasks import app
|
||||
|
||||
@app.task
|
||||
def run1():
|
||||
print("开始备份data2文件夹")
|
||||
|
||||
if __name__ == '__main__':
|
||||
tasks()
|
||||
|
||||
|
||||
```
|
||||
|
||||
编写完成任务和设置完成执行时间后,接下来需要发布任务和执行任务。发布任务需要运行Beat组件,执行任务则需要运行Worker组件,我来带你运行它们,并观察运行的结果,以此来分析定时任务是否被成功执行。
|
||||
|
||||
**运行Beat和Worker组件**
|
||||
|
||||
运行Beat组件需要在终端使用“celery -A tasks beat”命令。其中tasks是我们保存Celery实例化的目录,beat参数表示发布的是定时运行的任务。它的正确执行结果如下:
|
||||
|
||||
```
|
||||
SHELL$ celery -A tasks beat
|
||||
celery beat v5.0.5 (singularity) is starting.
|
||||
__ - ... __ - _
|
||||
LocalTime -> 2021-04-13 01:40:31
|
||||
Configuration ->
|
||||
. broker -> redis://127.0.0.1:6379/1
|
||||
. loader -> celery.loaders.app.AppLoader
|
||||
. scheduler -> celery.beat.PersistentScheduler
|
||||
. db -> celerybeat-schedule
|
||||
. logfile -> [stderr]@%WARNING
|
||||
. maxinterval -> 5.00 minutes (300s)
|
||||
|
||||
```
|
||||
|
||||
发布任务后,需要运行定时任务的计算机可以执行对应的Worker组件,执行的命令和结果如下:
|
||||
|
||||
```
|
||||
SHELL$ celery -A tasks worker
|
||||
|
||||
-------------- celery@edzdeMacBook-Pro-2.local v5.0.5 (singularity)
|
||||
--- ***** -----
|
||||
-- ******* ---- Darwin-20.3.0-x86_64-i386-64bit 2021-04-13 01:41:47
|
||||
- *** --- * ---
|
||||
- ** ---------- [config]
|
||||
- ** ---------- .> app: tasks:0x7ff03c9549d0
|
||||
- ** ---------- .> transport: redis://127.0.0.1:6379/1
|
||||
- ** ---------- .> results: disabled://
|
||||
- *** --- * --- .> concurrency: 4 (prefork)
|
||||
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
|
||||
--- ***** -----
|
||||
-------------- [queues]
|
||||
.> celery exchange=celery(direct) key=celery
|
||||
|
||||
|
||||
[2021-04-13 01:42:00,046: WARNING/ForkPoolWorker-2] 开始备份data2文件夹
|
||||
[2021-04-13 01:43:00,004: WARNING/ForkPoolWorker-2] 开始备份data2文件夹
|
||||
[2021-04-13 01:44:00,003: WARNING/ForkPoolWorker-2] 开始备份data2文件夹
|
||||
[2021-04-13 01:45:00,004: WARNING/ForkPoolWorker-2] 开始备份data2文件夹
|
||||
|
||||
```
|
||||
|
||||
由于我把“test2”任务设置为每分钟运行一次,因此在Worker组件可以看到每分钟会显示一次提示信息。当你确认定时任务能够正常运行后,可以使用下面两条命令实现不占用终端的Worker组件启动和停止,这样的话,在你关闭终端之后,Celery的Worker依然可以正常运行。
|
||||
|
||||
```
|
||||
celery multi start worker -A appcelery
|
||||
celery multi stop worker
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
最后,我来为你总结一下本讲的主要内容。通过对比任务的执行方式,我给你讲解了定时任务需要使用非交互方式,并且不能把程序的结果输出到终端中,否则会导致在终端没法显示运行结果,或者定时任务执行异常。
|
||||
|
||||
同时我还为你讲解了基于Windows的任务计划功能和Python的Celery实现的定时任务,它们在配置定时任务上的逻辑是相同的,但是在配置方法和执行方法上有较大区别:Windows的配置更加直接,而Celery的配置则需要掌握crontab()函数的参数。
|
||||
|
||||
此外,它们支持的操作系统也不同,Windows任务计划只能在Windows中使用,而Celery可以应用于Windows、Linux和Mac操作系统,但也因为需要有丰富的扩展性和灵活性,损失了它的易用性。
|
||||
|
||||
因此,我建议你在选择类似计划任务的某些功能时,应当优先考虑场景和软件的复杂度,基于不同的场景选择合适的软件,才能最大限地提高办公自动化。
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,我要为你留一道思考题,如果我希望定时任务能帮我实现每周一、三、五的18:00提交工作报告,你需要如何设置crontab()函数呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
@@ -0,0 +1,183 @@
|
||||
<audio id="audio" title="29|网络和邮件库:定时收发邮件,减少手动操作" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4d/88/4dc142f6d6eaccbced8f3f2bc2d2f488.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
相信在你的日常办公工作当中,对邮件肯定早就不陌生了。我们通过邮件既可以发送和接收正式的公文,也能够利用邮件编写周报、月报,以及订阅定期发布的新闻或者期刊,等等。
|
||||
|
||||
在这么多的应用场景当中,你会发现有些**收发邮件的工作是周期性**的。那我们就可以利用Python,将这些重复的邮件收发工作进行自动化。
|
||||
|
||||
设想一下:在我们接收邮件的时候,我们可以定时监察邮箱中的邮件,例如根据邮件中特定的主题,来自动判断是否为重要邮件。如果是的话,可以通过Python调用钉钉等即时通讯软件马上通知自己,实现邮件的额外通知功能。
|
||||
|
||||
还有在发送邮件的场景中,如果你发现周报、月报等邮件模版是可以通用的,那你可以利用Python的字符串编写来实现邮件内容的自动替换功能,让你在使用邮件发送周报、月报工作中节约大量的时间。
|
||||
|
||||
那么今天我就教你如何使用Python的yagmail、imaplib两个库,分别实现邮件的自动发送和自动接收功能,并利用正则表达式、字符串和变量功能,来替代手工的重复工作。
|
||||
|
||||
## 自动收邮件
|
||||
|
||||
我们先从如何自动收邮件开始学习。今天的案例是这样的:我希望能每隔五分钟检查一次收件箱,判断收件箱中是否有30天内未读的邮件。并利用正则表达式根据邮件主题判断其中是否包含“故障”这一关键字。如果包含的话,就通过钉钉等即时通讯工具通知到我,实现高优先级邮件处理的功能。
|
||||
|
||||
要想利用Python的正则表达式判断邮件主题是否出现了“故障”关键字,你必须要让Python实现**邮件接收和主题读取**功能。在Python中,**poplib和imaplib库都**支持邮件的接收协议,可以让我们登陆服务器接收邮件,从而实现邮件接收和主题读取。那这两个库该选择哪一个呢?
|
||||
|
||||
imaplib库支持IMAP协议,而poplib库支持POP3协议,IMAP协议在支持双向操作的功能上更加强大,并且能把客户端对邮件的删除等操作同步到服务端,也能把服务端对邮件删除的操作同步到客户端。与POP3协议只能把服务端的操作单向同步给客户端相比,会更加灵活。所以我在本讲中,就以imaplib库为例,为你讲解通过IMAP协议进行邮件的自动接收。
|
||||
|
||||
我们在确定采用IMAP协议接收邮件之后,接下来就要按照IMAP协议的要求,编写一个从邮件服务器下载邮件并分析邮件主题的代码。获取邮件主题的代码分为三个主要步骤,分别是**指定邮件服务器的IMAP地址和端口、验证用户名和密码的正确性以及下载邮件到本地并解析邮件得到邮件主题**。我们依次来学习一下。
|
||||
|
||||
### 获取邮件主题
|
||||
|
||||
第一步是**指定邮件服务器的IMAP地址和端口**。大部分对邮件安全比较重视的公司,为了防止黑客暴力发现邮件服务器用户的弱口令密码,默认是将IMAP服务的功能关闭的。你需要联系邮件服务器管理员或通过网页管理功能打开IMAP服务,允许你在家里连接IMAP服务器。
|
||||
|
||||
我以QQ邮箱为例,打开IMAP服务的方法是在[QQ邮箱的网页端](https://mail.qq.com)登陆成功后,通过设置-账号-IMAP服务,打开IMAP/SMTP服务。打开功能后,可以参考[官方文档](https://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=331)将IMAP服务器的地址指定为:“imap.qq.com”,“使用SSL”保证数据传输过程的安全,并将连接IMAP服务器的端口指定为“993”。打开IMAP服务的截图和官方文档的截图如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8d/4b/8d41dd963723da23c2daee2c7672ba4b.png" alt=""><br>
|
||||
<img src="https://static001.geekbang.org/resource/image/5a/e6/5ac71084beb997a4251114a12ce8fde6.png" alt=""><br>
|
||||
第二步是**使用用户名密码登录**。当服务器允许你从远程使用IMAP协议登陆服务器接收邮件后,就可以使用Python的imaplib库进行连接和登陆了。
|
||||
|
||||
imaplib库是Python的内置库,连接服务器可以使用IMAP4_SSL()函数,登录可以使用login()函数,连接和登陆的代码如下:
|
||||
|
||||
```
|
||||
import imaplib
|
||||
conn = imaplib.IMAP4_SSL(host="imap.qq.com", port = 993)
|
||||
conn.login("username@qq.com","password")
|
||||
print(conn.list())
|
||||
|
||||
```
|
||||
|
||||
在这段代码中的第三行,你需要把“username和password”替换为你的用户名和密码,替换之后才能正常登陆。如果登陆成功,可以通过list()函数查看邮箱中默认包含了哪些文件夹,默认的邮件都被放在“INBOX”文件夹中,而“INBOX”就是我们经常使用的收件箱。
|
||||
|
||||
如果没有登陆成功,在运行代码后会被提示连接超时或密码错误,这个时候你就需要根据错误提示,进一步优化你的网络质量或使用正确的密码。
|
||||
|
||||
最后一步是**解析邮件主题**。当你成功登录邮件服务器之后,你并不能直接读取邮件的内容,必须要将邮件下载到本地才能对邮件内容进行解码和内容查看。这时,你如果对收件箱中的邮件进行查询,只能得到未读邮件的唯一ID,我们称它为“邮件ID”,你需要**通过IMAP的fetch()命令将邮件ID对应的邮件内容下载本地后,才能进行解码**,解码后才能真正取得邮件的主题、内容和附件等邮件里的具体内容。
|
||||
|
||||
你还要注意的是,通过邮件ID下载的邮件内容需要**解码两次,才能看到邮件的主题**。这是因为每一封邮件都采用了邮件的标准编码方式MIME编码,MIME编码可以让邮件在服务器和客户端直接实现正常的传输,但是你无法查看使用了MIME编码之后的邮件内容,因此需要先对邮件的MIME编码进行解码。
|
||||
|
||||
而第二次解码呢,是**把MIME编码的邮件解码后的内容,转换成符合你当前操作系统的编码**,否则在查看邮件主题时会出现乱码,无法使用正则表达式进行内容匹配。
|
||||
|
||||
总结来说,在Windows中,默认的编码为“GBK”编码,mac为“UTF-8”编码。你需要把内容按照Python所在的操作系统再解码一次,这样才能得到正确的邮件标题,之后才能使用正则表达式处理我们得到的邮件主题。
|
||||
|
||||
### 使用的函数
|
||||
|
||||
虽然获得邮件的主题的步骤比较繁琐,无法一次性得到邮件主题,不过你不用担心,因为**每个处理步骤中只需要使用一个函数就可以搞定**了。那么接下来我先把从进入收件箱到取得主题的完整执行过程的代码提供给你,然后再为你具体讲解每个函数的作用。
|
||||
|
||||
```
|
||||
import email
|
||||
|
||||
# 默认为INBOX
|
||||
conn.select("INBOX")
|
||||
# 搜索邮件,ALL为全部,可以按照发件人使用FROM过滤,也可以使用日期过滤
|
||||
_, data = conn.search(None, 'unseen')
|
||||
|
||||
for mailid in data[0].decode().split(" "):
|
||||
# 取回每一封未读邮件的内容
|
||||
# data = [b'1 2 3 4 5']
|
||||
_, maildata = conn.fetch(str(mailid), '(RFC822)')
|
||||
# 对每一封邮件的内容进行解析
|
||||
msg = email.message_from_string(maildata[0][1].decode('utf-8'))
|
||||
# 取得标题
|
||||
subject_tmp = msg.get('subject')
|
||||
# 为标题解码
|
||||
sj_decode = email.header.decode_header(subject_tmp)[0][0]
|
||||
#打印每一封标题
|
||||
subject = sj_decode.decode('utf-8')
|
||||
print(subject)
|
||||
|
||||
# 将邮件标记为已读
|
||||
conn.store(mailid, '+FLAGS','\\seen')
|
||||
|
||||
```
|
||||
|
||||
我来为你依次解释一下imaplib是如何读取邮件并得到邮件主题的。
|
||||
|
||||
首先,我们需要**从收件箱中找到30天内未读的邮件ID**,通过邮件的ID才能从IMAP服务器下载邮件的内容。
|
||||
|
||||
我在代码第4行,使用了select()函数,指定要读取的文件夹为收件箱“INBOX”;再利用第6行的search()函数的“unseen”参数,来取得30天内未读的邮件。这里的“INBOX和unseen”都是IMAP协议定义的关键字,Python会将它们转译为IMAP的语法,并发送给服务器,而服务器则会把30天以内未读邮件的ID以列表形式返回,并把ID以字节方式存放在data列表的第一个元素中,data列表的具体格式,你可以参考第10行注释。
|
||||
|
||||
接下来,我们需要**根据每个邮件ID得到邮件的主题**。由于邮件ID为字节类型,因此我将它转换为列表之后,使用for循环进行遍历,把每个邮件ID用mailid变量进行了保存。同时,我在第11行通过fetch()函数,使用邮件ID向服务器发起请求,得到该邮件的所有数据。
|
||||
|
||||
最后,我们**把从服务器得到的邮件内容进行解析、取出标题部分,并进行解码**。当你使用fetch()函数取得了邮件的内容后,如果使用print()进行输出,会发现你无法看到邮件里真正的内容。为什么会出现这种情况呢?
|
||||
|
||||
原因就在于邮件采用的是MIME类型,这种类型是邮件的标准格式,需要专门的工具进行内容的解析。就像你无法通过记事本查看一张图片一样,通过记事本只能看到图片中混乱的数据,无法得知图片上面的颜色和内容。因此我需要再使用一个标准库email的message_from_string()方法,对MIME类型进行解析。
|
||||
|
||||
解析之后,你就能够得到主题、内容、附件等邮件的不同部分了,由于我在当前案例需要提取邮件的主题,所以使用第15行的get()函数通过参数“subject”取得了当前邮件的头部信息,并利用decode_header()取得了邮件的主题。
|
||||
|
||||
为了能够在Mac系统上也可以进行处理,我将主题采用“utf-8”编码进行解码后,就能够正常显示汉字了。如果你想要判断该主题是否包含“故障”关键字,可以使用我们多次使用到的正则表达式,使用re.search('故障', subject)进行正则匹配,并且你还可以增加钉钉通知、短信通知、自动修复故障等等各种自动化操作。
|
||||
|
||||
在学习了自动接收邮件并对主题进行判断的功能之后,还有两点需要你注意,这也是初次使用imaplib库的同学最容易犯的两个错。
|
||||
|
||||
第一个是**如果你没有将存储在IMAP服务器上的邮件标记为已读,会导致自动接收邮件程序重复处理该邮件**。由于fetch()函数的功能是从服务器下载邮件内容,并对邮件进行自动化处理,所以服务器上的邮件状态仍然为“未读”状态。这会导致你的程序陷入死循环,对匹配的主题进行无限重复的处理。
|
||||
|
||||
为了避免这一问题,你应该在处理完当前的邮件后,使用“conn.store(mailid, '+FLAGS','\seen')”方法,将当前操作的mailid在服务器设置为已读邮件。这样每个邮件就会只被处理一次了。
|
||||
|
||||
另一个经常出现的问题是,当你的所有邮件都为已读状态时,应当在遍历邮件ID功能前增加对data变量的判断,避免向服务器提交空ID,导致运行到fetch()函数时,服务器接收空ID报错。
|
||||
|
||||
具体的操作是:你可以在得到data变量后,使用if判断该变量是否为“None”,如果为None,则本次执行到此结束。如果有未读邮件,则再将邮件ID通过fetch()提交到服务器进行处理。
|
||||
|
||||
当你已经掌握了自动接收邮件的步骤之后,再来学习自动发送邮件就非常简单了。自动发送邮件采用了SMTP协议,而且也需要指定服务器地址、用户名、密码以及收件人、主题、内容和附件。由于发邮件和接收邮件的大部分概念相同,所以我们可以对比接收邮件来学习实现自动发送邮件的步骤。
|
||||
|
||||
## 自动发邮件
|
||||
|
||||
和自动接收邮件类似,自动发送邮件的步骤也是三个,分别是连接邮件服务器、编写邮件正文和发送邮件。
|
||||
|
||||
### 连接邮件服务器
|
||||
|
||||
在邮件服务的协议规范中,规定发送邮件采用的是SMTP协议,因此,在自动发送邮件这一步,我们需要采用和imaplib不同的包实现。
|
||||
|
||||
在标准库中发送邮件的包叫做smtplib,由于smtplib需要配置较多的通用参数,所以还有一个对它进行了更高级的封装的第三方库yagmail库。yagmail库将大部分的默认参数在底层实现了,发送邮件时,你只需要关注必须填写的服务器IP、用户验证以及邮件的内容即可。
|
||||
|
||||
yagmail第三方库的安装包和它同名,那么你可以使用pip命令直接安装,安装成功后把它导入并连接服务器即可。连接SMTP服务器的代码如下:
|
||||
|
||||
```
|
||||
import yagmail
|
||||
conn = yagmail.SMTP(
|
||||
user="username@qq.com",
|
||||
password="password",
|
||||
host="smtp.qq.com",
|
||||
port=465
|
||||
)
|
||||
|
||||
|
||||
```
|
||||
|
||||
yagmail库使用SMTP()函数与服务器建立连接,并在连接时指定用户名、密码、主机地址以及端口。
|
||||
|
||||
这里需要注意的是,SMTP()函数通过默认参数“smtp_ssl=True”使用了SSL协议,如果你所使用的邮件服务器采用了不同版本的SSL传输加密协议,你需要先将默认端口从465改为587。如果没有提示连接异常,表明建立连接是成功的,接下来就可以为这封邮件编写内容了。
|
||||
|
||||
### 编写邮件正文
|
||||
|
||||
编写邮件正文时,可以采用我们学习过的f-string**字符串的形式**来存放邮件的内容。例如你经常要发送的周报、月报都是相同的邮件格式,不同的数据内容或文字。这时候可以使用f-string字符串的变量替换功能,将格式编写为f-string的字符串,再将每次变动的内容使用变量进行替换,它的代码格式如下:
|
||||
|
||||
```
|
||||
content = "内容填充"
|
||||
body = f"模版 {content}"
|
||||
|
||||
```
|
||||
|
||||
当你编写好邮件的正文后,需要使用send()函数来发送邮件。**send()函数一般会使用四个参数,按照参数定义的顺序,它们分别是收件人邮箱、主题、邮件正文和附件**。我将这四个部分依次作为send()函数的参数后,就可以将邮件发送到SMTP服务器了。
|
||||
|
||||
这里我有一个小的建议,我会建议你先把邮件发给自己,如果出现发送失败,或发送内容与自己期望不符时,更方便对内容进行调整。我把发送命令和发送成功后的截图贴在下面,供你参考。
|
||||
|
||||
```
|
||||
conn.send("receiver@qq.com", "主题", body, "one.jpg")
|
||||
|
||||
```
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/86/51/866793bf73c8ac8773ab3b49a2dba651.png" alt="">
|
||||
|
||||
这就是利用yagmail实现自动发送邮件的完整过程,掌握之后,你可以把上节课学习的定时任务,以及自动生成图形的matplotlib库结合起来使用,将发送邮件功能定义为一个函数,从而实现周报和月报的自动发送功能。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,我来为你总结一下这一讲的主要内容。在本讲,我使用了yagmail库、imaplib库以及email库实现了邮件自动收发的功能。与Foxmail和Outlook比起来,使用Python实现的邮件客户端,能够在收取邮件后对主题等元素自行判断,并与正则表达式、IM通知等其他工具组合,实现更加自动化的功能。
|
||||
|
||||
利用yagmail自动发邮件前,你还可以为你的周期发送的邮件指定模版,通过Celery实现定时发送和周期发送邮件。
|
||||
|
||||
除了可以自动收发邮件外,我还为你详细讲解了接收邮件的处理过程,这一过程遵循了IMAP的协议规范,决定了代码编写的先后顺序,**如果邮件接收或发送是你自动化工作中主要优化的工具,那么我建议你用更多的时间来掌握IMAP与SMTP协议规范**。
|
||||
|
||||
当你掌握了IMAP协议能够支持哪些操作以及不能支持哪些操作之后,才能更好地进行邮件API的学习。对于IMAP支持的功能,你可以参考官方文档,找到函数及其参数,对于没有支持的功能,你需要自己实现自定义的函数。
|
||||
|
||||
最后的最后,除了微信、钉钉外,邮件可以说是我们职场中使用最广泛的通讯工具了。并且也是我们工作中最正式的通讯工具。因此我建议你能够多练习怎么通过Python更加熟练地自动化收发邮件,相信我,这会为你的工作带来更高效的输出。
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题,如果我希望每周六10点整,能够自动的将C盘上的一个目录作为邮件的附件发送到一个指定邮箱,你会使用哪些库来实现,你能否将实现的思路用自己的语言讲出来呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
@@ -0,0 +1,209 @@
|
||||
<audio id="audio" title="30|怎么快速把任意文件格式转成PDF,并批量加水印?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/79/88/795d285a9b4659af3cb429fa50eeb488.mp3"></audio>
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
在办公场景中,我们打交道最多的软件,要数Office的办公套件了,功能丰富且强大,使用方便。不过你可能也会发现,我们经常用于文字编辑的Word软件,它使用的docx扩展名的文件无论是在不同操作系统平台、不同的设备上,还是在内容安全性和格式的兼容性上,都没有PDF文件强大。Excel和PowerPoint中的文件也是如此。
|
||||
|
||||
例如:你需要把公司的合同范本发给其他公司审阅,为了保证文本不会被随意篡改,往往需要把Word或PowerPoint的文件转换为PDF文件,再通过邮件发给其他公司。而且,随着数字化转型的开始,原本在计算机上正常显示的表格,拿到手机上可能会缺少字体;也可能因为屏幕宽度不同,导致格式无法对齐;还可能会出现无法显示文字等格式问题。
|
||||
|
||||
不过这些问题呢,PDF统统可以解决。所以像是商业的条款、合同、产品介绍,既要保证安全,又要确保格式正确,如果你不想限制用户必须要用多宽的显示器,或者必须安装好特定的字体,才能看到你的Word、Excel、PowerPoint里的内容的话,那么使用PDF文件就是非常必要的了。
|
||||
|
||||
所以在今天这一讲中,我将带你学习如何把Word、Excel、PowerPoint的默认文件格式批量转换为PDF文件,并教你怎么给PDF文件增加水印,既保证样式美观,又确保文档的安全。
|
||||
|
||||
## 将常用办公文件转换为PDF格式
|
||||
|
||||
如果你以前遇到过类似转换为PDF文件的需求,那么在你搜索Python的第三方库时,就会发现,将Word、Excel、PowerPoint 的默认文件保存格式转换为PDF的库非常多。由于我要将多种格式的文件转换为PDF,那么为了我就需要使用一个能支持Office所有组件的库,而这个库就是pywin32库。
|
||||
|
||||
pywin32库支持了绝大多数的Windows API,因此它只能运行在操作系统是Windows上,当你需要使用pywin32操作Office各个组件时,可以利用pywin32库调用Offcie组件中的VBA实现Office组件的大部分功能。
|
||||
|
||||
接下来,我将从pywin32库的安装开始,来为你逐步讲解一下如何把Office组件中的Word、Excel、PowerPoint的默认文件保存格式转换为PDF文件。
|
||||
|
||||
虽然你要学习三种不同的软件格式转换为PDF文件,但是它们三个文件格式转换的思路和被调用的函数是相同的,你可以通过对照Word文件转换为PDF来去掌握其他两个软件的文件格式转换代码,来学习自动格式转换,这样学起来会更加轻松。
|
||||
|
||||
### 将Word文档转换为PDF
|
||||
|
||||
我先以Word为例,来为你讲解一下pywin32库的安装、导入,以及将Word文档进行转换的操作步骤。
|
||||
|
||||
由于pywin32是第三方库,所以你要使用pip命令把它安装到你的Windows计算机中。这里需要注意,pywin32的安装包和软件名称不同,而且导入的库名称也和pywin32不同。所以我把pywin32库的安装和导入时,使用的名称写在文档中,供你进行参考:
|
||||
|
||||
```
|
||||
SHELL$ pip3 install pypiwin32
|
||||
PYTHON> import win32com
|
||||
|
||||
```
|
||||
|
||||
我们**用于格式转换的库**叫做**“win32com”**,它是pywin32软件的库名称。安装它时,你要使用pypiwin32,作为它的安装包名称来使用。这是第一次接触该库最容易混淆的地方,我建议你在阅读后续内容前,先对“pywin32、pypiwin32、win32com”这三个概念进行区分。
|
||||
|
||||
在你正确区分上面三个概念之后,我们就可以开始导入win32com库,并调用VBA脚本,来把Word文档转换为PDF格式了。
|
||||
|
||||
为了让你更好地理解win32com库的执行过程,我来为你举个例子。我在"C:\data"文件夹下有一个Word格式的a.doc文件,现在要将它自动转换为a.pdf文件,并继续存储在该目录中。如果你手动进行格式转换,只是需要以下四个步骤:
|
||||
|
||||
1. 进入到C:\data文件夹;
|
||||
1. 使用Office的Word组件打开 a.doc文件;
|
||||
1. 使用“文件另存为”功能,保存为PDF格式,并指定保存目录;
|
||||
1. 保存并关闭Word文件,退出Word进程。
|
||||
|
||||
由于win32com库是调用Word的VBA脚本实现的格式转换功能,因此转换格式的Python代码步骤也和手动操作Word几乎相同,少数不同的地方是因为win32com支持的组件较多,需要指定当前转换采用的VBA脚本为Word文件的脚本。我将Word转换PDF的代码写在下方,供你参考。
|
||||
|
||||
```
|
||||
from win32com import client
|
||||
|
||||
def word2pdf(filepath, wordname, pdfname):
|
||||
worddir = filepath
|
||||
# 指定Word类型
|
||||
word = client.DispatchEx("Word.Application")
|
||||
# 使用Word软件打开a.doc
|
||||
file = word.Documents.Open(f"{worddir}\{wordname}", ReadOnly=1)
|
||||
# 文件另存为当前目录下的pdf文件
|
||||
file.ExportAsFixedFormat(f"{worddir}\{pdfname}", FileFormat=17, Item=7, CreateBookmarks=0)
|
||||
# 关闭文件
|
||||
file.Close()
|
||||
# 结束word应用程序进程
|
||||
word.Quit()
|
||||
|
||||
```
|
||||
|
||||
我来为你详细解释一下上面这段代码。这段代码中我定义了一个word2pdf()函数,它被Python调用时,会根据自己的参数将word文件的路径、word文件的名称和pdf名称传入参数中。
|
||||
|
||||
根据这些参数word2pdf()函数会调用DispatchEx()打开Word程序,再使用Open()函数打开a.doc文件,并使用ExportAsFixedFormat()函数将Word文件另存为PDF文件之后,使用Close()和Quit()关闭a.doc文件并结束Word进程。
|
||||
|
||||
由于win32com是用过Word的VBA脚本实现对Word进程行为的控制的,所以它的操作步骤和手动操作非常相似,所以这段代码也非常容易理解。
|
||||
|
||||
那在这里我需要提醒你注意两个容易被忽视的问题,第一个是由于win32com库在Windows系统上是直接调用Word进程实现格式转换的,因此你必须为当前的Windows安装Word以及Office的办公组件。那另一个是由于win32com对Word操作的方式是基于Word的VBA脚本,所以你想在转换时为ExportAsFixedFormat()函数增加新的参数,需要参考Office官方网站的文档。
|
||||
|
||||
我也将Office官方网站关于Word的VBA脚本所在网页提供给你做参考([https://docs.microsoft.com/zh-cn/office/vba/api/word.document.exportasfixedformat](https://docs.microsoft.com/zh-cn/office/vba/api/word.document.exportasfixedformat))。当你增加新的功能时,可以通过网页的内容来获得更多功能。
|
||||
|
||||
### 将Excel表格转换为PDF
|
||||
|
||||
在你掌握了如何使用win32com实现Word文档转换为PDF之后,我再来带你实现Excel表格自动转换为PDF的功能,你也可以对比着来学习掌握,同时也注意观察把Excel和Word文件转换为PDF的主要差别。
|
||||
|
||||
Excel表格默认保存的文件格式是xls或xlsx,将xls或xlsx格式转换为PDF的步骤和思路与Word文档转换为PDF相同,所以我先把代码提供给你,然后再为你讲解。代码如下:
|
||||
|
||||
```
|
||||
from win32com import client
|
||||
|
||||
def excel2pdf(filepath, excelname, pdfname):
|
||||
exceldir = filepath
|
||||
# 指定Excel类型
|
||||
excel = client.DispatchEx("Excel.Application")
|
||||
# 使用Excel软件打开a.xls
|
||||
file = excel.Workbooks.Open(f"{exceldir}\{excelname}", False)
|
||||
# 文件另存为当前目录下的pdf文件
|
||||
file.ExportAsFixedFormat(0, f"{excel}dir}\{pdfname}")
|
||||
# 关闭文件
|
||||
file.Close()
|
||||
# 结束excel应用程序进程
|
||||
excel.Quit()
|
||||
|
||||
```
|
||||
|
||||
对比word2pdf()和excel2pdf(),你会发现实现的基本逻辑是相同的,但是在实现Excel转换上,有两个主要函数的参数不同。
|
||||
|
||||
1. DispatchEx()函数的参数,这里使用了“Excel.Application”字符串作为该函数的参数,“Excel.Application”作为DispatchEx()参数的目的是,让pywin32库启动Excel进程,并让它读取“a.xls”文件。
|
||||
1. ExportAsFixedFormat函数的第一个参数从pdf路径变为保存的类型,你可以参考[Office的Excel VBA官方文档](https://docs.microsoft.com/zh-cn/office/vba/api/excel.workbook.exportasfixedformat),从中学习函数的每个参数。
|
||||
|
||||
### 将PowerPoint幻灯片转换为PDF
|
||||
|
||||
在你学习了Word文档和Excel表格转换PDF文件的基础上,我再来带你你对比学习一下如何将PowerPoint的默认保存文件ppt格式转换为PDF。参考word2pdf()和excel2pdf()两个函数,我直接将PowerPoint的幻灯片转换PDF文件的代码提供给你,你可以通过官方文档([https://docs.microsoft.com/zh-cn/office/vba/api/powerpoint.presentation.exportasfixedformat](https://docs.microsoft.com/zh-cn/office/vba/api/powerpoint.presentation.exportasfixedformat)),来试着分析ppt2pdf()函数的参数和函数里的每行代码。
|
||||
|
||||
```
|
||||
from win32com import client
|
||||
|
||||
def ppt2pdf(filepath, pptname, pdfname):
|
||||
pptdir = filepath
|
||||
# 指定PPT类型
|
||||
ppt = client.DispatchEx("PowerPoint.Application")
|
||||
# 使用ppt软件打开a.ppt
|
||||
file = ppt.Presentations.Open(f"{pptdir}\{pptname}", False)
|
||||
# 文件另存为当前目录下的pdf文件
|
||||
file.ExportAsFixedFormat(f"{pptdir}\{pdfname}")
|
||||
# 关闭文件
|
||||
file.Close()
|
||||
# 结束excel应用程序进程
|
||||
excel.Quit()
|
||||
|
||||
```
|
||||
|
||||
显而易见,PowerPoint幻灯片转换为PDF文件的代码逻辑和word2pdf()和excel2pdf()函数的逻辑完全相同,只有两处有所不同,一个是DispatchEx()的参数为“PowerPoint.Application”,它的功能是让win32com库打开PowerPoint进程。另一个是ppt.Presentations.Open()打开的对象不同,它打开的是每一页的PPT,而Excel打开的是每个sheet对象。
|
||||
|
||||
以上就是如何将Office组件的Word、Excel与PowerPoint的默认保存文件格式转换为PDF格式。在转换之后,我们经常为了保护知识产权,或者增强文件的安全性,需要给PDF文件增加水印。所以接下来我就带你学习如何通过增加水印提高PDF文件的安全性。
|
||||
|
||||
## 提高PDF文件的安全性
|
||||
|
||||
安全性包括很多方面,比如为文档增加密码,从而增强保密性;也可以为文档增加水印,提升它的不可伪造性。如果你对前面课程熟悉的话,就能联想到我们在第27节课讲过,可以利用我们自动压缩的方式来给文件增加密码,所以我在今天这节课,主要以如何给PDF文件增加水印。
|
||||
|
||||
### 为PDF增加水印
|
||||
|
||||
为PDF文件增加水印,你可以通过pyPDF2库来实现。使用pyPDF2库的好处是,你不需要在当前计算机上安装任何PDF编辑器,就可以给PDF文件增加水印。
|
||||
|
||||
基于pyPDF2库来给PDF文件增加水印的原理是,把只带有水印的PDF文件,和需要增加水印的PDF文件合并即可。根据这一原理,你大概就能想到增加水印的步骤了。主要有三步,分别是:准备水印文件、安装pyPDF2库以及合并两个PDF文件。
|
||||
|
||||
老规矩,我们还是先从准备水印文件开始学习。带有水印的PDF文件,可以使用Word软件来生成。具体操作方法是:通过Word的“设计”-“水印”功能,来定制你自己的水印文件,接着再把它另存为PDF格式,之后这个带有水印的文件你就可以反复使用了。
|
||||
|
||||
接下来是安装pyPDF2的第三方库。由于它的安装包和软件同名,所以可以使用pip命令直接安装。安装后,我需要通过该库实现PDF文件的读写。我使用了这个库用于读写PDF文件的“PdfFileReader, PdfFileWriter”两个包,导入的代码如下:
|
||||
|
||||
```
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
|
||||
|
||||
```
|
||||
|
||||
第三步,也是最重要的一步,**合并两个PDF文件**。合并文件需要使用**pyPDF2库的**mergePage()函数**实现。在实际工作中,我们通常需要给PDF文件的每一页都增加水印,此时我们需要使用for循环来迭代每一页,迭代之后,再把合并好的PDF文件保存为新的文件即可。
|
||||
|
||||
我把合并两个PDF文件的代码写在下面,然后再带你分析整个合并流程。
|
||||
|
||||
```
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
|
||||
def watermark(pdfWithoutWatermark, watermarkfile, pdfWithWatermark):
|
||||
|
||||
# 准备合并后的文件对象
|
||||
pdfWriter = PdfFileWriter()
|
||||
|
||||
# 打开水印文件
|
||||
with open(watermarkfile, 'rb') as f:
|
||||
watermarkpage = PdfFileReader(f, strict=False)
|
||||
|
||||
# 打开需要增加水印的文件
|
||||
with open(pdfWithoutWatermark, 'rb') as f:
|
||||
pdf_file = PdfFileReader(f, strict=False)
|
||||
|
||||
for i in range(pdf_file.getNumPages()):
|
||||
# 从第一页开始处理
|
||||
page = pdf_file.getPage(i)
|
||||
# 合并水印和当前页
|
||||
page.mergePage(watermarkpage.getPage(0))
|
||||
# 将合并后的PDF文件写入新的文件
|
||||
pdfWriter.addPage(page)
|
||||
|
||||
# 写入新的PDF文件
|
||||
with open(pdfWithWatermark, "wb") as f:
|
||||
pdfWriter.write(f)
|
||||
|
||||
```
|
||||
|
||||
在这段代码中,我定义了函数watermark(),用来实现为PDF文件增加水印的功能。它的实现思路是先读取水印PDF文件,再把水印PDF文件与需要增加水印的文件的每一页进行合并。合并之后,我通过使用PdfFileWriter()类,产生了新的对象pdfWriter,并将合并后产生的新PDF文件存储在pdfWriter对象中。最后,在所有页处理完成后,将合并后的PDF文件保存到“pdfWithWatermark”对象指向的文件中。
|
||||
|
||||
在这段代码中你需要注意的是,我使用了“with方式”打开了文件,在文件处理完成前如果关闭文件的化,会出现“file close error”异常。因此你需要注意代码第9、13行的with缩进,而写入新的文件可以在水印PDF文件和要增加水印的文件关闭后进行,所以代码的第25行“with语句”缩进可以在它上面的两个with代码块以外进行编写。
|
||||
|
||||
为了让你更直接地感知到增加水印后的结果,我把增加水印后的结果贴在下方,供你参考。
|
||||
|
||||
<img src="https://uploader.shimo.im/f/LR66ArSYYCVTazC6.png!thumbnail" alt="">
|
||||
|
||||
以上就是我使用PyPDF2库为PDF增加水印的全部流程。不过除了增加水印外,你还能使用pdfWriter对象,来实现很多实用的功能,比如为PDF文件设置密码、插入新的页面、旋转PDF页面等等。
|
||||
|
||||
此外,由于pyPDF2库封装得非常好,所以它的调用很简单,你只需一个函数就能实现我刚才提到的这些功能了。我将pyPDF2库的官方文档链接([https://pythonhosted.org/PyPDF2/](https://pythonhosted.org/PyPDF2/))贴在这里,当你需要操作PDF文件实现其他功能时,可以参考官方文档中PdfFileWriter()函数的参数,为不同的功能增加相应参数即可。
|
||||
|
||||
## 小结
|
||||
|
||||
最后,我来为你总结一下今天的主要内容。在本讲中,我为你讲解了如何通过pywin32库把Offce组件常用的doc、docx、xls、xlsx、ppt、pptx等文件转换为PDF文件格式的通用方法。这个通用的方法就是**通过pywin32库的COM编程接口,调用VBA脚本实现格式的转换。**
|
||||
|
||||
你学会了pywin32库之后,除了能把这些办公文件转换为PDF文件外,还能对Office组件中的任意一个软件进行**常见功能的调用**,因为**pywin32调用的VBA脚本和Office宏的脚本是完全相同的。
|
||||
|
||||
我在本讲中除了为你讲解了pywin32库外,还讲解了pyPDF2库。pyPDF2库能够在你将文件转换为PDF之后,还能对PDF的格式和内容进行微调,让你的PDF文件批量处理能达到手动处理的文件精细程度。
|
||||
|
||||
最后,你还可以把PDF文件和上一讲中的自动收发邮件,以及我们学习过的Word自动处理相结合,把PDF格式的合同作为邮件附件进行文件的自动生成和邮件的自动发送。
|
||||
|
||||
## 思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题。如果在一个文件夹中既有Word文件,又有PowerPoint文件,你该如何将文件夹中的这些类型的文件,批量转换为PDF文件呢?
|
||||
Reference in New Issue
Block a user