first commit
This commit is contained in:
237
专栏/Python自动化办公实战课/00导读入门Python的必备知识.md
Normal file
237
专栏/Python自动化办公实战课/00导读入门Python的必备知识.md
Normal file
@ -0,0 +1,237 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
00 导读 入门Python的必备知识
|
||||
你好,我是尹会生。
|
||||
|
||||
咱们这个课程是用Python解决办公低效问题,但是即使你从来没有用过Python,甚至没有任何的编程语言基础,也完全可以学会这门课。
|
||||
|
||||
为了解决你的语言问题,我特意准备了这节课。我会给你讲解 Python的五个最基础的语法知识,包括运行环境配置、变量、数据类型、控制语句和使用函数库的方法。
|
||||
|
||||
这节课的内容也不需要你马上掌握,其中提到的一些关键知识,我会在后面的课程中详细讲解,包括它们的使用场景和具体用处。学完这节课,你只要能对Python有一个初步的了解,可以看懂基本的Python代码就行了。
|
||||
|
||||
当然,如果你有一定的Python语言基础,那么这节课就相当于给你巩固复习了,帮你查漏补缺。
|
||||
|
||||
运行环境配置
|
||||
|
||||
我们先从Python的运行环境配置开始说起。初学者面对的最大难题,就是如何让自己的Python程序运行起来。
|
||||
|
||||
一般情况下,运行的Python代码会被保存到一个以.py作为扩展名的文件中,也就是Python脚本文件。要想让Python程序运行,我们需要打开终端应用程序(在Windows中开始-运行-cmd.exe):
|
||||
|
||||
python3 /py文件所在的路径/xxx.py
|
||||
或
|
||||
cd /py文件所在的路径
|
||||
python3 xxx.py
|
||||
|
||||
|
||||
这段代码中,xxx.py就是我们编写好的Python脚本文件,Python3 是Python脚本文件的解释器,它会把我们编写好的代码翻译给计算机,让计算机去执行。
|
||||
|
||||
在运行程序的过程中,有两点需要注意。
|
||||
|
||||
第一,Windows和macOS默认是没有安装Python解释器的,所以你需要通过Python官方网站下载不同操作系统下的Python解释器。关于Python的版本,我建议你使用官方建议的最新稳定版本,下载后直接安装就可以使用了。
|
||||
|
||||
第二,Python3 解释器需要在字符终端下运行。
|
||||
|
||||
以macOS系统为例,你需要打开终端来运行python3 -V 命令来验证Python解释器是否安装成功,如果显示了Python解释器的版本,那说明安装是成功的。如果提示没有找到这个命令,你需要重新安装,并注意安装过程中是否有错误提示信息。
|
||||
|
||||
python3 -V # 执行的命令
|
||||
Python 3.9.1 # 返回的结果
|
||||
|
||||
|
||||
搭建Python的开发环境,是运行一切程序的基础。无论你现在手边电脑操作系统是哪一种,我都建议你马上行动,先按照刚刚讲解的步骤,正确地安装和设置Python开发环境。这样在之后的课程中,你就能马上跟着我的讲解进行操作了。
|
||||
|
||||
变量
|
||||
|
||||
在正确配置好Python的开发环境之后,咱们继续学习一个重要概念:变量。
|
||||
|
||||
变量这个词来源于数学,在编程语言中,变量主要是用来存储计算结果或表示值的抽象概念。
|
||||
|
||||
计算机中的变量都有一个名字,我们把它称为变量名。这个名字一般是根据变量的英文名进行人为命名的,一般多用简短且易于记忆的名字。比如,我在课程中会使用src_path、dst_path两个变量表示操作源文件路径和目标文件路径。
|
||||
|
||||
我们还可以为变量指定一段数据,这段数据称作变量的值。计算机处理的数据很大一部分来源于用户输入的数据和运算的结果数据,如果这部分数据需要跟踪,我们就可以将数据存储在变量中。
|
||||
|
||||
对于初学者来说,变量的概念比较抽象,你可能现在有点蒙。别急,我现在给你举一个简单的例子,你一看就明白了。假如我要在Python当中计算两个数的和,代码如下:
|
||||
|
||||
x=100
|
||||
y=200
|
||||
z=x+y
|
||||
print(z)
|
||||
|
||||
|
||||
在上面的代码当中, 我们分别定义了x、y、z三个变量。 x、y、z称作变量名,定义变量名必须要遵从以下四个规则:
|
||||
|
||||
|
||||
变量名只能是字母、数字或下划线;
|
||||
变量名的第1个字母不能是数字;
|
||||
变量名也不能是Python的关键字;
|
||||
变量名是区分大小写的。
|
||||
|
||||
|
||||
上述代码中的=和+,称作运算符。
|
||||
|
||||
|
||||
“=”叫做赋值运算符。它的功能是将右边的表达式赋值给左边的变量。
|
||||
“+”叫做算术运算符。进行算术运算时还可以使用-、*、/、%等常用算术运算符号。
|
||||
|
||||
|
||||
代码的最后一行,为了能让你的计算结果在终端上显示,我们需要使用一个叫做print的内置函数。
|
||||
|
||||
内置函数是Python内置的一种功能。比如代码中的print就是用来进行输出的。它的最基本用法就是在print函数的括号中写入变量,除了输出最终的结果外,也可以将print函数放在程序运行的过程中,方便你输出更丰富的调试信息。
|
||||
|
||||
通过上面的讲解,我为你介绍了变量如何进行赋值和如何输出。其实,在Python中变量用来存储的数据还会区分类,这些类型也有专门的名称,叫做数据类型。
|
||||
|
||||
数据类型
|
||||
|
||||
Python支持的数据类型有数字、字符串、元组、列表、集合、字典6种,不同的数据类型用于描述不同的类别,比如姓名可以用字符串类型,性别可以用布尔型,身高可以用数字类型。下面的代码,我为你演示一下三种最简单的数据类型:
|
||||
|
||||
name = "yin huisheng"
|
||||
male = True
|
||||
hight = 180
|
||||
|
||||
|
||||
上面的三个变量,我分别为它赋予了字符串、布尔类型和浮点型这三种数据类型。 Python的变量不需要事先定义变量类型,跟其他语言相比更加简洁。Python可以直接对变量进行赋值,这样就可以根据变量值自动识别变量的类型了。
|
||||
|
||||
不过在上面的例子当中,你还需要注意两点。
|
||||
|
||||
一方面,如果你使用字符串类型,就必须给字符串增加引号。这是字符串的语法规定,如果不加引号会被识别为其他类型,在进行操作时就会得到不同的结果。
|
||||
|
||||
比如说,我定义Python的变量为“123”,加了引号会被识别为字符串,如果一个变量“123”没有加引号,则会被自动识别为数字。如果在Python程序中使用“123+123”会得到两个整数的和“246”,如果是“123”+“123”就会得到两个字符串的连接“123123”。
|
||||
|
||||
另一方面,使用布尔类型的时候,True和False首字母必须要大写,它们是Python的关键字,如果首字母没有大写会被当作变量处理,解释器也会报错。True在布尔类型表示真(对),False表示假(错)。比如算式10>5 是正确的,在Python中就会用True来表示结果是真,反之为假。
|
||||
|
||||
上面提到的整数、浮点数和布尔型都是属于数字类型。除了这些,Python还支持很多种数据类型,包括数字、字符串、元组、列表、集合、字典等。它们之间的关系,我用下面的思维导图为你进行展示。
|
||||
|
||||
|
||||
|
||||
我在课程中会逐一为你剖析它们的用处,让你能够全部掌握。当你学会这些类型的时候,可以更精确地描述数据,比如,我想用Python的数据类型存储“5个苹果和10个橘子”,我们就需要使用字典和字符串来表示这些数据。你可以看一下下面这个记录水果数量的例子:
|
||||
|
||||
fruits1 = {"apple":5, "orange":10}
|
||||
fruits2 = "apple,5,orange,10"
|
||||
|
||||
|
||||
我分别用字典和字符串记录了水果的名称和数量,可以看到,字典表达的水果和数量之间的映射关系更明确,而且不同的数据类型还能支持它特有的内置方法。例如上面定义了水果的字典数据类型,我们可以通过 fruits[“apple”] 直接得到苹果的数量5 , 有时候,这些便捷的内置方法也是我们选择数据类型的依据。
|
||||
|
||||
数据类型是一门编程语言的必要组成部分,学习编程语言主要就是为了掌握它的数据类型。这节课我只要求你能够了解Python有哪些基本数据类型就可以了,在之后的课程当中,我会为你重点介绍每一种数据类型的优缺点,以及该怎么使用它们。
|
||||
|
||||
流程控制
|
||||
|
||||
流程控制,就是我们经常说的程序执行的顺序控制。 从上面的几个例子当中,你会发现程序都是从上向下依次执行的。这也是Python语言最简单的结构——顺序结构。在Python中,除了顺序结构之外,还有分支和循环两种结构。
|
||||
|
||||
分支结构
|
||||
|
||||
分支结构用来判定某个条件是否成立,它会根据一条或多条语句的判定结果(是True还是False)来执行对应操作的语句,从而实现分支的效果。实现分支结构的语句叫做if语句。除了if这个关键词外,我们经常还会用if…else和if…else…if语句来实现更多的分支和更复杂的逻辑。
|
||||
|
||||
我们一起看一个比较数值大小的例子吧。
|
||||
|
||||
x = 20
|
||||
if x > 10: # 注意结尾的冒号:
|
||||
print("if的判断结果为True")
|
||||
print("x的值大于10")
|
||||
else:
|
||||
print("if的判断结果为False")
|
||||
print("x的值小于10")
|
||||
print("程序执行结束")
|
||||
|
||||
# 执行结果如下
|
||||
if的判断结果为True
|
||||
x的值大于10
|
||||
程序执行结束
|
||||
|
||||
|
||||
在上面这段代码当中,我们实现了判断变量x是否大于10的功能。在书写分支结构时,要注意书写格式问题,分支结构的书写格式比顺序结构要复杂,例如:
|
||||
|
||||
|
||||
if判断的书写格式,冒号标志着if判断的结束。
|
||||
if下方有缩进的代码,是在True的条件下才能运行的。在演示代码中,缩进代码由2行组成,其实,它也可以由很多行代码组成,多行代码的形式被称作代码块。代码块一直到缩进再次改变的时候,True条件下的代码才结束。
|
||||
|
||||
|
||||
需要你注意的是,在整个程序当中,缩进方式必须一致,要么使用Tab、要么使用4个空格进行缩进。Python对于缩进和复合语句结尾的冒号要求非常严格,如果弄错了就会报错。而且,缩进不同会大大影响一个程序的运行结果。例如上面的代码中“print(“程序执行结束”)” 如果有缩进,你就看不到“程序执行结束”这行执行结果了。
|
||||
|
||||
循环结构
|
||||
|
||||
在程序运行逻辑中,另一个重要的结构叫做循环结构。循环结构是可以多次执行同一段代码的语句结构。在Python当中有两种循环语句,分别是while语句和for语句。
|
||||
|
||||
while语句在循环开始前先对条件语句进行判断。如果条件语句的结果为True,则循环一次再次进行判断。如果条件允许的结果为False,则循环结束。这里所指的条件语句和if语句当中的条件是一样的。下面的例子是我使用while语句实现的从1~10的累加。
|
||||
|
||||
start = 1
|
||||
total = 10
|
||||
sum = 0
|
||||
while start <= total: # 条件为True 继续执行循环
|
||||
sum += start
|
||||
start += 1
|
||||
print(sum) #条件为False循环结束,输出结果
|
||||
|
||||
|
||||
另一个循环语句叫做for语句,它和while的功能有所不同。我们一般把for的功能称作遍历功能。一般我们会提供一个列表,通过for将列表中的每一个元素读取出来,进行处理。
|
||||
|
||||
举个例子,如果我们要依次输出列表中的每一个元素,就可以用下面的代码:
|
||||
|
||||
total = list(range(1,11)) # 定义一个1-10 的列表
|
||||
for i in total:
|
||||
print(i)
|
||||
|
||||
# 输出结果
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
|
||||
|
||||
流程控制语句可以改变Python程序的执行顺序,而我们通常会根据实际的业务场景来控制某些语句块进行重复执行的。
|
||||
|
||||
使用函数库
|
||||
|
||||
Python语言的强大之处,除了语法简单外,就在于它庞大的函数库了。
|
||||
|
||||
Python的函数库有标准库和第三方库(也叫扩展库)。标准库是Python自带的,而扩展库是需要再进行安装的库。你可以从Python的官方文档和PyPI网站中找到它们,其中,PyPI网站的库就是扩展库。
|
||||
|
||||
PyPI网站上面的项目超过28万个,几乎包含了各个行业和各个功能的应用,实现了很多你日常需要手动操作的功能。
|
||||
|
||||
这些库如何是用呢?首先看一下官方自带的库。
|
||||
|
||||
如果要使用官方自带的库,可以使用import关键字导入。例如我计算算数平方根功能,就可以使用如下方法:
|
||||
|
||||
import math
|
||||
math.sqrt(16)
|
||||
|
||||
|
||||
import 后面指定要使用的库的名称,使用库的具体功能,通过官方文档来找到相应的函数。例如,算数平方根使用sqrt函数就可以实现你想要的功能了。
|
||||
|
||||
标准库的使用非常容易吧,那如何使用扩展库呢?由于扩展库都需要通过网络进行下载,因此我们还需要提前在终端使用pip3命令进行下载和安装。例如我希望Python可以支持Word文件格式, 我就需要在终端执行下面的代码。
|
||||
|
||||
pip3 install python-docx
|
||||
|
||||
|
||||
下载和安装之后, 就可以像标准函数库一样使用import导入使用了。
|
||||
|
||||
总结
|
||||
|
||||
通过上面的内容,我为你简单介绍了一下入门Python需要掌握的五大部分,包括环境配置、变量、数据类型、控制语句和使用函数库的方法。这五个部分是Python最基础也是最必要的语法知识。希望你能通过我的介绍,对Python语言有所了解,消除对编程语言的畏惧心理。
|
||||
|
||||
最重要的是,即使今天这节课你没有完全懂,也没有关系。
|
||||
|
||||
一方面,我会在之后的课程中不断讲解每个部分的用法和具体使用场景。还是我在开篇词中说的学习方法,你需要在听我讲解的同时,动手操作,反复练习,这些语法知识就不会是太大的问题。
|
||||
|
||||
另一方面,我也希望你能在学习之后的课程时,如果遇到哪些关键语法知识不太明白,就回头来学习今天这节课。这节课相当于整个课程的基石,需要反复琢磨、反复学习。
|
||||
|
||||
如果你能跟着我的课程不断练习实操,那学完后,你都能够熟练地修改课程中涉及到的代码,并且还能根据自己的工作场景,自行编写提升工作效率的程序。
|
||||
|
||||
思考题
|
||||
|
||||
最后留一道思考题给你,你以前是如何学习编程语言的呢?有没有好的学习方法分享一下?或者你在学习过程中有遇到什么困难,也可以分享出来。
|
||||
|
||||
如果你觉得这节课能帮助你提升办公效率,欢迎点击“请朋友读”,分享给你的朋友或同事。
|
||||
|
||||
编辑小提示:专栏的完整代码位置是https://github.com/wilsonyin123/python_productivity,可点击链接下载查看。或者通过网盘链接提取后下载,链接是: https://pan.baidu.com/s/1UvEKDCGnU6yb0a7gHLSE4Q?pwd=5wf1,提取码: 5wf1。
|
||||
|
||||
|
||||
|
||||
|
146
专栏/Python自动化办公实战课/00开篇词重复工作这么多,怎样才能提高工作效率?.md
Normal file
146
专栏/Python自动化办公实战课/00开篇词重复工作这么多,怎样才能提高工作效率?.md
Normal file
@ -0,0 +1,146 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
00 开篇词 重复工作这么多,怎样才能提高工作效率?
|
||||
你好,我是尹会生。欢迎你来到《Python自动化办公实战课》,在接下来的3个月时间里,我们一起通过办公自动化来提升工作效率。
|
||||
|
||||
工作15年来,我在游戏公司做过技术总监,也在新浪网做过技术经理,负责公司运维平台建设和相关工具的开发工作,几乎每天都在和Linux打交道。
|
||||
|
||||
在这门课之前,我已经在极客时间开设了两门视频课程:《零基础学 Python》和《Linux实战技能100讲》,为Python语言的初学者和运维工程师、开发工程师提供系统化的课程,目的就是解决他们在学习、实践中遇到的80%的问题。
|
||||
|
||||
目前,这两门课程已经有3万多名同学加入学习了。看到这些内容能够切实地帮助同学们解决他们的问题,我非常开心,也很受鼓舞。
|
||||
|
||||
所以,这次我又为你带来了《Python自动化办公实战课》这门专栏课程。我总结了工作这些年来切身经历的、经常被问到的办公效率低下问题,以及对应的解决方案,我会把它们全部分享给你。
|
||||
|
||||
希望你学完这门课,能够告别重复、机械的工作,提高工作效率,同时也能把时间花在刀刃上,去提升自己的核心竞争力。
|
||||
|
||||
说到这里,你脑中是不是回忆起了一幕幕的低效工作场景:
|
||||
|
||||
|
||||
需要对Excel文件中的内容进行拆分和合并,你要一个个手动拆分再合并;
|
||||
文件夹里有大量图片需要重命名,你不得不挨个儿点击一张张图片,再重新命名;
|
||||
网络中有大量新闻和图片需要下载,你就要手动把每个网页另存为文件;
|
||||
需要把数据快速生成自己想要的图形,一个一个图形参数来设置;
|
||||
需要让计算机在凌晨执行任务,你只能让闹钟在半夜把自己叫醒,人工执行,观察执行结果;
|
||||
……
|
||||
|
||||
|
||||
这些工作其实难度都不大,但它们有一个共同的特点,就是重复、机械的手工劳动,非常耗费时间和精力。实际上,我们换一种方法,就可以快速完成这些工作,节省数倍的时间。如你所想,这些都是我要通过这门课帮助你解决的问题。
|
||||
|
||||
那具体怎么解决呢?
|
||||
|
||||
解决这些低效问题,我的思路是什么?
|
||||
|
||||
其实,我们现在的办公环境,已经基本实现自动化了,Word、Excel、邮件、思维导图等各种各样的工具都非常顺手,表格的合并拆分、使用快捷键进行文件的各种操作等各种技巧也是随手一搜就能得到。但相应的问题也就来了,我们怎么用好这些工具、怎么内化这些技巧呢?
|
||||
|
||||
静下心来想一下,现在我们掌握的绝大多数办公工具和技巧,都是基于计算机的。那这个问题,就可以用计算机的思维从根儿上去解决了。所以,我们不妨引入《设计模式》的思路:代码封装得越“高级”,解决的问题就越具体;越深入计算机底层,解决的问题就越通用。
|
||||
|
||||
如果你暂时不理解这句话的意思也没关系,只需要明白这样一点就可以:要想快速提高办公效率,解决方法不在各种小技巧和小软件,而在于理解底层逻辑,以及加快人和计算机的交互过程,能够高效地解决输入(格式转换)、输出(格式统一)、控制(内容处理)、运算(查找、替换)、存储(文件保存和绘图),自然就能解决大部分的效率问题。
|
||||
|
||||
只要对计算机的体系结构稍有了解,你就会听说过冯 · 诺依曼结构:计算机由运算器、控制器、存储器、输入设备和输出设备这五部分组成。所以,我们解决用好自动化办公工具和技巧的方法,和经典的冯 · 诺依曼结构是相吻合的,就是理解计算机的底层逻辑,从而提升我们和计算机交互的效率。
|
||||
|
||||
-
|
||||
|
||||
|
||||
说到这里,我可以给你分享一段我的工作经历。我曾经维护过日活超过3亿用户的微博私信平台,你可以看看我是怎么用计算机的思维,来提高自己和团队的工作效率的。
|
||||
|
||||
我们在一个业务模块中,需要批量替换200台服务器中的软件配置,而且每个服务器都有一个文件,需要将第五行内容, 由原有的接口版本v1统一替换成v2。
|
||||
|
||||
面对这样的需求,其实有很多挑战在里面。第一个是替换的实效性,如果手动替换接口版本,由于服务器过多,用户就有可能访问到还没来得及替换的接口上,如后就有可能看到自己的消息是已读状态,一刷新页面,又变成了消息未读。第二个就是服务器数量很多,手动替换还没做完,下一个需求就接着来了。第三,手动替换这么多服务,非常容易出现拼写错误,也就是我们常说的手误,导致你要再花更多的时间来排捉Bug。
|
||||
|
||||
这样很低效对不对?如果使用Python的话,我们就可以从3个方面来提升效率。
|
||||
|
||||
第一,用Python程序代替一个个的手动操作,实现文字内容的替换,这样就会解放人力,你的工作压力会减轻很多。-
|
||||
第二,我可以通过Python批量控制服务器,让服务器自动完成这些工作。-
|
||||
第三个就是灵活性方面的优化了, 我们可以让这段程序定时运行,又可以让它们能够同时运行,从一个一个执行,到五个五个执行。
|
||||
|
||||
这三方面的优化,我在课程中都会为你讲解到。
|
||||
|
||||
你可能会问,编程语言有那么多,比如Java、Go等等,我为什么要选择Python呢?或者说,为什么我会认为,Python非常适合用来提升我们的工作效率呢?如果你不会Python,可以学这门课吗?
|
||||
|
||||
不要着急,我来和你说下Python语言的几个特点,你就能明白了。
|
||||
|
||||
为什么选择用Python来解决?
|
||||
|
||||
首先,Python的用法非常简洁、灵活,就像汉语、英语这类自然语言一样容易理解和使用。世界著名程序员、软件开源运动旗手埃里克·雷蒙(Eric S. Raymond)在《如何成为一名黑客》中说道:
|
||||
|
||||
|
||||
如果你不懂任何计算机语言,我建议从 Python 入门。它设计整洁,文档良好,对初学者很友好。
|
||||
|
||||
|
||||
Python用法简单,但是对于初学者,尤其是没有Python基础的话,那该怎么来学这门课呢?其实这也是我在设计这门课时重点考虑的一个问题。所以不用担心,我为你准备好了0基础的学习路径。
|
||||
|
||||
第一,导读是整个课程的基石。导读部分我会为你讲解Python最基础且最重要的五大语法部分,让你对Python有一个初步的了解,能够看懂基本的Python代码。这也是你入门Python学习的第一步。
|
||||
|
||||
同时,导读在整个课程中会起到一个引领的作用。其中的语法知识我会在之后的课程中讲解,所以在学习时如果有不懂的话,仍然可以回过头来看导读部分。
|
||||
|
||||
第二,一定要动手来操作,毕竟看一遍我的操作,跟你自己操作是不一样的。
|
||||
|
||||
第三,如果你有时间的话,我还建议你去抄写每一段代码。要知道,看一遍课程和把看的东西写出来的感觉是完全不一样的。这个建议我在之前的《零基础学Python》这门课中也提过,很多用户反映学习效果确实不错,提升速度很快。
|
||||
|
||||
问题都是在不断学习中解决掉的,所以最后我也建议你遇到问题不怕畏惧,跟着课程来慢慢学习就行了。
|
||||
|
||||
其次,Python的扩展库很丰富,可以满足非常多的复杂场景的需求,能够替代非常多的手工操作。这也是Python相对于其他编程语言的一大优势。
|
||||
|
||||
目前,Python的扩展库已经覆盖了文件、声音、视频、数据科学、深度学习等众多行业。通过这些扩展库,我们可以用Python连接Excel、Word、邮件等常用办公组件,轻松应对各类工作场景,不用自己手动写很多功能代码了。而连接方法也很简单,以操作Word文档为例,你只要掌握下面这行代码就可以了:
|
||||
|
||||
import docx
|
||||
|
||||
|
||||
通过这样一行简单的代码,你的Python就能支持Word中的文字、字体、段落、样式、表格等各种功能进行读写操作了,当然了,为了实现你的各种定制需求,还需要继续编写代码(这里的优点是直接支持,其他语言是要先像是操作压缩文件一样,对docx格式解压缩,再对xml文件进行处理,非常复杂)。
|
||||
|
||||
最后,Python还有一个你无法抗拒的优点,那就是它的跨平台性。
|
||||
|
||||
也许有一天,你用的电脑的操作系统,从macOS变成了Windows,或者从Windows变成了macOS,那也完全不用担心。Python的跨平台特性,可以帮你做到不用修改任何一行代码,就可以让已经写好的程序直接在新的平台上运行。
|
||||
|
||||
总结来说,Python的简洁、扩展库丰富和跨平台特性这三点,就保证了你可以轻松学会这门课,用Python去实现办公自动化,提升自己的工作效率。
|
||||
|
||||
说了这么多,哪些工作可以用Python实现自动化,而我又会怎么给你讲这门课呢?
|
||||
|
||||
这门课是怎么设计的?
|
||||
|
||||
在开头的时候我也说了,要用计算机的思维去解决办公自动化工具和技巧的问题,所以我就把常见的30个机械、重复的工作场景,按照任务类型划分成了输入、运算、控制、存储和输出这5个模块。
|
||||
|
||||
“输入”模块:解决不同文件类型的批量合并和拆分问题
|
||||
|
||||
这类任务往往包含了格式相似的大量文件,比如Word、Excel、Txt文件,我会带着你用Python去进行批量合并和拆分。
|
||||
|
||||
“运算”模块:扩展常用的统计、搜索和排序功能
|
||||
|
||||
很多软件自带的统计、搜索和排序功能,都很好用,但不支持在多个文件或者跨类型文件中使用。所以,在这个模块我们要学习的就是,怎么通过Python进行扩展,让这些好用、常用的功能,可以支持多个文件或不同类型的文件。
|
||||
|
||||
“控制”模块:通过插件的方式增强办公软件以及周边软件、硬件的交互能力
|
||||
|
||||
办公软件的核心功能,通常是支持文字和表格等内容的相关操作,对控制外部设备相对较薄弱。例如,Word本身是不支持批量打印Word文件的,但批量打印又是一个常见的需求。这个需求,就可以通过脚本化来实现,达到打印自动化的目的。
|
||||
|
||||
“存储”模块:和文件相关的很多常用操作部分
|
||||
|
||||
在工作中,我们经常会面对这么几种需求:需要对大量文件进行重命名;需要通过网络批量下载视频和图片;需要在海量文件中快速找到自己想要的文件;等等。
|
||||
|
||||
这些需求最大的问题,就是我们需要手工重复操作,或者自带工具不好用。那么利用Python和文件、网络功能相结合,就完全可以实现目录下的批量改名、文件的批量下载,免去了手工重复操作的问题。
|
||||
|
||||
对于系统自带的文件查找工具来说,速度慢而且不够简洁,那我们可以使用Python根据自己定义的目录搜索,加快搜索文件的效率。
|
||||
|
||||
“输出”模块:智能化输出自己的工作成果
|
||||
|
||||
在这一部分,我要教你更直观和更智能地输出自己的工作成果。比如说你交付给同事的数据,可以通过Python一键转为图形,也可以根据你的需要将图形采用图片或网页的形式展示给你的同事,提高工作汇报的效率,更直观地展示自己的工作成果。
|
||||
|
||||
|
||||
|
||||
在讲解这5个模块、30个常见的办公场景的效率提升方法时,我还会带你剖析它们背后的原理,和你展示我分解任务、解决问题的思路。
|
||||
|
||||
因为提升办公效率的方法和技巧真的是非常非常多,但如果我们没能把这些方法和技巧做系统的梳理,那它们就是孤立的存在,知道得再多,也没办法根据自己的工作场景灵活运用。
|
||||
|
||||
如果到这里,你还是担心自己不会Python就学不会这门课、就没办法提升自己工作效率的话,我还可以再给你吃一颗定心丸,这也是我额外送你的一份礼物:在每节课的最后,我都会给你交付一个可运行的小程序。你只需要调整保存路径和几个简单的参数,就可以在自己的电脑上运行,就可以去应对你实际工作中相似场景的重复工作,提升自己的办公效率。
|
||||
|
||||
所以,不但能用Python让你的工作效率翻无数倍,还可以收获编程思维、解决问题的思路,以及30个小程序。
|
||||
|
||||
最后,我们来一起立个Flag吧:通过三个月的学习,让自己成为10X职场人!也欢迎你邀请你的小伙伴们一起来学习,共同成长!
|
||||
|
||||
编辑小提示:专栏的完整代码位置是https://github.com/wilsonyin123/python_productivity,可点击链接下载查看。或者通过[网盘链接](https://pan.baidu.com/s/1UvEKDCGnU6yb0a7gHLSE4Q?pwd=5wf1)提取后下载,提取码: 5wf1。
|
||||
|
||||
|
||||
|
||||
|
254
专栏/Python自动化办公实战课/01拆分与合并:如何快速地批量处理内容相似的Excel?.md
Normal file
254
专栏/Python自动化办公实战课/01拆分与合并:如何快速地批量处理内容相似的Excel?.md
Normal file
@ -0,0 +1,254 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
01 拆分与合并:如何快速地批量处理内容相似的Excel?
|
||||
你好,我是尹会生。今天是咱们的第一节课,我先带你学习下,如何用Python操作Excel。
|
||||
|
||||
Excel是我们在工作中用到的最频繁的软件之一,它有着强大的计算能力和便捷的图表功能。如果我们要在同一个Excel文件中进行操作,手工进行也很方便,但问题是,如果我们需要同时操作多个Excel文件,就是一件非常耗时的事情了。
|
||||
|
||||
在工作场景中,需要同时操作多个Excel的情况主要有2种:批量合并和批量拆分。我来带你看2个场景。
|
||||
|
||||
|
||||
批量合并。假设你需要对某些工作内容进行问卷调查,这时你用Excel做了调查问卷模版。我想你会这样做:先把Excel通过工作群分发给所有员工,再把群里收集到的反馈附件汇总成一个文件。
|
||||
批量拆分。假设你是公司的财务人员,你需要使用Excel对员工工资进行核算,之后再打印出来。但是公司要求员工薪水保密,所以每个员工的工资需要拆分成一个独立的文件,最后还需要打印出来。
|
||||
|
||||
|
||||
无论是合并,还是拆分,我们都面临着一个困境:没有现成的软件可以实现多个Excel文件的合并和拆分操作,所以你只好对每一个Excel文件都进行“打开-复制粘贴-保存”的工作。
|
||||
|
||||
很多人在面对这样的工作需求时,都忍不住立马去做,却很少停下来分析问题。其实,这三步是很简单的工作,不过也是无意义的重复工作,既浪费了时间,又没有真正产生价值。
|
||||
|
||||
幸运的是,这些工作都可以通过Python来解决。今天,我就给你介绍一下用Python实现重复工作自动化,快速实现Excel的合并和拆分的方法。
|
||||
|
||||
如何用Python手工操作一个Excel文件?
|
||||
|
||||
刚刚我们说到批量处理,其实也就是逐一处理多个文件。如果我们想要提升这类工作的效率,就可以先借助Python把每一次处理都自动化。所以,在讲具体的合并和拆分方法前,我们要解决的第一个问题,就是用Python代替你的双手来操作一个Excel文件。
|
||||
|
||||
如果要用Python操作Excel文件,首先就要支持读写Excel的功能。在Python中,要想实现对某一个功能的支持,就需要安装扩展库。
|
||||
|
||||
支持Excel读取的扩展库叫做xlrd库,支持Excel写入的扩展库叫做xlwt库。我们可以使用下面的命令行进行安装:
|
||||
|
||||
pip3 install xlrd
|
||||
pip3 install xlwt
|
||||
|
||||
|
||||
那么,如何使用Python读取Excel文件的内容呢?你可以使用这样的代码实现:
|
||||
|
||||
import xlrd
|
||||
|
||||
file = '/Users/user1/Desktop/a.xls'
|
||||
|
||||
data = xlrd.open_workbook(file)
|
||||
table = data.sheets()[0]
|
||||
value = table.cell_value(rowx=4, colx=4)
|
||||
|
||||
|
||||
虽然代码很简单,但是它对自动化操作Excel的意义非常大。
|
||||
|
||||
试想一下,如果能够使用Python替代全部的手工操作,大批量的文件就可以使用Python的循环功能自动化完成对每一个文件的自动处理工作了。
|
||||
|
||||
对于编程语言来说,文件合并的步骤可以分解为读取第一个文件,读取第二个文件,将第一个文件的内容追加到第二个文件下方。
|
||||
|
||||
所以在我们学会使用Python读取Excel文件之后,我们还需要掌握如何将读取的内容写入到Excel文件。写入文件的代码如下:
|
||||
|
||||
import xlwt
|
||||
|
||||
dst_file = '/Users/edz/Desktop/文章1/result/结果.xlsx'
|
||||
|
||||
workbook = xlwt.Workbook(encoding='utf-8')
|
||||
xlsheet = workbook.add_sheet("统计结果")
|
||||
|
||||
# 写入内容,假设取出的内容是value
|
||||
xlsheet.write(0, 0, value)
|
||||
|
||||
# 保存文件
|
||||
workbook.save(dst_file)
|
||||
|
||||
|
||||
可以看到,写入文件的时候,我们使用了一个叫做write的函数。它的前两个参数代表的写入位置,分别是指定写入的行和列坐标。无需多言,这个写入位置非常重要。如果按照上面的代码方式写入,也就是前两个参数均指定为0,就会覆盖这个Excel文件中的现有内容了。
|
||||
|
||||
所以,你如果想完成合并操作的话,就要实现对现有Excel内容进行追加写入。通常我们会先获取现有的内容一共有多少行、多少列,然后向后移动一个位置,再进行写入。
|
||||
|
||||
这种追加写入的方式,在我们的工作场景中非常常见。接下来,我们就看看怎么用追加写入的方式,实现多个Excel文件的合并吧。
|
||||
|
||||
怎样实现Excel的合并?
|
||||
|
||||
我们还是用前面提到的做调查问卷模板的场景,来具体讲一讲怎么实现Excel的合并。这里,我们就要用到一个重要功能了:循环功能。
|
||||
|
||||
循环功能的核心代码是:
|
||||
|
||||
from pathlib import Path, PurePath
|
||||
|
||||
# 指定要合并excel的路径
|
||||
src_path = '/Users/edz/Desktop/文章1/调查问卷'
|
||||
|
||||
# 取得该目录下所有的xlsx格式文件
|
||||
p = Path(src_path)
|
||||
files = [x for x in p.iterdir() if PurePath(x).match('*.xlsx')]
|
||||
|
||||
|
||||
在这段代码中,我使用了for语句,实现了Python的循环功能。通过这样的功能,我可以依次获取src_path变量指向的路径下所有的文件。同时,为了避免这个目录里的文件类型过多,我使用一个if语句用于条件判断,只提取.xlsx结尾的文件。
|
||||
|
||||
现在,用Excel实现调查问卷自动化的主要功能已经都实现了。接下来,我们看看怎样实现整个工作过程。我把它们的工作流程定义为三个步骤:
|
||||
|
||||
|
||||
找到整个工作过程当中重复操作的部分;
|
||||
将重复操作的部分需要哪些手工操作找出来,使用Python编写程序代替手工操作的部分;
|
||||
对重复的部分,使用循环语句进行批量处理。
|
||||
|
||||
|
||||
如果你对把手工操作改成Python程序,还没有任何经验的话,我还可以为你提供更直观的办法,叫做画时序图。
|
||||
|
||||
我先带你看看时序图是什么呢?如下图表:
|
||||
|
||||
|
||||
|
||||
简单来讲,时序图就是以时间顺序来排列程序中的事件的图表。通过上图,你应该很容易看出重复操作的这四个事件:
|
||||
|
||||
|
||||
打开文件;
|
||||
提取用户填写内容;
|
||||
粘贴到汇总文件;
|
||||
关闭文件。
|
||||
|
||||
|
||||
下面,我们就用Python来替代这四个事件。由于它们是重复的,所以我会使用for循环对它们依次进行处理。
|
||||
|
||||
回到我们的Excel做调查问卷的场景。当我们回收了调查问卷之后,每份问卷的格式是完全相同的,刚好可以利用上面提到的循环功能处理每份问卷。而问卷的选项则是我们需要提取出来用于汇总的,所以我们要使用Python实现读取Excel调查问卷的功能,最后再写入到一个新的Excel中。
|
||||
|
||||
好了,我们来看下这一功能的Python代码是如何编写的:
|
||||
|
||||
import xlrd
|
||||
import xlwt
|
||||
from pathlib import Path, PurePath
|
||||
# 导入excel和文件操作库
|
||||
|
||||
# 指定要合并excel的路径
|
||||
src_path = '/Users/edz/Desktop/文章1/调查问卷'
|
||||
# 指定合并完成的路径
|
||||
dst_file = '/Users/edz/Desktop/文章1/result/结果.xls'
|
||||
|
||||
# 取得该目录下所有的xlsx格式文件
|
||||
p = Path(src_path)
|
||||
files = [x for x in p.iterdir() if PurePath(x).match('*.xls')]
|
||||
|
||||
# 准备一个列表存放读取结果
|
||||
content = []
|
||||
|
||||
# 对每一个文件进行重复处理
|
||||
for file in files:
|
||||
# 用文件名作为每个用户的标识
|
||||
username = file.stem
|
||||
data = xlrd.open_workbook(file)
|
||||
table = data.sheets()[0]
|
||||
# 取得每一项的结果
|
||||
answer1 = table.cell_value(rowx=4, colx=4)
|
||||
answer2 = table.cell_value(rowx=10, colx=4)
|
||||
temp = f'{username},{answer1},{answer2}'
|
||||
# 合并为一行先存储起来
|
||||
content.append(temp.split(','))
|
||||
print(temp)
|
||||
# 输出
|
||||
# 韩梅梅,D,B
|
||||
# 李雷,D,C
|
||||
|
||||
# 准备写入文件的表头
|
||||
table_header = ['员工姓名', '第一题', '第二题']
|
||||
|
||||
workbook = xlwt.Workbook(encoding='utf-8')
|
||||
xlsheet = workbook.add_sheet("统计结果")
|
||||
|
||||
# 写入表头
|
||||
row = 0
|
||||
col = 0
|
||||
for cell_header in table_header:
|
||||
xlsheet.write(row, col, cell_header)
|
||||
col += 1
|
||||
|
||||
# 向下移动一行
|
||||
row += 1
|
||||
# 取出每一行内容
|
||||
for line in content:
|
||||
col = 0
|
||||
# 取出每个单元格内容
|
||||
for cell in line:
|
||||
# 写入内容
|
||||
xlsheet.write(row, col, cell)
|
||||
# 向右移动一个单元格
|
||||
col += 1
|
||||
# 向下移动一行
|
||||
row += 1
|
||||
# 保存最终结果
|
||||
workbook.save(dst_file)
|
||||
|
||||
|
||||
在这段代码中,Excel的读取和写入操作、for循环操作都派上了用场,它的整个工作过程就像我画的时序图一样:先打开用来汇总的Excel文件,依次对多个调查问卷进行读取,最后逐行写入到新建立的汇总文件中。
|
||||
|
||||
合并后的效果如下图:
|
||||
|
||||
|
||||
|
||||
有一点需要你注意的是,为了让你更好地理解Python循环的工作过程,我没有严格遵守编码规范,而是尽可能地让程序按照从上到下的顺序执行。如果你已经不是第一次使用Python,可以根据你的使用习惯,将程序进一步封装成Python里的函数,更加优雅地去实现它。
|
||||
|
||||
总的来说,在使用Python对Excel的合并操作时,需要你掌握的最核心的操作就是读写文件和行列坐标控制。熟练掌握这两个功能,你才能一次得到想要合并成的样子,不用再对Excel进行二次的手工操作。
|
||||
|
||||
怎样实现Excel的拆分?
|
||||
|
||||
对于批量操作Excel,还有一种情况是批量拆分。比如很多公司会用Excel记录和统计员工的薪水、记录货物信息、记录客户情况等数据。这些数据越来越多之后,文件会越来越大,打开文件和查找速度就会变得很慢,最后只好按照某些列进行Excel的拆分。
|
||||
|
||||
接下来,我就为你讲解一下如何进行Excel的批量拆分。让我们来看一个工资条的案例。
|
||||
|
||||
例如我在一个Excel中存放了工资信息,需要把第一行的表头和员工工资拆分成一个以员工名字命名的Excel文件。我来带你看下具体该怎么操作:
|
||||
|
||||
|
||||
|
||||
如果把拆分工作也画成时序图,就会发现,逐行读取可以使用循环功能批量操作,对每一行的内容处理,如果能使用Python进行自动化的话,一个Excel拆分的工作就全部能使用Python自动化实现了。所以,我打算设计一个for循环语句用于遍历所有的行,在for循环语句当中实现对每一行具体内容的处理。
|
||||
|
||||
我把文件拆分的关键代码写了出来,你可以参考一下:
|
||||
|
||||
for line in range(1,employee_number):
|
||||
content = table.row_values(rowx=line, start_colx=0, end_colx=None)
|
||||
# 将表头和员工数量重新组成一个新的文件
|
||||
new_content = []
|
||||
# 增加表头到要写入的内容中
|
||||
new_content.append(salary_header)
|
||||
# 增加员工工资到要写入的内容中
|
||||
new_content.append(content)
|
||||
# 调用自定义函数write_to_file()写入新的文件
|
||||
write_to_file(filename = content[1], cnt = new_content)
|
||||
|
||||
|
||||
在这段代码的第一行,我使用了一个range函数,它会生成从1到员工总数的数字范围。你可能会问,为什么没有直接写出Excel中总员工的数量,而是使用employee_number这样一个变量呢?
|
||||
|
||||
这是因为,如果直接写出员工数量,一旦遇到员工入职或员工离职等情况,你就需要根据Excel中的行数重新编写Python代码,而我现在使用的方式是每次打开Excel文件,会自动统计员工的数量(即行数),这种编写代码的方式能够让你的程序有更好的扩展性,一般这种方式用于处理文件内容经常变动的情况。
|
||||
|
||||
文件的批量拆分也是通过循环来实现逐行处理的功能的,但是你需要注意拆分以后的要保存的文件名称不要重复,不然很容易导致Excel中只有最后一次循环写入的内容。
|
||||
|
||||
小结
|
||||
|
||||
今天,我为你讲解了如何使用Python代替手工进行Excel的批量合并和拆分操作。一般来说,批量合并和拆分主要有几个步骤:
|
||||
|
||||
|
||||
手动或借助时序图找到需要重复操作的部分。
|
||||
将重复的部分用Python实现自动化操作。
|
||||
对Python实现自动化的脚本,再用循环功能实现批量操作。
|
||||
|
||||
|
||||
Python有着丰富的扩展库,当你掌握了熟练操作Excel的方法之后,对于WPS等其他办公软件,也可以通过相应的API进行类似的合并和拆分操作。
|
||||
|
||||
除了基本操作呢,我还给你介绍了分析问题的方法——时序图。通过时序图,你可以找到那些运行逻辑中重复的部分,利用循环完成自动化操作。
|
||||
|
||||
不管是利用Python代替手工操作节约时间,还是使用循环代替手工多次执行减少工作难度,这些都是提升工作效率的有效办法。希望你能在处理日常工作时多思考,有意识地把你的日常办公工作自动化。
|
||||
|
||||
思考题
|
||||
|
||||
欢迎你在课程后留言,告诉我使用Python解决了你工作中的哪些重复性问题。
|
||||
|
||||
如果你觉得这节课有用,能解决你的办公效率问题,欢迎你点击“请朋友读”,分享给你的朋友或同事。
|
||||
|
||||
编辑小提示:专栏的完整代码位置是https://github.com/wilsonyin123/python_productivity,可点击链接下载查看。或者通过网盘链接提取后下载,链接是: https://pan.baidu.com/s/1UvEKDCGnU6yb0a7gHLSE4Q?pwd=5wf1,提取码: 5wf1。
|
||||
|
||||
|
||||
|
||||
|
290
专栏/Python自动化办公实战课/02善用Python扩展库:如何批量合并多个文档?.md
Normal file
290
专栏/Python自动化办公实战课/02善用Python扩展库:如何批量合并多个文档?.md
Normal file
@ -0,0 +1,290 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
02 善用Python扩展库:如何批量合并多个文档?
|
||||
你好,我是尹会生。
|
||||
|
||||
在日常工作中,我们打交道最多的文件就要数Word和Excel了。我们经常面临这么一种场景:需要将Excel的内容合并到Word中。你可以想一想,完成这个需求,需要手动进行几个步骤的操作呢?很显然,有4步。
|
||||
|
||||
|
||||
首先,要手动打开Excel、Word文件;
|
||||
接着,复制一个单元格的文字到Word指定位置;
|
||||
然后,如果有多个单元格,就需要重复复制多次;
|
||||
最后,保存Word文件,并关闭Excel和Word文件。
|
||||
|
||||
|
||||
如果只有两个文件,这几步手动操作一定不成问题,不会耗费太多的时间。但是如果文件特别多,哪怕只有十几个,手动操作就相当耗费时间了,而且一不小心还容易出错。幸运的是,现在我们可以通过Python来实现批量文件合并功能,你只需要执行一个Python程序就能搞定所有文件的合并操作。
|
||||
|
||||
所以今天这节课,我们先从比较简单的内容讲起:用Python自动合并两个Word文件。然后再进阶,学习如何合并Word和其他类型的文件。一步一步来,相信你会掌握得既牢固又扎实。
|
||||
|
||||
手工操作和用Python操作的区别
|
||||
|
||||
首先我们要知道,为什么在合并文件的时候用Python更高效。我用一个例子来给你讲解手工操作和用Python操作的区别。比如下面这一段文字:
|
||||
|
||||
|
||||
简易流程——集团原则在每年1~5月、7~11月生产工作任务较重或考核时间较紧的情况下运用。
|
||||
|
||||
|
||||
在Word文件中,不但有文字内容,还有加粗、红色等格式,而且这些特殊的格式和文字内容是混合在一起的。
|
||||
|
||||
但如果用Python来读取Word文件,这段文字会被分为纯文字、段落、字体、字号以及表格等更具体的部分,而且每一个部分都对应着Python的变量和函数。
|
||||
|
||||
这样一来,你就可以根据自己的需求,只提取某一具体部分的内容。比如说,你看到哪一段文字的字体很好看,就可以直接读取之后套用到新的文字段落就行,非常便捷。
|
||||
|
||||
不过,用Python读取文件时,你需要记住很多个Python变量和函数。当然了,针对这一点,你也不用担心,这些变量和函数在Python的Word扩展库官方文档可以查看。所以如果你需要某个功能,但是不知道应该用什么变量和函数名称时,可以在官方文档中找到它的名字和描述信息。
|
||||
|
||||
总之,用Python读取文件的方式是非常有助于提高工作效率的。所以接下来我们用Python处理Word文件时,就需要通过刚才介绍的变量和函数来替代手动操作。
|
||||
|
||||
接下来,我先带你学习怎样用Python合并多个Word文件,然后再讲怎么把Word文件与纯文本、图片和Excel进行合并。
|
||||
|
||||
如何合并多个Word文件,只保留文字内容?
|
||||
|
||||
先从合并两个Word文件说起。假设你现在有两个Word文件,需要进行文件中的文字合并操作。两个文件的内容分别是:
|
||||
|
||||
文件一(内容包含字体、字号、颜色等额外信息):
|
||||
|
||||
1.简易流程——集团原则在每年1~5月、7~11月生产工作任务较重或考核时间较紧的情况下运用。
|
||||
|
||||
文件二(内容文字出现在表格中):
|
||||
|
||||
|
||||
|
||||
我把两个文件的信息总结如下:
|
||||
|
||||
|
||||
第一个文件中,字体使用了黑体和宋体字,此外还有红色字体和加粗等格式。现在我需要只提取其中的文字内容,不带任何格式。
|
||||
第二个文件中,文字被放在了一张表格里。现在我需要用Python把表格中的文字提取出来,合并成一个新的文件。
|
||||
|
||||
|
||||
我先把核心代码给你贴出来,然后再给你详细讲解具体的操作方法。
|
||||
|
||||
首先,我们可以使用一段Python代码提取Word文件里的内容,然后合并成一个文件。
|
||||
|
||||
import docx
|
||||
def merge_without_format(docx_files: list):
|
||||
'''
|
||||
只获取内容进行合并
|
||||
'''
|
||||
# 遍历每个文件
|
||||
for docx_file in sorted(docx_files):
|
||||
another_doc = Document(docx_file)
|
||||
# 获取每个文件的所有“段落”
|
||||
paras = another_doc.paragraphs
|
||||
# 获取所有段落的文字内容
|
||||
# paras_content = [para.text for para in paras]
|
||||
for para in paras:
|
||||
# 为新的word文件创建一个新段落
|
||||
newpar = doc.add_paragraph('')
|
||||
# 将提取的内容写入新的文本段落中
|
||||
newpar.add_run(para.text)
|
||||
|
||||
# 所有文件合并完成后在指定路径进行保存
|
||||
doc.save(Path(word_files_path, 'new.docx'))
|
||||
|
||||
|
||||
# 调用函数
|
||||
merge_without_format(files)
|
||||
|
||||
|
||||
在这段代码中你可以看到,我使用了一个Python的扩展库,它叫做python-docx,这也是我想重点给你讲解的一个扩展库。
|
||||
|
||||
python-docx是Python专门用来编辑Word文档的库,我在实现Word文档自动化操作的工作中经常会用到它。使用它的好处就是不必自己研究docx文件类型的底层实现细节,你可以像操作.txt文本一样直接打开、修改和保存关闭文件。可以说,python-docx扩展库降低了用户使用Python的复杂度。
|
||||
|
||||
我再举个例子展示一下具体的操作过程。例如python-docx库支持函数Document,它实现了Word文件的打开功能,底层也做了很多对Word格式的处理工作,让你可以直接使用paragraphs变量就能读取一整段Word文件。
|
||||
|
||||
Document函数格式如下:
|
||||
|
||||
Document(docx_file)
|
||||
|
||||
|
||||
还有,函数save也是python-docx扩展库提供的word文件保存函数。同样的,save函数在底层也做了很多对docx格式兼容的操作。像下面的代码一样,你就可以直接给这个函数传递一个文件路径,然后进行保存。是不是降低了编写代码的难度呢?
|
||||
|
||||
doc.save(Path(word_files_path, 'new.docx'))
|
||||
|
||||
|
||||
通过我举的例子,你就能更直观地感受到Python扩展库的方便之处了。接下来我们再回到刚才那两个文件的合并,合并之后的结果如下:
|
||||
|
||||
1.简易流程——集团原则在每年1-5月、7-11月生产工作任务较重或考核时间较紧的情况下运用。
|
||||
人力资源、生产、品管、财务等部门整理、提供绩效考核数据。
|
||||
人力资源部门收集各部门提供的考核数据,依据员工绩效考核评分标准对集团所有员工进行绩效考核得分计算。
|
||||
人力资源部门将核计的员工绩效考核结果提交部门经理确认后报集团主管领导核定。
|
||||
|
||||
|
||||
现在你已经掌握了两个Word文件的合并方法了。但如果我想让这段程序适用于三个、四个,甚至更多个Word文件的合并,那该怎么操作呢?
|
||||
|
||||
一个好消息就是,上面的代码我们不需要做任何修改,就可以合并多个Word文件。因为我使用了一个叫做函数的功能。函数有时候也被称作过程、方法,*它的作用是将那些需要反复使用的代码组合在一起*。
|
||||
|
||||
之前我们使用过函数,这些函数是Python自带的,或是扩展库提供的。这些函数我们可以直接拿来使用,使用函数在计算机术语中被称作函数调用。通过函数我们可以实现程序的模块化,多次使用可以多次调用,从而减少代码的重复性。但如果你需要自己编写函数怎么办呢?
|
||||
|
||||
你可以将重复的代码功能写在自己定义的函数中,在需要使用的地方调用就可以了。这种自己编写的函数就被称作自定义函数。自定义函数和Python自带的函数一样,也可以实现减少代码重复性的作用。
|
||||
|
||||
关于自定义函数,你需要熟悉它们的相关语法,主要是函数名、函数定义和调用方法。通常编写一个函数要为函数并起一个名字,这个名字叫做函数名。当你需要使用函数的功能时,可以使用函数名加“()”的方式来使用它,而且使用一个函数一般被称作调用函数。
|
||||
|
||||
我把函数定义和函数调用的写法单独拿出来给你看下,定义函数的格式是def后面跟着函数名称,调用函数是函数名称后面跟着一个”()” ,这是它的语法格式:
|
||||
|
||||
# 定义一个函数
|
||||
def 函数名(参数列表):
|
||||
函数体
|
||||
# 调用一个函数
|
||||
函数名(参数)
|
||||
|
||||
|
||||
知道了自定义函数的语法,接下来我们就可以在程序中使用自定义函数了。
|
||||
|
||||
举个简单的例子。像我在合并Word文件的程序中,第2行的merge_without_format就是我定义的一个函数,第24行merge_without_format(files) 就是对函数进行调用,files叫做函数的参数。通过函数参数,可以在调用函数的时候为函数指定要操作的对象。
|
||||
|
||||
相信你不难发现,使用函数以后,不但可以提高代码的重复利用率,还能提高代码的可读性。
|
||||
|
||||
那这段程序是怎样处理多个文件合并的呢? 我在调用函数merge_without_format时,使用了files变量作为参数,而files变量包含了大量的文件。因为是多个文件合并,所以在函数中我使用了一个小技巧,就是你熟悉的for循环语句,for循环语句能够遍历files变量的值,这样就可以将files指向的全部Word文件逐一进行文件内容的提取,进行两两合并,从而实现任意多个Word文件的合并操作。
|
||||
|
||||
通过对多个word合并,我希望你能学会怎么提取Word中的文字内容,如果你需要编写大量重复的代码,可以将它们写成自己定义的函数。
|
||||
|
||||
怎样合并不同类型的文件?
|
||||
|
||||
通过上面的例子,我们实现了Word文件之间的合并。在工作中,我们经常需要处理Word和Txt文件、图片、Excel这些类型合并的情况,又该如何操作呢?接下来,我就一个一个来讲一讲。
|
||||
|
||||
将纯文本和Word文件合并
|
||||
|
||||
如果是为了支持信息丰富,我们Word和Txt合并之后保存到新的Word中,会出现Txt里的字体字号和原有文件不统一的问题,我们可以使用python-docx扩展库为Txt文件中的文字增加格式。
|
||||
|
||||
如果合并前Word文件是仿宋字体,而且有下划线和红色字体,我们将Txt合并之后如何进行字体、样式和颜色的统一呢?我们可以使用下面这段代码。
|
||||
|
||||
def add_content_mode1(content):
|
||||
'''
|
||||
增加内容
|
||||
'''
|
||||
para = doc.add_paragraph().add_run(content)
|
||||
# 设置字体格式
|
||||
para.font.name = '仿宋'
|
||||
# 设置下划线
|
||||
para.font.underline = True
|
||||
# 设置颜色
|
||||
para.font.color.rgb = RGBColor(255,128,128)
|
||||
|
||||
|
||||
首先,我定义了一个叫做add_content_mode1的函数。考虑到Word合并Txt是否会有多个Txt进行合并操作,所以我使用自定义函数功能。
|
||||
|
||||
当你需要对多个Txt进行合并,就调用函数依次对它们进行处理,这样你就不用编写重复的代码了,这也是我在编写代码时进行提效的一个小技巧。
|
||||
|
||||
接下来,我们将每个新合并的txt内容作为一个新的段落合并到原有的文字中,这个功能使用python-docx的add_paragraph函数就可以增加了一个新的段落。
|
||||
|
||||
最后,把这一段所有文字设置成和原有的Word统一的字体、下划线和颜色,保证新的段落在格式上的统一。
|
||||
|
||||
在具体操作的时候,我还要提醒你,Word文件支持的格式丰富程度远远高于Txt文件,所以当这两种格式丰富程度不一致的文件进行合并时,要么向下兼容,去掉Txt不支持的格式;要么向上兼容,对Txt进行格式再调整。否则容易出现合并之后仍需要手动调整格式的问题,影响工作效率。
|
||||
|
||||
将图片和Word文件合并
|
||||
|
||||
我们再来看一下第2种情况,怎么把图片和Word文件进行合并呢?
|
||||
|
||||
想一下,我们经常见到的图片格式就有.jpg、.png、.gif等,由于这些格式应用范围广,格式没有被商业软件加密,所以python-docx库的add_picture函数就能实现把图片插入Word的功能。代码如下:
|
||||
|
||||
from docx import Document
|
||||
from docx import shared
|
||||
|
||||
doc = Document()
|
||||
# 按英寸设置宽度,添加图片
|
||||
doc.add_picture('test.jpg', width=shared.Inches(1))
|
||||
|
||||
|
||||
那有没有被商业保护、不能直接支持的格式呢?比如Pohotshop自带的.ps格式,我们如果将.ps格式插入Word文档,.ps格式不能被add_picture所支持,就只能以附件的形式添加到Word文件中,作为附件添加的文件无法直接展示图片的内容,和add_picture相比不够直观。
|
||||
|
||||
所以如果不需要进行内容的加密等商业目的的时候,建议使用通用和公开格式,这些格式对编程语言的兼容性更好。
|
||||
|
||||
总的来说,python-docx的功能非常强大,除了将文本和图片合并到Word文件中,还可以和第一节课我们学过的xlrd扩展库相配合,将Excel和Word进行合并。
|
||||
|
||||
将Excel和Word文件合并
|
||||
|
||||
为了让你更好地理解如何进行Word和Excel文件的合并,我用一个利用Excel和Word批量制作邀请函的例子来给你讲解。
|
||||
|
||||
我在Word中保存了邀请函的标准公文格式,但是其中的被邀请人、性别(先生、女士)以及发出邀请的时间,分别用“<姓名>”“<性别>”“<时间>”替代。邀请函格式如下:
|
||||
|
||||
尊敬的 <姓名> <性别>:
|
||||
|
||||
… 邀请函内容 …
|
||||
|
||||
<今天日期>
|
||||
|
||||
我在Excel的每一行中写了被邀请人的姓名、性别信息。格式如下:
|
||||
|
||||
|
||||
|
||||
现在,我们需要将Excel和Word进行合并操作,为每个被邀请人自动生成一个Word格式的邀请函。
|
||||
|
||||
虽然Word中自带的邮件功能可以批量制作邀请函,但是在灵活性还是较差的。比如我要在邀请函制作完成的时候自动添加制作时间等功能,就无法通过Word自带的邮件功能实现。接下来我就用Python来生成邀请函,代码如下:
|
||||
|
||||
def generat_invitation():
|
||||
'''
|
||||
生成邀请函文件
|
||||
'''
|
||||
doc = Document(invitation)
|
||||
# 取出每一段
|
||||
for para in doc.paragraphs:
|
||||
for key, value in replace_content.items():
|
||||
if key in para.text:
|
||||
# 逐个关键字进行替换
|
||||
para.text = para.text.replace(key, value)
|
||||
|
||||
file_name = PurePath(invitation_path).with_name(replace_content['<姓名>']).with_suffix('.docx')
|
||||
doc.save(file_name)
|
||||
|
||||
|
||||
对于这个问题,我是这样思考的。如果手动操作,我需要:
|
||||
|
||||
|
||||
先将Excel中的每一行中的姓名、性别填入Word文件中;
|
||||
再将当前日期填入到Word文件中;
|
||||
最后再按照姓名另存为一个文件。
|
||||
|
||||
|
||||
但如果使用Python来实现呢?就会非常简单。首先在整个过程中,Word文档是被反复使用到的,所以对Word文档进行修改的这个动作,我会将它写入到循环语句当中。
|
||||
|
||||
接着,我需要一个循环语句来处理Excel里的每一行循环,因为我们需要把Excel的每一行读取出来,然后替换“<姓名>”“<性别>”。
|
||||
|
||||
最后我们要解决的就是替换问题了。python-docx功能非常强大,它自带了替换函数–replace函数,能够将”<姓名>“”<性别>“替换成Excel真实的用户和性别。
|
||||
|
||||
我再用代码解释一下。对应上面的代码:
|
||||
|
||||
|
||||
第7行的for循环实现了遍历每个段落功能,para变量就是表示每个段落的变量。
|
||||
第8行我们将excel提前处理为python的基础类型–字典(链接), for循环实现了姓名、性别的遍历。
|
||||
第11行实现了内容的替换功能。
|
||||
第12行我将姓名作为文件名称,将.docx作为扩展名指定为新的文件名称,通过第13行的save函数进行了邀请函的保存。
|
||||
|
||||
|
||||
你看,多次读取Word文件的循环、多次按行读取Excel文件的循环、替换的函数都有了,那我们就可以实现自动化生成邀请函的功能了。
|
||||
|
||||
最终每张邀请函实现的效果如下图:
|
||||
|
||||
|
||||
|
||||
如你所见,我们在对不同类型文件进行合并时,要考虑不同的问题:
|
||||
|
||||
|
||||
对于支持格式丰富不同的文件时要考虑格式的兼容性;
|
||||
对于图片、音乐、视频和Word合并时要考虑是否是受到word支持的通用格式;
|
||||
对于像Excel格式于Word合并时能实现更复杂的功能,代码的复杂程度也会随之提高,一般需要先分析功能,再进行代码编写。
|
||||
|
||||
|
||||
小结
|
||||
|
||||
通过上面对Word文件的批量处理,我为你总结了Word和各种类型合并增效的几个通用法则。
|
||||
|
||||
|
||||
首先,尽量选择Word兼容的格式,这些格式往往也是python-docx库能直接支持的类型。
|
||||
第二,善于将手工操作转换为Python程序实现。如果无法直接转换为Python程序,可以尝试将手工操作继续细化拆分。
|
||||
第三,反复在程序中出现的代码可以编写为函数功能,函数可以让你的程序更健壮,较短的代码数量也减少了出现Bug的机率。
|
||||
|
||||
|
||||
思考题
|
||||
|
||||
在最后我也想留一个问题给你思考,如果邀请函的格式从Word文件改为图片,你将会如何去解决呢?
|
||||
|
||||
如果你觉得这节课有用,能解决你的办公效率问题,欢迎你点击“请朋友读”,分享给你的朋友或同事。
|
||||
|
||||
编辑小提示:专栏的完整代码位置是https://github.com/wilsonyin123/python_productivity,可点击链接下载查看。或者通过网盘链接提取后下载,链接是: https://pan.baidu.com/s/1UvEKDCGnU6yb0a7gHLSE4Q?pwd=5wf1,提取码: 5wf1。
|
||||
|
||||
|
||||
|
||||
|
251
专栏/Python自动化办公实战课/03图片转文字:如何提高识别准确率?.md
Normal file
251
专栏/Python自动化办公实战课/03图片转文字:如何提高识别准确率?.md
Normal file
@ -0,0 +1,251 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
03 图片转文字:如何提高识别准确率?
|
||||
你好,我是尹会生。
|
||||
|
||||
不知道你有没有遇见过这样的场景:在工作中,你遇见了一个紧急情况,对方给你发了一串儿聊天记录的长截图,当你处理完事情想要复盘的时候,必须要把这些记录处理成文字,然后就发现图片转文字过程中会出现很多问题,还需要自己二次手动校对。
|
||||
|
||||
经过不断尝试,就发现用互联网上的AI产品可以非常准确地识别出图片中的印刷体文字。而且再通过Python还可以实现把识别到的文字进行格式处理、文件保存等自动化操作。
|
||||
|
||||
那么今天,我就给你介绍两种能够精准、快速地把图片转成文字的方式:在线识别和离线识别。我会给你讲解具体的操作方法,当你再遇见这样的需求的时候,就可以很轻松应对了。
|
||||
|
||||
图片转文字的两种处理方法
|
||||
|
||||
我先来对图片转文字的两种处理方法进行介绍。
|
||||
|
||||
目前能够达到较高文字识别正确率的一般分为两种识别方式:一种是文字识别工作都需要在网络侧完成的方式,我们称为在线识别;另一种是不需要互联网功能的,我们称作离线识别。
|
||||
|
||||
根据不同的工作场景,我会选择不同的方式实现文字识别。那么,接下来我就带你了解下这两种方式各自的特性。
|
||||
|
||||
先看第一种,在线识别的方式。
|
||||
|
||||
在线识别方式最大的优点就是,它在初次进行文字识别的时候,准确率非常高。比如对聊天截图中的识别准确率就高达99%。因为在线识别使用了人工智能领域的深度学习算法和文字识别相结合的技术,能够把图片转换成文字后,还能在语义上把相近的字进行二次纠正。
|
||||
|
||||
比如说,被识别的内容包含英文单词“Hello”,一旦它的字母“o”被识别成数字“0”,在线识别软件就会根据上下文语境把这类错误纠正回来,而这种二次纠正的功能在离线识别软件中是没有的。
|
||||
|
||||
不过在线识别软件也有它的缺点,那就是识别文字的过程需要在公有云的服务器上完成。也就是说需要通过互联网把图片上传到服务器,那么一旦图片过大,或者图片数量比较多,就会导致上传时间过长。我们知道,一张高清图片至少有3MB大小,根据个人的网络情况至少要达到秒级上传才行。这就意味着在大批量文字识别的场景中,或对实时性要求很高的场景下,在线识别是不能满足要求的。
|
||||
|
||||
另外,图片需要经过互联网传输,识别以后的图片该怎么保存,怎么销毁,是不是会被其他人得到,这些都是安全风险。总之,信息泄露的风险比较大。所以像公司的合同、财务资料等涉密程度比较高的扫描件,很少使用在线识别。
|
||||
|
||||
再看第二种,离线识别的方式。
|
||||
|
||||
这种方式在识别过程中不需要连接网络,节省了在线传输图片的时间,适合那些对实时性要求比较高或网络信号比较差的场景。
|
||||
|
||||
但是离线识别方式的问题就在于,初次识别文字的准确率比较低,识别完之后必须要经过人工二次纠正才行。所以在前期人工校对,花费的时间相对来说会比较长。
|
||||
|
||||
我把它们各自的优缺点做了一张表格,如下:
|
||||
|
||||
|
||||
|
||||
那接下来,我就带你学习一下这两种识别方式怎样具体实现。
|
||||
|
||||
怎么进行在线文字识别?
|
||||
|
||||
我们先来看怎么进行在线文字识别。
|
||||
|
||||
在线文字识别方式,识别的主要功能的需要放在公有云的服务器中才能实现,所以在代码实现中就要考虑用户验证和图片加密传输问题。
|
||||
|
||||
用户验证能确保识别的结果交还给你本人,图片加密传输能确保图片上的信息不会被其他人窃取到。这些功能,各个公有云的AI产品都考虑得非常周全,一般会提供给用户一个扩展库。你要做的,就是安装这些扩展库。
|
||||
|
||||
举个例子,百度云的AI产品,你可以在终端下执行这样一个命令来进行安装。
|
||||
|
||||
pip install baidu-aip
|
||||
|
||||
|
||||
在这里我使用了百度云提供的在线文字识别产品,它提供了一个baidu-aip的安装包,安装之后提供了AipOcr函数实现用户验证、client.basicGeneral函数实现文字识别功能。代码如下:
|
||||
|
||||
from aip import AipOcr
|
||||
""" 你的 APPID AK SK """
|
||||
# APP_ID = '你的 App ID'
|
||||
# API_KEY = '你的 Api Key'
|
||||
# SECRET_KEY = '你的 Secret Key'
|
||||
|
||||
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
|
||||
""" 读取图片 """
|
||||
def get_file_content(filePath):
|
||||
with open(filePath, 'rb') as fp:
|
||||
return fp.read()
|
||||
image = get_file_content('example.png')
|
||||
""" 调用通用文字识别, 图片参数为本地图片 """
|
||||
result = client.basicGeneral(image)
|
||||
print(result)
|
||||
|
||||
|
||||
在这段代码里,实现了三个功能,分别是用户验证、读取图片和识别图片。为了让你更直观地看到效果,我来对一张电子发票的图片example.png进行识别。识别结果如下:
|
||||
|
||||
|
||||
|
||||
其实识别的准确率是非常高的,基本上没有错误。那接下来我具体给你介绍一下核心代码。
|
||||
|
||||
在代码的第一行,我使用了一个AipOcr库。AipOcr是百度云提供给用户的OCR Python SDK客户端,能够让你用Python语言和百度云进行交互。
|
||||
|
||||
一般情况下,我们进行用户认证、图片上传至服务器功能,都需要自己编写很多代码,但是使用AipOcr库之后,这些基础功能都被封装好了。你只需要填写三个变量,就能正式进入文字识别的环节了:
|
||||
|
||||
|
||||
第一个变量是APP_ID,它用来识别应用;
|
||||
第二个变量是API_KEY,用于识别用户;
|
||||
第三个变量是SECRET_KEY ,用来加密密钥。
|
||||
|
||||
|
||||
当把这三个变量传入AipOcr函数,使用AipOcr函数通过互联网交互后,就可以用来识别用户是不是被授权使用相应的产品,之后就可以把图片加密发送到AI产品的服务器上了。
|
||||
|
||||
这些就是所有公有云识别用户的通用做法,我把这个做法整理为三个步骤。
|
||||
|
||||
第一步, 安装SDK。代码是:
|
||||
|
||||
pip install baidu-aip
|
||||
|
||||
|
||||
第二步,注册用户。
|
||||
|
||||
以百度云为例,你需要登录https://ai.baidu.com/网址,以自己的手机为用户名注册一个新的用户。
|
||||
|
||||
第三步,申请应用。
|
||||
|
||||
成功登录网站之后,你会进入服务控制台界面,然后选择文字识别功能,再新创建一个文字识别类型的应用。创建应用之后,就可以在服务控制台中的应用列表中查看百度云提供的APP_ID、API_KEY、SECRET_KEY 三个变量。
|
||||
|
||||
在这三个变量中,APP_ID在百度云的控制台中创建用户之后会自动创建。-
|
||||
|
||||
|
||||
变量API_KEY与SECRET_KEY是在创建完毕应用后分配给用户的。
|
||||
|
||||
在你成功通过服务控制台得到这三个变量之后,就可以把这三个变量回添到代码中的第3-5行,然后去掉“#”开头的注释,这样就可以通过代码完成用户签名和验证功能。
|
||||
|
||||
你在掌握它们之后,也可以使用其他的公有云完成类似的用户签名和验证功能。
|
||||
|
||||
完成以上三个步骤之后,我们需要把指定路径的图片上传到百度云,通过第12行的get_file_content函数,把图片的路径和名称作为参数传入这个函数之后,再交给client.basicGeneral(image)函数处理,这样就能够完成图片的上传功能了,图片的识别和返回结果都会由AipOcr包自动处理以后放入result变量中。
|
||||
|
||||
总结来说,我在代码中实现了单个图片的在线识别文字功能,当你掌握了对单个图片实现文字识别的方法之后, 你就可以通过我们在第二讲学过的的for循环功能,遍历多个文件夹里的多个图片,你可以实现批量图片的识别功能了。
|
||||
|
||||
怎么进行离线文字识别?
|
||||
|
||||
我们再来看另一种文字识别形式——离线文字识别。被识别的图片同样是example.png这张发票的扫描件,代码如下:
|
||||
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
|
||||
# 打开图片
|
||||
image = Image.open('example.png')
|
||||
|
||||
# 转为灰度图片
|
||||
imgry = image.convert('L')
|
||||
|
||||
# 二值化,采用阈值分割算法,threshold为分割点,根据图片质量调节
|
||||
threshold = 150
|
||||
table = []
|
||||
for j in range(256):
|
||||
if j < threshold:
|
||||
table.append(0)
|
||||
else:
|
||||
table.append(1)
|
||||
|
||||
temp = imgry.point(table, '1')
|
||||
|
||||
# OCR识别:lang指定中文,--psm 6 表示按行识别,有助于提升识别准确率
|
||||
text = pytesseract.image_to_string(temp, lang="chi_sim+eng", config='--psm 6')
|
||||
|
||||
# 打印识别后的文本
|
||||
print(text)
|
||||
|
||||
|
||||
在进行离线识别的时候,有一个现成的文字识别库,那就是pytesseract库,这个库实现了对图片中的文字识别功能。使用pytesseract库,可以自动实现文字的切分和识别功能,识别效果如下:
|
||||
|
||||
|
||||
|
||||
通过识别结果,你可以发现英文和数字被准确识别出来的正确率是比较高的,达到90%以上。如果是纯英文的图片,识别之后的文字是可以直接使用的。但是在对中文、字母、数字混合的图片识别结果中,中文会出现较多的错误,正确率不足50%,这种情况下就需要人工去对每个字进行一遍纠正。
|
||||
|
||||
不过,如果我们想让初次识别的正确率就很高,那能不能实现呢?其实是可以的。不过在这个过程中我们需要花大量的时间把自己工作场景中经常出现的字手工进行纠错,形成新的识别模型。实现起来还是有一定难度的,为了让你更好地评估是不是值得自己优化离线识别软件的识别正确率,我先来带你看一下原理。
|
||||
|
||||
对图像进行文字识别的一般过程为5个步骤:-
|
||||
第一步,图像输入;-
|
||||
第二步,前期处理,比如二值化,图像降噪,倾斜纠正;-
|
||||
第三步,文字检测,比如版面分析,字符分割;-
|
||||
第四步,文本识别,比如字符识别,后期矫正;-
|
||||
第五步,也就是最后一步,输出文本。
|
||||
|
||||
在这5个步骤中,影响识别准确率最大的就是文本识别部分,这部分会受到对文字的标注和算法模型的大小这两个关键因素的影响。既然我们搞清楚了图像识别的步骤和原理,那能不能在保留本地识别的优势前提下,通过在线识别产品来优化离线识别产品的正确率呢?我们来具体分析一下。
|
||||
|
||||
首先,我们可以在文字标注方面优化识别正确率。
|
||||
|
||||
像身份证识别、票据识别、聊天截图等大部分文字识别场景,这些待识别图片采用了印刷体,每个字的间隔都是固定的,所以我们要想提升准确率,通常的做法是对每个字都进行人工纠错。
|
||||
|
||||
当Tesseract再识别到同一个字的时候,就会按照我们纠正过的来识别,那么逐个汉字纠正是离线识别方式提升识别正确率的必要方式,所以已经有高手为你写好了汉字纠正的工具了,它就是 jTessBoxEditorFX软件。
|
||||
|
||||
具体来说,这个软件可以把图片中的每个字切割开,并提示哪个方框对应哪个识别的文字,更正为正确的文字之后,就可以把更正的结果进行保存。这个保存好的结果就被称作新训练的模型。
|
||||
|
||||
|
||||
|
||||
所以整个过程你可以理解成三个步骤:-
|
||||
第一步,人工观察;-
|
||||
第二步,对比原始图像;-
|
||||
第三步,把错误的文字手工纠正为正确的汉字。
|
||||
|
||||
比如截图中的“某”字,多次识别错误,当我们人工把这些文字改为正确的“某”字之后,就可以生成一个训练文件,再把训练文件和文字识别的算法合并成新的识别模型。等下次再识别发票的时候,就可以采用新的模型进行识别。所以再次出现“某”字,就可以正确识别了。
|
||||
|
||||
这种文字标注对于单一场景非常有效,比如名片、火车票、飞机票、发票、车牌的识别场景,经过手工标注,不断增加样本数量之后,能够让正确率呈对数级别增加。
|
||||
|
||||
接下来我们再看一下在算法的模型上,能不能通过在线识别软件来优化离线识别软件。
|
||||
|
||||
在线识别软件的模型很大,通常是上百GB的容量,离线识别软件的模型往往只有几十MB,它们之间存在着巨大的差异,模型越大样本数量就越多,类似离线识别软件手工标注的模型一样,巨大的模型让在线识别软件的准确率在第一次识别就会非常高。
|
||||
|
||||
如果把在线模型直接放到你的笔记本当中,因为模型庞大,本地计算能力不足,识别的时间甚至超过在线识别的时间,那本地识别就不具有实时性的优势了。所以直接使用在线识别软件的模型方式不适合离线识别软件。
|
||||
|
||||
总的来说,由于手工标注和模型大小有限两个因素的影响,离线识别软件的应用场景大部分在企业内部票据识别上面。虽然离线识别中文的效果比较差,但是如果场景单一,在线识别的实时性达不到你的要求,那还是值得投入一部分时间人工标注汉字的。
|
||||
|
||||
识别出文字以后,我们就要提取文字内容并保存了。
|
||||
|
||||
识别成功后的文字处理工作
|
||||
|
||||
通过刚才案例中的识别结果,我们知道识别结果准确率很高,像单张图片已经可以把识别的结果直接保存使用了。不过一旦需要识别的图片比较多,手工操作起来就非常繁琐了,因为识别过程是使用Python编写的程序,后续可以继续利用Python对识别结果进行处理和保存。比如说在线识别的结果:
|
||||
|
||||
|
||||
|
||||
为了能够提取文本内容,去掉记录分段信息的”word”和 “{ }”,需要对这段文字再加工处理。
|
||||
|
||||
如果你仔细观察的话,就会发现这段文字是有一定规律的,它的写法就是Python的基本数据类型“字典”,字典类型会强调一对或多对数据之间的映射关系。为了将识别的文字结果进行保存,我们还会使用另一个基本数据类型“列表”,一般用来表示多段文字的并列关系,为了提取文本内容,我接下来把字典转换成列表就可以实现内容的提取了。
|
||||
|
||||
从字典到列表转换的功能怎么实现呢?首先来看一下两种数据类型的定义。
|
||||
|
||||
|
||||
字典由花扩号中的KEY和VALUE两部分组成的,它的格式是:{“words”:“1234567” } 。
|
||||
列表由方括号的VALUE组成,可以存放一个,也可以存放多个VALUE,格式是:[“1234567”,] 或[“123”, “abc”]。
|
||||
|
||||
|
||||
观察这两个数据类型不难发现,把字典的VALUE存放到列表里就能实现内容的提取了,如果字典里有多个值,可以使用for循环进行遍历,然后批量提取。对于识别的结果,我们可以采用一段代码进行从字典到列表的转换。代码如下:
|
||||
|
||||
info = []
|
||||
for i in result['words_result']:
|
||||
info.append(i['words'])
|
||||
print(info)
|
||||
|
||||
|
||||
经过数据类型的转换以后,我们可以得到处理好的文字内容:
|
||||
|
||||
|
||||
|
||||
最后,我们把处理好的结果保存至文件就行了。因为类型转换和保存我们都使用Python完成,所以我可以把这部分功能和文字识别功能放在同一个脚本中,避免手工操作。
|
||||
|
||||
不过我还要提醒你一下,在实现用户认证、图片上传、文字识别、数据清洗、文件保存的过程中,我们要尽可能实现流程的全自动化,避免中间步骤手工介入,这样可以更高效地提升工作效率。
|
||||
|
||||
小结
|
||||
|
||||
这节课我给你提供了两种文字识别的方式:在线识别和离线识别。不管哪一种识别方式,它们都需要经过图片输入、图片预处理、文字检测、文字识别和输出这样5个部分来实现完整的文字的识别功能。
|
||||
|
||||
但因为实现方式不同,两种方式各有优缺点。在线方式准确率更高、离线方式保密性更强。你需要根据自己的工作场景来选择不同的识别方式。不过不管哪种识别,最后我都建议你用Python对识别后的结果实现数据清洗和保存工作,毕竟减少手工操作才是实现办公效率提升最有效方式。
|
||||
|
||||
现在你可以开始动手将自己电脑里存储多年的图片,自动转成文字了。
|
||||
|
||||
思考题
|
||||
|
||||
在讲离线文字识别的时候,我只讲了单张图片转换文字的操作方法。如果是多张图片呢?该怎么实现呢?我建议你使用第二节课学过的for循环来进行操作。
|
||||
|
||||
欢迎把你对今天这节课的思考和想法分享在留言区,我们一起交流讨论。也欢迎你点击“请朋友读”,我们一起提高工作效率。
|
||||
|
||||
编辑小提示:专栏的完整代码位置是https://github.com/wilsonyin123/python_productivity,可点击链接下载查看。或者通过网盘链接提取后下载,链接是: https://pan.baidu.com/s/1UvEKDCGnU6yb0a7gHLSE4Q?pwd=5wf1,提取码: 5wf1。
|
||||
|
||||
|
||||
|
||||
|
204
专栏/Python自动化办公实战课/04函数与字典:如何实现多次替换.md
Normal file
204
专栏/Python自动化办公实战课/04函数与字典:如何实现多次替换.md
Normal file
@ -0,0 +1,204 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
04 函数与字典:如何实现多次替换
|
||||
04 函数与字典:如何实现多次替换
|
||||
|
||||
你好,我是尹会生。
|
||||
|
||||
“替换”是我们日常办公经常遇到的操作,比较常见是把文件中的英文标点符号(,.““”)替换成中文标点符号(,。“”)。有时候不仅是标点符号,还需要替换好几个词。还有一种情况不太常见,但是一碰到就很棘手,那就是根据数字范围进行替换,比如“0-6岁”替换成“少年”,“7-17岁”替换成“青年”,“18-65岁”替换成“中年”。
|
||||
|
||||
如果直接使用替换函数,你需要编写大量的代码逻辑,但是使用逻辑判断和字典就可以用更高效的代码来实现快速替换功能。那么今天这节课,我们就来学习下怎么实现快速替换。
|
||||
|
||||
用Python实现“替换”功能的原理
|
||||
|
||||
为了让你更直观地理解编程语言里的替换,我先来给你讲一讲用Python实现替换的原理。我用一个例子来给你讲解。比如我需要把字符串“新年快乐”替换为“恭喜发财”,在Python中,我是通过replace()函数来实现的:
|
||||
|
||||
string1="aaa新年快乐bbb"
|
||||
string2=string1.replace("新年快乐", "恭喜发财")
|
||||
print(string2)
|
||||
# aaa恭喜发财bbb
|
||||
|
||||
string3="aaa新年快乐bbb新年快乐ccc"
|
||||
string4=string3.replace("新年快乐", "恭喜发财", 2)
|
||||
print(string4)
|
||||
# aaa恭喜发财bbb恭喜发财ccc
|
||||
|
||||
|
||||
你可以看到,在这段代码中我使用了replace()函数来实现文件内容的替换。为什么使用的是字符串的替换函数呢?因为在编程语言中,我们通常会把文件内容读取到内存用变量临时储存,再进行处理。为了便于对文字进行查找替换这类的操作,通常会使用字符串这种数据类型的变量来存储文字内容。
|
||||
|
||||
实现字符串替换的replace()函数,是Python字符串内置的函数。它既可以实现匹配关键字进行一次替换,也能支持对多次出现的字符串进行替换。比如在代码的第7行我就增加了参数“2”,这就实现了“新年快乐”两次的替换。
|
||||
|
||||
刚刚说的replace()函数,实现字符串替换操作的原理,就是从字符串中从前向后,逐个字符去和replace()函数的第一个参数去比较,如果文字内容相同,就把匹配的内容替换为replace()函数第二个参数的字符串内容。如果不匹配,就继续对比下一个字符,直到整个字符串比对完成. 为了便于描述,我把这种逐一比对,找到相同字符进行替换的操作,称作“一对一替换”。
|
||||
|
||||
在实际工作中,你遇到的替换场景会更复杂。比如需要把字符串中出现的所有英文标点符号,全部一对一地替换成中文的标点符号。那么这个问题其实就变成了多个“一对一”的替换操作了。该怎么解决呢?
|
||||
|
||||
相信你会想到通过多个replace()替换函数来实现多个“一对一”替换操作。为了让你更好地理解这一逻辑,我使用了如下代码,给你演示一下它的基本功能。
|
||||
|
||||
string5='aaa,."bbb'
|
||||
string6=string5.replace(',', ',')
|
||||
string6=string6.replace('.', '。')
|
||||
string6=string6.replace('"', '“')
|
||||
# 需要更多的replace()匹配更多的标点符号
|
||||
print(string6)
|
||||
# aaa,。“bbb
|
||||
|
||||
|
||||
在这段代码当中,我使用了三个replace()函数实现“,.“” 三个符号的替换。不过一旦考虑替换更多的符号时,就要编写更多个replace()函数。这一行为虽然不会在运行效率上产生问题,但是会带来代码阅读上的障碍。
|
||||
|
||||
比如需要你通过Python把全国的省市地县的汉语拼音替换成汉字,像把“GUANGDONG”替换成“广东省”,你至少要编写上百个替换函数。那面对如此大量的“一对一”替换,我们该怎么高效地编写代码呢?
|
||||
|
||||
怎样实现批量替换?
|
||||
|
||||
我来解决这类问题一般会采用两种方式实现,一种方式是用字典+自定义函数替代replace()函数,另一种是用逻辑判断+自定义函数替代replace()函数。我们先来看字典+自定义函数的方式是怎么对带有大量replace()的程序进行优化的。
|
||||
|
||||
用字典+自定义函数替代replace函数实现批量“一对一”替换
|
||||
|
||||
我们还是用把城市名称的拼音替换成汉字的例子来讲解。为了让你更直观地比较字典+自定义函数方式和replace()函数的区别,我先给你演示一下实现替换功能的代码:
|
||||
|
||||
# 保存映射关系的函数,函数的主要功能是通过字典实现的
|
||||
def replace_city(city_name):
|
||||
return {
|
||||
"GUANGDONG":"广东省",
|
||||
"HEBEI":"河北省",
|
||||
"HUNAN":"湖南省",
|
||||
"HANGZHOU":"杭州市"
|
||||
}[city_name]
|
||||
|
||||
# 根据映射关系实现批量循环
|
||||
def replace_multi(my_citys, replaced_string):
|
||||
for pinyin_city in my_citys:
|
||||
replaced_string = replaced_string.replace(
|
||||
pinyin_city,replace_city(pinyin_city))
|
||||
return replaced_string
|
||||
|
||||
# 哪些城市要替换
|
||||
citys = ("GUANGDONG", "HUNAN")
|
||||
|
||||
# 需要替换的字符串
|
||||
string1 = """
|
||||
GUANGDONG,简称“粤”,中华人民共和国省级行政区,省会广州。
|
||||
因古地名广信之东,故名“GUANGDONG”。位于南岭以南,南海之滨,
|
||||
与香港、澳门、广西、HUNAN、江西及福建接壤,与海南隔海相望。"""
|
||||
|
||||
string2 = replace_multi(citys, string1)
|
||||
print(string2)
|
||||
# 广东省,简称“粤”,中华人民共和国省级行政区,省会广州。
|
||||
# 因古地名广信之东,故名“广东省”。位于南岭以南,南海之滨,
|
||||
# 与香港、澳门、广西、湖南省、江西及福建接壤,与海南隔海相望。
|
||||
|
||||
|
||||
我在代码里是通过两个核心函数来实现替换的,它们分别是replace_city()和replace_multi()函数。
|
||||
|
||||
我们先来分析一下replace_city()函数。它实现的是城市拼音和城市中文名称的全部对应关系,其中有两个技术细节需要你掌握。
|
||||
|
||||
第一个技术细节是字典的取值方式。如果我把一个字典定义为dict1,并且想取得字典的值,就可以使用这样的代码:
|
||||
|
||||
dict1["abc"]=123
|
||||
|
||||
|
||||
方括号中的字符串”abc”被称作字典的下标。通过下标,我们可以获得字典的值。为了定义字典以后可以反复使用,通常我们会给字典赋予一个变量名,以此作为字典名称。所以在这里dict1就是字典的名称。
|
||||
|
||||
当然,如果字典只使用一次,那也可以不使用字典名称。相应的,它的写法就变成了这样:
|
||||
|
||||
{"abc":123, "aaa":456}["abc"]
|
||||
|
||||
|
||||
通过这一行代码,你可以取出直接使用字典的值,而不需要对字典进行声明,也不需要为字典再起一个变量名。
|
||||
|
||||
第二个技术细节是我为replace_city()增加了一个参数city_name,以及一个关键字return。city_name作为城市的拼音传入函数后,会作为字典的key,通过字典的映射功能得到中文城市名称。而return关键字返回字典映射的结果,就是城市的中文名称。
|
||||
|
||||
通过这两个技术细节,就可以让函数replace_city()实现接收拼音并返回中文的功能。这样实现映射关系的好处是:函数调用一次就返回一个值,编写好这类函数之后,其他人可以拿去直接使用,不用考虑函数内部使用了哪种数据类型,有利于代码的重复使用。
|
||||
|
||||
除了replace_city()之外,还有一个核心函数replace_multi()函数,它通过for循环来实现批量“一对一”的替换。它的作用是避免重复编写大量的replace()函数,提高代码的可读性。
|
||||
|
||||
我在设计replace_multi()函数的时候,为它准备了两个参数。第一个是要替换的城市的拼音,第二个是要替换的字符串。
|
||||
|
||||
第一个参数我具体指定了哪些城市需要替换,这样编写会让我的程序更加灵活,不必把所有城市的拼音都进行拼音到中文的替换操作。
|
||||
|
||||
第二个参数也是为了让replace_multi()函数更加灵活,如果对多段文字进行替换,可以多次调用replace_multi()函数。同样的,如果replace_multi()函数需要多次调用,也可以通过循环结构批量来优化代码。
|
||||
|
||||
总结来说,通过字典+自定义函数替代字符串默认的替换函数replace()函数,可以避免编写大量的replace()函数,提高了代码的灵活性和可读性。如果你在工作中涉及这类大量的“一对一”替换时,可以考虑采用我教的这个方法来优化你的替代效率。
|
||||
|
||||
用逻辑判断+自定义函数替代replace()函数实现“多对一”替换
|
||||
|
||||
除了刚才我提到的大量“一对一”的替换场景,还有一种替换场景你也会遇到,并且一旦遇到就很棘手。
|
||||
|
||||
比如在Excel中,你需要根据年龄这一列单元格来把你的客户划分为少年、青年、中年、老年。如果把年龄的每个整数都进行一次替换,这种写法会非常啰嗦,所以我们可以使用逻辑判断来实现这一替换。我先把代码给你演示出来。
|
||||
|
||||
age = 18
|
||||
if age>0 and age<=6:
|
||||
value="少年"
|
||||
|
||||
elif age>7 and age<=18:
|
||||
value=青年"
|
||||
|
||||
elif age>19 and age<=65:
|
||||
value="中年"
|
||||
|
||||
else:
|
||||
value="老年"
|
||||
|
||||
|
||||
这段代码通过逻辑判断实现了从年龄到少年、青年等年龄段的替换功能。在代码中,“if”“elif”“else”是构成逻辑判断的关键字,它们表示了如果关键字到“:”之间的结果为True,则其他语句后面的代码不会被执行。
|
||||
|
||||
根据匹配的年龄要求,年龄是一个范围,所以我使用了and关键字连接两个判断逻辑。比如“age>0 and age<=6” 代码,意思就是当age同时满足大于0,并且小于等于6时,判断的条件才成立,这段代码的返回结果为True,因此value变量的值就是“少年”。
|
||||
|
||||
在这段代码中我使用了一个逻辑判断结构。逻辑判断结构用于判断age变量的范围,它可以根据判断的结果为value变量进行赋值。这种实现形式和城市的拼音替换不同,城市的拼音和汉字是逐一对应的,age变量的多个值,例如从1到6对应的都是“少年”,它实现的是一个范围映射到一个值上面的形式。为了便于描述,我把这种形式称作“多对一”的替换形式。
|
||||
|
||||
虽然使用逻辑判断进行替换操作,实现了的“多对一”的替换形式,但是我认为仍然存在着不利于代码复用(重复使用)的问题,因此在保证代码逻辑不变的前提下,我对这段程序进行了优化,将逻辑判断也放入函数中。代码如下:
|
||||
|
||||
def age_replace(age):
|
||||
if age > 0 and age <= 6:
|
||||
return "少年"
|
||||
|
||||
elif age > 7 and age <= 18:
|
||||
return "青年"
|
||||
|
||||
elif age > 19 and age <= 65:
|
||||
return "中年"
|
||||
|
||||
else:
|
||||
return "老年"
|
||||
|
||||
|
||||
print(age_replace(80))
|
||||
|
||||
|
||||
|
||||
我为你解释一下为什么要把替换操作放在函数中。这样使用有两点好处:
|
||||
|
||||
|
||||
提高代码的复用,当你下次需要做年龄到年龄段映射时,可以直接调用函数,不用重复编写逻辑判断的代码。
|
||||
对代码进行再设计的时候,方便将逻辑判断中类似“age > 0 and age <= 6”的判断逻辑再封装成函数。
|
||||
|
||||
|
||||
你可能会问了,代码不是应该先思考运行过程,经过设计之后再编码的吗?
|
||||
|
||||
在实际工作中,随着人们对代码的不断修改,原来设计好的代码结构,在整体结构上会增加很多的判断逻辑,代码质量会越来越混乱。这时候就需要你重新对代码逻辑进行优化。所以为了不让后续修改代码逻辑的行为破坏代码的可读性,就应该在初次设计和编写代码的时候考虑好代码的扩展性。
|
||||
|
||||
最后,我想再强调一下,使用if逻辑判断的目的是为了实现把一个范围映射到一个新的值,这样它就间接地实现了替换功能。所以当你解决替换问题的时候,不要把思维只局限在字符串自带的replace()函数中。
|
||||
|
||||
小结
|
||||
|
||||
我来给你总结一下今天的主要内容,围绕着“替换”这一功能,我给你讲解了三种实现替换的方法:
|
||||
|
||||
|
||||
字符串的replace()函数;
|
||||
使用字典做“一对一”映射,通过字典类型的键值对,实现内容替换;
|
||||
使用逻辑判断实现“多对一”映射,将if判断的条件替换为匹配成功的结果。
|
||||
|
||||
|
||||
替换操作要根据被替换内容的形式,选择合适的方法,replace()函数更适合单个替换,字典适合“一对一”替换,if逻辑判断适合将一个范围替换成一个值。
|
||||
|
||||
除了灵活掌握不同的替换方式,我还建议你把字典和逻辑判断放入自定义函数当中,当你遇到类似需求的时候就可以直接复用代码。
|
||||
|
||||
思考题
|
||||
|
||||
通过将城市的拼音替换成汉字的功能,你是否能实现一个自己的自动多文件标点符号替换函数,将英文符号替换为中文符号呢?
|
||||
|
||||
|
||||
|
||||
|
179
专栏/Python自动化办公实战课/05图像处理库:如何实现长图拼接?.md
Normal file
179
专栏/Python自动化办公实战课/05图像处理库:如何实现长图拼接?.md
Normal file
@ -0,0 +1,179 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
05 图像处理库:如何实现长图拼接?
|
||||
你好,我是尹会生。
|
||||
|
||||
我们在工作中,除了和文字、表格打交道之外,还会经常涉及到批量处理图片和视频的工作。比如:媒体从业者在发微博长图文时,需要把多个图片拼接成一幅长图;作为视频剪辑人员,需要从互联网下载多段视频,再进行合并。
|
||||
|
||||
这类工作可以用功能强大的商业软件实现,不过这些软件大都操作繁琐,而且还需要付费。为了降低学习成本和购买软件的成本,我们往往还会使用开源软件替代商业软件来实现图片和视频处理功能。但是开源软件通常都是以命令行方式运行的,所以我们不仅要记住命令,还得记住命令的常用参数。
|
||||
|
||||
不过,幸运的是,虽然直接使用开源软件不够友好,但如果通过Python来调用这些开源软件,那实现长图和视频拼接就轻而易举了,而且还能大批量地处理图片和视频。
|
||||
|
||||
Python是如何调用外部命令的
|
||||
|
||||
为了让你了解Python是如何操作这些开源软件的,我先来给你介绍一下Python调用外部程序的原理。
|
||||
|
||||
我们要想使用Python语言之外的功能,要依靠两大途径:导入函数库和调用外部命令。
|
||||
|
||||
在第一讲我使用的xlrd库是通过import xlrd命令导入到Python语言中的,Python语言默认是不支持Excel的。那么通过导入函数库,Python就可以获得对Excel的操作能力。
|
||||
|
||||
还有一种情况是,需要操作Python语言之外的功能,但这个功能没有人将它开发成函数库,那如果我们想要使用这些功能,使用的途径就是调用外部命令了,而调用外部命令就需要Python内部函数库的subprocess模块来实现。
|
||||
|
||||
这个模块的实现机制是:它的run()函数的参数可以指定一个可以运行的程序的路径,而Python会根据这个路径来运行可执行文件,然后再根据运行结果,以及Python的逻辑判断去进行后续的自动化处理工作。
|
||||
|
||||
这个实现机制并不难,我给你写一段简单的程序,帮你理解Python是怎样调用外部命令的。这里以macOS系统为例,我们通过Python获取当前目录下所有文件的功能。
|
||||
|
||||
from subprocess import run, Popen, PIPE
|
||||
|
||||
cmd1 = ["ls", "."]
|
||||
returncode = run(cmd1)
|
||||
|
||||
print(returncode)
|
||||
# CompletedProcess(args=['ls', '.'], returncode=0)
|
||||
# returncode是“ls .”的退出状态码.
|
||||
# 通常来说, 一个为 0 的退出码表示进程运行正常
|
||||
|
||||
# 使用Popen获取程序运行结果
|
||||
with Popen(cmd1, shell=True, stdout=PIPE, stderr=PIPE, encoding="utf-8") as fs:
|
||||
|
||||
# 如果程序在 timeout 秒后未执行完成,会抛出 TimeoutExpired 异常
|
||||
fs.wait(2)
|
||||
|
||||
# 从标准输出中读取数据,知道文件结束
|
||||
files = fs.communicate()[0]
|
||||
|
||||
print(files)
|
||||
|
||||
|
||||
|
||||
这段代码中最核心的函数是run()函数和Popen类。subprocess模块就是通过这两个函数实现的外部程序调用。我来为你重点剖析一下它们的功能、参数,以及何时选择run()函数、何时选择Popen类。
|
||||
|
||||
为了实现Python调用可执行文件,首先在代码的第一行,我是这样编写的:
|
||||
|
||||
from subprocess import run, Popen, PIPE
|
||||
|
||||
|
||||
这样一行代码,它和我第一讲使用的import方式导入函数库的区别是,这种形式可以让你直接使用模块中的类和方法。
|
||||
|
||||
如果你使用 “import subprocess”方式导入subprocess库的话,在调用run()函数的时候,就需要用 “库.函数”的形式在Python中使用库当中的函数,即“subprocess.run()”。在你多次调用run()函数时,代码会较长,那么使用“from import”方式导入,就可以在当前代码文件中直接使用run()函数,为代码的阅读带来更好的体验。
|
||||
|
||||
接下来,我定义了一个变量cmd1。这个变量的值是macOS命令行能够运行的“ls .”命令,这个命令的执行结果是显示当前目录下所有文件和文件夹的名称。
|
||||
|
||||
run()函数的主要功能就是执行一个新的程序,它的用法非常简单,把第一个参数指定为要执行程序的路径就可以了。如果要执行的程序带有参数,那就可以使用列表数据类型存放可执行程序名称和参数,像是我在程序中定义的cmd1变量一样。如果你需要运行其他命令,把代码中的ls替换为你想要运行的其他程序就行了。
|
||||
|
||||
为了让Python自动化处理程序更强大,除了运行程序外,你还可以得到可执行程序的运行结果。在这种情况下,我们就需要使用Popen类替代run()函数实现外部程序的调用。
|
||||
|
||||
可以看到,我在代码的第12行先通过Popen类执行了“ls .”命令,接着通过参数stdout=PIPE 将命令的执行结果放入到PIPE对象中, 最后再通过communicate()函数将PIPE中的内容读取出来,存放到files变量中,这样就实现了读取命令执行结果的功能。
|
||||
|
||||
这个功能是无法在run()函数实现的,因此在你需要通过Python读取程序执行结果的时候,就可以选择Popen类。不过如果只需要运行可执行程序,那使用run()函数就能满足你的要求了。如果你想更深入地了解它们,我建议你阅读subprocess库的官方文档。
|
||||
|
||||
以上就是我用subprocess库实现Python调用可执行程序的方法。Python之所以被我们称作最佳的“胶水语言”,就是因为它能轻易“粘合”可执行程序。利用Python灵活的逻辑判断、循环语法可以实现程序的批量执行和流程管理。
|
||||
|
||||
接下来,我们就使用subprocess来实现长图拼接和视频拼接的功能。
|
||||
|
||||
长图拼接
|
||||
|
||||
当我进行微博文案推广的时候,需要将多个图片拼接成一个长图。拼接图片的功能Python本身是不具备的,因此就需要引入外部命令来实现图片拼接功能。
|
||||
|
||||
我在macOS平台上找到了一个非常强大的图像处理软件叫做ImageMagick,它能对图片进行编辑、合并、切割、旋转等90多种操作。 ImageMagick软件实现图片拼接的命令格式是这样的:
|
||||
|
||||
composite 图片1.jpg 图片2.jpg ... 图片n.jpg 最终合成结果.jpg
|
||||
|
||||
|
||||
在这段命令格式中,composite命令的参数包含了多个图片文件,每个图片需要对照着文件将图片的路径和文件名写在参数中。如果手工输入图片名称,不仅效率低,而且容易遗漏。另外,如果需要大量重复使用composite,还需要精细调整合并结果,给composite程序增加很多参数。
|
||||
|
||||
因此,我就可以通过Python调用可执行程序的subprocess库,对composite拼长图的工作进行脚本化编程。它的核心实现代码如下:
|
||||
|
||||
p = Path(jpg_path)
|
||||
|
||||
# 增加命令
|
||||
cmd = ["composite",]
|
||||
|
||||
# 增加参数
|
||||
for x in p.iterdir() if PurePath(x).match('*.jpg'):
|
||||
cmd.append(x)
|
||||
|
||||
# 增加结果
|
||||
cmd.append(result_path)
|
||||
|
||||
run(cmd)
|
||||
|
||||
|
||||
由于composite可以把长图合成的结果直接输出为文件,因此采用run()函数即可实现程序执行的功能。另外,当你需要调整composite参数时,可以直接修改cmd变量的值,并不需要改动程序其他部分。当你要对新的一组图片进行合成的时候,重新设置jpg_path变量就行了。
|
||||
|
||||
总结来说,使用Python调用composite合并的好处就是:你不用记住程序使用的繁杂的命令行参数,也不用记住运行逻辑,因为Python程序已经事先把逻辑编写好了。
|
||||
|
||||
视频的拆分与合并
|
||||
|
||||
在了解了如何使用subprocess调用composite实现长图拼接之后,我再给你讲一下如何使用subprocess库调用可执行程序,来进行视频的拆分与合并。
|
||||
|
||||
我们先来学习下视频拆分的原理。
|
||||
|
||||
你在电脑本地经常见到的视频格式是MP4,但如果要把视频放在互联网上,为了减少首次播放的加载时间,你就必须把一个MP4切分成多个文件,而且切分之后还需要把格式转换为.TS格式的视频文件。
|
||||
|
||||
为什么不直接使用MP4格式,而是要把MP4格式改成.TS格式呢?这是因为.TS格式可以保证多个文件之间的视频无缝播放,而且还会保证视频不会在播放下一个文件的时候,出现破音或画面中断等影响用户体验的情况。
|
||||
|
||||
当我们将一个视频切分成多个文件的时候,就要考虑文件的播放顺序问题了。为了记录顺序,我们需要在切分之后引入一个索引文件,这个索引文件不用手动编写,我们直接使FFmpeg命令就行了,它可以实现视频格式的转换、合并和拆分。FFmpeg命令会在切分之后,自动产生一个以.M3U8结尾的索引文件。
|
||||
|
||||
我来解释一下这个索引文件。M3U8文件是指UDF-8编码格式下的M3U视频索引,播放器通过这个索引文件就可以找到视频下所有的分段,并依次播放视频。
|
||||
|
||||
看到这儿你应该就能明白了,想要使用Python进行视频拆分,我们首先需要FFmpeg命令,然后通过Python设置FFmpeg的参数,最后再指定MP4文件和.TS文件的路径,这样就能实现拆分视频的功能了。因此我使用这样的代码来实现视频拆分:
|
||||
|
||||
from subprocess import run
|
||||
input_video = "/Users/edz/Desktop/05/xxx.mp4"
|
||||
segment_time = 10
|
||||
m3u8_list = "/Users/edz/Desktop/05/xxx.m3u8"
|
||||
output_video = "/Users/edz/Desktop/05/video-%04d.ts"
|
||||
|
||||
cmd1 = ["ffmpeg", "-i", input_video, "-f", "segment", "-segment_time", str(segment_time), "-segment_format",
|
||||
"mpegts", "-segment_list", m3u8_list, "-c", "copy", "-bsf:v", "h264_mp4toannexb", "-map", "0", output_video]
|
||||
|
||||
run(cmd1)
|
||||
|
||||
|
||||
|
||||
在代码中,我通过FFmpeg把MP4切分成了多段TS文件。你要想实现相同功能,首先需要在电脑中安装FFmpeg命令,它的下载地址为:https://ffmpeg.org/download.html。
|
||||
|
||||
为了实现MP4文件格式的分割,需要使用ffmpeg非常多的参数。不过使用Python进行调用的好处,就是你不用记住复杂的参数。我们把输入文件路径、切分大小、输出的M3U8和TS文件指定为四个变量,这样只修改这四个变量,就可以实现拆分功能了。
|
||||
|
||||
如果你需要离线观看视频,就要将网络上的视频下载到本地,这时你会发现从互联网下载的格式是M3U8和TS文件。那又怎么把它们合并成MP4文件呢?
|
||||
|
||||
你同样可以使用FFmpeg命令,但是FFmpeg的参数不同。我将FFmpeg的命令写在这里:
|
||||
|
||||
ffmpeg -allowed_extensions ALL -protocol_whitelist "file,http,crypto,tcp,https" -i index.m3u8 -c copy out.mp4
|
||||
|
||||
|
||||
如果你不想背诵这么长的参数,完全可以仿照Python整合拆分视频的代码来实现合并功能。先FFmpeg命令和参数放入列表,再把M3U8文件和MP4文件放入变量,便于你合并新的视频的时候进行重新赋值。
|
||||
|
||||
所以你看,相比直接使用FFmpeg,subprocess调用FFmpeg的优势就在于两点,一是不用记住复杂参数,二是对批量转换视频非常有利。举两个例子。
|
||||
|
||||
如果你是视频剪辑的专业工作者,肯定要大量使用FFmpeg更复杂的功能,这些功能对应的参数一般都比较多,而且参数很多都使用了简写和大小写, 很难记忆。但要是使用Python调用的话,你可以直接更改要操作的文件路径,就不必记录大量的参数。
|
||||
|
||||
另外需要进行视频的批量转换时,可以通过第一讲的循环操作对视频任务批量处理,这样就避免了手动逐个修改书写文件的操作,从而提高视频转换的效率。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下这节课的主要内容。
|
||||
|
||||
通过对subprocess库的讲解,你知道了怎样通过它实现Python加载外部可执行程序,并且能够对程序执行的结果进行处理。
|
||||
|
||||
我也为你讲解了长图拼接和视频拆分合并的两个案例,帮你更好地理解Python为什么会被称作“胶水”语言。
|
||||
|
||||
我还想强调一下,通过Python调用可执行程序的用法非常常见,特别是在多媒体处理、自然科学、AI等领域里。在这些专业领域,为了加快计算速度,通常会使用C++语言实现专业程序。
|
||||
|
||||
这些专业程序参数多、功能单一,且使用命令行执行,当你需要多次执行这些程序,又不想背诵它们的参数的时候,就可以利用Python的判断循环功能,结合C++语言实现的专业程序,来实现批量执行和减少参数手动输入的工作,提高你的工作效率。
|
||||
|
||||
最后,我也把这节课的代码附上,你可以查看。本讲代码
|
||||
|
||||
思考题
|
||||
|
||||
在最后也请你思考一下,你在工作当中是否会使用命令行工具呢?它们能否用Python进行包装,从而避免手写复杂参数呢?
|
||||
|
||||
如果你觉得这节课有用,能解决你的办公效率问题,欢迎你点击“请朋友读”,分享给你的朋友或同事。
|
||||
|
||||
|
||||
|
||||
|
200
专栏/Python自动化办公实战课/06jieba分词:如何基于感情色彩进行单词数量统计?.md
Normal file
200
专栏/Python自动化办公实战课/06jieba分词:如何基于感情色彩进行单词数量统计?.md
Normal file
@ -0,0 +1,200 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
06 jieba分词:如何基于感情色彩进行单词数量统计?
|
||||
你好,我是尹会生。
|
||||
|
||||
在涉及运营、市场的工作中,我们经常需要根据产品评论的情感分析,来了解某一产品的口碑。所谓的情感分析,就是指根据用户对产品的评论,分析出用户对产品的喜好程度。
|
||||
|
||||
最简单的,我们会区分产品的评价是正向还是负向的,然后根据反馈结果改变产品的特性。稍微复杂一点的,我们会根据情感色彩将产品的评价关键词提取出来,进行统计和分类(用于更深入的分析产品)。
|
||||
|
||||
如果靠人工对产品评价进行辨析,有很大的局限性:一个是不够公平,因为每个人对词语感情色彩的理解并不是完全一致的;另一个是产品评价有很多,而且还会不定期增加,人工分析很难保证及时性。
|
||||
|
||||
因此,在进行词语的情感分析时,我通常都会使用Python的jieba库,来自动化实现文本情感分析功能。一般需要经过三个步骤,分别是分词、优化分词结果和情感分析。
|
||||
|
||||
那我就先带你看看为什么要进行分词,以及如何进行分词操作。
|
||||
|
||||
如何分词?
|
||||
|
||||
要想判断一段话表达的情感是正向还是负向,就需要根据这句话中的关键词来得到情感的倾向。例如一段话中出现了“开心”“高兴”“物超所值”等正向的词语,我们就可以认定这条产品的评价是偏正向的。相反,出现“不喜欢”“差”等词语,评价就是偏负向的。
|
||||
|
||||
但是,要想从一句话中将这些表达情感的词一个一个找出来,就需要依靠专业的工具把一句话根据语义划分成多个词,再把表达情感的词语提取出来,进行情感分析。
|
||||
|
||||
为什么要先根据语义来划分词呢?这主要是因为中文句子里的每个词中间没有用空格进行分隔,没有分隔就没法进行之后的情感分析。而对中文句子按照语义进行切割的这种操作,我们就称为“分词”。
|
||||
|
||||
Python中有非常成熟的分词库,其中最流行的库是jieba库。在计算机中,实现语义分割的技术有两种,一种是从统计学的角度分词,另一种是从词库的角度基于TF-IDF算法实现分词。jieba就是采用第二种,基于词库的角度对文章进行自动分词的。
|
||||
|
||||
那我就以电商网站上的一段商品评论为例,给你演示一下jieba库是如何实现分词的。
|
||||
|
||||
import jieba
|
||||
|
||||
words1="速度快,包装好,看着特别好,喝着肯定不错!价廉物美"
|
||||
|
||||
words2 = jieba.cut(words1)
|
||||
|
||||
print("/".join(words2))
|
||||
# 速度/快/,/包装/好/,/看着/特别/好/,/喝/着/肯定/不错/!/价廉物美
|
||||
|
||||
|
||||
在这段代码中,我利用jieba库的cut()函数实现了自动分词功能。我刚才讲了,jieba分词是依靠词库实现的,词库里包含了提前准备好的词和词性。下图就是jieba词库的内容:
|
||||
|
||||
一鼓 ru
|
||||
一鼓作气 ru
|
||||
一马当先 ru
|
||||
... ...
|
||||
|
||||
|
||||
这些词库中的词,jieba是怎么识别的呢?
|
||||
|
||||
在你使用pip命令安装了jieba库之后,它会附带一个默认的词库。在官方文档中,将这个词库称作“字典”文件。这个文件包含了日常汉语的词语、词性。jieba库会先基于“字典”对文章中所有可能出现的词进行匹配。匹配之后,会生成句子中的汉字所有可能形成的词。然后再将这些词构成的有向无环图(DAG),并采用动态规划算法查找最大概率路径,尽可能不会将一个词拆分成单个汉字。最后再从“字典”找出基于词频的最大切分组合,把这分词的组合从句子中找出来,形成一个一个的词。
|
||||
|
||||
而且,为了提高分词的准确率,jieba对没有记录在字典的词(称作未登录词)也使用了分词的模型,它就是大名鼎鼎的基于汉字成词能力的HMM模型(隐马尔可夫模型)。对词库中的词和未登录词进行处理之后,jieba就可以实现自动化分词了。
|
||||
|
||||
不过,分词之后,我们还需要对分词结果进行优化。因为在分词结果中存在着大量的标点符号,还有“看着”“喝着”“包装” 等和表达产品评价的情感无关的词语,为了加快计算词语的情感速度、避免无关词语影响情感倾向判断,我们就要优化分词的结果。
|
||||
|
||||
优化分词结果
|
||||
|
||||
优化分词结果主要从两个方面进行优化:一方面是移除标点符号;一方面是删除和情感无关的助词、名词等。
|
||||
|
||||
我先来带你学习下怎么从分词结果中移除标点符号。
|
||||
|
||||
移除标点符号一般有两种方法:
|
||||
|
||||
|
||||
删除停止词(Stop Words);
|
||||
根据词性提取关键词。
|
||||
|
||||
|
||||
先来看看第一种,删除停止词。
|
||||
|
||||
所谓的停止词,就是指为了节省空间和提高匹配词语情感倾向的效率,在进****行情感分析前自动过滤掉的某些字或词。
|
||||
|
||||
停止词主要是标点符号,也可以是“啊呀呢”等语气助词。把标点符号写入停止词列表后,再使用for循环功能,将jieba分好的词和停止词列表依次匹配。如果jieba分好的词出现在列表中,就将这些词删掉。如果没有出现在列表中,就把这些词再组合成一个新的列表,后续就可以对新的列表进行情感分析。
|
||||
|
||||
删除停止词的代码如下。通过删除停止词,我们就可以得到只有汉字的分词结果。
|
||||
|
||||
words2 = jieba.cut(words1)
|
||||
words3 = list(words2)
|
||||
print("/".join(words3))
|
||||
# 速度/快/,/包装/好/,/看着/特别/好/,/喝/着/肯定/不错/!/价廉物美
|
||||
|
||||
stop_words = [",", "!"]
|
||||
words4 =[x for x in words3 if x not in stop_words]
|
||||
print(words4)
|
||||
# ['速度', '快', '包装', '好', '看着', '特别', '好', '喝', '着', '肯定', '不错', '价廉物美']
|
||||
|
||||
|
||||
另一种优化分词结果的方式叫做根据词性提取关键词。这种方式的优点在于不用事先准备停用词列表,jieba库就能够根据每个词的词性对其进行标注。
|
||||
|
||||
我这里为你提供了一张paddle(paddle是百度开源的深度学习平台,jieba使用了paddle的模型库)模式词性表作为参考,你可以根据jieba自动分析得到的词性结果,手动将助词、虚词(标点符号)移除。
|
||||
|
||||
-
|
||||
我把这个基于词性移除标点符号的代码也提供给你:
|
||||
|
||||
# words5 基于词性移除标点符号
|
||||
import jieba.posseg as psg
|
||||
words5 = [ (w.word, w.flag) for w in psg.cut(words1) ]
|
||||
# 保留形容词
|
||||
saved = ['a',]
|
||||
words5 =[x for x in words5 if x[1] in saved]
|
||||
print(words5)
|
||||
# [('快', 'a'), ('好', 'a'), ('好', 'a'), ('不错', 'a')]
|
||||
|
||||
|
||||
|
||||
在这段代码中,我在使用jieba库的posseg类实现分词的同时,也对词性进行了标注。为了让你看到更直接的结果,我只保留了形容词,因此,变量saved的列表参数就只有一个‘a’,表示保留的词类型为形容词。
|
||||
|
||||
如果你希望保留更多的词性,可以将词性表中代表每种词的英文缩写写入saved列表中,其中,我建议你在处理之后把形容词、副词、动词都保留下来,这些都有助于你进行下一步的语义情感分析。
|
||||
|
||||
在优化分词结果之后,我们就得到了只有形容词的处理结果。那么,接下来,我们需要基于这些形容词来获取产品评价的正向或负向结果,以及基于词语的情感色彩来统计单词的数量。
|
||||
|
||||
语义情感分析
|
||||
|
||||
对于已经分好词的语句,我们需要使用另一个库统计词的正向、负向情感倾向,这个库就是snownlp库。
|
||||
|
||||
snownlp库既能实现分词,也能计算词出现频率,以及进行情感分析。那你可能就发出疑问了:为什么不直接使用snownlp进行分词,而要使用jieba分词呢?
|
||||
|
||||
原因就在于,snownlp的算法问题,会让它对否定词划分得不够准确。例如“不喜欢”,snownlp会把这个词划分为两个独立的词,分别是“不”和“喜欢”。那么,在计算语义情感时,就会产生较大的误差。所以我们会先采用jieba进行分词,分词之后再采用snownlp来实现语义情感分析功能。
|
||||
|
||||
接下来,我带你看一下如何使用snownlp得到完成分词之后的情感分析结果。代码如下:
|
||||
|
||||
from snownlp import SnowNLP
|
||||
words6 = [ x[0] for x in words5 ]
|
||||
s1 = SnowNLP(" ".join(words3))
|
||||
print(s1.sentiments)
|
||||
# 0.99583439264303
|
||||
|
||||
|
||||
这段代码通过snownlp的Bayes(贝叶斯)模型训练方法,将模块自带的正样本和负样本读入内存之后,再使用Bayes模型中的classify()函数进行分类,这样就得到了sentiments属性的值,sentiments的值表示情感倾向的方向。在snownlp中:
|
||||
|
||||
|
||||
如果情感倾向是正向的,sentiments的结果会接近1。
|
||||
如果情感倾向是负向的,结果会接近0。
|
||||
|
||||
|
||||
可以看到,我们在刚刚的代码中得到的情感分析的结果是0.9958,非常接近1,因此这条产品的评价就是正向的。
|
||||
|
||||
情感倾向结果趋近于1或者趋近于0都是非常理想的情况,可以直接得到感情色彩比较强烈的产品评价。但是,有时候感情色彩不太强烈,在这种情况下,我们就需要根据评价的数值范围对评论进行分组,统计每组包含多少个评价。
|
||||
|
||||
这个功能也可以通过snownlp实现,我把代码写在这里,你可以参考:
|
||||
|
||||
positive = 0
|
||||
negtive = 0
|
||||
for word in words6:
|
||||
s2 = SnowNLP(word)
|
||||
|
||||
if s2.sentiments > 0.7:
|
||||
positive+=1
|
||||
else:
|
||||
negtive+=1
|
||||
|
||||
print(word,str(s2.sentiments))
|
||||
print(f"正向评价数量:{positive}")
|
||||
print(f"负向评价数量:{negtive}")
|
||||
# 快 0.7164835164835165
|
||||
# 好 0.6558628208940429
|
||||
# 好 0.6558628208940429
|
||||
# 不错 0.8612132352941176
|
||||
# 价廉物美 0.7777777777777779
|
||||
# 正向评价数量:3
|
||||
# 负向评价数量:2
|
||||
|
||||
|
||||
通过snownlp库配合jieba分词的结果,你就可以实现批量产品评论的自动语义情感分析了。同时,你还可以根据不断累积产品的评价,来持续优化你的产品。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下对文件进行情感倾向分析的关键步骤和注意事项。实现语义情感分析功能,你必须掌握分词、优化分词结果、语义情感分析这三个步骤。
|
||||
|
||||
其中分词是实现中文语义分析的第一步,也是最基础的部分。分词的好坏决定了对每个词的情感进行标注的准确程度。如果默认的jieba分词没有正确地把词语划分,你也可以使用jieba自带的suggest_freq()函数进行词频调节。
|
||||
|
||||
举个小例子,“中”“将”两个字可以组成“中将”的词语,也可以拆开来用“我们中/将有人成功考上北大”。在不同的语言环境中,我们要通过词频调节来让它们以词的形式出现,还是以两个字的方式出现。调整的方法是:
|
||||
|
||||
jieba.suggest_freq(("中", "将"), tune = True)
|
||||
|
||||
|
||||
可以看到,利用调节词频使“中”“将”都能被分出来,而不会被错误地识别为一个词“中将”,通过这种方式,就可以提升jieba的识别正确率。
|
||||
|
||||
在优化分词结果这一步,你可以通过减少虚词和标点符号,通过停止词、词性的选择,来降低它们对情感分析结果的干扰。
|
||||
|
||||
最后,你还可以为snownlp增加新的流行词和网络用语,帮你更准确地分析用户对产品的喜好程度,从而提高产品定位的精确度。
|
||||
|
||||
在snownlp中,通过train()和 save()两个函数把模型训练和保存之后,就能实现扩展默认字典的功能了。此外,我在工作中还会利用这种方式增加emoji表情对应的情感倾向分析功能,以此来进一步提升snownlp分析情感倾向的准确度。
|
||||
|
||||
我将训练模型和保存训练后的模型的函数也写在这里供你参考,希望通过训练自己的模型,能够让你的产品分析更加准确。
|
||||
|
||||
sentiment.train(neg2.txt,pos2.txt); # 训练用户自定义正负情感数据集
|
||||
sentiment.save('sentiment2.marshal'); # 保存训练模型
|
||||
|
||||
|
||||
今天用到的代码,我都放在了 GitHub 上,你可以点击这个链接查看。
|
||||
|
||||
思考题
|
||||
|
||||
我给你留一道思考题,我在最后一段代码分别统计了正向和负向评价的数量,你能否根据这段代码统计一段文字中包含了多少个动词、多少个名词和多少个形容词呢?欢迎你在课程评论区留言,和我一起讨论。
|
||||
|
||||
|
||||
|
||||
|
299
专栏/Python自动化办公实战课/07快速读写文件:如何实现跨文件的字数统计?.md
Normal file
299
专栏/Python自动化办公实战课/07快速读写文件:如何实现跨文件的字数统计?.md
Normal file
@ -0,0 +1,299 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
07 快速读写文件:如何实现跨文件的字数统计?
|
||||
你好,我是尹会生。这节课,我们来学习下统计多个文件字数的方法。
|
||||
|
||||
之前我在出版图书的时候,每个章节都写入了一个单独的Word中,这就导致我无法使用Word自带的字数统计功能,一次性统计所有章节的字数,自然也就不能分别统计多文件的汉字、英语和标点符号的字数了。如果你在工作中也遇到过类似的情况,需要一次性统计大量的文本文件的字数,应该怎么解决呢?
|
||||
|
||||
今天我就来教你,怎样使用Python来批量统计多个文件的字数和标点符号。
|
||||
|
||||
首先呢,我们先学习一下简单的操作:统计单个文件的字数。掌握了Python统计字数的基本操作,批量统计也就不在话下了。
|
||||
|
||||
怎样实现单个文件字数统计
|
||||
|
||||
统计单个文件的字数,需要用到Python的文件读写函数和变量这两个功能。Python的读写函数是对文件内容的读取操作,变量呢,用来存储统计好的文件字数。
|
||||
|
||||
我把统计单个文件字数的操作分为三个步骤,分别为:
|
||||
|
||||
|
||||
先把要统计的文件读入内存;
|
||||
再对读入到内存的字数数量进行统计,并用变量保存;
|
||||
最后是将结果写入统计字数的专用文件中。
|
||||
|
||||
|
||||
我先来带你学习一下用Python读取文件需要使用哪些函数和语法。
|
||||
|
||||
1.把文件内容读取到内存中
|
||||
|
||||
Python实现读取文件内容到内存的函数有三个,这三个函数原理相同,但是读取的内容多少有些不同,分别为:
|
||||
|
||||
|
||||
read() ,逐个字符读取,一直读取到文件全部内容结束;
|
||||
readline(),读取一行内容;
|
||||
readlines(),读取多行内容。
|
||||
|
||||
|
||||
对于统计单个文件字数的需求,选择哪个读取函数更合适呢?由于统计字数需要逐个字读取再对字数进行累加,因此,我使用read()函数将文件中所有的文字读入到内存中,相比按行读取,实现起来更加简单。
|
||||
|
||||
将文件内容读入变量后,变量中的文字内容和保存在文件中的文字内容相同,接下来我再通过统计字数的函数对变量进行统计,就能统计出一个文件里的文字有多少字数了。
|
||||
|
||||
具体如何实现呢?我给你举一个例子。
|
||||
|
||||
例如,我这里有一个需要统计字数的文件e.txt,它的内容为:
|
||||
|
||||
e约等于2.718281828
|
||||
|
||||
|
||||
核心实现代码如下:
|
||||
|
||||
import pathlib
|
||||
|
||||
file_name = "e.txt"
|
||||
|
||||
# 取得脚本所在目录
|
||||
current_path = pathlib.PurePath(__file__).parent
|
||||
|
||||
# 和脚本同目录下的文件绝对路径
|
||||
file = current_path.joinpath(file_name)
|
||||
# 打开文件
|
||||
with open(file, encoding='utf-8') as f:
|
||||
# 读取文件
|
||||
content = f.read()
|
||||
words = content.rstrip()
|
||||
number = len(words) # 统计字数
|
||||
print(number)
|
||||
# 15
|
||||
|
||||
|
||||
通过这段代码,你会发现,文件的读取函数是实现字数统计的关键功能。下面,我重点给你讲解下Python是怎么读取文件的。
|
||||
|
||||
Python要想读取一个文件,需要经过打开、读取和关闭这三个步骤。
|
||||
|
||||
在代码的第11行,我通过open()函数实现了文件的打开功能。需要注意的是,我为open()函数设置了两个参数,分别是:
|
||||
|
||||
|
||||
file变量,表示要打开文件的名称;
|
||||
encoding变量,表示文件的编码格式。
|
||||
|
||||
|
||||
我来解释下这两个参数。第一个参数file变量的值是要操作的文件路径和文件名称,你需要正确指定它所在的路径和文件名称。第二个参数encoding变量的值指定了以哪种字符编码打开文件,错误的字符编码会导致内容读取异常,文字内容显示为“乱码”。
|
||||
|
||||
我们来具体看一下第一个参数file变量的路径是怎么正确进行设置的。
|
||||
|
||||
对file变量进行赋值时,需要注意赋值时的路径和文件的真实路径是否一致,如果file变量中的路径和真实路径不一致的话,Python程序运行的时候会提示找不到文件的错误。那应该如何确保路径正确呢?
|
||||
|
||||
我们通常的做法是事先在字符终端上取得要操作的文件的完整路径,再将完整路径作为字符串赋值给变量file,这样你就可以直接使用正确的文件路径了。但是这样操作步骤繁琐,且不够灵活。所以还有一种比较灵活的方法,那就是将要操作的文件和Python脚本文件放在相同的目录里,通过获取Python脚本文件的目录,间接获得要操作的文件目录。
|
||||
|
||||
例如我将e.txt和Python脚本文件放在同一目录,那么我在打开e.txt文件时,就要先获取Python脚本所在的目录位置了。我这里使用了一个特殊的变量__file__,来取得当前脚本所在的路径。
|
||||
|
||||
__file__变量比较特殊,它是在Python中预先定义好的一个变量,默认情况下它的值会被Python自动设置为当前脚本的完整路径名称。有了它,我就可以通过Python脚本的路径取得e.txt的完整路径了。获取完整路径的具体的步骤如下:
|
||||
|
||||
|
||||
在代码中使用pathlib库的PurePath()函数,把当前Python脚本的路径转化为标准的pathlib格式。
|
||||
通过parent属性去掉脚本的文件名,取得当前脚本所在的路径。
|
||||
再通过joinpath()函数把脚本所在的路径和e.txt文件名进行拼接,得到e.txt的完整路径。
|
||||
|
||||
|
||||
这种取得脚本同目录下的其他文件完整路径的用法,要比直接指定文件完整路径用法更灵活,我在后续内容中会使用这种方法处理文件的路径,请你认真掌握。
|
||||
|
||||
我们继续来学习open()函数的第二个参数。第二个参数的作用是设置open()函数使用何种字符编码打开文件。我使用了”encoding=utf-8” 作为open()函数调用的第二个参数。
|
||||
|
||||
不过你可能会问了,为什么指定这个参数的时候,要特意写出变量名称encoding呢?我在04讲的时候,给你讲过函数有定义和调用两种用法。
|
||||
|
||||
在函数定义的时候我们可以为函数指定一个或多个参数,如果使用了多个参数,你在调用函数的时候需要按顺序依次传入每一个参数。但是如果你想要使用某个参数默认值或不想按照顺序传入参数时怎么办呢?函数调用时,还可以根据参数的关键字来指定为函数传入哪些参数。比如说open()函数的参数有7个,为了便于你理解关键字参数,我将open()函数如何定义的前四个参数写在这里,open()函数的定义如下:
|
||||
|
||||
def open(file, mode='r', buffering=-1, encoding=None, ...):
|
||||
...
|
||||
|
||||
|
||||
在open()函数的定义中,encoding参数是open()函数的第4个参数。如果我们只需要第1个和第4个参数,其他参数需要保持默认时,就可以使用如下格式调用open()函数:
|
||||
|
||||
open(file, encoding="utf-8", ...):
|
||||
...
|
||||
|
||||
|
||||
在代码中,调用open()函数时,由于file参数是open()函数的第一个参数,因此可以不用指定参数的关键字。encoding参数并非open()函数定义的第二个参数,而是第四个参数,所以需要指定它的参数名称为encoding,根据open()函数的定义,encoding=“utf-8”会作为open()函数的第四个参数使用。这种使用关键字作为函数的参数的方式,也被称作函数的关键字传参方式。
|
||||
|
||||
通过encoding参数可以指定文件的字符编码,一般在macOS系统文字的编码为UTF-8编码,Windows下为GB2312编码。不同操作系统下打开文本文件需要使用不同的文件编码,这样可以避免文件打开出现乱码的问题。
|
||||
|
||||
在你正确使用了open()函数的两个参数以后,会返回一个表示文件的对象f, 只有设置了正确的文件路径、文件名称和字符编码,才能继续进行文件读取操作。文件的读取操作使用的是read()函数,read()函数会根据文件的对象f,按照open()函数定义好的打开方式进行逐个字的读取操作。Python对文件的其他操作,全都需要通过文件操作函数调用对象f来完成。
|
||||
|
||||
另外要特别注意的是,在open()函数这一行的开头,我还使用了一个关键字with,with关键字下面的代码是缩进形式,和with关键字形成了一个语句块(具体参见导读),在with语句块结束之后,不必手动调用close()函数关闭文件,Python会自动将文件关闭。因此,你会看到我在程序中调用了open()函数打开文件,但是没有调用关闭文件的close()函数。
|
||||
|
||||
总的来说,相对于手动调用close()函数来关闭文件,使用with函数减少了打开过多文件造成系统资源浪费和数据丢失的风险。
|
||||
|
||||
2.统计文件的字数
|
||||
|
||||
打开了文件之后,我们就可以来统计文件的字数了,我们可以直接利用Python的内置函数len()来实现。
|
||||
|
||||
len()函数在Python中最初设计的功能就是统计字符串的长度,即字符串中有多少字符。因此我利用len()函数可以对字符串数据类型进行操作的特性,将文件一次性读入内存,放入字符串数据类型中。我在统计字数之前还增加了一个rstrip()函数,它会自动剔除出现在文件末尾的空行、空格,让你统计的结果更精确。
|
||||
|
||||
在进行文件字数统计的时候,需要注意,在代码中我使用了read()函数实现了对文件内容的一次性读取到内存的功能。如果你的文件较大,我建议你使用readline()函数按行读取,并逐行统计字数,否则容易出现内存不足的问题。
|
||||
|
||||
3.将统计结果写入文件
|
||||
|
||||
如果你需要将统计结果写入文件,可以对open()函数打开文件方式进行简单的修改,就可以实现文件写入功能了,写入文件的代码如下:
|
||||
|
||||
with open("total.txt", "w", encodong="utf-8") as f:
|
||||
f.write("15个字符")
|
||||
|
||||
|
||||
通过上面的代码,我把”15个字符”这串字符串写入到空白文件total.txt中了。
|
||||
|
||||
open()函数的第二个参数表示文件的打开方式,它默认值是“r”,代表了文件以读取方式进行打开。这时对象f只能进行文件读取操作,我们需要将第二个参数指定为”w”,就可以使用wirte()函数对文件进行写入操作了。而write()函数的第一个参数就是即将要写入文件的内容。
|
||||
|
||||
在为你介绍完单个文件的读写操作之后,我带你掌握了将文件读取到字符串变量,通过统计字符串的长度计算文件字数的方法,那么接下来我为你讲解一下如何统计多个文件的字数。
|
||||
|
||||
怎么统计多个文件的字数?
|
||||
|
||||
通过单个文件的字数统计功能的实现,我们发现每次对文件进行字数统计的时候,都需要进行文件的打开、读取和关闭操作。因此我们对多个文件的字数统计,就可以使用for循环来实现批量读取多个文件的内容。在这里我先直接给出代码。
|
||||
|
||||
p = Path(src_path)
|
||||
files = [x for x in p.iterdir() if PurePath(x).match('*.txt')]
|
||||
for file in files:
|
||||
with open(file, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
words = content.rstrip()
|
||||
number = len(words)
|
||||
|
||||
|
||||
|
||||
我来解释一下多文件统计的代码和单文件统计代码的区别。
|
||||
|
||||
你会发现,统计多个文件的字数时,除了使用到第一节课讲的for循环之外,还要使用合适的变量数据类型,用来存储多个文件字数统计过程中的文件字数。
|
||||
|
||||
在导读中我也给你介绍过,Python支持的数据类型包括:数字、字符串、列表、元组、字典五种,我在这里比较一下它们各自的特点,让你知道该怎么选择合适的数据类型来存放计算的中间结果。
|
||||
|
||||
首先是我们在前面几讲用到最多的数字和字符串类型,这两种类型也被称作基本类型。就像它们的名字一样,分别用来存储数值和一串字符。
|
||||
|
||||
数字类型是一种非常适合进行整数、浮点数的转换和算数几何计算最常用的类型。在单文件字数统计的场景,用来储存每个文件的字数是最合适的类型。如果把场景扩展到统计多个文件的字数,使用一个数字类型的变量是不够的,你需要在循环中再增加一个新的数字类型的变量,对多个文件的统计结果进行依次累加就可以了。
|
||||
|
||||
那其他的类型适合用来进行字数统计吗?
|
||||
|
||||
字符串类型会使用英文的单引号或双引号来创建一串字符,它内置的功能一般为多个字符串进行连接和截取字符串里的某些字符的,显然不适合进行字数累加的统计。
|
||||
|
||||
那列表和元组有什么特性呢?当你需要记录多个并列结果的数字或字符串时,我们就可以把数字和字符串放在列表和元组数据类型中。而列表和元组中的每个元素都会被分配一个位置,也有人把这里的位置称作索引,第一个元素的索引是0,第二个是1,以此类推。
|
||||
|
||||
例如对多个文件进行字数统计的场景下,我们就可以将五个文件字数统计的结果分别放入到列表当中,它的写法如下:
|
||||
|
||||
list1 = [15, 20, 35, 40, 50 ]
|
||||
|
||||
|
||||
list1 就是一个列表数据类型的变量,它包含了五个元素,第一个元素或索引为0的元素是15,第二个是20。
|
||||
|
||||
元组和列表极为相似,不同之处在于元组的元素不能修改,这个特性在Python中,叫做内容“不可变”。这是它与列表的主要区别,元组查找的效率要远远高于列表,因此在进行查找操作时,经常将列表类型转换为元组类型,再进行内容查找操作。
|
||||
|
||||
最后一种类型,也是我们在第二讲接触过的字典类型。字典的每个键值对都用冒号进行分隔,每个键值对之间用逗号分割,字典需要被包括在花括号 {} 中,字典类型强调键值对的映射关系。如果你需要记录文件名和统计结果时,可以使用字典方式保存。保存形式如下:
|
||||
|
||||
dict1 = {"file1.txt":15,"file2.txt": 20, "file3.txt":35}
|
||||
|
||||
|
||||
好了,掌握了python支持的五种数据类型,以及它们各自的适用场景,我把它们的特点总结在下面的表格。
|
||||
|
||||
|
||||
|
||||
那我接下来就给你分享进行字数统计时的两种常见的场景需求。
|
||||
|
||||
多个文件记录字数,用列表数据类型更适合
|
||||
|
||||
由于我们的需求是实现跨文件的字数统计,那么就需要一个数据类型来记录每一个文件的字数有多少。根据我们刚才对不同数据类型的分析,列表就是非常适合存储每个文件字数的数据类型。
|
||||
|
||||
在多文件字数统计的场景下,我们只需要其中的append()内置方法,就可以实现对列表添加元素了。如果你想了解列表类型更多的内置方法,可以参考官方文档来查看它们的定义和用法。
|
||||
|
||||
append()内置方法,可以在列表的最后一个位置添加一个新的元素。例如当前统计的五个文件字数就可以采用列表方式存储,它的定义方式如下:
|
||||
|
||||
list1 = [15, 20, 35, 40, 50 ]
|
||||
|
||||
|
||||
list1 变量经过赋值,得到的列表包含了5个数字类型。当统计第6个文件的字数,假设字数为“30”个字时,就可以使用list1.append(30) 的方式在列表后增加元素。增加后list1变量为:
|
||||
|
||||
list1 = [15, 20, 35, 40, 50, 30 ]
|
||||
|
||||
|
||||
对于多个文件的字数统计,要操作的数据类型为列表,而len()函数只能支持字符串,不能支持列表。所以我们需要把统计列表元素和的函数由len()改为sum()函数。另外,sum()是能够直接支持列表类型的求和函数,所以这6个文件的字数,就可以使用sum()函数进行累加,从而得到多个文件的总字数了。它的代码如下:
|
||||
|
||||
sum(list1)
|
||||
|
||||
|
||||
上面就是我们通过for循环和列表数据类型实现了对多个文件字数的统计和累加。接下来我们把需求再扩展一下,如果我需要分类统计,比如说想要统计中文、英文、标点符号各自的数量,那应该选择什么样的数据类型呢?
|
||||
|
||||
扩展需求:统计中文、英文和标点符号各自的数量
|
||||
|
||||
想要实现中、英文和标点符号各自的数量统计,我们就需要考虑继续使用列表数据类型是否能够存储各自的数量统计结果?是否有更适合的数据类型?
|
||||
|
||||
关于存放每个文件的统计结果,我有两种考虑。
|
||||
|
||||
|
||||
一种是中、英文和标点符号各自的数量统计以后,需要比列表更合适的数据类型,用来存放每个文件的统计结果。
|
||||
一种是每个文件计算字数之后,如何和前一个文件的字数进行累加。
|
||||
|
||||
|
||||
先来看第一种。
|
||||
|
||||
要想统计中、英文和标点符号各自的数量,你需要正确地书写每个字符。在Pyhon字符串类型中,有区分每个字符类型的内置函数。
|
||||
|
||||
因此,我使用字符串类型先对每个文件包含的字符进行中文、英文、数字、空格、特殊字符的划分,并使用数字类型的变量对每种类型的数量进行了记录。
|
||||
|
||||
我将实现这一功能的核心代码贴在这里供你参考:
|
||||
|
||||
import string
|
||||
for s in str:
|
||||
# 英文
|
||||
if s in string.ascii_letters:
|
||||
count_en += 1
|
||||
# 数字
|
||||
elif s.isdigit():
|
||||
count_dg += 1
|
||||
# 空格
|
||||
elif s.isspace():
|
||||
count_sp += 1
|
||||
# 中文
|
||||
elif s.isalpha():
|
||||
count_zh += 1
|
||||
# 特殊字符
|
||||
else:
|
||||
count_pu += 1
|
||||
|
||||
|
||||
在这段代码中,我使用了for循环来遍历每个字,然后通过if分支结构进行判断,根据不同的类型对每次指定的类型进行累加。这样就完成了每个文件对字符数量的统计。
|
||||
|
||||
完成统计后,又该如何对它们进行保存呢?我这里使用了字典+列表的方式,用列表存储每个文件每一种字符的数量,为了能区分列表,我为它们分别取了一个名字,并把它们统一存储在字典当中。形成了字典的key是字符串,value是列表的数据类型,它的格式如下:
|
||||
|
||||
{'count_en': [7, 7], 'count_dg': [0, 0], 'count_sp': [1, 1], 'count_zh': [2, 4], 'count_pu': [5, 5]}
|
||||
|
||||
|
||||
如何得到这种格式呢? 为了得到这种格式,我继续使用了列表的内置的append()函数,通过下面的代码实现了每个文件的每种类型分开进行统计的功能。
|
||||
|
||||
word_count["count_en"].append(count_en)
|
||||
word_count["count_dg"].append(count_dg)
|
||||
word_count["count_sp"].append(count_sp)
|
||||
word_count["count_zh"].append(count_zh)
|
||||
word_count["count_pu"].append(count_pu)
|
||||
|
||||
|
||||
总结来说,在进行不同类型的字数统计的过程中,我通过字符串和数字这两种数据类型实现了单个文件的字数统计。在多个文件的数量合并功能中,我又使用了字典和列表两种数据类型实现了多个文件中间结果的存储和最终字数统计。
|
||||
|
||||
Python丰富的数据类型,可以让你更灵活地处理工作中的数据。
|
||||
|
||||
我将完整代码放在github中,你可以参考完整代码来理解我是如何在计算过程中使用字典、列表和字符串类型的。
|
||||
|
||||
总结
|
||||
|
||||
通过跨文件字数统计这个场景,我为你讲解了Python的文件操作函数和如何选择更合适的数据类型。
|
||||
|
||||
在进行文件的读写操作时,你需要注意被读写文件的路径、打开模式、字符编码这三个主要问题。同时,这三个问题也是新手在操作文件时会经常出现的错误的地方。
|
||||
|
||||
尤其是打开模式的错误的设置为“w”将重要文件覆盖写入的问题,在python初学者中经常出现,建议你先采用判断文件是否可读取的方式,避免对重要文件进行误写。
|
||||
|
||||
现实工作中的场景往往是比较复杂的,我们经常要根据不同的场景,把文件内容读取到不同的数据类型的变量中。Python丰富的数据类型可以让你更灵活的操作工作中的数据,通过选择合适的数据类型,也能使用各种数据类型自带的函数,减少手动实现自定义函数的代码,节省你的代码编码时间。
|
||||
|
||||
思考题
|
||||
|
||||
最后我想留一道思考题给你,在进行多个文件的中、英文和标点符号各自的数量统计时,你会选择哪种数据类型,用来存放每个文件的字数统计数据呢?欢迎你来说出自己的想法。
|
||||
|
||||
|
||||
|
||||
|
179
专栏/Python自动化办公实战课/08正则表达式:如何提高搜索内容的精确度?.md
Normal file
179
专栏/Python自动化办公实战课/08正则表达式:如何提高搜索内容的精确度?.md
Normal file
@ -0,0 +1,179 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
08 正则表达式:如何提高搜索内容的精确度?
|
||||
你好,我是尹会生。
|
||||
|
||||
开始上课之前,我想先带你看两种常见的工作需求:
|
||||
|
||||
|
||||
领导让你搜索出一个文档里所有的手机号码, 而你只知道手机号的模式是11位数字,那该怎样搜索呢?
|
||||
你需要在一个文档中搜索一串带区号的电话号码,比如010-12345678,099-1234567 ,不过在这个文档中,有很多组电话号码,它们的区号和电话号码长度都不同,你该怎么一次性把它们全部搜索出来呢?
|
||||
|
||||
|
||||
其实,这两种场景具有一个共同点:就是你要一次搜索出所有的结果,且不能使用具体的数字,这该怎么办呢?答案是使用某种模式进行搜索。
|
||||
|
||||
就像搜索手机号,我可以使用11位数字这种模式。搜索电话号码我就可以用3-4个数字或7-8个数字加上“-”的模式。这就是利用模式匹配的方法把手机和电话号码从文档中找出来,提高文本搜索的效率。
|
||||
|
||||
对于模式匹配,使用Python的正则表达式是最便捷、也是最有效率的。这节课,我就来手把手教你用正则表达式来提高搜索效率。
|
||||
|
||||
使用正则表达式进行搜索的整个过程
|
||||
|
||||
首先,我先带你来看下使用正则表达式进行搜索的整个过程,利用正则表达式可以按照模式来匹配搜索结果,而且比自己编写函数搜索功能更加强大,而且实现起来也更加简洁。
|
||||
|
||||
要实现搜索,我们就要用到Python的正则表达式库(叫做re库)中的search()函数,这是利用正则表达式实现搜索的核心函数。如果能够搜索到匹配模式的结果,就返回匹配字符串出现的文件的位置,以及匹配的字符串内容。接下来我们重点看一下它的参数。
|
||||
|
||||
re.search()函数有三个参数,它的前两个参数我们在工作中较为常用。
|
||||
|
||||
|
||||
参数pattern:代表要匹配的模式。
|
||||
参数string:是要匹配的字符串。如果模式能够匹配成功,则会返回一个re的对象,re对象里存储的是匹配位置和匹配内容;如果匹配失败,就会返回空,在Python中用变量“None”表示空。
|
||||
参数flags:表示search()函数在匹配之前可以进行各种特殊处理,比如使用search()函数进行搜索之前,可以通过flag参数忽略要匹配字符串的大小写功能。
|
||||
|
||||
|
||||
那具体怎么使用呢?我以搜索手机号码为例来给你解释下。我可以使用re.search()按如下的代码搜索一串手机号码:
|
||||
|
||||
re.search("[0-9]{11}","13855556666")
|
||||
<re.Match object; span=(0, 11), match='13855556666'> #执行结果
|
||||
|
||||
|
||||
代码的第一行,search()函数是一个基于正则表达式进行搜索的函数,它的第一个参数是正则表达式,第二个参数是要匹配的文字,目前它只有一个手机号码。
|
||||
|
||||
第二行代码是search()函数执行的结果。因为匹配成功之后会返回比较丰富的信息,所以我们需要根据搜索结果来不断优化正则表达式,达到精确匹配的目的,但是搜索结果还很复杂,所以我要带你分步骤地去分析匹配的结果都包含了哪些主要内容。
|
||||
|
||||
第一步:观察匹配的结果。
|
||||
|
||||
如果模式匹配成功,search()函数会返回一个re对象。如果匹配不成功的话,它的返回结果则是None。我们可以利用不同的返回结果,使用Python的if判断条件来控制程序的后续运行逻辑。
|
||||
|
||||
第二步:观察字符串的子串是在哪些位置被匹配成功的。
|
||||
|
||||
代码中的span=(0, 11) 表示第1个字符到第11个字符匹配成功。这里需要注意的是Python字符串的下标是以0开头的。通过下标,你可以确认匹配的内容是否是你编写的模式,如果匹配的位置或者内容不正确,可以通过下标来帮你定位匹配错误的位置,以便优化匹配模式。
|
||||
|
||||
最后一步,观察匹配成功的子串是否是我们想要查找的字符串内容。
|
||||
|
||||
如果是的话,那么此次查找结束。如果不是,我们就需要根据匹配的位置和结果,使用search()函数重新编写正则表达式。
|
||||
|
||||
这就是使用正则表达式进行搜索的整个工作过程。那接下来我们就来学习一下正则表达式的两种匹配方式。
|
||||
|
||||
两种匹配方式
|
||||
|
||||
编写正则表达式之前,一定要根据字符出现的不同模式来选择不同的匹配方式。匹配方式分为精确匹配和模糊匹配两种。
|
||||
|
||||
精确匹配需要在匹配前,就知道将要匹配的字符是什么,且出现了多少次。这类匹配使用的是一组元字符。模糊匹配是在匹配前不清楚出现的字符具体内容或不清楚字符的重复次数,这种匹配需要使用另一组元字符。我们先来看看精确匹配方式。
|
||||
|
||||
精确匹配
|
||||
|
||||
在手机号码搜索的场景中,我使用了正则表达式 “[0-9]{11}”,你可以看到,我除了用数字外,还使用了很多特殊字符。这些特殊字符有一个专用的名字,叫做元字符,正则表达式就是利用元字符进行模式匹配工作的。
|
||||
|
||||
在正则表达式“[0-9]{11}”中包含了两组元字符,“[]”和“{}” 。
|
||||
|
||||
|
||||
“[]”的作用是进行内容模式匹配;
|
||||
“{}”的作用是指定匹配长度。
|
||||
|
||||
|
||||
我们先从[0-9]来认识元字符吧。
|
||||
|
||||
在正则表示式“[0-9]” 中, “[]”表示同一个位置可能出现的各种字符。例如:我需要从文档中匹配到一个数字,可以使用[1234567890]的形式,也可以使用[0987654321]的形式。而在[]中,字符排列的顺序并不会影响它的功能,出现重复字符也不会影响它的功能。
|
||||
|
||||
不过为了代码更整洁,我不建议你在[]中使用重复的字符。
|
||||
|
||||
使用[]对一个数字进行匹配的时候,如果从0写到9,一一列出比较麻烦,所以在[]中还支持一个“-”符号,叫做连字符。把连字符与数字写在一起,表示数字的范围。当然,你肯定会想到还有英文字母的场景,比如从A到Z或者从a到z,表示英文字母的范围。具体写法如下:
|
||||
|
||||
[0-9] #匹配任意一个数字
|
||||
[a-zA-Z] #匹配任意一个字母
|
||||
|
||||
|
||||
除了可以自己手写匹配字母和数字外,还有另外一种写法,就是把我们经常需要匹配的字符进行定义。我为你提供一张POSIX字符组合和ASCII字符组合的常用对照表,帮你来记住它们。
|
||||
|
||||
|
||||
|
||||
在你掌握了元字符“[]”的用法之后,我们接着来看怎么通过“{}”来限定“[]”的匹配长度。
|
||||
|
||||
在正则表达式 “[0-9]{11}”中, 我使用了{11}来限定它前边的字符出现11次。{}还可以使用**{m,n}的形式,表示限定的次数从m到n次**。这是在匹配不确定字数的情况下,经常用到的写法,例如进行电话号码的区号匹配,可以使用“[0-9]{3,4}”的形式匹配3个或4个数字。
|
||||
|
||||
使用“[]、{}” 两种元字符,属于精确的模式匹配。如果你在编写正则表达式之前就知道,即将匹配的字符是数字还是英语,以及它们出现的次数,那就使用这两个元字符对将要进行搜索字符进行匹配。
|
||||
|
||||
模糊匹配
|
||||
|
||||
不过还有一种情况就是,你只知道某个元素会重复出现多次,但是在匹配前不知道具体会出现多少次,或者只知道要匹配的是五个字符,但是不知道这五个字符到底是数字、汉字还是英文,这种匹配的行为就叫做模糊匹配。
|
||||
|
||||
例如下面三个元字符“+”、“*”、“?”可以实现出现次数的模糊匹配功能。
|
||||
|
||||
|
||||
“+”表示前边的元素出现的是1到无穷多次。
|
||||
“*”表示前面的元素出现的是0次到无穷多次。
|
||||
“?”表示前面的元素出现的是0次或一次。
|
||||
|
||||
|
||||
我以正则表达式“ab+c”为例,它表示在这个模式中b至少出现了一次,最多出现无穷多次。也就是说,我们能够通过search()函数搜索到abc、abbc、abbbc、abbbbc等模式。
|
||||
|
||||
如果匹配的模式再复杂一些,假设说在匹配之前我们只知道第一个字母是a,最后一个字母是b,中间是五个任意的字母。在这种情况下,就需要使用元字符“.”符号, 它在正则表达式中能匹配任意的单个字符。
|
||||
|
||||
为了让你更好理解元字符“.”, 我们就来看一个搜索任意5个字符的例子,代码如下:
|
||||
|
||||
re.search(".....", "aaa13855557890bbb")
|
||||
re.search(".{5}", "aaa13855557890bbb") # 也可以这样写
|
||||
|
||||
|
||||
上面代码匹配出的结果是”aaa13”。
|
||||
|
||||
这里需要提醒你注意,如果你想匹配的字符串只包含5个字符,那就需要增加匹配的限定条件的元字符“^”和“\(”。**“^”表示从第1个字符开始匹配。“\)”表示从结尾开始匹配。**所以你需要将正则表达式”…..“改为”^…..$“形式,才能实现只匹配5个字符的模式。
|
||||
|
||||
通过上面的“[] {} . ? * +”这六种元字符组合,你就能够使用search()函数通过正则表达式搜索到大部分的模式了。
|
||||
|
||||
提取和替换:如何处理搜索到的结果
|
||||
|
||||
当你搜索到你想要的文字之后,往往还需要进行后续的内容提取和替换处理,这些后续处理如果手动完成也属于低效的工作。因此,接下来我来教你怎样将search()函数搜索到的内容进行提取,以及如何使用sub()函数实现将正则表达式匹配到的内容进行替换。我们先来看怎么进行内容的提取。
|
||||
|
||||
以电话号码为例,如果需要将搜索的结果提取出来,可以使用group()函数,进行search()函数搜索之后的内容提取操作。
|
||||
|
||||
我们先来看一下提取搜索结果的例子:
|
||||
|
||||
import re
|
||||
re.search("[0-9]{3}-[0-9]{8}",
|
||||
"我的电话号码:010-12345678.").group(0)
|
||||
'010-12345678' # 执行结果
|
||||
|
||||
|
||||
通过group(0)函数的参数“0”,如果搜索过程中能够匹配成功,search()函数就会把匹配到的第一个字符串作为执行结果,返回给用户继续进行自动化处理。
|
||||
|
||||
再进一步,如果我要替换匹配到的字符串,那就需要把search()函数改为sub()函数。sub()函数包含三个参数,依次是:要匹配的正则表达式、匹配后被替换的字符串、匹配的原始文档。
|
||||
|
||||
那我就以替换字符串为例,来教你怎么把文档中的yes字符串替换为no字符串。代码如下:
|
||||
|
||||
re.sub("(Y|y)(es)*", "No", "aayesbb")
|
||||
'aaNobb' # 执行结果
|
||||
|
||||
|
||||
在这段代码中,为了将“Y、yes、Yes”,三个字符串都替换为“No”。同时,为了不用多次执行sub()替换函数,我使用了“()”分组功能和“|”选择功能。那么通过(Y|y)这一写法,就能实现第一个字符匹配“Y”和“y”;利用“()”分组功能,使用“(es)*”正则表达式实现了第二、三个字符是“es”或是没有字符的功能。
|
||||
|
||||
总结来说,通过group()函数和re.sub()函数,我们可以在搜索之后自动化实现内容的提取和替换操作。
|
||||
|
||||
小结
|
||||
|
||||
在最后我来为你总结一下搜索中的常见问题,以及对应的解决方案。
|
||||
|
||||
如果遇到了大量的需要按照某种模式才能得到搜索结果的场景,你可以在第一时间采用正则表达式的方法进行内容的匹配。如果字符长度固定,可以使用精确搜索的元字符“[]”和“{}” 。反之,在字符长度不固定的情况下,那就使用模糊搜索的元字符“+”、“*”、“?”“.”“^”和“$”。
|
||||
|
||||
正则表达式的元字符组合非常灵活,为了方便你记忆,我把它们分为三类:
|
||||
|
||||
|
||||
匹配单个字符,要使用“[ ]”和“.”元字符。
|
||||
控制元字符出现次数,要使用“?”、“+”和“*”元字符。
|
||||
控制元字符的顺序和位置,要使用“^”、“$ ”、“ | ”和“ ()”元字符。
|
||||
|
||||
|
||||
在掌握正则表达式的正确编写基础上,你就可以通过re库的函数实现按模式搜索、内容提取和内容替换了。正则表达式不但在Python中能够使用,它还能在搜索引擎以及大部分的编程语言中使用,被广泛应用与字符串搜索和替换工作中。
|
||||
|
||||
我把这节课相关的代码给你放在了GitHub中,你可以参考。
|
||||
|
||||
思考题
|
||||
|
||||
最后我给你留一道思考题,你将如何使用re库的search()函数,实现身份证的匹配和提取功能呢?
|
||||
|
||||
|
||||
|
||||
|
304
专栏/Python自动化办公实战课/09扩展搜索:如何快速找到想要的文件?.md
Normal file
304
专栏/Python自动化办公实战课/09扩展搜索:如何快速找到想要的文件?.md
Normal file
@ -0,0 +1,304 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
09 扩展搜索:如何快速找到想要的文件?
|
||||
你好,我是尹会生。
|
||||
|
||||
工作时间越久,你就会发现,自己电脑里存储的办公文件也越来越多。一旦需要紧急找到某个文件,你又恰巧忘记了文件被存放的具体位置,就只能通过Windows的搜索功能对文件名称进行搜索,不过你大概率需要长时间等Windows的搜索结果。
|
||||
|
||||
之所以查找文件的时间过长,主要有两个原因。
|
||||
|
||||
|
||||
搜索范围太大。搜索过程中可能包含了大量的操作系统或应用软件的缓存文件等无效路径,导致搜索时间过长。
|
||||
受到硬盘文件数量过多的影响。硬盘的文件数量越多,完整搜索一遍所有文件的时间就越长。
|
||||
|
||||
|
||||
那有没有办法提高搜索的效率,快速找到你想要的文件呢?基于以上两种原因,相应的,我们可以在Python中采用指定搜索路径范围和提前建立文件索引的两种方案实现文件的搜索功能。
|
||||
|
||||
这两种方案都是基于Python的基本搜索方法实现的,因此我先来带你学习一下如何用Python的pathlib库实现基础文件的搜索。
|
||||
|
||||
基础搜索方法:用pathlib库搜索文件
|
||||
|
||||
用Python搜索文件时需要使用pathlib库的glob()函数和rglob()函数,glob()函数可以实现基于文件名的搜索方法,rglob函数可以实现基于扩展名的搜索方法。
|
||||
|
||||
我先来给你看一下Python实现基础搜索功能的代码,通过代码来为你讲解一下Python是如何搜索到一个文件的:
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
base_dir = '/Users/edz/Desktop/'
|
||||
keywords = '**/*BBC*'
|
||||
|
||||
# 遍历base_dir指向的目录下所有的文件
|
||||
p = Path(base_dir)
|
||||
|
||||
# 当前目录下包含BBC的所有文件名称
|
||||
files = p.glob(keywords)
|
||||
# files的类型是迭代器
|
||||
# 通过list()函数转换为列表输出
|
||||
# print(list(files))
|
||||
|
||||
# xlsx结尾的文件
|
||||
files2 = p.rglob('*.xlsx')
|
||||
print(list(files2))
|
||||
|
||||
# 遍历子目录和所有文件
|
||||
files3 = p.glob('**/*')
|
||||
print(list(files3))
|
||||
|
||||
|
||||
在这段代码中,我实现了Python的基础搜索功能。由于搜索的优化要基于glob()函数和rglob()函数进行,为了能更好的提升Python的搜索的效率,我来带你逐一分析一下glob()函数和rglob()函数的参数和返回值。
|
||||
|
||||
首先我来带你看一下glob()函数和它的参数, 由于glob()进行匹配的是文件的路径和文件名称方式,如: “c:\somepath\to\filename_include_BBC_voice.exe” , 而我们进行文件搜索的时候一般会使用关键字,如“BBC”,因此在搜索时我们需要为关键字加上通配符的形式,如“_BBC_” 。
|
||||
|
||||
通配符是类似正则表达式的元字符的一种特殊符号,它不能用在正则表达式中,只能用在glob(全称global)匹配模式中。
|
||||
|
||||
我将glob()和rglob()函数常用的通配符,以及它们的功能整理成一个表格,供你学习和参考。
|
||||
|
||||
|
||||
|
||||
通过表格我希望你能掌握如何将通配符和要搜索的关键字进行组合。比如说,通过使用“**/_BBC_”的方式,我就可以搜索到Path()函数指定目录下所有包含BBC关键字的文件名。
|
||||
|
||||
接下来我再来带你学习一下rglob函数和它的参数。rglob函数是从文件路径末尾向前进行匹配的,这是它和glob()函数的主要区别, 基于rglob()函数的搜索顺序特点,经常被我们用于进行扩展名的搜索,比如说采用rglob(’*.xlsx’)就可以搜索所有的xlsx扩展名文件,要比使用glob()编写的模式匹配更简单,参数的含义也更清晰。
|
||||
|
||||
最后我再来带你看一下glob()和rglob()函数的返回值,有一点我需要提醒你:它们的执行结果是我们之前课程中没有接触过的一种新的数据类型,这种类型叫做“迭代器”。
|
||||
|
||||
顾名思义,迭代器的特点是这个数据类型可以支持迭代操作,执行一次glob()或rglob()函数只返回一个结果。要想得到迭代器所以的值可以使用两种方法。
|
||||
|
||||
一种是使用list()函数将迭代器转换为我们所熟知的列表数据类型,例如我在列表中就是用了“list(files3)”方式将迭代器转换为了列表。
|
||||
|
||||
还有一种方式是使用for循环的方式对迭代器的值进行逐一处理。
|
||||
|
||||
这两种用法不但可以获取glob()返回值,今后我们遇到的迭代器都可以使用list()函数和 for循环取得它的全部值。
|
||||
|
||||
通过对glob()和rglob()函数的参数和返回值的学习,相信你已经掌握了使用功能Python搜索文件的基础方法,接下来我来带你通过指定搜索路径和建立索引文件提高搜索的效率。
|
||||
|
||||
提升搜索效率的两种方法
|
||||
|
||||
用Python的pathlib库实现文件搜索,只是在灵活性上比Windows默认的搜索更好,但是搜索效率上并不能带来任何提高。为了减少搜索的等待时间,接下来,我就教你使用指定搜索路径和建立索引文件两个方法,提高pathlib库的搜索效率。
|
||||
|
||||
指定搜索路径
|
||||
|
||||
我们先来看第一种,指定搜索路径。我们需要通过三个步骤实现:
|
||||
|
||||
|
||||
先生成配置文件,把要搜索的路径写入到配置文件中;
|
||||
再编写读取配置文件和搜索的自定义函数,把配置文件中的路径读取出来,逐个目录搜索;
|
||||
最后,将多个目录的搜索结果合并输出,便于你通过结果快速找到自己想要的文件。
|
||||
|
||||
|
||||
先说第一步,怎么使用Python读取配置文件。以往我们会把要搜索的路径写入到变量,并把定义路径的变量名称放在代码前几行的位置,便于下次修改搜索目录的时候找到这个变量。但是对于代码工程稍微复杂的程序来说,往往会有多个代码文件,仍然不利于每次搜索的时候进行搜索路径的修改。
|
||||
|
||||
现在我教你一个新的方法,就是把变量放入到一个单独的文件中,这个文件被称作该代码的配置文件。这种方法的好处是你修改搜索目录时不用打开代码文件。假设你的朋友也需要类似功能,那你就可以把代码和配置文件一起发给他,哪怕他完全不会Python,也能使用你编写的程序实现高效搜索。
|
||||
|
||||
那怎么给Python脚本增加配置文件呢?
|
||||
|
||||
配置文件一般为文本文件。配置文件的格式,一般由软件作者基于软件的功能和自己的习惯来指定,不过也有通用的配置文件格式。比如在Windows系统中,最常见的配置文件是扩展名为.ini的文件,在今天这节课,我们就把.ini文件格式作为配置文件的标准格式。
|
||||
|
||||
.ini文件格式包含三个部分,分别是节、参数和注释。格式如下:
|
||||
|
||||
节
|
||||
[section]
|
||||
参数
|
||||
(键=值)
|
||||
name=value
|
||||
注释
|
||||
注释使用“;”分号表示。在分号后面的文字,直到该行结尾都全部为注解。
|
||||
;注释内容
|
||||
|
||||
|
||||
基于.ini文件的格式,我把配置搜索路径的配置文件修改为如下:
|
||||
|
||||
[work]
|
||||
;工作文件保存路径
|
||||
searchpath=/Users/edz,/tmp
|
||||
|
||||
[game]
|
||||
;娱乐文件保存路径
|
||||
searchpath=/games,/movies,/music
|
||||
|
||||
|
||||
在这段代码中,我设置了work和game两个“节”,分别代表工作和娱乐。这样设置的好处是,我可以根据不同的用途来搜索不同的目录。如果搜索时使用了更少的目录,也会相应减少搜索的等待时间。
|
||||
|
||||
另外,你会发现两个“节”中的参数我都指定成相同的名字–searchpath,这样设置的好处是我将搜索范围从“工作”改为“娱乐”时,只需要在代码里修改搜索的“节”,不用修改搜索的参数。
|
||||
|
||||
除了“节”和“参数”,在配置文件中,你还应该关注我对参数searchpath设置值的方式,它的值是我想要进行搜索的路径范围,为了在程序中能够更方便得读取多个路径,我使用逗号来分隔多个路径。
|
||||
|
||||
在编写好配置文件之后,下一步就是取得.ini文件的完整路径,我把.ini文件和脚本放在了相同的目录,我在第七讲为你详细剖析过,怎样通过__file__变量和pathlib库相结合,实现读取和脚本在同一目录的文件操作,我就不多说了,如果你不太记得了,可以回去复习下。
|
||||
|
||||
找到search.ini文件完整路径之后,接下来需要读取并分析.ini文件格式,Python有实现这个功能的的库,它叫做configparser库,通过这个库你可以直接读取.ini文件中的searchpath参数,不用通过read()函数读取文件内容,手动编写分析.ini文件的脚本了。
|
||||
|
||||
接下来,我来带你看一下Python读取.ini文件的代码,帮你理解Python是怎样通过配置文件来实现在多个路径搜索文件功能的。
|
||||
|
||||
import configparser
|
||||
import pathlib
|
||||
from pathlib import Path
|
||||
|
||||
def read_dirs(ini_filename, section, arg):
|
||||
"""
|
||||
通过ini文件名,节和参数取得要操作的多个目录
|
||||
"""
|
||||
current_path = pathlib.PurePath(__file__).parent
|
||||
inifile = current_path.joinpath(ini_filename)
|
||||
|
||||
# cf是类ConfigParser的实例
|
||||
cf = configparser.ConfigParser()
|
||||
|
||||
# 读取.ini文件
|
||||
cf.read(inifile)
|
||||
|
||||
# 读取work节 和 searchpath参数
|
||||
return cf.get(section, arg).split(",")
|
||||
|
||||
def locate_file(base_dir, keywords):
|
||||
p = Path(base_dir)
|
||||
files = p.glob(keywords)
|
||||
return list(files)
|
||||
|
||||
|
||||
dirs = read_dirs('search.ini', 'work', 'searchpath')
|
||||
# ['/Users/edz', '/tmp']
|
||||
keywords = '**/*BBC*'
|
||||
|
||||
# 定义存放查找结果的列表
|
||||
result = []
|
||||
|
||||
# 从每个文件夹中搜索文件
|
||||
for dir in dirs:
|
||||
files = locate_file(dir, keywords)
|
||||
result += files
|
||||
|
||||
# 将PosixPath转为字符串
|
||||
print( [str(r) for r in result] )
|
||||
|
||||
|
||||
在这段代码中,读取配置文件和搜索这两个最主要的功能由两个自定义函数实现,它们分别是read_dirs()函数和locate_file()函数。
|
||||
|
||||
read_dirs()函数实现了读取.ini文件,并将返回的多个路径处理为列表类型。列表类型适合多组并列的数据,多个目录刚好可以使用列表这种数据类型来存放要搜索的目录名称。
|
||||
|
||||
locate_file()函数通过代码的第35行循环功能,对每个目录进行了搜索,并将搜索的结果存入result变量。result变量是一个列表数据类型,由于搜索到的文件可能包含多个匹配的文件路径,我需要将搜索到的结果依次存入result列表中,再继续搜索下一个目录,继续通过append()函数将结果放入列表,直到所有的目录搜索完成,整个搜索的程序才真正执行结束。
|
||||
|
||||
最后还有一点需要你注意,在进行路径处理的过程中,pathlib库为了规避不同操作系统路径写法的差异,就把路径统一定义为PosixPath()对象。因此,你在使用这些路径的时候,需要先将PosixPath对象转换为字符串类型。我在代码最后一行通过Python内置函数str()函数把PosixPath对象逐个转换为字符串类型,并再次存入到列表当中。
|
||||
|
||||
通过限制在指定的目录搜索这个功能,我们就可以规避在搜索时错误匹配系统文件和软件缓存文件而导致的查找过慢问题。
|
||||
|
||||
不过如果我们指定的目录仍然有非常多文件的话,那用这个方法查找起来依然会很慢。别担心,接下来我就教你一种利用空间换时间的方法,提高对指定目录文件搜索效率的方法。
|
||||
|
||||
建立索引文件
|
||||
|
||||
什么是“空间换时间”呢?我来解释一下。
|
||||
|
||||
我们知道,文件越多,搜索的时间就越长,因为搜索的时间是随着文件数量呈线性增长的。就像一本书越厚,你从第一页读到最后一页的时间就越长一样。那怎样能快速搜索到书中的内容呢?你会想到图书都有目录功能,通过目录可以加快你找到你想看的图书内容的速度。
|
||||
|
||||
对于操作系统来说,一个文件也是由文件名称、大小、文件内容等多个部分组成的,搜索文件的功能只需要文件名称就行了,不需要其他的部分。因此,我们可以在硬盘中新开辟一块空间,将所有的文件名提前存储下来作为文件的索引,下次再查找的时候直接查找索引文件,就能得到搜索结果,而不必再通过硬盘查找真实的文件了。这就是“空间换时间”。
|
||||
|
||||
既然索引文件可以加快搜索速度,那我们去建立索引文件就可以了。
|
||||
|
||||
建立索引文件不需要你重新再写新的程序,我们可以基于指定搜索路径的程序进行改造:
|
||||
|
||||
|
||||
先把配置文件目录下所有文件路径的保存方式由列表改为文件;
|
||||
再把搜索功能改为从文件搜索。
|
||||
|
||||
|
||||
我把改造后的代码写出来供你参考。
|
||||
|
||||
def locate_file(base_dir, keywords='**/*'):
|
||||
"""
|
||||
迭代目录下所有文件
|
||||
"""
|
||||
p = Path(base_dir)
|
||||
return p.glob(keywords)
|
||||
|
||||
def write_to_db():
|
||||
"""
|
||||
写入索引文件
|
||||
"""
|
||||
current_path = pathlib.PurePath(__file__).parent
|
||||
dbfile = current_path.joinpath("search.db")
|
||||
|
||||
with open(dbfile, 'w', encoding='utf-8') as f:
|
||||
for r in result:
|
||||
f.write(f"{str(r)}\n")
|
||||
|
||||
# 读取配置文件
|
||||
dirs = read_dirs('search.ini', 'work', 'searchpath')
|
||||
|
||||
# 遍历目录
|
||||
result = []
|
||||
for dir in dirs:
|
||||
for files in locate_file(dir):
|
||||
result.append(files)
|
||||
|
||||
# 将目录写入索引文件
|
||||
write_to_db()
|
||||
|
||||
|
||||
|
||||
在代码中我增加了write_to_db()函数,它在代码的第16-18行,我通过写入文件方式替代了写入列表的功能。同时,为了能遍历所有的目录,我还修改了locate_file()函数的第二个参数,将它改为“keywords=’/*‘”。通过这两处的修改,就把所有文件路径全部保存到search.db文件中了。**
|
||||
|
||||
search.db的文件内容如下,这里记录了配置文件指定的所有目录下的所有文件路径:
|
||||
|
||||
/tmp/com.apple.launchd.kZENgZTtVz
|
||||
/tmp/com.google.Keystone
|
||||
/tmp/mysql.sock
|
||||
/tmp/com.adobe.AdobeIPCBroker.ctrl-edz
|
||||
/tmp/com.apple.launchd.kZENgZTtVz/Listeners
|
||||
/tmp/com.google.Keystone/.keystone_install_lock
|
||||
... ...
|
||||
|
||||
|
||||
接下来,我再把搜索功能从列表搜索改造为从文件搜索,为了避免每次搜索要重新生成一次search.db文件,我要编写一个新的脚本,专门用于从文本中搜索关键字,并将搜索的结果显示出来。
|
||||
|
||||
相信你一定想到了我在上一讲为你讲解的正则表达式功能,通过re.search()函数刚好可以实现文本的搜索。下面的locate.py脚本文件就是我使用正则表达式实现的文本搜索功能:
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
keyword = "apple"
|
||||
|
||||
# 获取索引文件路径
|
||||
current_path = pathlib.PurePath(__file__).parent
|
||||
dbfile = current_path.joinpath("search.db")
|
||||
|
||||
# 在索引文件中搜索关键字
|
||||
with open(dbfile, encoding='utf-8') as f:
|
||||
for line in f.readlines():
|
||||
if re.search(keyword, line):
|
||||
print(line.rstrip())
|
||||
|
||||
|
||||
|
||||
在代码中我利用正则表达式的re.search()搜索函数,以keyword变量作为搜索的关键字,对search.db索引文件的每一行进行了匹配,最后将符合关键字“apple”的文件路径和名称一起显示在屏幕上。
|
||||
|
||||
使用这种方式来搜索文件,要比使用操作系统自带的搜索工具快得多,因为我将原本Windows搜索硬盘上的文件所消耗的时间拆分成了两部分。
|
||||
|
||||
|
||||
一部分是updatedb.py建立索引的时间;
|
||||
一部分是从search.db索引文件查找关键字的时间。
|
||||
|
||||
|
||||
当搜索等待的时间被提前转换为建立索引的时间后,搜索的效率自然就提高了。
|
||||
|
||||
但是请你注意,这种方式建立的索引文件会有时效性的问题,一旦硬盘中的文件被删除、移动或改名,你就得重新建立索引文件。而索引文件保存的文件路径和当前真实的文件并不能一一对应,所以你需要及时更新索引文件。
|
||||
|
||||
为了保证索引文件的时效性,你还可以把updatedb.py脚本加入到Windows的开机启动脚本中,每次打开计算机就会自动更新索引文件。通过这种方式就可以让索引文件里的文件路径更加准确。
|
||||
|
||||
总结
|
||||
|
||||
最后,我来为你总结一下。这节课,我为你讲解了如何使用pathlib库搜索文件,以及如何使用配置文件和索引文件加快搜索。
|
||||
|
||||
利用索引文件减少等待时间,实际上是将Windows的搜索进行了拆分,提前将搜索的路径保存到了索引文件中,从索引文件搜索时,就不用再进行真正的文件查找工作了,这就减少了搜索的等待的时间。
|
||||
|
||||
这种搜索方法在服务器领域已经被广泛使用,像Linux和MacOS操作系统中都存在着利用索引文件的搜索命令,对于服务器上变化频率较低的场景,利用索引文件搜索非常实用。
|
||||
|
||||
思考题
|
||||
|
||||
我给你留一道思考题。如何扩展locate.py的搜索功能,实现搜索.jpg扩展名的文件呢?
|
||||
|
||||
欢迎把你的思考和想法分享在留言区,我们一起交流讨论。如果课程帮你解决了一些工作上的问题,也欢迎你把课程分享给你的朋友、同事。
|
||||
|
||||
|
||||
|
||||
|
221
专栏/Python自动化办公实战课/10按指定顺序给词语排序,提高查找效率.md
Normal file
221
专栏/Python自动化办公实战课/10按指定顺序给词语排序,提高查找效率.md
Normal file
@ -0,0 +1,221 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
10 按指定顺序给词语排序,提高查找效率
|
||||
你好,我是尹会生。
|
||||
|
||||
之前我在游戏行业工作的时候,经常需要取得用户在线时长TOP3、用户战斗力TOP5、用户完成任务数量TOP10等数据,相信你在工作中也会有遇到从大量数据取得TopN这类需求。
|
||||
|
||||
提取TopN的操作,本质上是对大量数据先进行排序,然后根据排序的结果取出前N个值。而实现提取TopN的值,用Python来实现会非常高效,这节课,我就来讲一讲具体怎么操作。
|
||||
|
||||
使用sorted()函数实现排序
|
||||
|
||||
在Python中,已经内置了排序函数sorted()函数,它是Python中实现排序最简单且最直接的形式,可以解决80%的排序问题。那么,我们就来学习一下怎么用sorted()函数对常见的数据类型进行排序。
|
||||
|
||||
先看sorted()函数的定义。
|
||||
|
||||
sorted(iterable, cmp=None, key=None, reverse=False)
|
||||
|
||||
|
||||
sorted函数共有四个参数,第一个参数是要排序的对象,后面三个参数是排序的方式。
|
||||
|
||||
如果要为某个对象排序的话,你可以直接将它作为sorted()函数的第一个参数,返回结果会将对象的值进行从小到大的排序。
|
||||
|
||||
如果sorted()返回的排序结果不满足你的需要,比如你想要从大到小的排序,那你就可以利用后面三个参数来改变排序的方式,实现自定义排序。
|
||||
|
||||
所以今天这节课,我会带你学习怎么使用sorted()函数实现默认排序和自定义排序,来解决你实际工作中遇到的多种排序问题。我们先来看怎么使用sorted()实现默认排序。
|
||||
|
||||
默认排序
|
||||
|
||||
sorted()函数的默认排序是按照从小到大的顺序进行排序的。例如一家公司即将上市,需要在职的前1000名员工的工号和姓名,工号越小的员工配股越多。这时候就需要给所有员工的工号进行排序,并取得工号排在前1000个员工的名字。具体怎么做呢?
|
||||
|
||||
我们来看sorted()函数的用法。我们从它的第一个参数开始学习。第一个参数是sorted()函数要排序的对象,我以列表为例,把员工的工号放到列表中,我们来看一下sorted()函数是怎么对列表中的工号进行从小到大的排序的。
|
||||
|
||||
sorted_list = sorted([30, 50, 20, 10, 40])
|
||||
print(sorted_list)
|
||||
# [10, 20, 30, 40, 50]
|
||||
|
||||
|
||||
我在代码中给sorted()函数指定了一个列表类型的参数“[30, 50, 20, 10, 40]”。按照sorted()函数的定义,这个列表会被sorted()函数按照数字从小到大进行排序,并返回排序后的结果。并且,我把结果放入sorted_list对象中,你从代码中可以看到sorted_list已经把数据按照默认从小到大的顺序排列好,并把结果放在一个列表数据类型中。
|
||||
|
||||
这就是sorted()的基本用法,其实是比较简单的,不过这里有三点需要你注意。
|
||||
|
||||
首先,sorted()函数的默认排序方式是从小到大进行排序,那么对于列表中的数字就会按照默认的算法进行大小比较。但是除了数字外,列表中还会出现字母或字母与数字混合的方式,你也需要掌握这两种方式的排序处理方法。
|
||||
|
||||
比如说英文字母,会按照列表中字符串的第一个字母从A到Z、从a到z进行排序。如果字符串的第一个字母相同,就会比较第二个字母,以此类推,直到比较完字符串中的所有字符。
|
||||
|
||||
由于默认排序的时候不支持同时对英文字母和数字进行排序,那么如果列表中既包含了字母,又包含了数字,在默认排序时程序就会提示一个异常,那就是无法使用“<”比较整数和字符串。
|
||||
|
||||
TypeError: '<' not supported between instances of 'int' and 'str'
|
||||
|
||||
|
||||
因此,当列表中出现类似“[“a”, 1, “bb”]”这种,既包含字符串又包含数字的列表,进行排序操作的时候,你需要先统一类型,把数字使用str()函数转换为字符串类型,再对列表进行排序。我把代码写出来,供你参考:
|
||||
|
||||
sorted_list2 = sorted(["a", str(1), "bb"])
|
||||
print(sorted_list2)
|
||||
|
||||
|
||||
其次,你需要注意,sorted()函数不会对原有的列表进行修改,它会把排序好的结果存入到一个新的列表当中。
|
||||
|
||||
很多人刚开始使用Python时,会把sorted()函数和列表自带的函数sort()函数混淆。
|
||||
|
||||
它们虽然名字很像,但支持的数据类型、调用的方式以及返回的结果都不同。
|
||||
|
||||
要记住,sort()函数是列表数据类型自带的,所以只能对列表数据类型进行排序,不能对其他数据类型排序,但sorted()函数可以支持任何可迭代的对象。
|
||||
|
||||
在调用方式上,sort()函数使用的方式是“列表.sort()”格式,这也是调用时和sorted()函数的差别。
|
||||
|
||||
还有一个最大的差别是,sort()函数会直接修改当前列表(这种修改称作“原地修改”),并返回一个空值None。而sorted()函数不会对原有的列表进行修改,它会把排序好的结果存入到一个新的列表当中。
|
||||
|
||||
为了让你更好地区分sorted()函数和sort()函数,我把sort()函数的执行结果也提供给你做参考。
|
||||
|
||||
list3 = ["a", "c", "bb"]
|
||||
no_value = list3.sort()
|
||||
print(list3)
|
||||
# ['a', 'bb', 'c'] 执行结果
|
||||
print(no_value)
|
||||
# None 执行结果
|
||||
|
||||
|
||||
在代码的第二行,是sort()函数的调用格式。在第三行,我输出了排序之后的列表,并对列表的值进行了重新排序。在第五行,我们可以看到no_value变量的返回值为None,也就是sort()函数会对列表进行原地修改,并使用None作为返回。
|
||||
|
||||
最后,你需要注意sorted()函数能够支持的数据类型非常多,既能支持基础数据类型,又能支持Python自带的内置函数。根据它的函数定义,第一个参数是“iterable”对象,表示只要该对象可迭代,sorted()函数就能对它进行排序。
|
||||
|
||||
在基本数据类型中,序列和映射都可以迭代,序列是指列表、元组、字符串这三种基本数据类型的总称,映射就是我们使用过的字典。除了基本数据类型之外,像是range() 、map()、zip()等内置函数都是可迭代对象,因此掌握sorted()函数,可以对以上多种类型和函数进行排序。
|
||||
|
||||
掌握了这三点注意事项,在使用sorted()为可迭代类型进行排序时,就不会出现什么问题了。那接下来,我来带你学习一下它的自定义排序功能,通过自定义排序,可以让sorted()基于不同的数据类型实现更加灵活的排序。
|
||||
|
||||
自定义排序
|
||||
|
||||
自定义排序是在基本排序基础上,能够支持更多的排序方式和更复杂的数据类型。比如说:
|
||||
|
||||
|
||||
在排序方式上,我们通过参数,可以把默认的从小到大的排序改为从大到小。
|
||||
在对数据类型的支持上,像列表中包含元组这种复杂数据类型,可以通过指定元组的任意一列进行排序。
|
||||
|
||||
|
||||
我们先学习自定义排序是怎么支持更多的排序方式的,它的前提是要先更改默认的排序方式。
|
||||
|
||||
例如我想要实现列表的从大到小的排序,并提取Top 3这一需求,我首先需要使用函数的第四个参数reverse参数改变默认的排序方式,排序后我需要使用列表的切片方式提取前三个元素。
|
||||
|
||||
我先把代码写出来,然后再给你详细解释。可以看到,我在代码中使用了sorted_list4作为排序后的结果,并对sorted_list4进行切片,从而得到指定下标对应的值。
|
||||
|
||||
sorted_list4 = sorted([30, 50, 20, 10, 40], reverse=True)
|
||||
print(sorted_list4[:3])
|
||||
# [50, 40, 30]
|
||||
|
||||
|
||||
在代码中,我为sorted()函数增加了参数reverse。reverse参数在定义的时候是sorted()函数的第四个参数,由于sorted()函数的第二、三个参数我希望保持默认,不需要在调用的时候传入,因此reverse参数在调用时就要作为第二个参数来使用。
|
||||
|
||||
由于在调用时它的位置和定义的位置不同,那么我必须使用关键字参数 “reverse=True”的形式,把reverse指定为sorted()函数的第二个参数。
|
||||
|
||||
增加reverse参数之后,sorted()执行的结果也和默认结果不同,sorted会将默认排序的结果进行反转。而当五个数字的排序结果反转后,也就以从大到小的方式进行了输出。
|
||||
|
||||
你看,利用sorted()函数的reverse参数就能实现TopN场景的排序工作,工作中往往需要只得到TopN的结果,不需要将全部的排序结果进行输出,这时候你可以将列表按照你的工作需要提取其中的某一部分,这种操作也被称作列表的切片。
|
||||
|
||||
假如你想取得sorted_list4列表的Top3元素,可以对列表排序后,使用sorted_list4[0:3]的写法提取列表前三个值,这种写法的“0”“3”表示列表的下标,“:”表示获取下标的范围。因为列表的下标从0开始,因此要写作[0:3]的形式,也可以使用它的省略形式[:3],这样列表切片操作就会从0开始取值。
|
||||
|
||||
总结来说,通过reverse参数和列表切片,我们可以在默认排序的基础上,实现基于列表的TopN场景下的排序,以及TopN结果的提取。
|
||||
|
||||
不过现实场景往往都是比较复杂的,要排序的数据类型除了列表中包含数字和字符串外,你最经常见到的还有对列表中包含元组这种复合类型进行排序,以及对字典的键或值进行排序。接下来我来带你学习一下两种主要类型,一种是列表+元祖的类型,一种是字典类型。
|
||||
|
||||
列表+元组的排序方式
|
||||
|
||||
列表+元组形成的组合数据类型,适合存放包含多个属性的对象。我给你看个例子,
|
||||
|
||||
“姓名、性别、身份证号”和“学校、姓名、学号”这两组数据就适合用元组存储。因为元组存储的值是不可变的,而这些数据填入之后一般不会修改,刚好和元组的特性吻合。
|
||||
|
||||
而且,这些用户信息在工作中往往会被大量存储在一起,为了便于对它们进行排序和查找。你还需要将多个元组再保存到列表当中,这样就形成了列表+元组这种组合类型。
|
||||
|
||||
对列表+元组的形式进行排序,就需要用自定义排序的字段来实现。那就是在默认排序的基础上,增加key参数,并通过lambda表达式取得元组第三个位置的学号。
|
||||
|
||||
lambda表达式是简化自定义函数的定义和调用而使用的一种语法,使用lambda表达式取得学号的字符串之后,sortd()就可以实现对元组按照学号进行排序了。
|
||||
|
||||
在这里,key没法通过下标对元组排序,而必须通过函数取得参数具体的值。这是sorted()函数为了更灵活地实现排序功能,把设置排序关键字这一功能全部开放给用户造成的。事实上,这也是一种典型的通过牺牲易用性来增加灵活性的设计模式。
|
||||
|
||||
这一知识点比较难理解,不过不用担心,我在后续课程陆续为你讲解这类函数,直到你能熟练使用它们为止。
|
||||
|
||||
到这里,我们就了解了sorted()函数的key参数必须使用函数做参数的原因,那就继续来看key参数的lambda表达式是怎么简化自定义函数的。
|
||||
|
||||
比如我想取得元组中包含“Jerry”的学号“1003”,以及包含“Tom”的元组学号的“1005”,可以使用这样一段代码来实现函数定义和调用方式:
|
||||
|
||||
def s(my_tuple):
|
||||
return my_tuple[2]
|
||||
sorted(students, key=s)
|
||||
|
||||
sorted(students, key=lambda s: s[2])
|
||||
|
||||
|
||||
在代码的1-3行、第5行,分别是通过自定义函数和lambda表达式实现的提取元组第三个参数的功能。对比来看,自定义函数的定义要比lambda表达式复杂。自定义函数的定义和调用部分在Python中要分开编写,这也是简单函数更适合用lambda表达式编写的原因。
|
||||
|
||||
lambda表达式通常在函数只有一行语句,且不需要强调函数名称的时候使用,因此它还有一个名字叫做匿名函数。它的结构只包含四部分,即lambda关键字、需要接收的参数、一个冒号和对接收参数的处理,并且它会把处理结果自动返回。
|
||||
|
||||
因此在sorted()函数中通过lambda表达式实现按关键字排序,会比使用自定义函数排序更加简洁,所以当你遇到只有一行语句的函数场景时,可以考虑使用lambda表达式替代自定义函数的定义和调用。
|
||||
|
||||
字典类型的排序方式
|
||||
|
||||
除了列表+元组的复合类型外,我们经常还需要对字典类型进行排序,字典类型包含键和值,*所以排序的时候可以基于键来排序*,也可以基于值来排序。
|
||||
|
||||
我们还是以学生信息为例。我把学生的姓名定义为字典的键,把学号定义为字典的值。接下来我们看看sorted()是怎么对字典的键和值进行排序的,实现排序的代码如下:
|
||||
|
||||
student_dict1 = {'Jerry':'1003',
|
||||
'Tom':'1005',
|
||||
'Beta':'2001',
|
||||
'Shuke':'2003'
|
||||
}
|
||||
|
||||
# 输出字典的键和值
|
||||
print(student_dict1.items())
|
||||
# dict_items([('Jerry', '1003'), ('Tom', '1005'), ('Beta', '2001'), ('Shuke', '2003')])
|
||||
|
||||
# 按照字典的键排序
|
||||
print(sorted(student_dict1.items(), key=lambda d: d[0]))
|
||||
# [('Beta', '2001'), ('Jerry', '1003'), ('Shuke', '2003'), ('Tom', '1005')]
|
||||
|
||||
# 按照字典的值排序
|
||||
result = sorted(student_dict1.items(), key=lambda d: d[1])
|
||||
print(result)
|
||||
[('Jerry', '1003'), ('Tom', '1005'), ('Beta', '2001'), ('Shuke', '2003')]
|
||||
# 将结果转换为字典
|
||||
print(dict(result))
|
||||
# {'Jerry': '1003', 'Tom': '1005', 'Beta': '2001', 'Shuke': '2003'}
|
||||
|
||||
|
||||
在这段代码中,我实现了基于字典的键和字典的值进行排序的功能。由于字典是使用键值对的形式存储数据的,所以我先通过字典自带的函数items(),把键值对的形式转换成列表+元组的形式。经过转换以后,字典的键就变成了元组的第一个参数,值就变成了元组的第二个参数。
|
||||
|
||||
因此在代码中,我把参数key的值设置为“lambda d: d[0]”,这样就取得了元组的第一个元素,排序之后就实现了按照字典的键进行排序的需求。相应的,使用“lambda d: d[1]”,可以取得元组第二个元素的值,就能实现按照字典的值进行排序的需求。
|
||||
|
||||
由于排序后的数据类型已经变成了列表+元组形式。所以我们在代码最后,可以通过dict()函数把排序的结果再转换为字典,和排序前的数据类型保持一致。
|
||||
|
||||
这就是通过sorted()函数对字典进行排序的解决方法,你在工作中或许还会遇到和这节课不一样的数据类型,但是它们的解决思路是相通的。
|
||||
|
||||
|
||||
如果能转换成列表,可以采用更改lambda下标的方式,实现对指定字段的排序。
|
||||
如果不能转换成列表,可以尝试将复杂的类型中,不需要进行排序的部分进行删减,简化成列表或字典类型,这样就也能使用sorted实现数据的排序功能了。
|
||||
|
||||
|
||||
小结
|
||||
|
||||
最后,我来给你总结一下这节课的核心内容。我们通过sorted()函数实现了列表、列表+元组、字典类型的排序,通过排序后的结果,你可以快速提取TopN,也可以利用你学习过的其他语言的算法实现更复杂的查找。
|
||||
|
||||
在sorted()函数中,我们通过编写key参数的值,使用了lambda表达式替代了简单的函数,让我们的程序更加简洁。当你在日后的工作场景中也出现了只有单个语句的函数时,也可以考虑采用lambda表达式替代自定义函数,增加代码的可读性。
|
||||
|
||||
正是因为sorted()功能的强大,我们在python中实现排序几乎不需要通过自行编写代码来实现排序,不过你在进行海量数据的时候,我还想给你提供两条有用的建议。
|
||||
|
||||
第一,如果对包含海量数据的列表进行排序时,建议将列表通过tuple()函数转换为元组,能够让查找效率有较大提升。
|
||||
|
||||
第二,在Python标准库collections库中提供了OrderedDict扩展数据类型,它的特点是对OrderedDict数据类型进行赋值时,会自动进行排序。当你需要一个有序字典时,可以考虑选择OrderedDict作为数据存储的类型,从而避免手工对数据排序。
|
||||
|
||||
我也把这节课的相关代码放在了GitHub上,你可以去学习查找。
|
||||
|
||||
思考题
|
||||
|
||||
在最后,我想给你留一道开放性的问题。为什么Python语言不将所有的数据类型都设计成默认有序的,即存入数据时自动进行排序?欢迎你说出自己的理由。
|
||||
|
||||
欢迎把你对问题的思考和想法分享在留言区,我们一起交流讨论。如果课程帮你解决了一些工作上的问题,也欢迎你把课程分享给你的朋友、同事。
|
||||
|
||||
|
||||
|
||||
|
234
专栏/Python自动化办公实战课/11通过程序并行计算,避免CPU资源浪费.md
Normal file
234
专栏/Python自动化办公实战课/11通过程序并行计算,避免CPU资源浪费.md
Normal file
@ -0,0 +1,234 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
11 通过程序并行计算,避免CPU资源浪费
|
||||
你好,我是尹会生。
|
||||
|
||||
在我为运营工作提供技术咨询的时候,遇到过这样一个场景:这场运营活动,需要在电脑和手机端的多个不同应用程序,同时推送产品宣传图片和视频。这些大量的图片需要有不同的格式和尺寸,视频也需要根据不同的App截取不同的时长。
|
||||
|
||||
如果这类需要大量计算的多个任务成为你的日常工作,会花费你不少的时间和精力。不过别担心,我们可以通过程序并行计算,来提升任务效率。
|
||||
|
||||
不过你可能会说,用Python自动化执行,也可以提高计算效率啊,那为什么还要学习并行计算呢?
|
||||
|
||||
要知道,Python默认的自动化只能利用CPU的一个逻辑核心,如果采用并行计算,那就能够最大化地利用CPU资源,从而成倍提升大量计算的任务效率。接下来我就详细分析一下并行计算的高效之处。
|
||||
|
||||
为什么要进行并行计算
|
||||
|
||||
还是我在开头提出的运营工作的场景。如果你从这类任务消耗计算机主要资源的角度去考虑,会发现这类需求有两个共同的特点。
|
||||
|
||||
第一,它们都需要进行大量的计算,而计算主要是通过CPU来实现的。CPU的硬件指标上有两个和计算效率最相关的概念,分别是主频和多核。
|
||||
|
||||
主频决定CPU处理任务的快慢,多核决定处理的时候是否可以并行运行。这和生活中超市的收银一样,收银员的工作效率和超市开放了多少个收银台的通道,都决定了你能否以最快的速度购买到你想要买的商品。
|
||||
|
||||
第二,这些任务往往都需要运行相同的程序,但是程序的参数却需要根据不同的需求进行调整。
|
||||
|
||||
虽然咱们可以使用Python自动化执行这些程序,从而减少手动操作时间,但是我们还可以利用CPU的多核特性,让程序尽可能并行执行,发挥CPU的全部计算能力,提高运行效率。
|
||||
|
||||
那么接下来,我就来教你怎样利用Python的多进程库,来实现程序的并行计算,以及怎么提高并行计算的效率。
|
||||
|
||||
怎样实现并行计算
|
||||
|
||||
要想实现程序的并行计算,需要使用到标准库中的multiprocessing多进程库。你可能会问,进程是什么呢?
|
||||
|
||||
进程,是计算机用来描述程序运行状态的名词。一个进程在运行时需要消耗一定的资源,包括CPU的时间、内存、设备I/O等。如果两个进程互相独立,在同一个任务处理过程中,没有前后依赖关系,那你可以利用multiprocessing库同时运行多个进程,这样就能成倍地减少多个任务执行的总时间。
|
||||
|
||||
接下来,我就以计算1-100的平方为例,看看怎么使用multiprocessing实现并行计算。代码如下:
|
||||
|
||||
from multiprocessing import Pool
|
||||
|
||||
# 计算平方
|
||||
def f(x):
|
||||
return x*x
|
||||
|
||||
with Pool(8) as p:
|
||||
# 并行计算
|
||||
res = p.map(f, range(1, 101))
|
||||
print(f'计算平方的结果是:{res}')
|
||||
|
||||
|
||||
在这段代码中,我通过Pool包的map()函数来求1到100平方计算,由于每次计算平方的过程和下一次计算没有直接关联,我就可以使用并行的方式进行计算,提高计算效率。
|
||||
|
||||
为了让map()函数能够实现并行计算,我们必须在使用它之前,通过Pool()包为它指定并行计算的进程数量,设置要执行的函数名称f,以及f()函数所需参数。那么接下来,我就带你学习一下我是怎样使用with语句来设置函数的参数,并正确执行map()函数的。
|
||||
|
||||
首先来看最关键的map()函数,它是Pool包实现并行计算的函数之一。在代码中我为map()函数赋值了f和range()函数两个参数。
|
||||
|
||||
第一个参数是函数对象。
|
||||
|
||||
函数对象会作为map()函数创建进程以后,即将执行的主要任务。因此,由于这里的含义是指定f对象将要被创建的进程执行,而不是将f()函数执行的结果作为新的进程执行,所以第一个参数必须使用函数对象f,而不能使用f()函数。
|
||||
|
||||
第二个参数要求必须是可迭代的对象。
|
||||
|
||||
例如我在代码中需要为f函数传递参数为1-100的整数,就可以使用range()函数产生1到100的整数并直接返回,因为它的返回值就是可迭代对象。
|
||||
|
||||
如果参数不是数字,就可以采用列表、元组、字典等支持迭代的数据类型,代替range()函数,作为f()函数的参数。举个例子,如果你需要并行调整多个视频的时长,就可以采用字典存储路径和要调整的视频时长,并把这个字典作为map()函数的第二个参数,map()函数会为字典的每个键值对创建一个进程来并行处理它们。
|
||||
|
||||
接下来是map()函数中的三个主要部分,我来分析一下它们各自在并行计算中的功能。
|
||||
|
||||
第一,with语句。这是我们在第七讲学习怎么使用Python打开文件之后,第二次用到with语句了。
|
||||
|
||||
和文件操作类似,进程打开后也需要妥善关闭,但是进程关闭属于较为底层的操作,如果你不进行操作系统层面的程序设计,是不需要对关闭进程的函数进行修改的,因为使用默认关闭进程的行为,就能满足编写并行计算的需求。
|
||||
|
||||
因此,multiprocessing库对Pool包,支持了比较友好的进程打开和关闭方式,即with语句。也就是说,multiprocessing库把对进程的操作写在with语句块中,而with语句就会自动处理进程的打开和关闭,这样在实现并行计算的代码中,你就不用学习进程的基本操作,也能减轻你学习并发程序的负担。
|
||||
|
||||
在了解了with语句可以操作进程的打开和关闭后,我们来看代码中我是怎么使用with语句的。
|
||||
|
||||
我在代码中使用了“ with Pool(8) as p ”这条语句,这里的Pool()类是多进程库支持的进程池功能,它的作用是指定一个多进程的程序,最多能够并行执行的进程数量。它的参数“8”,表示map()函数最多同时运行8个进程。
|
||||
|
||||
剩下两个部分是range()函数和f()函数。
|
||||
|
||||
range()函数的作用是产生1-100的整数,这些整数会在每次创建新的进程时,依次作为f()函数的参数并赋值。而f()函数得到参数后,会把计算结果返回给map()函数。当f()函数处理完所有的参数后,map()函数还会返回一个列表作为运行的结果,并进行输出。
|
||||
|
||||
以上就是实现并行计算的主要过程。
|
||||
|
||||
如何提高并行计算的效率
|
||||
|
||||
我们除了需要掌握并行计算的基本方法外,还可以继续提升并行计算的效率。所以在程序中还有两个地方需要优化。
|
||||
|
||||
一个是为并行程序自动指定并行度。在并行计算的基本方法中,我使用了手动指定并行度的方式,来指定进程最多能够运行多少个。不过手动指定的并行度并不能适合所有的电脑,因此就需要根据计算机的CPU核数设置合理的并行度。而且,每台计算机的CPU资源是固定不变的,那么设置合理的进程数量能让你的并行计算任务充分利用CPU资源。
|
||||
|
||||
另一个是统计程序运行的时间。当你对并行计算的数量做了修改后,那程序是否对计算效率起到了提升效果呢?就还需要更精确的测量,这样才能得到更准确的结果。所以我们还需要使用Python统计出程序执行过程一共消耗了多长的时间。
|
||||
|
||||
我们先来看怎么自动设置适合你的电脑的并行度。
|
||||
|
||||
为并行程序自动指定并行度
|
||||
|
||||
计算类的任务包括数字计算、数据统计、视频的编解码等,都属于计算密集型应用,它们最大的资源开销就是CPU时间的消耗,设置的并行度过大或过小都不能达到最好的运行效率。
|
||||
|
||||
|
||||
如果并行度设置过小,比如运行的进程数量小于逻辑CPU的数量,就会造成部分逻辑CPU因为无法被充分利用而处于闲置状态。
|
||||
如果并行度设置过大,由于现代的操作系统为了保证每个进程都能公平得到CPU资源,所以会造成CPU把时间大量消耗在进程切换上。那么并行度设置过大,会导致CPU还未完成一个进程的处理时,就得切换至下一个进程进行处理,而多进程之间来回切换也会消耗CPU时间,造成CPU资源的浪费。
|
||||
|
||||
|
||||
那并行度该怎么设置才合理呢?通常情况下,我们会把并行度设置为逻辑CPU数量的两倍。不过假如计算任务达到小时级别(这类任务需要长时间占用CPU资源),为了减少切换任务时的开销,我建议计算的并行度和逻辑CPU数量保持相等。
|
||||
|
||||
这就又有一个问题了,该怎么获得计算机的逻辑CPU个数呢?Windows可以通过任务管理器获得,MacOS可以通过活动监视器获得。如果你希望取得逻辑CPU的个数之后,可以根据它的数量自动设置创建进程的数量,那么可以通过安装第三方包psutils,利用其中的cpu_count()函数取得逻辑CPU个数。
|
||||
|
||||
我把并行度自动设置为当前逻辑CPU两倍的代码写在下面,供你参考。
|
||||
|
||||
from multiprocessing import Pool,Queue
|
||||
import os
|
||||
import psutil
|
||||
|
||||
# 逻辑cpu个数
|
||||
count = psutil.cpu_count()
|
||||
|
||||
# 定义一个队列用于存储进程id
|
||||
queue = Queue()
|
||||
|
||||
# 用于计算平方和将运行函数的进程id写入队列
|
||||
def f(x):
|
||||
queue.put(os.getpid())
|
||||
return x*x
|
||||
|
||||
with Pool(count*2) as p:
|
||||
# 并行计算
|
||||
res = p.map(f, range(1, 101))
|
||||
print(f'计算平方的结果是:{res}')
|
||||
|
||||
# 并行计算用到的进程id
|
||||
pids = set()
|
||||
while not queue.empty():
|
||||
pids.add(queue.get())
|
||||
|
||||
print(f'用到的进程id是: {pids}')
|
||||
|
||||
|
||||
在代码中,我使用了 psutil.cpu_count() 函数来获取逻辑CPU的个数,它把“count*2”作为参数传递给Pool()类,并以逻辑CPU两倍作为最大创建进程数量,从而计算1-100的平方。
|
||||
|
||||
这里有两点需要你注意。第一,psutils是process and system utilities的缩写,所以它除了获取逻辑CPU数量外,还可以获取内存、硬盘、网络连接等和操作系统相关的信息。如果你在工作中需要取得操作系统的运行状态,就可以采用psutils包。
|
||||
|
||||
第二,psutils是第三方库,因此,在Windows上你需要通过cmd命令行执行pip3 install psutil安装后,才能释放psutils包,否则会出现模块无法找到的错误。
|
||||
|
||||
由于map()函数的第二个参数可能会被传入不可迭代对象,这时有可能会导致只运行了一个进程,因此我就在多进程执行过程中,增加了记录进程ID的功能。而在这一功能中,我使用的是os库、队列库和集合数据类型,按照下面三个步骤来实现对所有创建的进程ID的统计。
|
||||
|
||||
首先,使用os库的getpid()函数获取进程ID。
|
||||
|
||||
由于map()函数会根据Pool()类的参数,事先创建好指定数量的进程,而每次运行f()函数都在创建好的进程中执行,所以我就采用os库的getpid()函数取得运行f()函数进程的唯一标识,这就是使用os库的用途。
|
||||
|
||||
接下来,使用队列库存储每次运行进程的ID。
|
||||
|
||||
为了把每次运行的进程ID存到一个对象中,我使用了multiprocessing库的队列包。因为在多进程的程序中,不能采用标准数据类型来传递数据,所以multiprocessing库还提供了方便进程间通信的对象——Queue队列。
|
||||
|
||||
map()函数每执行一次f()函数,我就把进程ID作为队列的put()函数的参数,并把进程ID放入队Queue中,直到所有的f()函数执行完成,队列里就会记录每次执行的进程ID信息。
|
||||
|
||||
最后,使用集合数据类型存储本次f()函数运行的所有进程ID。
|
||||
|
||||
为了实现这一功能,我需要通过while循环结构,根据队列不为空的条件,把队列中的进程ID使用get()函数取出来,放入pids变量中。
|
||||
|
||||
pids变量是集合数据类型,集合是一个无序的不重复元素序列,需要使用set()创建。你可以把集合当作一个只有键没有值的字典来记忆,它的特点是集合里的元素不能重复。
|
||||
|
||||
由于f()函数会多次在一个进程中执行,因此在队列中会记录重复的进程ID,我把进程ID从队列中取出后,放入集合数据类型中,自己就不用编写程序,自动把重复的进程ID去掉了。而且通过对集合pids中的进程ID进行输出,可以看到进程ID的数量刚好和Pool()类指定的并行进程数量相等。
|
||||
|
||||
这种用法是我经常在进行多进程程序调试的一种简单用法,我还会把它们的结果写入文件保存,以便程序出现异常执行结果时,可以根据调试的信息进行问题的定位。
|
||||
|
||||
统计程序运行的时间
|
||||
|
||||
我们除了需要掌握判断程序的并行度外,还可以统计并行计算比顺序计算节省了多少时间。那么再遇到相同场景的时候,你可以选择并行方式来运行程序,提高工作效率。接下来我来教你怎样统计Python程序运行的时间。
|
||||
|
||||
在Python中我们可以利用time库的time()函数,来记录当前时间的功能。
|
||||
|
||||
|
||||
首先,需要在统计时间代码的前后各增加一次time.time()函数,并把它们统计时间的结果存放在time1、time2两个不同的变量中。
|
||||
然后再把两个变量相减,这样就能取得程序的运行时间了。
|
||||
|
||||
|
||||
我把核心实现代码写在下面供你参考。
|
||||
|
||||
# 并行计算时间统计
|
||||
with Pool(4) as p:
|
||||
# 并行计算
|
||||
time1 = time.time()
|
||||
res = p.map(f, range(1, 10001))
|
||||
time2 = time.time()
|
||||
# print(f'计算平方的结果是:{res}')
|
||||
|
||||
print(str(time2-time1))
|
||||
|
||||
|
||||
# 串行计算时间统计
|
||||
list1 = []
|
||||
|
||||
time1 = time.time()
|
||||
for i in range(1, 10001):
|
||||
list1.append(f(i))
|
||||
time2 = time.time()
|
||||
|
||||
print(str(time2-time1))
|
||||
|
||||
|
||||
在这段代码中,通过time1和time2的时间差就可以得到程序运行的时间了,那么根据运行时间,我们可以把并行程序和串行程序执行时间的性能进行对比。
|
||||
|
||||
这里你需要注意,由于计算平方的CPU开销较小,比较难体现并行计算的优势,你就可以采用并行访问网页,或其他CPU开销较高的程序,这样会让两个程序的时间差别更加明显。
|
||||
|
||||
总结
|
||||
|
||||
在最后,我来为你总结一下实现并行计算的基本方法和三个注意事项。
|
||||
|
||||
通过multiprocessing的Pool包可以实现基于进程的并行计算功能,Pool包的map()函数会根据Pool包指定的进程数量实现并行运行。这里还有三点需要你注意:
|
||||
|
||||
|
||||
作为map()函数的第一个参数你需要传递函数对象f,不能传递函数的调用f()形式,这是初学者实现并行任务最容易出现的错误。
|
||||
为了让并行度更适合你的电脑,应该根据逻辑CPU的个数设置并行度,并根据运行时间来对并行数量进一步优化。
|
||||
实现并行计算任务的程序除了使用多进程模型外还可以使用多线程模型。多进程的并行计算更适用于计算密集型应用,即程序运行过程中主要为计算类CPU开销大的程序,多线程模型适合I/O密集型的应用,例如:通过互联网进行批量网页访问和下载。如果你想将多进程的并发模型改为多线程的并发模型只需在导入库的时候将“multiprocessing”改为“multiprocessing.dummy”就能实现多线程并行访问网页。我将多进程和多线程两种方式导入库的代码贴在下方供你参考。
|
||||
|
||||
|
||||
# 多进程模型
|
||||
from multiprocessing import Pool
|
||||
|
||||
# 多线程模型
|
||||
from multiprocessing.dummy import Pool
|
||||
|
||||
# multiprocessing.dummy的Pool用法和multiprocessing库相同
|
||||
|
||||
|
||||
我把这节课的相关代码放在了GitHub上,你可以自行查找、学习。
|
||||
|
||||
思考题
|
||||
|
||||
我为你留一道思考题,有一个软件包requests,可以通过requests.get(’http://www.baidu.com’).text 方式访问一个网站,并能够得到网页的源代码。假设我为你提供了几十个需要访问的网站,你是如何实现这些网站的并行访问的,你又能否通过Python对比出逐个访问网页的时间是并行访问的几倍吗?
|
||||
|
||||
|
||||
|
||||
|
232
专栏/Python自动化办公实战课/12文本处理函数:三招解决数据对齐问题.md
Normal file
232
专栏/Python自动化办公实战课/12文本处理函数:三招解决数据对齐问题.md
Normal file
@ -0,0 +1,232 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
12 文本处理函数:三招解决数据对齐问题
|
||||
你好,我是尹会生。
|
||||
|
||||
当你在工作中利用Excel向同事展示业务数据时,肯定遇到过数据无法对齐的问题。
|
||||
|
||||
比如在展示日销售额报表数据时,如果数字、日期、文字这些常见的元素没有对齐,你不仅会因为格式显示混乱而难以分析数据变化的趋势,而且也会因为报表格式不够工整,给领导留下一个技术能力不扎实的不良印象。
|
||||
|
||||
而且,如果一个个手动来调整格式,不仅麻烦,而且还很容易出错。那么今天这节课,我就教你怎样用Python的文本处理函数,利用format()、split()、join()和strip()四个函数来对Excel中的数字、日期和文字进行长度调整和移除多余空格等处理,解决数据对齐问题。
|
||||
|
||||
数据对齐的思路
|
||||
|
||||
为了更好地掌握Python的文本处理函数,我先带你了解Excel中数据的默认对齐方式,以及需要使用Python中的哪些文本函数。
|
||||
|
||||
Excel中的数据是有默认对齐方式的,对齐的方式一共有三种,分别是右对齐、左对齐和居中对齐。数据类型不同,默认对齐方式也不同。
|
||||
|
||||
|
||||
数值型数据会自动靠右对齐,比如日期、时间、数字。
|
||||
文本型数据会自动靠左对齐,比如汉字、字母、英文、引号开头的数字。
|
||||
|
||||
|
||||
当你把数据粘贴到Excel中时,如果是同类型数据没有实现对齐,你就要考虑在粘贴到Excel之前对数据进行处理。
|
||||
|
||||
具体怎么处理呢?我们可以根据Excel的默认对齐方式,以及导入到Excel中的内容,来分情况考虑。
|
||||
|
||||
在右对齐的类型中,数字、日期、时间是我们接触最多的数值类型,而数字和日期、时间的处理方式又有差别,所以我们要分开来学习和掌握它们。
|
||||
|
||||
在左对齐的类型中,文字、字母、英文和引号开头的数字,它们的对齐方式相同,都是去掉前方的空格。所以在你掌握文字前后的空格处理之后,就可以举一反三地对其他三种数据类型实现左侧对齐。
|
||||
|
||||
还有最后一种对齐方式是居中对齐。居中对齐的场景只有一种,那就是作为主题使用。这种对齐方式可以直接通过手动操作来完成。
|
||||
|
||||
因此,我通过对数字、日期、文字三种类型的处理,可以让你应对大多数数据对齐的场景了。总结来说,那就是:
|
||||
|
||||
|
||||
采用format()函数,实现数字对齐;
|
||||
使用split()和join()函数,实现日期右侧对齐;
|
||||
使用strip() 函数,实现文本型数据的左侧对齐。
|
||||
|
||||
|
||||
接下来我就详细讲解一下这三种解决方法。
|
||||
|
||||
数据对齐的办法
|
||||
|
||||
第一招 使用format()函数,实现数字对齐
|
||||
|
||||
数字没有对齐,主要是因为数字位数不同。所以我们可以通过补全数字位数的方法,来对齐数字。
|
||||
|
||||
我们来看一个例子。在这个例子中,我提供了5个浮点数,它们的内容和格式如下:
|
||||
|
||||
12.34
|
||||
123.456
|
||||
1.2345
|
||||
123456.78
|
||||
123.4
|
||||
|
||||
|
||||
在这组浮点数中,它们各自的小数位数也不同。如果直接粘贴到Excel,会因为小数位数不同而无法直观地比较它们的大小。
|
||||
|
||||
在这种情况下,你可以使用Python的内置format()函数,把它们都保留至小数点后四位,这样既能保证每个浮点数的精度,又能按右侧对齐小数点。
|
||||
|
||||
那我对这5个浮点统一使用format()函数进行处理,它的代码是:
|
||||
|
||||
string1 = 123.45
|
||||
print("{:.4f}".format(string1))
|
||||
# 123.4500
|
||||
|
||||
|
||||
这段代码把“123.45”使用format()函数进行格式处理后,就会保留小数点后四位,输出“123.4500”。所以如果把浮点数用format()函数进行处理,再粘贴到Excel中,就可以实现自动右侧对齐了。
|
||||
|
||||
那我们接下来就学习一下format()函数的调用方式和格式,看format()函数是怎么来补齐数字小数的位数,并且调整整数的位置的。
|
||||
|
||||
format()函数是Python的2.6版本新增的内置函数,它增强了字符串的格式处理功能。你应该想到了,format()函数是内置函数,所以它和我们之前学习过的print()、int()、str()函数一样,可以在Python中直接调用,不用预先定义和import导入。
|
||||
|
||||
使用format()函数对数字格式调整时,调用format()的Python语句是“{:数字的格式}.format(要转换格式的数字)”,在这条语句里,“:”后面的数字的格式包含三个部分。
|
||||
|
||||
第一部分表示符号和空格,可以使用+、-、(空格)。
|
||||
|
||||
|
||||
“+”表示在正数前显示 +;
|
||||
“-”表示负数前显示 -;
|
||||
(空格)表示在正数前加空格。
|
||||
|
||||
|
||||
第二部分表示宽度,可以使用. < > ^, “.”来保留小数点后的位数。
|
||||
|
||||
|
||||
.4表示保留小数点后四位;
|
||||
^, <, > 分别是居中、左对齐、右对齐,后面也需要使用数字指定宽度,如果不满足宽度自动补充空格。
|
||||
|
||||
|
||||
第三部分表示类型,常见的有“b、d、f”,分别表示二进制、十进制和浮点数。比如我可以使用如下代码,来表示整数100占用6个字符的宽度,并靠右侧对齐。
|
||||
|
||||
print("{:>6d}".format(100))
|
||||
#执行结果
|
||||
(空格)(空格)(空格)100
|
||||
|
||||
|
||||
可以看到,format()函数有着非常丰富的参数,除了可以进行数字对齐外,还可以调整字符串格式,所以我也经常用format()函数在工作中进行格式化字符串操作。不过这些参数和数字对齐的关系不大,我在这节课就不把它们一一列出来了,如果你想详细了解,可以参考官方文档。
|
||||
|
||||
接下来我们来看一下如果是日期类型,怎么使用字符串函数实现它的右侧对齐。
|
||||
|
||||
第二招 使用字符串函数,实现日期右侧对齐
|
||||
|
||||
日期对齐和数字对齐相比,最主要的区别就是它的年、月、日三个部分都可能出现不等长的情况。我给你举个例子,你一看就明白了。
|
||||
|
||||
2021-3-18
|
||||
21-3-18
|
||||
2021-12-21
|
||||
2021-3-18
|
||||
21-3-1
|
||||
|
||||
|
||||
在这5个日期中,由于年、月、日的位数不同,导致粘贴到Excel后没法实现右侧对齐。因此我们实现日期对齐的前提,就是要补齐日期。而补齐日期的过程,我们需要按照先拆分日期、再调整格式、最后合并日期的顺序。
|
||||
|
||||
我先把它的代码写出来,供你参考,然后再给你讲解补齐日期的过程。
|
||||
|
||||
date_demo = [
|
||||
"2021-03-18",
|
||||
"21-3-18",
|
||||
"2021-12-21",
|
||||
"2021-3-8",
|
||||
"21-3-1",
|
||||
]
|
||||
|
||||
for dd in date_demo:
|
||||
# 拆分日期
|
||||
year, month, day = dd.split('-')
|
||||
# 调整格式
|
||||
if len(year)== 2 :
|
||||
new_year = 2021
|
||||
else:
|
||||
new_year = year
|
||||
|
||||
month = "{:>02d}".format(int(month))
|
||||
day = "{:>02d}".format(int(day))
|
||||
# 合并日期
|
||||
new_date = [str(new_year), str(month), str(day)]
|
||||
new_date = "-".join(new_date)
|
||||
|
||||
print(new_date)
|
||||
|
||||
# 执行结果
|
||||
2021-03-18
|
||||
2021-03-18
|
||||
2021-12-21
|
||||
2021-03-08
|
||||
2021-03-01
|
||||
|
||||
|
||||
|
||||
通过这段代码的执行结果,可以看到我把长度不同的5个日期,按照年、月、日三个部分别进行了长度处理,并使用“-”作为日期分隔符,从而实现日期的等长。
|
||||
|
||||
接下来,我按照程序的执行顺序,为你详细讲解一下我是怎么对日期进行拆分、调整和合并的。
|
||||
|
||||
为什么要先进行拆分这一操作呢?因为在我们已知的函数中,没法对年、月、日分别进行调整。如果整体进行调整,比如调整年,很容易影响到月和日的数字,我把它称作调整的副作用。所以我就需要通过split()函数先对完整日期进行拆分。
|
||||
|
||||
split()函数是我们这节课要重点掌握的一个函数,我来为你讲解一下它的主要用法。
|
||||
|
||||
按照学习其他函数的经验,你需要掌握split()函数的功能、参数和返回值。实际上对于所有函数的学习,我们都需要先关注函数功能、参数个数和类型、返回值个数和类型三个部分,之后再去学习参数函数里的业务逻辑功能。这其实也是掌握函数的通用做法。
|
||||
|
||||
split()函数是对字符串进行拆分的函数,也是根据字符串中指定的分隔符,它的参数“-”就是拆分字符串的分隔符,它的返回值是拆分以后字符串形成的列表。
|
||||
|
||||
我在返回值中使用了连续赋值的功能,那么就可以通过以下代码来实现:
|
||||
|
||||
year, month, day = dd.split('-')
|
||||
|
||||
|
||||
这种方式,让变量year、month、day依次得到了列表中的三个元素。
|
||||
|
||||
由于在代码的后续运行过程中,我需要对每个变量进行单独处理,而使用列表还需要通过下标访问每个列表。那么这里我就用三个变量代替列表来作为split()函数的返回值。不过我用变量代替列表还有一个原因,那就是在代码的阅读上,使用变量方式更易于理解。
|
||||
|
||||
接下来是调整内容阶段,我依然使用format()函数对日期的每个部分进行长度上的调整。你可以通过代码来学习,如何使用format进行日期的每个部分调整的。
|
||||
|
||||
最后,我把调整好的年、月、日,使用join()函数进行合并。为了和原始字符串保持一致,我继续使用“-”作为连接符号,对函数的参数new_date列表进行连接。而且,join()函数返回的类型是字符串,刚好和处理之前的类型保持一致,如果你的代码里包含了对日期进行操作的功能,调整长度后这些代码就可以保持不变。
|
||||
|
||||
此外,这里还有两点需要你注意:
|
||||
|
||||
|
||||
工作中经常会遇到一个字符串中多处需要修改的内容,例如刚才举的日期例子,你可以把split()和join()函数配合使用,拆分成多个变量,对变量进行处理后,再进行合并,减少处理的复杂度。
|
||||
在使用split()前,你还可以通过正则表达式来处理不规范的分隔符,让字符串每个字段都能按照相同的分隔符进行拆分。
|
||||
|
||||
|
||||
第三招 使用字符串函数,实现文本型数据的对齐
|
||||
|
||||
Excel中的文字默认是左侧对齐,那文字类型为什么会出现没法对齐的情况呢?主要是因为文字在开头或结尾包含了一个或多个空格。
|
||||
|
||||
对于这些空格,可以使用字符串自带的strip()函数来去掉。我把删除文字前后空格的代码写在下方,供你参考:
|
||||
|
||||
string = " 广东省广州市 "
|
||||
newstring = string.strip()
|
||||
print(f"|{newstring}|")
|
||||
|
||||
# 输出结果
|
||||
|广东省广州市|
|
||||
|
||||
|
||||
在这段代码中,我通过内置的strip()函数,去掉了字符串前后的空格,并通过f-string调整了字符串的格式。
|
||||
|
||||
strip()函数是我们这节课新学习的函数,它的主要功能就是自动移除字符串开头和结尾指定的字符,如果没有为strip()函数指定参数,默认会移除字符串开头和结尾连续的多个空格。
|
||||
|
||||
另一个新学习的函数是f-string,我在代码的第三行使用了它,用于调整newstring字符串的输出结果。f-string是在Python3.6版本引入的一种新的字符串,它的写法是在字符串前增加f关键字,增加后我们就能在一般的字符串中使用“{}”关键字,“{}”中允许使用变量、运算以及和format()相同的格式描述符。
|
||||
|
||||
为了让你更直观地看到空格是不是被移除掉了,我还利用f-string在newstring字符串前后增加了“|”,这里可以用任意一个字符,主要用于观察竖线和文字间是否有空格。这样你通过结果,就可以看到“|”和“ 广东省广州市 ”前后的空格已经被自动删除掉了。
|
||||
|
||||
可以看到,f-string和format()函数都具有对字符串输出格式调整的功能,不过前者更适用于字符串和变量连接的场景,后者更适用于调整字符串的格式。此外,f-string除了可以用来观察文字对齐的结果外,你还可以在输出变量的同时也输出字符串。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下这节课的核心内容。今天这节课我们主要讲了四个字符串处理函数,它们也是文本处理工作中最经常用到的函数:
|
||||
|
||||
|
||||
format()函数,可以对字符串或数字进行格式化,对浮点数的小数位数进行调整,实现小数的对齐。
|
||||
split()函数,按指定的分隔符分隔字段,实现字符串中的部分字符串处理。
|
||||
join()函数,能够将列表、元组中的每个元素按照指定的分隔符连接成字符串,如果是字符串和变量的连接,你还可以使用f-sring方式实现字符串连接功能。
|
||||
strip()函数,它是自动去掉字符串前后空格的函数,经常用于从文本提取内容后,对文本前后空格进行删除,优化文字内容显示结果的函数。
|
||||
|
||||
|
||||
我把今天这节课用到的代码都放在了GitHub上,文稿里也有链接,你可以去学习掌握。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,最后我来为你留一道思考题。如果你需要将文本文件中的“小时:分钟:秒”处理成等长格式,再存入Excel中,你需要使用哪些函数做怎样的处理,才能实现时间的对齐呢?
|
||||
|
||||
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以扫描课程详情页的二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。
|
||||
|
||||
|
||||
|
||||
|
188
专栏/Python自动化办公实战课/13Excel插件:如何扩展Excel的基本功能?.md
Normal file
188
专栏/Python自动化办公实战课/13Excel插件:如何扩展Excel的基本功能?.md
Normal file
@ -0,0 +1,188 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
13 Excel插件:如何扩展Excel的基本功能?
|
||||
你好,我是尹会生。
|
||||
|
||||
我们在讲了怎么利用Python优化Excel的输入和计算效率之后,相信你已经知道该怎么使用Python的循环和文件库进行多个文件的处理,怎么使用正则表达式对内容进行查找和替换。
|
||||
|
||||
但是有时会因为临时的需求,为了得到查询的结果而进行一次性的表格处理,这时候如果用Python来解决,估计你有一种“杀鸡用牛刀”的感觉,未免小题大做了。
|
||||
|
||||
所以在接下来的三节课里,我会利用Excel中自带的插件和更简单的两个脚本工具,来完成这种临时的、简单的重复性任务,让你用更便捷的方式实现办公自动化。
|
||||
|
||||
今天这节课呢,我就给你介绍一个在Excel中非常著名的Power Query插件,利用这个插件你可以完成我们经常遇到的数据清理工作。
|
||||
|
||||
Power Query的主要用途
|
||||
|
||||
我先来介绍一下Power Query这个插件。从它的名字,你应该就能猜到它的主要用途,那就是在查询方面对Excel进行优化。
|
||||
|
||||
我所说的查询优化是泛指,它的涵盖范围比较广。为了方便使用Excel统计数据,往往需要在统计数据前去调整Excel表格的格式、内容以及字段类型,这些在Excel中统称为查询操作。
|
||||
|
||||
如果能够把新增数据自动更新到已经处理的数据中,还能自动化地按照之前的操作步骤对表格中的数据进行调整,这就是Power Query比手动调整Excel更有效率的地方。
|
||||
|
||||
在查询大量数据中,最耗时的就是清理数据工作了,具体来说,就是把需要统计的数据从多个文件进行提取和合并,把不符合统计格式的数据进行自动化处理,最终形成符合数据统计的规范格式。
|
||||
|
||||
比如:数据被存储到多个文件里,而你需要把多个文件的内容手动合并到一个Excel中。再比如:Excel中的日期包含了年月日和具体时间,如果要按日期汇总这些数据,那你就需要先把年月日和时间拆分。不方便的是,如果表格里的数据更新了,你就必须要重新再手动拆分一遍。
|
||||
|
||||
其实这两个例子,代表了清理数据工作中需要自动化最典型的两个场景,那就是文件导入和格式调整。而Power Query 就是优化这两个主要功能的插件。
|
||||
|
||||
那怎么用Power Query来优化呢?你需要掌握三个主要步骤,分别是获取数据、转换和加载。只要是用Power Query来优化Excel的查询,这三个步骤是必须要掌握的。我也再来具体解释一下每个步骤的具体操作。
|
||||
|
||||
获取数据,指的是把数据传入Power Query 的过程。通常我们会新建一个工作簿,把Excel打开之后,然后再切换到“数据”选项卡。接着,单击“获取数据”按钮,在下拉菜单中选择“自文件”命令,继续在下一级菜单中选择“从文件夹”命令,通过弹出的“文件夹”对话框,就可以加载文件夹中的所有Excel文件了。具体操作示范如下图。
|
||||
|
||||
|
||||
|
||||
转换,指的是对数据的清理工作。在这一步,你需要把你加载到Power Query中的数据从文件到sheet,再从sheet到行列依次处理,对合并好的数据再进行添加、删除列、筛选、添加自定义计算等操作。
|
||||
|
||||
这一步是把不符合统计需求的数据调整为符合统计需求数据的最主要操作步骤,也是最繁琐的一步。例如把销售代表字段拆分成姓氏和名字,将订单日期的时间字段去掉,就可以使用转换这一操作来完成。
|
||||
|
||||
我给你画一幅图,用来帮你直观理解转换前后的变化,图片如下:
|
||||
|
||||
|
||||
|
||||
第三步是加载,指的是把已经清理和转换的数据返回到Excel中。这一步比较简单,这里我就不多讲了。
|
||||
|
||||
讲完了用Power Query优化Excel查询的三个核心步骤,那接下来我就用两个典型案例,来,带你实践一下具体的操作。
|
||||
|
||||
如何用Power Query优化Excel的查询
|
||||
|
||||
我选用了两个不同的案例,覆盖了我在第一部分所说的文件导入和格式调整这两种最经常需要手动操作的场景,这两个场景就是:
|
||||
|
||||
|
||||
把多个文件合并到一个Excel中;
|
||||
把单元格内容调整为统一格式。
|
||||
|
||||
|
||||
那么接下来,我就教你怎么使用Power Query加快手工操作Excel的效率。
|
||||
|
||||
把多个文件合并成一个Excel
|
||||
|
||||
把多个文件合并成一个Excel表格,是处理大量数据的先决条件。那么涉及到处理大量数据的工作,都要进行合并这一步操作。
|
||||
|
||||
像是数据被分成多个文件,每个文件里有多个表的情况,在工作中非常常见。如果数据是按月、按天分成不同的工作簿,合并它们是一件更麻烦的事情。
|
||||
|
||||
接下来我就以半年的销售数据表格为例,分7个步骤,来讲解怎么使用Power Query实现多个Excel文件的合并功能。
|
||||
|
||||
第一步,获取数据。
|
||||
|
||||
通过Excel的“数据选项卡”-“获取数据”-“自文件”-“从文件夹”命令,会弹出一个对话框。此时,你可以选中需要合并的文件夹,这样该文件夹内的所有文件都会被识别出来。
|
||||
|
||||
由于每个文件中可能会有不同的表,不能把它们直接合并。因此,我们需要点击“编辑”按钮,进入Power Query编辑界面。
|
||||
|
||||
这个界面是Power Query 的主要工作界面,在你需要对多个文件进行自动化批量修改时,都需要通过这个界面来完成操作。同时,这个界面也会把你操作的中间结果显示出来。
|
||||
|
||||
我们来从左到右依次看一下界面的内容。由于Power Query的界面较大,我先把左侧和中间的界面放在下图:-
|
||||
|
||||
|
||||
界面的中间会显示导入的文件名称和数量,文件的内容会保存在Content列中。
|
||||
|
||||
另外,我要提醒一下,你还应该关注左侧和右侧的提示信息。左侧会显示你进行的多次清理操作,每次清理被称作查询。查询的名字可以在右侧的“查询设置”里进行名称修改,右侧的“应用的步骤”列表还能记录操作历史,如果你认为处理结果不符合期望,那就可以返回上一步,重新调整查询结果。
|
||||
|
||||
我把右侧的名称和步骤也贴在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
第二步,我们需要把工作簿中的数据从Content列中解析出来,并且添加在现有内容的右侧。
|
||||
|
||||
在这里,我们可以使用一个公式来添加新的列。你需要切换到“添加列”选项卡,单击“自定义列”按钮,然后在弹出的对话框中输入公式“Excel.Workbook([Content],true) ”, 最后点击确定。
|
||||
|
||||
我把添加自定义列的操作图片也放在下方。
|
||||
|
||||
|
||||
|
||||
可以看到,通过自定义列的功能,你将会在原有表格的基础上再添加新的一列,而这一列的内容是Table,表示文件中所有的表格内容。这里有一点你需要注意的,那就是自定义列的公式需要区分大小写。
|
||||
|
||||
第三步,调整每个sheet的每一行。
|
||||
|
||||
你需要点击第二步添加“自定义”列右侧的数据展开按钮,然后取消勾选“使用原始列名作为前缀”复选框,并点击确定。如图:
|
||||
|
||||
|
||||
|
||||
第四步,在调整完行之后,你还需要调整每个文件中要查询的sheet和列,它们也是以复选框的形式为你展示的。
|
||||
|
||||
例如我在每个文件只使用了一个sheet,每个sheet中的所有列都需要进行查询,所以我就可以选中所有的sheet和列。我把操作截图贴在下面方便你学习。
|
||||
|
||||
|
||||
|
||||
选择指定的sheet功能,你可以点击Item列右侧的下拉菜单,勾选需要的sheet。点击确定之后,Power Query编辑器界面就会产生新的列,即“Data”列。
|
||||
|
||||
Data列保存了我们在第二步选中的sheet的所有表格,在我们今天的案例中,由于我需要所有的列,因此我就“选择所有列”,并点击“确定”按钮,展开Data列,这样就得到了我需要查询的数据。
|
||||
|
||||
第五步,通过上面四个步骤,就可以把所有数据按照你需要的sheet、行、列,导入到Power Query编辑器了。不过这时编辑器界面还有要处理的中间数据,因此我可以通过删除其他列的方式,来删除额外的数据。
|
||||
|
||||
具体做法是:使用主页选项卡的“删除列”下拉列表,选择“删除其他列”。我把删除前后的截图贴在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
第六步,由于处理完数据内容后,Excel的每一列类型会自动变成文本类型,导致最终执行结果显示错误,所以我们还需要继续修改类型。可以看到,我把“订单日期”改为日期时间类型,把销售额改为小数类型。具体修改方式是点击列标题前的类型图标,通过弹出的下拉列表,选择指定的类型,如图所示:
|
||||
|
||||
|
||||
|
||||
第七步,也是最后一步。我把编辑器的数据保存回Excel中,点击“主页”选项卡上的关闭并下载,这样就实现了把多个文件合并到一个Excel的功能。
|
||||
|
||||
通过这个例子,我把Power Query的三个主要处理步骤:获取数据、转换和加载,分解成了具体的七个操作步骤,来编写Power Query的主要流程。这七个步骤其实并不繁琐,并且你要是再细心点儿,就会发现我是把“转换”这一部分拆解成了五个步骤,其他两部分并没有变。
|
||||
|
||||
总结来说,相比较Python,Power Query不需要编程就能实现多个文件的合并,操作也更加简单。而且在每一步操作之后,你还能通过图形界面及时观察每一步骤的执行结果,这要比Python更加直观,也比Python更适用于一次性的多文件合并场景。
|
||||
|
||||
Power Query不但在获取数据的时候可以实现自动化查询功能,而且在单元格的转换和添加列上,也能实现自动化查询功能。那么接下来我就以拆分列功能为例,给你演示一下Power Query的转换功能是如何实现自动化查询的。
|
||||
|
||||
单元格的拆分
|
||||
|
||||
拆分列,是自动化查询转换功能时最常用到的选项。例如你在按日期汇总数据时,发现日期这列除了年月日,还包括时间信息,这样就没法按照相同的年月日进行合并,而必须要先对日期单元格进行拆分,拆分之后才能按照年月日这一列汇总数据。
|
||||
|
||||
还有,当你需要对销售人员的姓氏、名字分别处理时,也要按照字数对姓名单元格拆开,然后分别处理。
|
||||
|
||||
拆分的功能在Excel中也是可以实现的,但是Power Query能够在拆分之后,对新导入的文件也能通过点击刷新实现自动拆分。那么接下来我就教你怎样在Power Query里,使用拆分列功能来拆分日期时间和姓名,并在增加文件后实现自动更新。
|
||||
|
||||
首先,我们需要再次打开Power Query编辑器。你可以在Excel的数据选项卡,通过“获取数据”下拉列表,选择“启动Power Query编辑器”。
|
||||
|
||||
其次,你需要在Power Query编辑器,选中“转换”选项卡。通过选中“dt_订单日期”列,使用拆分列下拉列表的“按分隔符拆分”按钮,把分隔符改为空格后,再点击确定,这样就可以把订单日期拆分为两个新的列。
|
||||
|
||||
最后,删除“小时”这一列后,订单日期就实现了拆分功能。
|
||||
|
||||
你看,通过对不必要信息的拆分删除,就可以对列的内容进行自动化调整了。
|
||||
|
||||
这里我也再补充一点,除了“按分隔符拆分”外,你还可以按字的个数进行拆分。例如,我把销售代表的“姓名”进行拆分,你可以使用“按字符数拆分列”选项,如下图:
|
||||
|
||||
|
||||
|
||||
通过这张图就可以看到,我通过调整字符数和拆分次数,就把“姓”和“名字”进行了拆分。拆分后的结果如下图,供你参考。
|
||||
|
||||
|
||||
|
||||
总结来说,通过日期的拆分,你可以自动化移除字符串中不必要的内容。而通过对姓名的拆分,你可以在后续操作中分别对姓名进行处理。
|
||||
|
||||
不过,如果后续有了新的文件,那该怎么在当前执行结果上,再把新的文件进行自动拆分呢?
|
||||
|
||||
具体做法是:你可以在存放半年销售数据的文件夹中,直接放入下一个月的销售数据。只要原始的Excel文件格式和之前的6个文件相同,那么当你点击“刷新”按钮后,Power Query就会自动加载新的Excel文件,并自动把新的数据中的姓名、日期进行拆分。
|
||||
|
||||
这样就能实现自动查询的功能,不用重复执行获取数据和转换功能,也大大提高了数据的处理效率。
|
||||
|
||||
其他功能怎样学习
|
||||
|
||||
我利用了文件合并和单元格拆分,为你演示了Power Query的工作过程和常见功能。不过Power Query在自动化查询工作中,还能实现非常丰富的“转换”、“添加列”功能,他们分别在转换和添加列选项卡下,如果你需要掌握更多的功能,可以参考官方文档进行学习。
|
||||
|
||||
小结
|
||||
|
||||
在这节课中,我使用Power Query实现了多个文件的合并,以及单元格的拆分处理,为你演示了它的自动化查询功能。
|
||||
|
||||
如果你跟着操作下来,就会发现,Power Query使用了图形界面,比Python处理数据更直观。通过Power Query编辑器,你可以一边观察处理结果,一边调整处理的功能。它的自动化体现在增加新的输入源或新的列,通过“刷新”功能,都能自动化识别和按照执行过的步骤对新增内容进行自动化处理。
|
||||
|
||||
此外,Power Query除了使用界面外,还支持脚本语言,也叫M语言。通过M语言,Power Query可以实现功能更丰富的转换和添加列功能,M语言也有函数、判断和循环逻辑等脚本语言的语法,在你掌握Python之后,学习它就更加轻松了。
|
||||
|
||||
总结来说,Power Query弥补了工作中处理一次性需求的短板,它比Excel更自动化,比Python更简单,为自动化查询工作提供了高效的解决方案。
|
||||
|
||||
我把这节课用到的Excel作为附件放在百度云网盘里,你可以点击下载,提取码为supu。-
|
||||
合并文件到Excel.rar
|
||||
|
||||
思考题
|
||||
|
||||
最后,我给你留一道思考题:如何在Power Query编辑器中实现统计每个月的销售额,以及如何实现每种产品名称半年的销售额统计功能。
|
||||
|
||||
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。
|
||||
|
||||
|
||||
|
||||
|
209
专栏/Python自动化办公实战课/14VBA脚本编程:如何扩展Excel,实现文件的批量打印?.md
Normal file
209
专栏/Python自动化办公实战课/14VBA脚本编程:如何扩展Excel,实现文件的批量打印?.md
Normal file
@ -0,0 +1,209 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
14 VBA脚本编程:如何扩展Excel,实现文件的批量打印?
|
||||
你好,我是尹会生。
|
||||
|
||||
打印,是办公中必不可少的一步,比如在会议上,我们需要通过Excel表格向客户/领导展示工作成果。
|
||||
|
||||
但在使用Python对Excel进行打印的时候,我们还得给Python安装上Excel、Windows和硬件设备管理的库,过程极其复杂,远远达不到我们自动化办公的需要。尤其是面对类似的临时性需求,就更没必要使用Python了。
|
||||
|
||||
庆幸的是,Excel自带了打印功能,而我们可以利用Excel的扩展——“宏”来实现打印,并且通过VBA脚本增强“宏”的功能,从而实现批量打印,满足我们自动化办公的要求。
|
||||
|
||||
那么在今天这节课,我就带着你学习Excel的另一个自动化功能:“宏”和VBA脚本。
|
||||
|
||||
宏和VBA脚本的用途
|
||||
|
||||
宏是Excel自带的扩展功能,可以记录的内容包括对Excel格式和文字的修改,它会像录像机一样记录下你在Excel中的操作。当你有一系列的动作需要多次执行,并且每次执行动作的顺序又完全相同,就可以重新播放,把这些操作自动再执行一遍。所以对于办公中临时性的需求,使用宏要比掌握每个Excel操作对应的Python函数要更简单。
|
||||
|
||||
你可以使用宏的录制功能,把格式调整、复制粘贴、打印等重复操作记录下来,并保存成一个快捷键。当你需要重复执行这条流水线作业时,就可以通过执行快捷键实现自动化操作。
|
||||
|
||||
不过,虽然宏能够像录像机一样通过重放功能实现自动化,但是它默认情况下只能实现部分功能的录制,也就是半自动化,要想把另一部分也自动化,就要使用宏的底层实现VBA脚本,例如像批量打印这些需求,就需要VBA脚本的循环扩充宏来实现自动化。
|
||||
|
||||
所以我在这节课,还会带你学习宏的底层实现VBA脚本,利用VBA脚本可以扩展宏的功能,把手动操作部分实现自动化。
|
||||
|
||||
那么接下来,我就通过对Excel的任意一个工作表进行打印的案例,给你具体讲解一下宏的录制和执行。
|
||||
|
||||
如何实现Excel的批量打印
|
||||
|
||||
使用宏,实现单个工作表的打印
|
||||
|
||||
为了方便你学习,我准备了一个包含6个工作表的Excel文件,这6个工作表命名分别为sheet1-sheet6。要想基于宏实现自动化打印,需要通过这个步骤来实现:
|
||||
|
||||
|
||||
录制宏;
|
||||
手动执行一次操作;
|
||||
停止宏录制;
|
||||
通过快捷键执行宏。
|
||||
|
||||
|
||||
首先我先来带你看一下,怎么把打印的过程录制为宏。
|
||||
|
||||
录制宏,需要指定宏名称和快捷键。你需要在Excel的“视图”菜单栏找到“宏”下拉列表,在其中选择“录制宏”按钮,点击按钮就会弹出“录制宏”窗口。此时你需要把宏名改为“打印工作表”,再把快捷键改为Ctrl+Shift+P,点击确认之后,你接下来对Excel的操作就会被宏自动记录了。
|
||||
|
||||
这里以录制“打印sheet2工作表”这个操作为例,我把录制宏的窗口截图贴在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
录制前有两点需要你注意:
|
||||
|
||||
|
||||
快捷键如果和Excel默认的快捷键发生冲突, 那么默认的快捷键功能就会失效,因此在录制前,你在给宏指定快捷键的时候,应当避开默认快捷键。
|
||||
另一点需要注意的是,点击确定后宏就开始录制了,因此任何需要重复操作之外的操作步骤,都有可能会影响执行宏时的结果,所以我们在录制过程中应尽量减少不必要的操作。
|
||||
|
||||
|
||||
接下来,我需要手动执行一遍打印操作,并把操作过程录制为宏。具体操作步骤是:
|
||||
|
||||
|
||||
录制开始前先激活sheet2,以免把激活sheet2的步骤记录到打印过程。
|
||||
在录制宏窗口点击“确定”按钮,开始录制。
|
||||
选择文件-打印,为了让你能更好地观察到结果,我把打印机设置为打印到PDF,然后点击打印按钮。
|
||||
设置pdf的路径,并手动填入文件名sheet2.pdf。
|
||||
点击保存。
|
||||
|
||||
|
||||
第三步是停止宏的录制。在点击Excel左下角方形按钮后,就可以停止录制。停止之后,一个新的宏就录制完成了。
|
||||
|
||||
停止录制的截图我放在下方,可以帮你快速找到这一功能在Excel中的位置。
|
||||
|
||||
|
||||
|
||||
最后一步是执行宏。例如我需要把Sheet4打印成PDF,可以先激活Sheet4,并执行快捷键Ctrl+Shift+P,进行打印。
|
||||
|
||||
通过使用宏进行单个工作表打印操作,你会发现宏的优点和缺点。
|
||||
|
||||
它的优点是记录操作步骤的方式简单,尤其适合对Excel进行多次重复的格式和内容调整。而且掌握宏还不用学习Excel之外的编程技能,就能实现重复任务的自动化。
|
||||
|
||||
不过它的缺点也很明显,在使用宏之前,我们激活工作表和执行宏之后指定保存文件的名称,都需要手动操作。这就和我们使用Python自动化处理Excel是类似的,我们把自动化处理工作分成三个部分:为重复任务准备变量、为重复任务编写了一个for循环程序反复执行,为执行之后的结果自动保存结果。
|
||||
|
||||
可以看到,宏实现的就是for循环中的流水线操作。而对于使用宏之前以及使用宏之后的操作,我们是可以通过VBA脚本来进行优化,从而把相关操作实现自动化。
|
||||
|
||||
所以今天这节课,我们就再来学习一下VBA脚本。Excel的宏是基于VBA脚本实现的,如果你需要将打印多个工作表的手工操作也使用宏自动实现,需要通过VBA脚本来扩展宏。
|
||||
|
||||
使用VBA脚本的循环,打印多个工作表
|
||||
|
||||
接下来,我就教你怎样查看当前宏的VBA脚本,并通过新的VBA脚本来扩展当前的宏,从而实现工作表的自动化批量打印。
|
||||
|
||||
首先,我们需要查看当前宏的VBA脚本。我们可以使用视图-宏-查看宏按钮,选中要查看的宏,并点击右侧的编辑按钮,这样就可以在打开的VBA脚本编辑器窗口查看当前宏的VBA代码。
|
||||
|
||||
我把查看方法和代码都贴在下方,供你学习。
|
||||
|
||||
|
||||
|
||||
这个VBA脚本就是宏实现打印单个文件的全部代码,它由三部分组成,分别是Sub过程、注释、实现打印的语句。
|
||||
|
||||
|
||||
过程,是VBA代码完成一个任务的所有操作的集合。例如在上面这段代码中,实现打印任务的所有操作都被放在“Sub 打印工作表()”这一过程中,当你按快捷键Ctrl+Shift+P之后,Excel会按照Sub过程中的代码来运行。而我们要想实现自动打印到文件,就需要在Sub过程中扩展宏记录的打印任务。
|
||||
注释,是指用于向其他人描述“过程”实现的目的,注释的内容不会被VBA执行。它的格式是使用引号开头行,这一行都是注释的内容。这和Python中以#开头的行注释用法是相同的。
|
||||
实现打印的语句。代码中这两行就是实现打印的语句,我把代码从截图中单独拿出来供你参考。
|
||||
|
||||
|
||||
ActiveWindow.SelectedSheets.PrintOut Copies:=1, _ Collate:=True, IgnorePrintAreas:=False
|
||||
|
||||
|
||||
这段代码由三部分组成,这三部分分别是要操作的对象,对象的属性和方法,方法的参数。我们依次来看下:
|
||||
|
||||
第一部分,对象是指工作簿、工作表、单元格、图片、图表、透视表等Excel中的具体元素。在Sub过程中要对哪个元素做修改,就需要在过程中指定该对象。
|
||||
|
||||
例如代码中的“ActiveWindow代表了活动的Excel窗口对象,它意味着宏将要对当前活动的窗口做一些动作。
|
||||
|
||||
第二部分对象的属性和方法,指的是对象的一部分或一种行为。这里需要注意,属性和方法是有区别的。
|
||||
|
||||
|
||||
对象的属性用来描述对象的性质和特点。比如字体的颜色、字号等;
|
||||
对象的方法是指在对象上执行的某个动作,比如要移动、删除、打印这个对象。
|
||||
|
||||
|
||||
例如下面这一行代码:
|
||||
|
||||
ActiveWindow.SelectedSheets.PrintOut
|
||||
|
||||
|
||||
在这一行代码中,SelectedSheets是属性,它代表当前活动窗口下被选定的工作表,PrintOut是方法,它被ActiveWindow对象调用,表示将要执行打印这一行为。总结来说,这条语句的作用就是打印当前活动的Excel窗口下选定的工作表。
|
||||
|
||||
第三部分是PrintOut方法的三个参数,它的三个参数更改了打印的默认行为。我把这三个参数写在下面供你参考:
|
||||
|
||||
Copies:=1, Collate:=True, IgnorePrintAreas:=False
|
||||
|
||||
|
||||
|
||||
Copies参数指定打印份数为1份;
|
||||
Collate参数指定逐份打印;
|
||||
IgnorePrintAreas则忽略打印区域并打印整个对象。
|
||||
|
||||
|
||||
宏就是按照上面这两行VBA代码实现打印的。不过在你对一个新的工作表执行宏的时候,会发现,你不仅需要手动选择要打印的工作表,还需要手动指定要保存的工作表的文件名称,所以我们可以扩展宏的默认功能,让VBA脚本从半自动化到自动化。
|
||||
|
||||
具体怎么做呢?在宏打印工作表的VBA语句的基础之上,我们只需要增加遍历工作表和自动指定输出文件名这两个功能,就可以实现工作表的自动化批量打印。
|
||||
|
||||
因此,我要在Sub过程中改造打印方法,增加这两项功能,增加之后的代码如下:
|
||||
|
||||
Sub 打印工作表()
|
||||
'
|
||||
' 打印工作表 宏
|
||||
'
|
||||
' 快捷键: Ctrl+Shift+P
|
||||
|
||||
With Application.FileDialog(msoFileDialogFolderPicker)
|
||||
If .Show = -1 Then filepath = .SelectedItems(1) & "\"
|
||||
End With
|
||||
|
||||
For Each sht In ActiveWorkbook.Worksheets
|
||||
sht.Select
|
||||
ActiveWindow.SelectedSheets.PrintOut Copies:=1, _
|
||||
Collate:=True, IgnorePrintAreas:=False, _
|
||||
printtofile:=True, _
|
||||
prtofilename:=filepath & sht.Name & ".pdf"
|
||||
Next
|
||||
|
||||
End Sub
|
||||
|
||||
|
||||
|
||||
通过快捷键“Ctrl+Shit+P”再次运行宏,你会发现改造之后的代码与改造前相比,有这样两个区别。
|
||||
|
||||
第一个区别是,改造前我们需要手动点击一个工作表,让它处于激活状态。而改造之后,宏会自动依次选择每个工作表。
|
||||
|
||||
第二个区别是,改造前每次执行宏的时候,需要手动输入要打印的pdf文件名。而改造后,宏会弹出选择存放打印文件的目录。当你选择了一个目录之后,宏会自动把所有的工作表,均以“工作表名称.PDF”作为文件名进行打印。
|
||||
|
||||
可以看到,改造之后的代码比默认的宏效率更高了。接下来我就来详细解释一下我是怎么使用VBA脚本遍历工作表,以及怎么自动输出文件名的。
|
||||
|
||||
在代码的第11行,“For Each … In ”结构是VBA脚本的遍历功能,遍历的对象是“ActiveWorkbook.Worksheets”,这个对象表示当前激活的是工作簿中所有的工作表。那么我们把每个表存入sht后,就可以实现工作表的遍历操作了。
|
||||
|
||||
这里我还要再提醒你一下。VBA的遍历和Python的主要区别是,前者遍历需要使用Next语句结束,而Python是依靠缩进实现循环语句块结束的,这也是使用Python编写程序的同学经常会遗漏Next语句的地方。
|
||||
|
||||
再来看怎么实现自动打印PDF文件的功能。这一功能的实现由弹出保存文件夹的对话框和整合文件路径两部分组成。
|
||||
|
||||
第一部分是在代码的第7、8行,我使用了FileDialog对象,这个对象会弹出对话框让用户选择路径。同时,filepath 变量会得到用户选择的文件保存路径。
|
||||
|
||||
第二部分在代码的15、16行,我为PrintOut打印方法增加了两个参数。分别是:printtofile和prtofilename。
|
||||
|
||||
printtofile参数类似一个开关,使用这个参数的目的,是让打印函数由默认的弹出对话框让用户手动输入文件名改为“将对象打印到文件”。
|
||||
|
||||
另一个prtofilename参数指定了打印的对象将以什么文件名来保存打印结果。为了把保存的文件名设置为“路径+表名.pdf”的格式,我通过“&”符号连接了两个变量“filepath 、 sht.Name”和字符串 “.pdf”” ,这一就组成了“filepath & sht.Name & “.pdf””的文件命名形式。
|
||||
|
||||
在这里我还想强调一下FileDialog的代码位置,我把FileDialog对象写在遍历之前,是因为我不希望每次读取工作表的时候,都需要选择一次存放路径,这样会让批量打印再次变成需要手动指定路径后才能执行,因此我就把FileDialog对象的代码放在批量读取工作表之前,这样也会提高VBA脚本的自动化程度**。
|
||||
|
||||
通过VBA脚本增强了默认录制宏的功能,实现了批量打印工作表的功能。
|
||||
|
||||
小结
|
||||
|
||||
在今天这节课,我教你使用了“宏”这个强大的功能。通过宏的录制与回放,你可以实现Excel的自动化操作。此外,当有些操作没法被宏自动记录的时候,你还可以通过手动编写VBA脚本,来扩展宏默认的功能,让对工作表或单元格的批量操作从半自动化到自动化。
|
||||
|
||||
我在这节课中用批量打印的例子,给你展示了VBA的通过对话框指定保存路径、遍历工作表增强默认宏的代码,为你展示了迭代、判断和变量赋值,以及VBA中最重要的对象和操作对象的属性和方法。
|
||||
|
||||
需要说明的是,VBA支持Office的所有对象,通过对象的属性和方法,再配合VBA语法的判断循环,就能弥补默认宏的不足,实现几乎所有Office办公的自动化操作。
|
||||
|
||||
不过VBA支持的对象多达上百个,我在这节课中只给你介绍了其中一个对象,也就是激活工作表这个对象。通过VBA脚本的语法配合激活工作表对象,你可以掌握VBA自动化的基本流程。如果你想了解VBA支持的所有对象及其属性方法,可以参考官方文档获得更详细的介绍。当你用到哪个对象,从文档里搜索关键字,找到它即可。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,最后我要给你留一道思考题。你能否通过InStr函数(判断包含在字符串中的某个关键字是否存在,存在返回关键字位置,不存在返回0 参考)改造批量打印脚本,让脚本实现包含关键字“汇总”,然后再打印报表?
|
||||
|
||||
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。
|
||||
|
||||
|
||||
|
||||
|
230
专栏/Python自动化办公实战课/15PowerShell脚本:如何实现文件批量处理的自动化?.md
Normal file
230
专栏/Python自动化办公实战课/15PowerShell脚本:如何实现文件批量处理的自动化?.md
Normal file
@ -0,0 +1,230 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
15 PowerShell脚本:如何实现文件批量处理的自动化?
|
||||
你好,我是尹会生。
|
||||
|
||||
在工作中,会遇到和Windows操作系统紧密结合又需要批量操作的工作需求,比如文件的批量重命名,还有按照扩展名搜索文件。那么今天这节课,我将给你介绍一个主要应用于Windows操作系统自动化的脚本–PowerShell。
|
||||
|
||||
你肯定会有疑问,为什么我们不用Python,而是要另外学习PowerShell脚本呢?原因就在于,遇到这类工作需求,PowerShell脚本会比Python功能更强大,使用更方便,学习起来也更容易。
|
||||
|
||||
首先,Windows的所有操作,都有对应的PowerShell操作,可以达到办公自动化的目标。而使用Python,会因为Windows没有提供接口,有些功能就不能完全实现,或者有些操作还需要手动执行。
|
||||
|
||||
其次,PowerShell的语法简洁,比Python更加友好,降低了你阅读代码的难度。这一点你在这节课我讲解的例子中会有更深刻的体会。
|
||||
|
||||
最后,PowerShell在Windows上能做到开箱即用,安装完成后就可以正常运行了。而Python还需要安装解释器和配置环境。比如在金融和证券领域中,基于公司的规定,你可能无法安装操作系统之外的软件,这时候PowerShell的优势就体现出来了。
|
||||
|
||||
什么是PowerShell?
|
||||
|
||||
PowerShell是开源的终端命令解释器,之所以被称作解释器,是因为它会把用户输入的命令翻译给操作系统去执行,也能把操作系统执行的结果返回给用户。
|
||||
|
||||
作为PowerShell的初学者,你刚开始理解起来会有点难度,不过别担心,我举个例子你就明白了。
|
||||
|
||||
比如,在商场中的娃娃机,你需要通过对摇杆的控制来移动爪子的位置,抓取想要的礼物,因为你自己是没法直接移动爪子的。那么把你移动摇杆的操作翻译成爪子移动操作的功能,这就是解释器。
|
||||
|
||||
为了强化你对PowerShell的理解,我再把使用PowerShell前后的情况给你做个对比。
|
||||
|
||||
Windows操作系统在日常工作中,我们会采用图形界面(GUI)进行各类办公操作。在没有使用PowerShell的时候,如果我要想添加、删除、修改用户,就得点击各种按钮、选择下拉菜单、鼠标右键点击用户图标。
|
||||
|
||||
想象一下,如果我给100个新员工创建账户,创建一个账户需要5分钟,那仅仅在用户添加这一个操作上,我就需要花费500分钟,效率是不是很低?
|
||||
|
||||
但是利用PowerShell,就可以自动化完成用户添加这一重复性的操作了。
|
||||
|
||||
从Windows Server 2012版本(一听就是个很古老的操作系统版本)开始,所有的GUI操作就完全可以基于PowerShell实现了。这意味着,你能用GUI界面实现Windows操作系统中的所有功能。
|
||||
|
||||
你看,微软已经给了我们这么现成的自动化工具,所以我们可以放心大胆地把Windows操作系统上的重复操作,都交给PowerShell,提高工作效率。像这些和Windows操作系统紧密结合又需要批量操作的工作需求,都是PowerShell最适用的工作场景。
|
||||
|
||||
那么接下来,我就以Windows中最常用的文件管理为例,为你讲解一下PowerShell如何对文件进行批量处理。
|
||||
|
||||
怎样使用PowerShell实现文件批量处理?
|
||||
|
||||
在对文件的批量处理中,我们经常会遇到两类场景。
|
||||
|
||||
|
||||
文件夹下的文件按照某一规则批量重命名。比如,为了项目文件名称工整,需要批量给文件夹/文件按照某些规律进行重命名。
|
||||
将符合多个条件的扩展名从大量文件中找出来。比如,为了找到outlook里附件中的所有的压缩包,你需要使用搜索功能,一次性查找包含.zip .rar .7z多个扩展名的文件。
|
||||
|
||||
|
||||
接下来就以文件批量的重命名,来为你讲解一下用Powershell怎么实现。
|
||||
|
||||
一行代码,实现文件批量重命名
|
||||
|
||||
使用PowerShell脚本实现文件的批量重命名,我们只需要一行代码。不过在这一行代码中,需要你掌握的PowerShell的概念比较多。所以我会通过一个案例,帮助你在学习的同时也能迅速掌握、内化。
|
||||
|
||||
首先我通过一行代码创建了10个文本文件,创建的PowerShell脚本是:
|
||||
|
||||
foreach($num in (1..10)) { New-Item $num".txt" -type file }
|
||||
|
||||
|
||||
通过这行脚本,我在当前目录下创建了名字为“1.txt-10.txt”这样10个文件。由于批量改名操作也会用到类似的语法,所以我先给你详细讲解一下批量创建文件的脚本。这个脚本包含了循环、变量和命令三个部分。
|
||||
|
||||
第一个部分,是我在脚本中使用的foreach的循环功能。
|
||||
|
||||
循环的运行次数由in关键字后面的次数确定,这里我为了创建10个文本文件,就使用了“1..10”的形式来产生1到10这十个数字,这样的话,foreach就会执行10次,而每一次循环num变量都会得到一个新的数字,并在foreach循环体“{ }”中被New-Item项执行一次。
|
||||
|
||||
第二个需要你掌握的部分叫做自定义变量。
|
||||
|
||||
在PowerShell中,变量名都是以”\(”开头的,剩余字符可以是字母、数字和下划线等任意字符。并且PowerShell的变量名是不区分大小写的,比如“\)num”和“\(NUM”都表示相同的变量。我在代码中使用了“\)num”自定义变量来记录每次循环的数字。
|
||||
|
||||
第三个需要掌握的部分叫做命令。
|
||||
|
||||
PowerShell的命令有三种类型,例如New-Item叫做Cmdlet类型, foreach()叫做工作流类型,还有PowerShell内置的函数和自定义函数类型。这三种命令类型有一个共同的特点,就是都能被PowerShell执行。那么为了讲解方便,我在这节课中把这三种命令类型统称为“命令”。
|
||||
|
||||
在代码中,“New-Item”命令用来创建新的文本文件。我在创建时使用了两个参数,第一个参数“$num”.txt””用来指定文件名,文件名为num变量和字符串“.txt”连接后的名字。第二个参数“-type”用来指定创建的类型是文件(而非文件夹)。
|
||||
|
||||
PowerShell的脚本编写完成之后,接下来我们就需要运行它。
|
||||
|
||||
它的执行方式非常简单。在Windows中使用快捷键“win+R”可以弹出运行界面,在运行界面输入“powershell”之后再按回车,便会弹出PowerShell的交互界面,此时我们只需要把代码输入到交互界面就行了。
|
||||
|
||||
如果你希望脚本内容能多次运行,你就可以把脚本保存为以“ps1”作为扩展名的文件中。例如,我可以把创建10个文件的脚本保存到“newtxt.ps1”文件中,反复执行时,可以进入PowerShell交互界面,并输入“.\路径\newtxt.ps1”来运行它。
|
||||
|
||||
我把执行的命令和结果截图,贴在下方供你参考。
|
||||
|
||||
|
||||
|
||||
创建了10个文本文件后,我想把它们统一进行改名,新的名字是“new_数字_new.txt”,即在这10个文件的文件名前后,都增加“new”字符串,改名之后的格式如下图:
|
||||
|
||||
|
||||
|
||||
我把批量改名的代码从截图中单独拿出来,来重点讲解一下其中的dir命令、管道符和Rename-Item命令及其参数,因为PowerShell就是通过管道符将多个命令组合在一起,实现批量改名的。
|
||||
|
||||
dir *.txt | foreach { Rename-Item $_ -NewName ("new_"+$_.BaseName+"_new.txt") }
|
||||
|
||||
|
||||
|
||||
我来带你按照代码的执行顺序,从左到右,依次讲解这行代码的主要实现逻辑。
|
||||
|
||||
首先,这行代码最左侧的dir命令,它的功能是在终端输出当前目录下所有文件和目录的名称。
|
||||
|
||||
如果你要操作的文件没有在当前目录,可以使用“cd 要访问的目录”方式进入该目录下,再使用“dir”命令查看。为了更改文本文件的名称,我需要先通过“dir *.txt”来获得当前目录下所有以“.txt”结尾的文件和目录。
|
||||
|
||||
接下来是这行代码中出现的“|”,被称作管道符。就像它的名字一样,用一条管道把两个程序连接在一起。它的作用主要有两个:
|
||||
|
||||
|
||||
连接两条命令,PowerShell会从左到右依次执行。如果左侧程序没有执行完成,右侧程序则会进入阻塞状态,等待左侧程序执行完成后再执行。
|
||||
把左侧命令的执行(输出)结果,通过管道,作为右侧程序的输入。在这行代码中,“dir”命令的输出,会通过管道符成为“Rename-Item”命令的输入,再通过foreach循环,实现对每个文本文件进行重命名的操作。
|
||||
|
||||
|
||||
最后,我们来学习一下“Rename-Item”命令和它的参数。和“New-Item”一样,“Rename-Item”也是PowerShell独有的Cmdlet类型命令,它的功能是实现文件的改名。
|
||||
|
||||
PowerShell中一共有9个与“Item”相关的Cmdlet,你可以使用下面的命令获得所有的命令和它的帮助。
|
||||
|
||||
PS> Get-Command -Noun Item
|
||||
|
||||
CommandType Name Definition
|
||||
----------- ---- ----------
|
||||
Cmdlet Clear-Item Clear-Item [-Path] <String[]...
|
||||
Cmdlet Copy-Item Copy-Item [-Path] <String[]>...
|
||||
Cmdlet Get-Item Get-Item [-Path] <String[]> ...
|
||||
Cmdlet Invoke-Item Invoke-Item [-Path] <String[...
|
||||
Cmdlet Move-Item Move-Item [-Path] <String[]>...
|
||||
Cmdlet New-Item New-Item [-Path] <String[]> ...
|
||||
Cmdlet Remove-Item Remove-Item [-Path] <String[...
|
||||
Cmdlet Rename-Item Rename-Item [-Path] <String>...
|
||||
Cmdlet Set-Item Set-Item [-Path] <String[]> ...
|
||||
|
||||
|
||||
“Item”被官方文档翻译为“项”,你可以和我一样,把它们理解为要操作的对象,通过上面这些Cmdlet命令,就可以实现这些“项”的增删改查。当你需要改名,就可以用“Rename-Item” Cmdlet来实现。
|
||||
|
||||
我们了解了“Rename-Item”命令的功能后,再继续学习它的参数格式和内容。它的参数格式是: “Rename-Item 旧的文件名 -NewName 新的文件名”。我在代码中使用了这样一行命令对文件名称进行了修改:
|
||||
|
||||
Rename-Item $_ -NewName ("new_"+$_.BaseName+"_new.txt")
|
||||
|
||||
|
||||
在“Rename-Item”命令的参数中,你需要关注“$_”这个内置变量。它的功能是表示当前对象。在我们的例子中,它表示foreach每次循环时,通过“|”传入的文件名称。
|
||||
|
||||
不过当你需要改名时,还有另外一种方法,那就是使用“\(_.BaseName”来获得文件(不包含扩展名的)基本名称,以及通过“\)_.extension”来获得扩展名。例如:“10.txt”文件的基本名称是“10”,扩展名是“.txt”。当我把“10.txt”赋值给“\(_”之后,就可以使用“\)_.BaseName”取得基本名称“10”,使用“$_.extension”取得扩展名“.txt”。
|
||||
|
||||
在“Rename-Item”命令的参数“(“new_”+$_.BaseName+”_new.txt”)”,这部分代码中,除了提取了“10.txt”的基本名称“10”之外,我还利用“+”符号把”new”字符串和基本名称“10”连接,形成新的文件名“new_10_new.txt”字符串。也就是说,把将、新的字符串作为“Rename-Item”的参数,就实现了文件重命名。
|
||||
|
||||
在这里有一点需要你注意,“\(\_”从PowerShell3.0版本开始,就可以使用“\)PSItem”
|
||||
|
||||
替代“\(_”,虽然“\)PSItem”在阅读上更加友好,但“\(_”能够向后兼容,而且输入的内容更少。所以我会更推荐你使用“\)_”。
|
||||
|
||||
通过对批量改名代码的例子与分析,你会发现虽然只有一行代码,但是其中包含了内置变量、循环、管道和重命名命令等功能的组合,PowerShell就是这样一种简洁而功能强大的脚本语言。
|
||||
|
||||
为了让你能对PowerShell更加得心应手,我再为你讲解一个利用同样逻辑,就可以实现的场景:从一个文件夹找到.zip和.rar扩展名的文件,带你来一起看一下怎么使用PowerShell一行命令,来按扩展名搜索文件。
|
||||
|
||||
按扩展名搜索文件怎么实现
|
||||
|
||||
当工作时间久了,你会发现自己的Outlook文件夹里有海量的附件,特别是压缩包占用空间非常大。如果我们逐一搜索,就会像大海捞针一样,找到自己想要的文件非常困难。那么此时,我们就可以按照扩展名通过一行脚本将它们全部找出来进行备份或整理到新的文件夹中。
|
||||
|
||||
要想实现按扩展名搜索文件,我们需要继续利用“dir”命令来取得当前目录下所有文件,以及利用“$_.extension”来取得文件的扩展名。
|
||||
|
||||
所以我首先使用如下命令,来获取当前目录下的扩展名都有哪些,便于接下来的搜索和查找。
|
||||
|
||||
dir | foreach{$_.extension} | Get-Unique
|
||||
|
||||
执行结果
|
||||
.txt
|
||||
.zip
|
||||
.rar
|
||||
|
||||
|
||||
这行代码会把当前目录下所有文件的扩展名显示出来,为了避免重复的扩展名会影响我后续的脚本编写,那么在这里,我使用了“Get-Unique”命令,把相同的扩展名去重,只保留一个。
|
||||
|
||||
代码的执行过程依然是从左向右依次执行。
|
||||
|
||||
|
||||
首先,通过“dir”命令获取当前目录下所有的文件名称,把所有的文件名称作为“foreach”循环的输入,赋值给“$_”。
|
||||
然后再通过“$_.extension”取得每个文件的扩展名,之后进行输出,再次通过管道符,作为“Get-Unique”命令的输入。
|
||||
最后,“Get-Unique”会把所有扩展名作为输入处理,输出不重复的扩展名。
|
||||
|
||||
|
||||
通过观察所有扩展名的脚本执行结果,确定要搜索的目录中,包含了我们需要的“.zip”和“.rar”之后,我们再取出符合扩展名的文件名称。为了实现这一功能,我们还需要使用一个新的命令:“Where-Object”。
|
||||
|
||||
“Where-Object”也是Cmdlet类型的命令,它的用法是对管道中的每个对象进行筛选,把不符合条件的对象删除。那怎么判断管道中的对象是否符合条件呢?依据就是“Where-Object”命令后面“{}”中的参数。
|
||||
|
||||
“{}”中可以使用命令、也可以使用比较运算符。如果“{}”中使用命令,且命令执行结果为True,则“Where-Object”会保留对象进行输出或传入后续的管道,如果命令结果为False,则删除对象。
|
||||
|
||||
此外,如果“{}”中使用了比较运算符,就根据比较运算符的结果对对象进行相应操作。同命令结果一样,如果为True,则保留对象;如果为False,则删除对象。
|
||||
|
||||
比较运算符一共有10个,它是以“-”开头,跟着运算符名称,运算符的名称是大于、等于、小于的首字母缩写,我把比较运算符整理成表格,供你参考:
|
||||
|
||||
|
||||
|
||||
为了便于你理解比较运算符,我通过一个例子,来给你讲解怎么搜索“.zip”扩展名的文件。
|
||||
|
||||
dir | Where-Object{ $_.extension -eq ".zip" }
|
||||
|
||||
|
||||
在这段代码中,我使用了“-eq”比较运算符,实现了文件扩展名是否和“.zip”字符串相等的判断。
|
||||
|
||||
脚本通过dir命令可以获得当前目录下所有的文件,再通过“Where-Object”命令,依次对管道中的对象进行判断。如果扩展名为“.zip”则比较的结果为True,“Where-Object”命令执行完成后,PowerShell会在终端显示该文件名称,否则就会删除该对象,不在终端进行显示。
|
||||
|
||||
如果你还需要对文件大小、文件名称相似性等其他方式比较,可以参考比较运算符表格进行实现。
|
||||
|
||||
我们实现了单个扩展名的搜索之后,再来让这一行脚本的功能继续增强,让它能搜索多个扩展名。
|
||||
|
||||
为了同时搜索到“.zip”和“.rar”文件,我们需要引入逻辑运算符。在“Where-Object”命令的参数中,使用逻辑运算符,然后根据它两侧的执行结果是否为True,实现对扩展名的组合判断。例如下面的脚本:
|
||||
|
||||
dir | Where-Object{ ($_.extension -eq ".zip" ) -or ($_.extension -eq ".rar" ) }
|
||||
|
||||
|
||||
这行代码,过滤了管道中文件的扩展名是否包含“.zip”或者包含“.rar”。我在代码中使用的“-or”逻辑运算符的含义是,它的两侧扩展名和“.zip”、“.rar”任意一个相等,返回结果就是True,“Where-Object”命令就会把文件名称打印到屏幕上,如果返回的结果为False,对象仍然会被删除掉。
|
||||
|
||||
标准逻辑运算符我也为你整理了一个表格,你可以参考。
|
||||
|
||||
|
||||
|
||||
总结来说,通过增加逻辑运算符,我们就能实现对多个扩展名的搜索功能。
|
||||
|
||||
小结
|
||||
|
||||
最后我来为你做个总结,我通过批量改名和按多个扩展名搜索文件这两个例子,为你展示了PowerShell脚本的命令和管道。
|
||||
|
||||
命令中的Cmdlet类型能够支持Windows中所有的对象,其中对文件操作最常用的四个Cmdlet类型命令是:New-Item、Rename-Item、Where-Object和Get-Unique。它们也是我在这节课中为你着重介绍的命令。在PowerShell中,你掌握的Cmdlet越多,能够实现的功能就越强大,因此,我把官方文档地址提供给你,你可以通过扩展学习,掌握更多的Cmdlet命令。
|
||||
|
||||
此外,命令中的工作流类型可以实现循环,我在这节课给你介绍了foreach循环,通过foreach就可以实现批量操作,优化办公效率。
|
||||
|
||||
不过在你掌握了足够多的命令后,还需要掌握管道符。因为通过管道符能够连接命令,让命令可以按顺序执行,而通过对命令的组合,就可以实现多个命名的自动化运行,今儿实现Windows操作系统相关操作的自动化。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题,你能否通过官方文档查找到删除项的命令,并实现“.txt”扩展名文件的批量删除?请你大胆尝试一下。
|
||||
|
||||
|
||||
|
||||
|
278
专栏/Python自动化办公实战课/16循环与文件目录管理:如何实现文件的批量重命名?.md
Normal file
278
专栏/Python自动化办公实战课/16循环与文件目录管理:如何实现文件的批量重命名?.md
Normal file
@ -0,0 +1,278 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
16 循环与文件目录管理:如何实现文件的批量重命名?
|
||||
你好,我是尹会生。
|
||||
|
||||
作为产品经理/运营,你经常需要做竞品调研,来跟自家产品对比优劣。这时,你就需要下载大量与该类产品相关的图片,并进行整理归类。而归类操作中,必须要走的一步就是对这些图片进行重命名。
|
||||
|
||||
还有你在搜集资料时,往往会从网络中下载大量不同格式的文件,比如电子书、视频、音频等,为了方便归纳整理,你也需要对文件进行重命名。
|
||||
|
||||
通过我例举的这两个场景,你应该发现了,这些需求都是把批量改名和网络功能结合,而且还需要Mac和Windows系统支持重命名。那怎么来实现批量重命名的操作呢?
|
||||
|
||||
如果你还记得上节课的内容,肯定会说,使用PowerShell就可以了。不过你要是对PowerShell相关知识掌握得扎实的话,也会记得我说过,PowerShell适合在Windows平台下独立运行的临时性任务。所以在非Windows系统,以及需要和网络下载功能结合的需求上,它就没有Python的兼容性好。
|
||||
|
||||
那么今天这节课,我会带你回到Python,使用Python来实现文件的批量重命名。
|
||||
|
||||
如何使用os库实现文件批量重命名
|
||||
|
||||
还是老规矩,我们学习一个新的功能,首先要学习它所需要的Python库和函数。
|
||||
|
||||
例如,我把友商的产品介绍图片,通过网络批量下载后,存放在“/Users/user1/Desktop/pic”文件夹中,这些文件名称长短不一,那我希望能从数字1开始,为它们批量重命名。
|
||||
|
||||
在第15节课我就说了,重命名是一种贴近操作系统层面的操作。因此在Python标准库中,我把“文件”和“操作系统”作为关键字,从官方文档中找到了“os”库,os库中包含了操作系统相关的操作。通过“os”库,你就可以轻松取得三个批量重命名必备操作,那就是目录中的文件名、文件名后缀处理以及文件改名。
|
||||
|
||||
那我先把批量重命名的代码提供给你,让你更直观地理解运行过程,然后再来帮你分析这三个操作是怎么通过os库实现的。
|
||||
|
||||
import os
|
||||
# 保存图片的目录
|
||||
file_path = "/Users/user1/Desktop/pic"
|
||||
# 需要批量重命名的扩展名
|
||||
old_ext = ".jpg"
|
||||
# 取得指定文件夹下的文件列表
|
||||
old_names = os.listdir(file_path)
|
||||
# 新文件名称从1开始
|
||||
new_name = 1
|
||||
|
||||
# 取得所有的文件名
|
||||
for old_name in old_names:
|
||||
|
||||
# 根据扩展名,判断文件是否需要改名
|
||||
if old_name.endswith(old_ext):
|
||||
|
||||
# 完整的文件路径
|
||||
old_path = os.path.join(file_path, old_name)
|
||||
|
||||
# 新的文件名
|
||||
new_path = os.path.join(file_path, str(new_name)+".JPG")
|
||||
|
||||
# 重命名
|
||||
os.rename(old_path, new_path)
|
||||
|
||||
# 文件名数字加1
|
||||
new_name = int(new_name)+1
|
||||
|
||||
# 显示改名后的结果
|
||||
print(os.listdir(file_path))
|
||||
|
||||
|
||||
|
||||
这段代码会把”/Users/user1/Desktop/pic”目录中“jpg”扩展名的文件进行重命名,把它们分别命名为“1.jpg”,“2.jpg”,“3.jpg”,以此类推。
|
||||
|
||||
那批量改名的这三个操作具体是怎么实现的呢?其实是通过os库中的三个函数来实现的,分别是listdir()、path.join() 、rename()。这是你在这节课要掌握的重点,我也会着重讲解。
|
||||
|
||||
第一个是listdir()函数,它的功能是打印指定目录下的文件名称。如果再给这个函数指定一个参数file_path,那么它会把file_path中的所有文件名称以一个列表的类型返回。使用列表类型方便后续迭代,便于进行单个文件改名。
|
||||
|
||||
为了只对“.jpg”扩展名的文件改名,我使用了endswith()函数对列表进一步筛选,过滤掉不需要改名的文件。
|
||||
|
||||
第二个是path.join()函数,它可以连接路径和文件名,从而得到一个带有完整路径的文件名称格式。这里我要给你着重强调一下,改名操作必须要指定文件正确的路径,因此改名前必须要进行路径和文件名的连接。
|
||||
|
||||
最后是改名函数rename()函数,它的两个参数分别为改名前文件的路径+文件名和改名后文件的路径+文件名,均为path.join()函数处理过的完整文件名称。通过rename()函数改名后,文件名称会自动变为新的文件名称。
|
||||
|
||||
将上面三个操作放入for循环语句中,就能实现批量重命名的功能。
|
||||
|
||||
这一段代码虽然功能正常,但是继续增加新功能时,必然要再增加新的代码,如果只是按照执行的前后顺序把多个不同功能的代码写入一个文件,它的可读性会变差,别人阅读你写的代码就会有障碍。
|
||||
|
||||
就像我们从超市购买的各类商品一样,你一定会把调料放在厨房、把鸡蛋放在冰箱、把袜子放在衣柜,对商品分门别类放置,绝不会按照购买的时间顺序摆放在你的房间中。
|
||||
|
||||
同理,代码的摆放位置,也不应该按照执行顺序依次存放。正确的做法是,你要把每一组功能相同或相近的代码写入到一个函数中,并把该功能中经常变动的部分作为函数的参数,乃至整个脚本的参数,这样才能给有多个功能的脚本带来更好的阅读体验。代码的整洁程度高,也为你排除代码的故障带来更高的效率。
|
||||
|
||||
那么接下来,我就教你怎么重构批量改名脚本,提高代码的可读性。
|
||||
|
||||
重构程序
|
||||
|
||||
首先,我来带你学习一下什么是重构代码,以及怎么重构代码。
|
||||
|
||||
重构代码是指在代码可以正常实现的前提下,为了提高它的可维护性,需要对代码的结构进一步调整。就像你需要定期收拾房间一样,代码也需要进行维护。特别是经常修改和添加新的功能的代码,它的逻辑结构会像你炒菜之后的厨房一样,越来越混乱,为了代码和代码之间的逻辑关系更清晰,你需要掌握如何调整代码的结构。
|
||||
|
||||
我来给你举个例子,比如我在批量改名的程序中又增加了新的需求,要求将改名的路径和扩展名从变量赋值改为从命令行参数赋值。这样就不用进入到脚本中修改代码了。
|
||||
|
||||
根据这个新的需求,你会发现,当前的代码有3个地方需要调整。
|
||||
|
||||
第一个是代码的结构层次需要调整。当前的代码只包含了一个批量改名的功能,当你再为代码增加命令行参数处理功能时,新的代码和当前代码放在一起,会破坏改名功能的完整性,这时候,你就可以把每个功能单独放在一个函数中,通过函数来让一个功能和另一个功能代码相互独立。
|
||||
|
||||
第二个是代码开始执行的位置需要调整。由于函数定义的代码块会在函数调用以后才运行,但是根据Python的语法,你必须将函数定义写在函数调用的上方,这就导致了代码开始执行的位置出现在文件的中间和结尾。所以我需要一个更明显的标记,告诉阅读代码的人,从该位置开始阅读代码,该位置才是代码执行的第一条语句,而不是让阅读的人从代码文件开头一行以后的找程序的入口。
|
||||
|
||||
第三个是命令行参数的处理需要调整。由于Python默认是不去处理命令行的参数的,因此我们需要增加一个专门处理命令行参数的函数,来读取用户输入的正确参数,而对错误的参数,则需要提示用户。
|
||||
|
||||
这三个地方的调整,我会依次采用函数、内置变量和命令行参数来实现对代码的重构,我来依次带你看一下优化的具体代码。
|
||||
|
||||
封装到函数
|
||||
|
||||
为了让代码结构逻辑更加工整,我把每一个独立的功能都放入到单独的函数中。每个函数组成的语句块,就像自然段一样,将一整篇文章,按照功能进行了划分。由于当前的代码只有批量改名这一个功能,所以我就把改名功能的所有代码都放到一个函数当中。
|
||||
|
||||
封装函数的时候,一个是要考虑功能的完整性,另一个要考虑函数用到的其他变量如何与它进行交互。调用函数时使用参数,就是函数和其他变量交互最好的办法。
|
||||
|
||||
对于批量改名这一功能,主要交互的对象有两个,它们是批量改名的路径,以及要修改的文件扩展名。所以我就把这两个对象作为改名函数rename()函数的参数,rename()函数得到这两个参数后,会按照函数的定义,把这两个参数传入rename()函数中实现改名的逻辑,对文件进行批量改名。封装之后的核心代码如下:
|
||||
|
||||
def rename(file_path, old_ext):
|
||||
# 批量改名的代码
|
||||
... ...
|
||||
|
||||
rename("/Users/user1/Desktop/pic", ".jpg")
|
||||
|
||||
|
||||
|
||||
这段代码实现的功能和没有重构之前完全相同,都是对指定目录的指定扩展名文件进行批量重命名。但是在代码结构上,要比直接在文件实现的代码逻辑更清晰,可以看到,改名功能被放在函数定义中,执行的时候就可以直接调用rename()函数。
|
||||
|
||||
将改名功能封装为函数的好处就是,代码更工整了,新的功能也可以继续采用函数的形式添加到当前代码中。比起把所有代码按执行顺序都写在一个文件中,这样的格式会让你更容易区分开代码中的每一个功能。
|
||||
|
||||
明确执行位置
|
||||
|
||||
把批量改名的功能封装为函数之后,对程序的执行顺序也会带来一些变化。我把前后变化给你做个对比:
|
||||
|
||||
|
||||
封装函数之前,程序的执行顺序是导入库之后依次执行。
|
||||
封装为函数之后,执行顺序就变为导入库之后,就开始执行rename()函数的调用。
|
||||
|
||||
|
||||
当这个脚本再陆续添加新的函数的话,那么找到哪一行是脚本第一个执行的命令,就非常麻烦了。因此在Python中有一个参考C语言设置代码入口的方法,让你能快速定位代码是从哪一行开始执行的。这个方法就是通过对内置变量“name”的值进行判断,判断它是不是和字符串“main” 相等。
|
||||
|
||||
在Python中,执行代码的方式有两种。
|
||||
|
||||
|
||||
一种是单独运行,也就是用Python加脚本的名称方式运行。
|
||||
另一种方式是把.py结尾的脚本文件作为自定义的模块使用“import”关键字导入,导入后通过“模块.函数()”的格式运行。
|
||||
|
||||
|
||||
如果一个脚本文件独立运行,那么它的内置变量“name”的值就是“main”,通过“if name == “main“” 的判断,结果必然为True,则该判断逻辑下的代码块就会执行。如果作为模块导入,那么“name”的值就是False,则不被执行。
|
||||
|
||||
我们可以把函数的调用全部放入 “if name == “main“”语句块中,这样就可以指定这条if语句作为代码单独运行的入口,既方便你快速找到入口对程序进行修改,又方便你把它作为其他程序的模块进行导入。
|
||||
|
||||
我把实现对“name”变量判断的脚本写在下方,你可以对照代码学习。
|
||||
|
||||
def rename():
|
||||
... ...
|
||||
def func1():
|
||||
... ...
|
||||
def func2():
|
||||
... ...
|
||||
def func3():
|
||||
... ...
|
||||
|
||||
# func1() # 在__name__之外执行,不推荐
|
||||
|
||||
if __name__ == "__main__":
|
||||
func3()
|
||||
rename("/Users/edz/Desktop/pic", ".jpg")
|
||||
func1()
|
||||
func2()
|
||||
|
||||
|
||||
在代码中,我定义了4个函数,对于四个函数的调用,都放在了 “if name == “main“”语句块中。在使用这种方式设置程序入口时,有两点需要你特别注意。
|
||||
|
||||
一方面,这种设置方法是人为指定程序入口,因此你需要把代码中所有函数调用都放在if语句块下,这样才能实现作为入口的功能。虽然放在if语句块之外也可以运行,但函数调用写在if语句块之外,就很容易给代码阅读带来障碍。
|
||||
|
||||
另一方面,使用“name” 作为入口的判断变量,只能在单独运行的时候才为”main“,如果使用Python交互方式执行,就无法对“name” 变量进行判断。
|
||||
|
||||
我们通过指定代码的入口,让程序的逻辑更加清晰。那么接下来就是为这段代码添加命令行参数,在不修改代码的前提下,通过命令行参数来设置批量改名的目录和扩展名。
|
||||
|
||||
命令行参数处理
|
||||
|
||||
使用命令行参数的优点,就是在调用脚本的时候一并传入要操作的对象,这会比修改配置文件和变量更直接。那么在原有代码基础上,我们还需要增加两个参数,也就是要操作的目录和扩展名,并使用argparse库实现对这两个参数的处理。
|
||||
|
||||
参数处理是一个比较笼统的概念,它包括参数的接收、参数数量的判断和参数的解析三个部分。“argparse”库是命令行解析模块,它负责在脚本运行时,接收和处理脚本执行时的参数。
|
||||
|
||||
首先是参数的接收,在本讲之前,我们执行Python脚本的方式是:
|
||||
|
||||
python3 脚本名称.py
|
||||
|
||||
|
||||
在脚本中使用“argparse”库后,脚本能够支持在该命令后面增加参数,并在脚本内获取参数的内容。哪些参数能够被脚本处理,需要使用argparse库的add_argument()函数指定。
|
||||
|
||||
接下来是参数的判断,add_argument()函数可以接收两种参数格式,分别是“-”和“–”,后面再跟着英文。按照惯例,一个“-”一般后面会使用单个英文字母, 两个“–”后面是完整名称。
|
||||
|
||||
以对目录改名的参数为例,我需要接收“-p”或“–path”两种形式的参数指定的方法是:
|
||||
|
||||
add_argument("-p", "--path", required=True, help="path to rename")
|
||||
|
||||
|
||||
同时,我还为“–path”参数所在的add_argument()增加了两个额外的参数,一个是要求用户执行程序,必须输入“-p”或“–path”,如果执行不指定会报错的required参数。
|
||||
|
||||
另一个“-p”或“–path”参数含义的帮助信息“help”参数。
|
||||
|
||||
增加参数处理后,如果你没有输入完整参数,argparse库会自报错,并提示你如何正确使用该脚本的参数。你也可以直接使用“-h”得到执行帮助。
|
||||
|
||||
我把参数输入不完整和通过-h获取帮助的执行结果,贴在下面供你学习。
|
||||
|
||||
SHELL$ python3 rename_v2.py -p /path/to/rename/files -e
|
||||
usage: rename_v2.py [-h] -p PATH -e EXT
|
||||
rename_v2.py: error: argument -e/--ext: expected one argument
|
||||
|
||||
SHELL$ python3 rename_v2.py -h
|
||||
usage: rename_v2.py [-h] -p PATH -e EXT
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-p PATH, --path PATH path to rename
|
||||
-e EXT, --ext EXT files name extension, eg: jpg
|
||||
|
||||
|
||||
最后是参数的解析,它是在参数数量正确的前提下自动完成的。完成解析后,会以“–path”参数后的英文字面“path”作为属性名称,以“–path”后面的参数,作为属性值。
|
||||
|
||||
比如我在取得用户参数后,就可以使用“args.path”来得到命令行“-p”参数后面参数的值,以及使用“args.ext”得到“-e”参数后面参数的值。此外,我还把这两个属性作为批量改名函数rename()函数的参数,这样就可以把命令行参数作为重命名函数的参数使用了。
|
||||
|
||||
获取命令行参数的核心代码我也为你整理了出来,放在下方供你参考:
|
||||
|
||||
import os
|
||||
import argparse
|
||||
|
||||
def rename(file_path, old_ext):
|
||||
"""批量改名函数"""
|
||||
... ...
|
||||
def args_opt():
|
||||
"""获取命令行参数函数"""
|
||||
|
||||
#定义参数对象
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
# 增加参数选项、是否必须、帮助信息
|
||||
parser.add_argument("-p", "--path", required=True, help="path to rename")
|
||||
parser.add_argument("-e", "--ext", required=True, help="files name extension, eg: jpg")
|
||||
|
||||
# 返回取得的所有参数
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# args 对象包含所有参数,属性是命令行参数的完整名称
|
||||
args = args_opt()
|
||||
|
||||
# 调用重命名函数,将命令行参数作为重命名函数的参数
|
||||
rename(args.path, "."+args.ext)
|
||||
|
||||
# 输出改名之后的结果
|
||||
print(os.listdir(args.path))
|
||||
|
||||
|
||||
通过重构后,代码的整体结构就变成了导入库、函数定义、函数调用三个部分,对经常需要变动的替换路径和扩展名,也从修改变量改为命令行参数,无论从阅读代码还是后续继续扩展代码,整体结构都要比顺序执行代码逻辑更清晰。
|
||||
|
||||
小结
|
||||
|
||||
最后让我来为你总结一下,这一讲我为你讲解了使用Python如何实现批量改名,以及如何对越写越长的代码进行重构。
|
||||
|
||||
批量改名属于操作系统中的文件相关操作,这类功能在编程语言中往往会提供事先定义好的编程接口,无需自己实现从应用层到操作系统的全部代码,建议你在遇到这类需求时,先从标准库中搜索相关模块,再从第三方库搜索,尽量避免手工编写,提高工作效率。
|
||||
|
||||
为了让批量改名的脚本逻辑更清晰,也更方便执行,我对代码还进行了三个方面的重构:
|
||||
|
||||
|
||||
通过使用函数增加代码的逻辑性。
|
||||
通过“name”变量增加了程序入口,便于你直接找到程序开始执行的位置。
|
||||
通过增加命令行参数,让你不用修改代码,就能实现函数的参数的修改。
|
||||
|
||||
|
||||
增加程序的可读性、提高执行便利性也能为以后编写代码效率提升带来改进,这些改进会在后续章节代码越来越多的时候起到更明显的效果。
|
||||
|
||||
我把这节课的相关代码都放在了GitHub上,供你学习参考。
|
||||
|
||||
思考题
|
||||
|
||||
最后我来为你留一道比较有趣的思考题,你能否通过命令行为代码指定两个参数,当这两个参数为整数时,脚本自动计算这两个参数的“和”和“差”,并将执行结果打印到屏幕上。
|
||||
|
||||
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,然后扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
261
专栏/Python自动化办公实战课/17不同操作系统下,如何通过网络同步文件?.md
Normal file
261
专栏/Python自动化办公实战课/17不同操作系统下,如何通过网络同步文件?.md
Normal file
@ -0,0 +1,261 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
17 不同操作系统下,如何通过网络同步文件?
|
||||
你好,我是尹会生。
|
||||
|
||||
你有没有过这种经历:慌慌张张地去会议室开会,突然发现自己需要的文件却在工位的台式电脑中。因为文件比较大大,通过互联网下载需要很长时间。如果用网盘来中转放在两个电脑上的文件,传输速度又很慢。
|
||||
|
||||
我猜这个时候你就会想:如果能用一台电脑实现文件的上传和下载,那文件传输是不是就非常方便快速了。
|
||||
|
||||
这当然是可以实现的。所以今天,我就教你怎样用Python基于HTTP协议,来实现跨操作系统的文件上传和下载功能。
|
||||
|
||||
今天要学习的代码都不长,比较精简,又和文字相得益彰,所以学起来也不会太累。那接下来就跟着我的思路走,我们先来了解一下使用Python来实现文件上传和下载的优势。
|
||||
|
||||
为什么要使用Python?
|
||||
|
||||
实现文件下载的软件,相信你也用过很多,比如微信、QQ。用这些软件来传输文件,不仅要连接互联网,而且还有文件大小的限制。那如果用商业软件呢?传输虽然方便,但是就不能跨操作系统,而且还有可能需要付费。
|
||||
|
||||
所以综合下来,既要免费,又要传输快、没有大小限制的实现方式有没有呢?聪慧如你,一定知道接下来我会开始讲怎么用Python来实现文件的上传和下载功能了。别着急,我还要再唠叨几句关于用Python来实现的优势,这样你会学得更有劲儿。
|
||||
|
||||
首先,用法简单。只要一行代码,就能实现文件的浏览和下载功能。
|
||||
|
||||
其次,跨操作系统,适用范围广。只要安装了Python默认支持的文件下载需要的模块,那么在Windows、macOS、Linux上就都能用。
|
||||
|
||||
最后,传输速度快。和网盘、微信不同,Python的文件下载功能,是基于局域网通信的,不需要通过互联网中转,所以也就没有了传输速度和文件大小的限制。
|
||||
|
||||
知道了用Python来实现的优势,那接下来我们就进入正题。我会结合代码,来给你讲解用Python怎么实现文件的浏览和下载功能。代码不会很长,所以你学起来也不会很累。
|
||||
|
||||
一行代码,实现文件的浏览和下载
|
||||
|
||||
使用Python实现文件的浏览和下载,只需要一行代码。如下:
|
||||
|
||||
python3 -m http.server 8080
|
||||
|
||||
|
||||
通过在命令行运行这行代码之后,就能通过“http://你的IP地址:8080”浏览和下载文件了。
|
||||
|
||||
这行代码很简单,但你要仔细看的话,会发现这行代码的执行过程和我们之前执行脚本的过程有比较大的差别,而且通过“http.server”模块的加载就直接运行了Python的脚本,这两种功能都是我们之前没有接触过的。
|
||||
|
||||
虽然没有接触过,但是学起来不会很难,那么接下来我就从怎么通过命令行运行模块,以及怎么使用模块提供一个HTTP服务这两方面来讲解这行代码。
|
||||
|
||||
如何通过命令行运行模块
|
||||
|
||||
要通过命令行运行一个模块,我们需要先通过Python命令找到“http.server”模块的第一条命令,然后再来执行。而找到“http.server”模块,非常关键的就是”-m”参数。我来重点讲解一下。
|
||||
|
||||
从执行方式上,这行代码和我们以往执行的代码不同。我在Python命令和模块之间使用了“-m”参数,而且“-m”参数后面会跟着要执行的Python的模块“http.server”。
|
||||
|
||||
“http.server”在你电脑中保存的路径是“/模块所在目录/http/server.py”,它也是一个“.py”结尾的文件,会被保存在你电脑上Python文件夹中的“lib”文件夹下。
|
||||
|
||||
如果不使用“-m”参数,那就像我们之前执行的代码一样,Python会执行当前目录下的.py 文件。所以在这里你要特别注意一下,增加了“-m”参数前后,执行的.py文件位置是不同的。
|
||||
|
||||
如果要查看这个模块是怎样通过Python实现的,那么我们需要先找到这个模块的所在目录。核心实现代码我写了出来,供你参考。
|
||||
|
||||
$ python3
|
||||
>>> import http
|
||||
>>> http.__file__
|
||||
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/http/__init__.py'
|
||||
|
||||
|
||||
在这段代码中,我使用了我们之前学习过的内置变量“file”,得到了该模块所在的位置。在以后的工作中,你可以使用这种方式查找任意一个你想要了解的模块位置。
|
||||
|
||||
不过你也要知道,由于你在安装Python时,会根据自己的习惯选择自定义的目录,或者你使用的是Windows操作系统,所以你得到的目录可能会和我不同,但这并不影响你阅读查找该模块的实现代码。
|
||||
|
||||
如果你还想查看Python其他模块保存在哪个目录,可以在没有加载模块的前提下,获得所有模块的位置,代码如下:
|
||||
|
||||
import sys
|
||||
sys.path
|
||||
|
||||
# 执行结果
|
||||
['', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']
|
||||
|
||||
|
||||
你会发现,在执行“sys.path”得到的列表中,还会出现一个“site-packages”目录,这个目录是你使用pip3安装的第三方模块默认的位置。当你需要学习Python库的代码时,可以使用这个方法找到所有安装的包的目录。
|
||||
|
||||
“-m”参数不但能让Python从“sys.path”中找到要执行的模块,还能够自动执行这个模块。以“http.server”为例,Python会首先找到“lib/python3.7/http/server.py”, 然后运行“server.py”文件。
|
||||
|
||||
由于该文件中使用了“if name == ‘main’:”这样一条语句,所以Python会从这一行开始执行“server.py”文件。此外,我还为“http.server”指定了参数“8080”, “server.py”会通过“argparse”库对“8080”这个参数进行处理。
|
||||
|
||||
如何使用“http.server”模块提供HTTP服务
|
||||
|
||||
刚才我们讲了怎么通过命令行来运行“http.server”模块,事实上,这个模块****是基于HTTP协议实现的文件浏览和下载功能。接下来我们就先了解一下HTTP协议的主要工作过程。
|
||||
|
||||
使用HTTP协议的好处是它能够跨平台,而且还比其他协议简单。那么要想掌握HTTP协议,你得要知道HTTP协议提供了两种角色:
|
||||
|
||||
|
||||
为其他人提供服务的服务端;
|
||||
享受服务的客户端。
|
||||
|
||||
|
||||
我们一般把“http.server”称为HTTP服务端,把浏览器称作HTTP客户端。服务端和客户端通信时,会采用它们的主要协议–HTTP协议。
|
||||
|
||||
它们的通信过程就像是在打电话,当你给对方打电话时,首先要输入对方的手机号码。同理,在计算机中,手机号码就是服务端的IP地址和端口,接通电话后,双方要想互相听懂要传递的信息,必须使用一种双方都能理解的语言,这个语言在计算机中就是HTTP协议。所以一句话总结就是,相同的语言就是文件传输的协议。
|
||||
|
||||
了解了HTTP协议的主要工作过程,那接下来就是它建立连接的过程了。就像我为你举的例子一样,对方的手机号码在HTTP协议就是IP地址和端口。
|
||||
|
||||
比如我为HTTP服务器指定的端口是8090,我的IP地址是“192.168.0.100”,那我就可以通过浏览器使用“http://192.168.0.100:8090”进行访问。可以看到,在访问的时候,我手动指定了协议、IP地址和端口。
|
||||
|
||||
所以“http.server“模块不仅可以提供HTTP协议,还是一个灵活指定IP和端口的HTTP服务端。这也就是说,http.server模块运行后,能让浏览器访问到服务端。
|
||||
|
||||
由于客户端服务端都采用HTTP协议,那么服务端列出的文件目录会自动被浏览器翻译给客户端的用户,你也就能浏览器查看到服务器上的文件名称,并把服务器的文件下载到客户端的电脑上,这就是“http.server”模块能够实现下载的原理和过程了。
|
||||
|
||||
另外,我还要提醒你,在文件下载时,一定要注意共享的安全性。因为那些没有用户认证功能的HTTP文件下载方案,其他人都可以通过IP地址和端口直接获取你电脑中的文件,由此造成信息泄漏。因此在共享完成后,你需要把服务端及时关闭。
|
||||
|
||||
不过由于“http.server”默认没有提供文件上传的功能,手动编写也需要比较复杂的代码逻辑,因此,我来通过另一个Flask模块,它能通过简单的代码实现文件上传。
|
||||
|
||||
如何实现文件的上传
|
||||
|
||||
虽然我们要利用最精简的代码来把文件上传到服务端,但是它也要比下载功能复杂得多,因为基于HTTP协议的上传,我们需要自行编写HTML页面,来提示用户怎么上传,怎么使用POST方法访问服务器,以及怎么指定上传后文件的保存位置。
|
||||
|
||||
我根据Flask模块的官方文档的上传代码,进行了精简,考虑到你目前对编程的理解还比较基础,所以我把用户验证和文件扩展名验证功能去掉后,得到了如下的代码。通过这段代码,可以实现基于Python的文件上传。
|
||||
|
||||
我把代码放在文稿中,供你学习和参考。同时,我也再给你详细讲解上传的过程,以及用到的代码。
|
||||
|
||||
import os
|
||||
from flask import Flask, request
|
||||
app = Flask(__name__)
|
||||
app.config['UPLOAD_FOLDER'] = os.getcwd()
|
||||
|
||||
html = '''
|
||||
<!doctype html>
|
||||
<title>Upload new File</title>
|
||||
<h1>Upload new File</h1>
|
||||
<form action="" method=post enctype=multipart/form-data>
|
||||
<p><input type=file name=file>
|
||||
<input type=submit value=Upload>
|
||||
</form>
|
||||
'''
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
filename = file.filename
|
||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||
return html
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8090)
|
||||
|
||||
|
||||
根据上传的常规流程,我把代码按照四个步骤来实现文件上传,这四个步骤分别是运行服务器、获得网页内容、上传文件、保存文件。这四个步骤就是我们上传文件的四个关卡,那接下来我们就一关一关过。
|
||||
|
||||
运行服务器
|
||||
|
||||
第一步是服务器的运行。Flask是第三方函数库,因此需要用一行命令进行安装,安装后才能使用。命令如下:
|
||||
|
||||
pip3 install flask
|
||||
|
||||
|
||||
|
||||
Flask要想正确运行,首先要对它进行初始化。所以我在代码第2行导入Flask后,紧接着就对它进行了初始化,并给初始化后的Flask取了个名字App。这就意味着,在当前脚本下对Flask的操作都可以用过app对象来完成。
|
||||
|
||||
在代码第4行,我还给App对象增加了一个参数“UPLOAD_FOLDER”,这个参数用来指定上传的文件将会保存在哪一个目录中。
|
||||
|
||||
接下来,你就可以使用“app.run()”方法运行了。和下载使用的“http.server”是一样的,由于Flask也是作为HTTP服务端,所以在Flask运行时,也必须指定它运行的IP地址和端口。
|
||||
|
||||
在代码中我把IP地址指定为“0.0.0.0”可以让它监听服务器所有的IP地址,我把端口设置为“8090”端口,你可以通过这一端口访问到Flask服务端。
|
||||
|
||||
获得网页内容
|
||||
|
||||
了解完怎么运用Flask之后,我再带你看一下我是怎么把服务器上的网页传输到浏览器的。
|
||||
|
||||
浏览器要想获得网页内容,必须要用户发起到服务器的HTTP请求。发起请求后,浏览器会得到服务器经过HTTP协议传送回来的网页源代码。当你使用服务器的正确IP和端口访问到Flask服务器后,会看到这样一个界面,如下:
|
||||
|
||||
|
||||
|
||||
这个网页内容对应的是第7-14行的代码,这段代码是把HTML语言赋值给变量html,并通过upload_file()函数传递给浏览器。
|
||||
|
||||
你看到的网页内容,其实就是html变量中的HTML语言,它被浏览器接收后,会被浏览器解析,解析之后的结果就是你看到的网页。
|
||||
|
||||
所以编写这段HTML语言的目的,也就是让你可以通过浏览器的“选择文件”按钮弹出窗口,选择要上传的文件,并通过点击“upload”按钮上传。
|
||||
|
||||
把HTML语言的代码传递给浏览器的函数是upload_file()函数,它是通过这5行代码实现的:
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
... ...
|
||||
return html
|
||||
|
||||
|
||||
在第一行中,我们使用的是函数的装饰器,它的作用是在不改变被装饰函数的内容的前提下,给函数增加新的功能,装饰器也是由函数实现的,它的语法格式是在装饰器前增加一个“@”符号。这里的装饰器“@app.route()”就是用来增加“upload_file()”函数功能的。
|
||||
|
||||
我来详细为你讲解一下装饰器的两个参数。
|
||||
|
||||
|
||||
一个是“/”,它的作用是请求URL的路径为“/”时,调用upload_file()函数;
|
||||
另一个是“methods”,限制请求“/”的方式只能是“GET”和“POST”方式。
|
||||
|
||||
|
||||
我来举个例子,你会更容易理解。比如用浏览器访问“http://127.0.0.1:8090/”,由于浏览器默认的请求方式是“GET”方式,请求的URL是“/”,那么Flask会自动调用“upload_file()”函数。在函数中,再次判断请求的方式,由于默认是“GET”方式,所以函数会返回html变量,也就是你看到的网页内容。
|
||||
|
||||
上传文件
|
||||
|
||||
在你掌握了浏览器加载HTML之后,我们接下来就需要学习上传文件的代码执行过程。 要想上传一个文件,需要先点击浏览器的“upload”按钮,它是“form表单”的提交功能。
|
||||
|
||||
“form表单”的作用是在你点击按钮后,把文件以指定的方式和数据类型上传到服务器。指定方式和数据类型都是采用表单的参数进行指定的,它们分别是method和enctype参数。
|
||||
|
||||
第一个参数是“method=post”,它指定了表单是通过“POST”方式访问服务器的。通常上传文件会采用POST方式,主要原因GET方式最大只允许使用1024个字节,而POST方式在理论没有大小限制,完全取决于服务端的设置和内存大小。
|
||||
|
||||
第二个参数是“enctype=multipart/form-data”。这个参数是专门用来存放容量较大的文件的,它会把文件放到“request.FILES”中。
|
||||
|
||||
当你点击“upload”按钮之后,文件就开始上传到服务器的内存中。那接下来就到了最后一步,把内存中的数据保存成文件。
|
||||
|
||||
保存文件
|
||||
|
||||
要把内存中的数据保存到文件,我们可以通过“upload_file”函数的这5行代码来实现。
|
||||
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
file = request.files['file']
|
||||
filename = file.filename
|
||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||
|
||||
|
||||
在这段代码的第二行,对请求方式进行了判断:
|
||||
|
||||
|
||||
如果是“GET”方式,会直接返回网页;
|
||||
如果是“POST”方式,才会对文件进行处理。
|
||||
|
||||
|
||||
具体的处理过程是:
|
||||
|
||||
|
||||
先从“request.files”中取出上传到服务器的文件数据;
|
||||
再通过“file.filename”取得上传时使用的文件名;
|
||||
接着,通过path.join()函数将事先指定好的路径和文件名拼接组成当前目录下的文件名后;
|
||||
最后,通过file.save()函数将内存中的文件内容,保存到服务器的同名文件中。
|
||||
|
||||
|
||||
小结
|
||||
|
||||
今天的核心内容就是这些,我也再给你总结一下。今天这节课,我为你讲解了怎样使用Python的模块来实现最简单的文件上传和下载功能,这一功能在公司内进行跨操作系统的文件传输不但速度快,而且极为便捷。
|
||||
|
||||
在讲解文件上传下载的同时,我还给你讲解了两个库的使用,分别是http.server 和 Flask两个模块。其中Flask是Python中非常著名的WEB服务端模块,除了可以进行文件上传外,它还能作为Web服务器提供网页访问。
|
||||
|
||||
在文件的上传和下载场景下,我还给你介绍了两种请求HTTP服务器的方式,它们分别是GET和POST方式。
|
||||
|
||||
|
||||
GET方式一般用于获取服务器的信息,类似从服务器上查找数据;
|
||||
POST方式一般用于向服务器上传信息,类似向服务器写入。
|
||||
|
||||
|
||||
对服务器的请求方式还有更新、删除、更改单个值等不同的方式,其中GET、POST是最常用的形式,日常应用中,所以你只需要记住这两个请求方式即可。
|
||||
|
||||
最后,我希望通过Flask库、HTTP协议的请求方式、简单的表单及其实现它的HTML代码,能够让你对HTTP协议以及WEB服务器有初步的了解。
|
||||
|
||||
因为Python的高效便捷,一方面体现在可以通过简单的语法在一台电脑上实现提效,另一方面,它能够通过极少的代码开发出功能非常强大的WEB服务器,这对你在办公数据的集中管理和网页自动化管理上都会有非常有效的帮助。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,我还要给你留一道思考题。题目是:如果我想在Flask展示表单的页面中,展示当前目录下的所有文件,那要怎么修改Flask的代码呢?
|
||||
|
||||
欢迎把你的思考和想法写在评论区,我们一起交流讨论。如果你学完有所收获,也欢迎你把课程分享给你的朋友、同事,一起提升办公效率。好了,那我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
287
专栏/Python自动化办公实战课/18http库:如何批量下载在线内容,解放鼠标(上)?.md
Normal file
287
专栏/Python自动化办公实战课/18http库:如何批量下载在线内容,解放鼠标(上)?.md
Normal file
@ -0,0 +1,287 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
18 http库:如何批量下载在线内容,解放鼠标(上)?
|
||||
你好,我是尹会生。
|
||||
|
||||
我在前段时间遇到了下载大量图片的需求。具体来说,是在我训练AI识别猫时,必须要在电脑中存储大量猫的图片。但搜索到的图片都在网页中,我就需要先把它们一个个手动下载下来,再保存,然后才能进行后续的工作。
|
||||
|
||||
而且,随着我对AI训练工作的日益增多,这类需求会越来越丰富,我不仅要下载猫的图片,还要下载大量其他各种动物的图片。相信类似这种批量下载网页中的图片的需求,你在工作中会经常遇到。而这类需求,刚好能够使用Python的“requests-html”库实现批量下载,提高我们的工作效率。
|
||||
|
||||
因此呢,这节课我就以搜索到的猫的图片为例,给你讲解一下,我是怎么来批量下载图片的。
|
||||
|
||||
批量下载图片的准备工作
|
||||
|
||||
我把实现图片自动化批量下载的过程,拆分成四段难度逐渐递增的代码。这四段代码分别实现的是以下四个功能:
|
||||
|
||||
|
||||
访问HTTP服务器,得到搜索结果的整个网页;
|
||||
在访问服务器之后下载一张图片;
|
||||
找到多张图片的相似地址;
|
||||
提取相似地址,下载多张图片。
|
||||
|
||||
|
||||
前两个功能,是批量下载图片的准备工作和前提。掌握了这两个功能,那么批量下载图片实现起来就容易多了。所以接下来,我们先来学习这两项准备工作。
|
||||
|
||||
访问HTTP服务端的资源
|
||||
|
||||
我们从难度最低的一段代码开始,怎么通过访问HTTP服务器,从而得到猫的搜索结果的整个网页。
|
||||
|
||||
访问HTTP服务器,我需要使用Python的第三方库requests-html库来实现。虽然Python在标准库中也自带了访问服务器urllib库,但它的实现代码不够简洁,所以我一般数情况下会使用requests-html库替代它,来进行HTTP服务器的访问。
|
||||
|
||||
使用reqeusts-html库,你先需要通过“pip”来安装,再通过get()方法访问HTTP服务器。
|
||||
|
||||
安装requests-html库的方法,和我们上一讲安装flask库是一样的。这里我强调一下,在Python中,绝大多数的第三方库,它们的名称和安装包的名称是相同的,所以除了特殊的安装方法外,像这种通用的安装方法,我就不会每次都重复贴出来了。reqeusts库的安装方法,代码如下:
|
||||
|
||||
pip3 install requests-html
|
||||
|
||||
|
||||
在命令行执行后,requests-html库就安装成功了。接下来,我们就使用它的get()方法去请求HTTP服务器,而得到的返回数据,就是网页的代码。
|
||||
|
||||
相信你还记得在上一讲中,我介绍了两种常见的HTTP服务端请求的方式,分别是“GET”和“POST”。requests-html库就是使用了同名函数实现的“GET”方式访问。
|
||||
|
||||
那么接下来,我将使用reqeusts库来访问一个无版权的图片网站,并得到网页的源代码。我把代码写出来,供你参考。
|
||||
|
||||
from requests_html import HTMLSession
|
||||
|
||||
# URL
|
||||
name = "猫"
|
||||
url = f"https://unsplash.com/s/photos/{name}"
|
||||
|
||||
# 启动
|
||||
session = HTMLSession()
|
||||
|
||||
# GET请求
|
||||
result = session.get(url)
|
||||
|
||||
# 结果
|
||||
print(result.status_code)
|
||||
print(result.html.html)
|
||||
|
||||
|
||||
|
||||
在这段代码中,我用requests-html代替浏览器来作为HTTP客户端,通过“session.get()”函数,向upsplash网站提供的HTTPS服务器发起“GET”请求。发送请求后,“result”变量会得到HTTP服务端响应的结果,即网页的源代码。
|
||||
|
||||
你肯定会有疑问,为什么我要用requests-html来代替浏览器呢?原因就在于,浏览器得到了网页内容后,如果你想再通过网页下载图片,还需要通过鼠标和网页的交互。这就会带来一个新的问题:交互式命令是不能进行图片的自动化下载的。也就是说,我们必须让交互操作变成非交互操作,才能进行批量下载图片。
|
||||
|
||||
总之呢,在这段代码中,我使用了requests-html代替浏览器实现了一次完整的网址的访问,这一过程为后续的自动化下载打下了坚实的基础。
|
||||
|
||||
而在这次网址的访问中,最核心的就是发起“GET”请求了。我把这次完整的“GET”请求拆分成了四个步骤。
|
||||
|
||||
其中前两个步骤是准备工作。准备工作是指在发送正式的数据之前,需要确定你需要向哪一个互联网当中的服务器发起请求,以及尝试和他建立连接,判断是否能够正常访问。
|
||||
|
||||
这就像你跟其他人打电话一样,你要确定你输入的电话号码是正确的,而且在拨打电话以后,还可以根据电话那个提示音,知道对方的电话是否处于可接通状态。
|
||||
|
||||
那后两步呢,就是在这个基础上和对方正式开始通话,但是他和打电话的区别是 HTTP协议会采用一问一答的形式,也就是发送一次请求会得到一次结果,不发请求也不会得到任何结果。接下来我们就来详细看一下这4个步骤。
|
||||
|
||||
第一步,设置请求的URL。
|
||||
|
||||
通过使用requests-html请求该网站,你会发现请求的大部分地址都是固定的,只有搜索的关键字会随着搜索的内容不同而发生变化。
|
||||
|
||||
所以我把搜索的关键字单独提取出来,放在一个字符串“name”中,当你需要更改搜索的关键字时,直接修改“name”变量就可以了。而其他在请求过程中没有发生变化的字符串,则放在url字符串中。与此同时,我还把url设置为“f-string”,方便我进行变量“name”的替换。
|
||||
|
||||
这一步是比较简单的,不过我在里面花了点小心思。仔细观察字符串,像字符串中只有某几个位置发生了变化,那么其余的部分就是重复的逻辑。针对这种重复的逻辑,可以被定义成变量来重复使用,减少重复工作。
|
||||
|
||||
还有我们以前接触过的,比如把文件目录和文件名拼接后,会形成一个很长的完整文件名。当文件名被多个函数重复使用时,你就不必每次都执行拼接,而是可以在拼接后被定义成变量来重复使用。
|
||||
|
||||
我补充这一点是想告诉你,你在写代码时也要多观察代码中哪些部分是重复的逻辑,这些重复的逻辑往往都可以使用变量来进行定义。一个小小的技巧,有时候就能给你的重复工作带来非常大的效率提升。
|
||||
|
||||
第二步,启动一个会话。
|
||||
|
||||
会话是HTTP协议中的一个概念。当你的客户端和服务端建立了连接时,我们就称一个会话建立成功了。它就像你和你的小伙伴使用微信进行视频,当对方接通视频的那一刻,就建立了一个新的会话。
|
||||
|
||||
在这段代码中,我使用了“HTMLSession”类初始化了一个叫做“session”的会话,用来后续向HTTP服务端继续发送请求。
|
||||
|
||||
第三步,发送“GET”请求。
|
||||
|
||||
建立会话之后,就要可以开始传输数据了。在这段代码中,我使用了get()函数把数据包装成标准的HTTP协议,然后向我在第一步定义的“URL”发起“GET”方式请求。
|
||||
|
||||
第四步,得到返回结果。
|
||||
|
||||
请求发送完成之后,“result“变量会自动得到服务器的结果,无论结果是成功还是失败,“result”变量都会得到返回的状态码以及数据。
|
||||
|
||||
所以在这一步,我们需要着重掌握怎么判断服务器返回的是成功还是失败的结果。我使用了“result.status_code”来得到返回的状态码。状态码是HTTP协议规定的标准,正确返回为200 ,其他返回你可以参考维基百科。
|
||||
|
||||
根据不同的状态码,你能判断网页返回的状态,并进行后续处理。比如我们经常使用状态码“200”确认网页是正确返回了内容,这意味着我得到“200”这个返回码之后,就可以继续处理网页数据了。
|
||||
|
||||
如果返回码是“4xx或5xx”,说明出现了请求过程的客户端或者服务端错误,HTTP客户端并没有得到网页的内容,这时候你就必须停止程序后续的逻辑,并向执行者提示错误信息。具体的判断方法就是我们的老朋友if语句了。
|
||||
|
||||
在这段代码中,我们得到了状态码200,那接下来我就使用“result.html.html”,得到了完整的网页源代码。
|
||||
|
||||
这四个步骤,就是使用“requests-html”库得到指定网页源代码的完整流程。接下来就要分析网站的行为,来下载单张图片。
|
||||
|
||||
使用requests-html下载一张图片
|
||||
|
||||
下载单张图片是图片批量下载的基础。为什么要这么说呢?因为我们可以通过观察一张图片的下载行为,把浏览器的下载操作转换成Python的代码,这样就可以循环批量下载图片了。而观察单张图片的下载行为,换句话说就是用Python来模拟浏览器。
|
||||
|
||||
具体思路是这样的:在使用Python按照浏览器的行为进行下载后,我们可以对服务端的HTML代码进行分析,从而得到多个图片在服务端存储的代码逻辑,之后就可以利用循环批量下载图片了。那接下来我就用例子带着你来学习一下。
|
||||
|
||||
使用浏览器下载一张图片的时候,当你把鼠标移动到下载按钮,你会发现它是在访问图片的URL后面增加了一串“download”字符。
|
||||
|
||||
我用一张图片的访问和下载给你举个例子,例如下载下面这张图片:
|
||||
|
||||
|
||||
|
||||
这张图片的访问地址和下载地址我贴了出来,供你学习。
|
||||
|
||||
在你清楚浏览器是如何下载一张图片的之后,我们就可以使用“requests-html”下载单张图片了。下载单张图片的完整代码如下:
|
||||
|
||||
from requests_html import HTMLSession
|
||||
|
||||
# URL
|
||||
url = "https://unsplash.com/photos/NLzaiXOELFY/download"
|
||||
|
||||
# 启动
|
||||
session = HTMLSession()
|
||||
|
||||
# GET请求
|
||||
result = session.get(url)
|
||||
|
||||
# 结果
|
||||
print(result.status_code)
|
||||
|
||||
# 保存图片
|
||||
with open("one.jpg", "wb") as f:
|
||||
f.write(result.content)
|
||||
|
||||
|
||||
|
||||
这段代码中的保存文件功能是我想重点为你讲解的内容。在访问网页得到图片的下载地址的基础上,我增加了“with open” 语句,打开了一个文件,并以“wb”作为参数,实现文件的二进制写入。
|
||||
|
||||
由于图片是由“01010… …”形式的二进制组成的,所以写入和下载都必须使用二进制方式实现。可以看到,在设置文件写入方式的时候,我增加了b参数,让文件以二进制方式写入文件,这样我们就能从服务器获得的图片内容的同时,也以二进制方式得到了返回结果的修改。
|
||||
|
||||
接着,我把“result.html”改成了“result.content”,使用二进制方式来获取图片内容。通过这样的方式,最终就把文件保存成“one.jpg”了。当你运行脚本后,在运行脚本的目录下就会产生一个“one.jpg”的图片。
|
||||
|
||||
这就是完整的使用“requests-html”下载单个图片的过程。
|
||||
|
||||
如何批量下载图片
|
||||
|
||||
请求网页和下载图片是实现批量下载的基础,这两部分已经使用Python自动完成了,接下来就是如何将它们组合起来。我来组合它们的思路是这样的:
|
||||
|
||||
首先,你要把它们当作两个独立的功能,找出两个功能的输入和输出是什么。
|
||||
|
||||
请求网页的输入是一个即将要请求的URL地址,输出则是网页源代码。那下载图片的输入输出是什么呢?我们以下载单个图片为例,下载图片的输入是图片的地址,执行下载图片的功能之后你会得到保存在硬盘上面的一张图片。因此,下载图片的输入是图片地址,输出是图片文件。
|
||||
|
||||
接下来,需要从前一个功能的输出找到后一个功能输入需要的数据。
|
||||
|
||||
这里的前后是指执行顺序的前后,那么从请求网页得到的网页源代码中找到下载图片的地址是关联这两个功能最关键的技术点。而在编码的时候,我会将这两个功能编写为两个函数,函数的执行结果就是功能的输出,而函数的参数实现的就是功能的输入,通过前一个函数的输出和后一个函数的参数就能实现它们之间的功能连接了。
|
||||
|
||||
最后是找规律。尽可能地找到多张图片之间的HTML代码的规律,从而实现批量下载。
|
||||
|
||||
根据上面我提到的组合两个函数的思路,你不难发现,要想实现批量下载图片函数,必须先得到每个图片的下载地址,而下载地址会出现在请求网页的函数执行结果中。接下来我们就从请求网页函数的执行结果–网页的HTML代码入手,寻找图片的下载地址,并从这些下载地址中寻找规律,批量提取下载地址。
|
||||
|
||||
使用XPath,匹配多张图片下载地址
|
||||
|
||||
想要找到图片的下载地址以及它们的规律,最直观的办法就是通过浏览器的调试界面,观察它们的代码。
|
||||
|
||||
你可以使用浏览器访问 “https://unsplash.com/s/photos/猫” URL后,再通过浏览器的调试功能,打开调试界面。我以最流行的Chrome浏览器为例,它的调试窗口打开快捷键是“F12”,打开调试界面后的截图如下,供你参考:
|
||||
|
||||
|
||||
|
||||
截图左侧就是调试页面,截图的右侧是网页的内容,你可以通过截图左上角的“选择元素按钮”-
|
||||
-
|
||||
点击图片,左侧的调试界面会自动定位到图片对应的HTML代码。而你向上查找,会发现图片下方的“”标签的“href”属性,就记录了图片ID和下载地址。
|
||||
|
||||
这里我有必要为你介绍一下HTML的标。你在网页上看到的每个元素,都是由不同类型的标签组成的。
|
||||
|
||||
例如图片使用的是“”标签,超链接使用的是“”标签。每个标签还使用了相应的属性,来记录该标签的具体信息。比如把一个人作为标签,他可以有性别、身高、年龄等属性。“”标签的“href”属性就记录了超链接的地址,我们可以使用超链接地址来完成图片的批量下载。
|
||||
|
||||
那用什么方法能一次性找到链接呢?聪明的你一定想到了我们在第8讲学过的正则表达式。正则表达式是分析网页的常用工具之一,不过还有一种比正则表达式更方便的网页内容搜索工具,那就是XPath。我们可以使用XPath来找到超链接。
|
||||
|
||||
如果你对使用正则表达式分析网页也感兴趣,你可以把它跟XPath进行对比,选择一个自己顺手的工具,这也是提升工作效率的技巧之一。
|
||||
|
||||
我先把XPath查找的方法和结果写在下面的代码中, 然后再为你分析XPath的用法。
|
||||
|
||||
print(result.html.xpath('//figure[@itemprop="image"]//a[@rel="nofollow"]/@href'))
|
||||
|
||||
# ['https://unsplash.com/photos/NLzaiXOELFY/download?force=true',
|
||||
# 'https://unsplash.com/photos/3JyEfhb8Zgo/download?force=true',
|
||||
# 'https://unsplash.com/photos/4Y6UYds0cIo/download?force=true',
|
||||
# ... ...
|
||||
# ]
|
||||
|
||||
|
||||
在这段代码中,使用result.html.xpath()函数就能实现XPath查找网页的功能。XPath查找网页时也有它自己的语法,我在这里使用了路径、标签、属性三个功能。我来依次带你学习一下它们的用途。
|
||||
|
||||
首先是路径,HTML的整体结构是由头部标签“”和主体标签“”组成的,而网页的内容是在主体标签下逐层编写的,它的结构如下:
|
||||
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div num=1>
|
||||
<figure itemprop="image">
|
||||
... ...
|
||||
<div num=2>
|
||||
... ...
|
||||
<a rel="nofollow" href=http://...>
|
||||
... ...
|
||||
</div>
|
||||
</figure>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
以上面的结构为例,如果网页内容简单,你可以像使用windows的路径一样,使用“/body/div/fugure”方式搜索到“figure”标签。但是当匹配路径超过3个时,把路径逐个写在XPath搜索表达式中不太容易,那我们就一般使用模糊路径来搜索“//”。比如要搜索一个
|
||||
|
||||
标签,就可以使用“//
|
||||
|
||||
”的方式。
|
||||
|
||||
接着是标签。我在xpath()函数中,使用了figure和a标签。“finger”是自定义标签,“a”是超链接的标签,它们和路径组成了下面这行代码的形式:
|
||||
|
||||
xpath('//figure//a')
|
||||
|
||||
|
||||
这一条XPath的功能是从任意位置开始搜索“figure”标签,并在它下面任意一级目录中找到“a”标签。这显然不符合我们只提取图片下载地址的要求,因此我们要通过属性增加这两个标签的限定条件。
|
||||
|
||||
接下来,我为“figure”标签增加了限定条件,要求它的属性和属性的值,必须和xpath()函数搜索的属性和值完全相同。
|
||||
|
||||
xpath()的匹配规则中要求“figure”标签必须包含“itemprop”属性,且这一属性的值是“image”,为“a”标签设置查找的属性必须为“rel”, 且它的值必须是“nofollow”。这样xpath()中的两个标签就变成了如下的写法:
|
||||
|
||||
xpath('//figure[@itemprop="image"]//a[@rel="nofollow"]')
|
||||
|
||||
|
||||
|
||||
通过增加属性,限定了搜索的标签,可以得到需要下载的“”标签,“”标签的规范写法要求链接地址写@href属性,链接的内容要写在它的值中,因此我们再为xpath()提取的“”标签增加“@href”属性,即可以实现提取只包含图片地址的列表。我把完整的xpath()函数写在下方。
|
||||
|
||||
xpath('//figure[@itemprop="image"]//a[@rel="nofollow"]/@href')
|
||||
|
||||
|
||||
这就是利用xpath()的路径、标签、属性搜索超链接的完整实现思路。我建议你打开网页调试器,通过“选择元素按钮”点击网页内容,对照这段网页源代码和xpath()语法来学习,这样更有利于你快速掌握怎么使用Xpath匹配多张图片的下载地址。
|
||||
|
||||
使用循环,下载多张图片
|
||||
|
||||
在你得到了多张图片的下载地址列表后,接下来我们只需要把下载单个图片的代码和列表功能进行组合,这样就能实现图片的批量下载了。我先把核心代码写出来,供你参考,然后再讲解思路。
|
||||
|
||||
down_list = result.html.xpath('//figure[@itemprop="image"]//a[@rel="nofollow"]/@href')
|
||||
|
||||
def down_one_pic(url):
|
||||
result = session.get(url)
|
||||
filename = get_picID_from_url(url)
|
||||
with open(filename, "wb") as f:
|
||||
f.write(result.content)
|
||||
|
||||
for one_url in down_list:
|
||||
down_one_pic(one_url)
|
||||
|
||||
|
||||
|
||||
实现文件批量下载的主要思路,就是把下载地址列表使用for循环逐个进行处理。通过循环取得了每个下载地址的变量“one_url”之后,再把“one_url”作为下载函数的参数,并通过URL提取文件名后,把文件下载并保存。
|
||||
|
||||
小结
|
||||
|
||||
以上就是我们今天的全部内容了,我来为你总结一下。在本讲中主要以批量下载图片为例,为你讲解了Python中实现HTTP客户端常用的“requests-html”库、进行网页搜索的XPath搜索工具。
|
||||
|
||||
通过这两个库的配合使用,你可以利用Python代替浏览器,实现图片、文字、视频等资源的批量下载,在你熟练掌握“XPath“之后,再遇到需要批量下载的工作,你就可以利用Python解放你的鼠标,实现网页内容的批量自动化下载了。
|
||||
|
||||
思考题
|
||||
|
||||
在第16讲中,我们实现了文件的批量改名功能,你能否将批量改名功能也封装成一个函数,结合今天的批量下载,实现下载之后的文件自动改成你需要的文件名呢?
|
||||
|
||||
欢迎把你的思考和想法分享在留言区,我们一起交流、讨论。也欢迎你把我们的课程分享给你的朋友、同事,一起做职场上的效率人。我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
248
专栏/Python自动化办公实战课/19http库:如何批量下载在线内容,解放鼠标(下)?.md
Normal file
248
专栏/Python自动化办公实战课/19http库:如何批量下载在线内容,解放鼠标(下)?.md
Normal file
@ -0,0 +1,248 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
19 http库:如何批量下载在线内容,解放鼠标(下)?
|
||||
你好,我是尹会生。
|
||||
|
||||
在上节课,我们学习了怎么使用“requests-html”库来批量下载图片,这足以帮你应对大部分需要批量下载互联网资源的场景了。
|
||||
|
||||
但是除了批量下载这一典型场景外,还有两种场景“requests-html”工具无法覆盖,其中就包括一些网站的每日签到功能,明明登录网站后点击一个按钮就能领到虚拟货币,但是每次还要手动输入账户和密码,再用鼠标点击领取按钮。相信已经学会了如何用Python访问网页的你,早就想把签到进行自动化了吧。
|
||||
|
||||
那么今天,我就以京东自动签到领金豆为例,为你介绍一款比“requests-html”更加强大的浏览器工具“selenium”,通过selenium我将带你实现自动化的用户登录和模拟鼠标点击功能。
|
||||
|
||||
selenium的适用场景
|
||||
|
||||
我刚才也提到了,在你把“requests-html”库应用到各种批量下载场景之后,你会发现有两种场景下使用“requests-html”无法实现批量下载。
|
||||
|
||||
一种场景是,有的网页为了防止你用工具下载,会对下载工具进行检测。如果你的HTTP客户端不是浏览器,那就不允许你访问该网站下的所有内容。
|
||||
|
||||
另一种场景是,一些网页为了更好的交互性,就使用了JavaScript脚本语言。而JavaScript脚本语言需要在浏览器运行,才能获得服务器的数据。所以如果使用“requests-html”来获取这些数据的话,你就必须再编写Javascript脚本。
|
||||
|
||||
基于这两种原因,我们可以使用“selenium”来解决。这也正是为什么有了“requests-html”库之后,我还要再带你学习“selenium”库。
|
||||
|
||||
它的实现原理,即:通过“WebDriver”组件,把Python与浏览器连接起来,让Python来控制浏览器的行为,向浏览器发送各种模拟用户操作的指令,此时,真实的浏览器会按照Python发的各种指令对服务器进行访问。
|
||||
|
||||
这样既可以解决网页加密的问题,又避免了再编写Javascript脚本,弥补了“requests-html”在批量下载场景中的缺憾。无论从操作的友好程度,还是实现功能的简单程度上,都要比“requests-html”强大。
|
||||
|
||||
那今天这节课,我们就来学习如何使用“selenium”来实现自动签到。不过考虑到你是第一次接触“selenium”,所以在学习实现方法之前,我还是先来带你学习怎么安装和执行它。
|
||||
|
||||
“selenium” 的安装与初次运行
|
||||
|
||||
“selenium”是基于“Webdriver”连接Python和浏览器的,而“Webdriver”的组件比较多,所以它的安装过程也要比一般的Python第三方库复杂一点。
|
||||
|
||||
考虑到你是第一次使用它,我就把具体的安装步骤以及注意事项,拆分成了5个步骤,保证你能顺利安装,并且能用它访问网页。
|
||||
|
||||
第一步:根据浏览器版本来下载“Webdriver”。
|
||||
|
||||
由于“selenium”会通过“Webdriver”调用你当前电脑上的浏览器访问HTTP服务器,所以必须找到和浏览器版本一致的“Webdirver”程序,然后下载下来。
|
||||
|
||||
我这里以工作中最常用的Chrome浏览器为例,首先要通过Chrome的“设置”—“关于”菜单查看浏览器的版本。我的Chrome浏览器版本如下图:
|
||||
|
||||
|
||||
|
||||
接着,根据我的浏览器版本“89.0”访问“Webdrive”国内的镜像站,下载与浏览器相匹配版本的“Webdriver”。这里要注意,不同的操作系统,“Webdriver”的安装包不同,所以需要你选择对应的操作系统版本。“Webdriver”的下载网页内容截图如下:
|
||||
|
||||
|
||||
|
||||
我这里以“mac”操作系统为例,下载了“chromedriver_mac64.zip”压缩包后,解压缩之后,会得到“chromedriver”文件。这个文件就是连接Python和浏览器的工具。
|
||||
|
||||
第二步,把“Webdriver”放在命令的搜索路径下。
|
||||
|
||||
“chromedriver”文件是一个可执行文件,我们需要把它放入命令的搜索路径中,这样文件才能在Python调用浏览器时被直接运行,我们就不用输入它的完整路径。
|
||||
|
||||
我在这里解释一下命令的搜索路径。它是每个操作系统都事先定义好的,就像它的名字一样,你在命令行运行的任意一条命令,都可以从命令搜索路径中依次查找到。
|
||||
|
||||
那么在mac操作系统下,我们可以使用命令行运行“echo $PATH”,来查看当前系统的命令搜索路径都有哪些。
|
||||
|
||||
在这个命令行中,变量“PATH”就是专门用于指定命令搜索路径的环境变量。我电脑上的命令搜索路径和搜索顺序如下。
|
||||
|
||||
SHELL$ echo $PATH
|
||||
/Library/Frameworks/Python.framework/Versions/3.7/bin
|
||||
:/usr/local/bin:/usr/bin
|
||||
:/bin
|
||||
:/usr/sbin
|
||||
:/sbin
|
||||
|
||||
|
||||
基于操作系统会按照上面的目录对命令进行搜索,我可以把“chromedriver”放在上面的任意一个目录当中。不过我更推荐你把它放在Python相同的目录中,便于你后续版本更新时,对它进行文件替换。
|
||||
|
||||
我的Python安装目录是“/Library/Frameworks/Python.framework/Versions/3.7/bin”,因此我就把“chromedriver” 放在这个目录中。放入之后,我就可以在命令行运行“chromedirver”。如果能够正确运行,则会提示运行成功。否则会提示找不到这条命令,你需要再检查一下它被存放的目录。正确运行的截图如下,供你参考。
|
||||
|
||||
|
||||
|
||||
在完成了最复杂的“Webdriver”安装之后,接下来就可以安装Python的库,并尝试使用Python库调用浏览器了。
|
||||
|
||||
第三步,安装“selenium”库。
|
||||
|
||||
安装“selenium”库可以继续使用pip命令。它的库和安装包同名,你可以在命令行运行以下命令,进行正确安装。
|
||||
|
||||
pip3 install selenium
|
||||
|
||||
|
||||
第四步,使用“selenium”访问网页。
|
||||
|
||||
安装成功之后,我们可以通过任意一个网页的访问,来测试从Python到“Webdriver”再到浏览器,整个工具链是否能正确运行。我把代码贴在下方供你参考。
|
||||
|
||||
from selenium import webdriver
|
||||
import time
|
||||
|
||||
browser = webdriver.Chrome()
|
||||
|
||||
browser.get("http://www.jd.com")
|
||||
time.sleep(10)
|
||||
browser.quit()
|
||||
|
||||
|
||||
这段代码实现了调用浏览器访问京东的网页,并在10秒后自动关闭浏览器的功能。如果能够正确运行,那么说明整个工作链是配置正确的。
|
||||
|
||||
这段用来访问京东来验证工作链的代码,由导入库、浏览器的初始化、控制浏览器行为三个部分组成。我来解释一下。
|
||||
|
||||
首先是导入库。我在代码的前2行,除了导入“selenium”库用来调用“Webdriver”外,还导入了“time”库。
|
||||
|
||||
导“time”库的原因就在于,我们需要使用“selenium”库模拟手工操作浏览器的流程,而手工操作会有一定的延迟。所以我就基于“time”库的“sleep()”函数,来模仿用户查看网页的行为,在打开网页等待了10秒后,再关闭网页。
|
||||
|
||||
其次是浏览器的初始化。浏览器的初始化在Python内部做了两件事情,第一个是找到之前安装的“chromedriver”文件,检查它是否可执行。第二件事情是根据调用的“Chrome()”函数,找到当前电脑中的浏览器并运行它。
|
||||
|
||||
最后控制浏览器的行为。我使用了“get()和quit()”两个函数,它们分别实现的是向服务器发起HTTP的“GET”请求,以及关闭浏览器的功能。
|
||||
|
||||
请求如果成功发送, 你会在“Webdriver”打开的浏览器看到网页内容,我把正确执行的结果截图放在下面供你参考。
|
||||
|
||||
|
||||
|
||||
第五步,取得网页的源代码并打印。
|
||||
|
||||
通过“selenium”打开浏览器之后,除了控制浏览器的行为,你还需要像上一讲的批量下载图片一样,批量获取网页的资源。这时你可以使用如下代码来获取当前浏览网页的源代码。这样在关闭浏览器之后,你仍然可以对网页的内容进行操作。代码如下:
|
||||
|
||||
content = browser.page_source
|
||||
print(content)
|
||||
|
||||
|
||||
掌握了“selenium”和“Webdriver”的安装和运行,接下来我们就可以利用“selenium”来实现京东的自动签到功能了。通过这一场景,来为你演示如何通过“selenium”模拟浏览器并实现自动签到。
|
||||
|
||||
如何实现京东自动签到
|
||||
|
||||
要想实现自动签到,你既要实现用户登录,又要模拟用户点击按钮。随着技术的日新月异,很多登录和按钮都不再使用简单的“POST”请求了。这时你就需要用“selenium”基于标签名字找到按钮的链接,像普通用户请求网站链接一样,不必理解登录和签到的内部的原理,就能实现自动化访问。
|
||||
|
||||
自动签到的原理与思路
|
||||
|
||||
“selenium”之所以能替代手动操作,达到自动化的效果,核心原因在于,我们可以通过“selenium”来模拟浏览器的操作。而我们可以通过对浏览器的功能拆解,把浏览器的交互行为,一一对应到“selenium”的非交互命令,之后就能实现自动化操作了。
|
||||
|
||||
这也正是自动签到的原理。再进一步说,“selenium”的行为越像浏览器,自动签到的功能就模拟得越完整。换言之,签到功能也就更自动化。那我们就来看看浏览器是如何实现登录和签到工作的。
|
||||
|
||||
使用浏览器进行登录有三个步骤,分别为:
|
||||
|
||||
|
||||
打开登录页面;
|
||||
切换到用户密码登录选项卡;
|
||||
点击登录按钮。
|
||||
|
||||
|
||||
我们依次来分析一下这三个步骤如果用“selenium”,各自是怎么实现的。
|
||||
|
||||
首先,打开登录页面,在浏览器中是鼠标点击“登录”按钮实现的,它的实现是向指定的URL发送“GET”请求,发送后服务端返回网页,就是你见到的登录页面了。
|
||||
|
||||
其次,打开登录页面后,默认的登录方式是二维码登录。
|
||||
|
||||
为了能让“selenium”能够模拟,我们需要通过用户名和密码方式登录。那接下来就切换到用户密码登录这一选项卡,这一切换动作在浏览器中可以通过使用鼠标实现。不过在“selenium”中,我们需要通过“GET”请求来替代它,才能实现非交互式的登录。
|
||||
|
||||
登录切换页面的网页源代码以及网页,我把相关截图一并放在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
最后,你需要在浏览器输入用户名密码,而后点击“登录”按钮,实现登录。
|
||||
|
||||
由于京东的登录界面是经过加密的,所以不能使用“requests-html”直接发送“POST”请求来实现登录,这里仍然要继续使用“selenium”获取用户名、密码的输入框,输入之后,模拟点击按钮的“GET”请求到服务器之后,才能实现登录。
|
||||
|
||||
使用“selenium”模拟浏览器,实现自动登录
|
||||
|
||||
在了解了浏览器的原理之后,你就可以使用“selenium”模拟浏览器实现登录了。我把“selenium”登录京东的代码写在下方供你参考。
|
||||
|
||||
from selenium import webdriver
|
||||
import time
|
||||
|
||||
browser = webdriver.Chrome()
|
||||
|
||||
# 访问主页
|
||||
browser.get("http://www.jd.com")
|
||||
time.sleep(2)
|
||||
|
||||
# 访问登录页
|
||||
browser.get("https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fwww.jd.com%2F")
|
||||
time.sleep(2)
|
||||
|
||||
# 切换为用户密码登录
|
||||
r = browser.find_element_by_xpath(
|
||||
'//div[@class="login-tab login-tab-r"]')
|
||||
browser.execute_script('arguments[0].click()', r)
|
||||
time.sleep(2)
|
||||
|
||||
# 发送要输入的用户名和密码
|
||||
browser.find_element_by_xpath(
|
||||
"//input[@id='loginname']").send_keys("username")
|
||||
time.sleep(1)
|
||||
for i in "password":
|
||||
browser.find_element_by_xpath(
|
||||
"//input[@id='nloginpwd']").send_keys(i)
|
||||
time.sleep(1)
|
||||
|
||||
# 点击登录按钮
|
||||
browser.find_element_by_xpath(
|
||||
'//div[@class="login-btn"]/a').click()
|
||||
time.sleep(10)
|
||||
|
||||
# 退出浏览器
|
||||
browser.quit()
|
||||
|
||||
|
||||
我在代码中,为了模拟浏览器的登录行为,一共使用了5个函数,每个函数都对应着“selenium”的一种操作。按照代码的运行顺序,我分别使用了请求网页、使用XPath查找标签、执行脚本、模拟键盘输入以及模拟鼠标点击五个动作,来实现登录的模拟操作。接下来我们先从请求网页开始,来为你逐个分析这五个函数。
|
||||
|
||||
第一个函数get() ,使用“GET”方式请求网页。当你需要点击超链接来到达新的页面时,都可以使用“get()”函数来实现。所以在代码中,请求主页和请求登录页面都是用了这个函数。
|
||||
|
||||
第二个函数find_element_by_xpath() 。在网页中,你需要对某一标签进行操作时,可以使用“XPath”先找到该标签。类似地,在浏览器中,“find_element_by_xpath() ”就是找到你想操作的标签。
|
||||
|
||||
比如我在代码中找到用户名和密码的选项卡、用户名密码的输入框、登录按钮等元素,都是用过它进行定位的。定位的方法和上节课讲的方式相同,通过浏览器的调试页面找到网页元素对应的代码,再把代码利用“XPath”提取出来即可。
|
||||
|
||||
第三个函数execute_script() ,是用来执行网页中自带的JavaScript脚本的。当你切换用户密码选项卡时,会发现网站的代码是用JavaScript来实现的,而你又不想再去手动编写脚本(执行“class”属性为“login-tab login-tab-r”的“div”标签下面的JavaScript脚本),那就可以使用这个函数,对第一个参数的JavaScript进行点击。
|
||||
|
||||
第四个函数send_keys(),用于在输入框填写内容。我在脚本中把“username”和“password”作为用户名和密码自动填入输入框中,你可以把它们替换为真正的用户名和密码。
|
||||
|
||||
这里需要注意的是,我对密码的填入增加了间隔时间,否则登录时会弹出图形验证码,阻止你进行自动登录。
|
||||
|
||||
第五个函数click() ,用于模拟鼠标点击超链接的动作,它和取得超链接中的href属性,并用“GET”方式访问是相同的功能,但是“click()”函数会比“get()”函数更直接,减少了从“a”标签再提取“href”属性的麻烦。
|
||||
|
||||
以上就是使用“selenium”模拟浏览器实现登录的主要思路。登录之后,我们想要实现签到的障碍就全都解决了。那么接下来我来带你继续实现自动签到功能。
|
||||
|
||||
利用“selenium”,实现自动签到
|
||||
|
||||
要想实现自动签到,还需要访问签到页面和点击签到按钮。这两个功能相信你在我为你分析了登录的代码之后,一定会想到访问页面可以使用“get()”函数,签到按钮可以使用“click()”函数。
|
||||
|
||||
我把自动签到的代码放在下面,一并提供给你。通过和登录代码的整合,你就可以利用“selenium”实现自动签到的功能了。
|
||||
|
||||
# 访问签到页面
|
||||
browser.get("https://mall.jd.com/index-1000002826.html")
|
||||
time.sleep(2)
|
||||
|
||||
# 签到并领金豆
|
||||
browser.find_element_by_xpath('//div[@class="jSign"]/a').click()
|
||||
time.sleep(10)
|
||||
|
||||
|
||||
这段代码就是访问签到页面并领取金豆的代码。需要你注意的是,访问页面和签到两个操作之间需要等待几秒,否则会因为网页加载过慢,导致签到按钮还没加载完就发送了点击动作,让自动签到失败。
|
||||
|
||||
总结
|
||||
|
||||
最后,我来为你总结一下本节课的主要内容。在这一讲中,我通过“selenium”实现了浏览器的模拟,并把浏览器的点击链接、用户登录、切换标签等常用功能使用“selenium”的函数和XPath转换为用Python可以控制的操作。转换之后,你就可以利用Python控制浏览器的行为,把这些需要鼠标的交互式操作编写为非交互式的代码,达到自动化控制浏览器的目的了。
|
||||
|
||||
除了实现自动签到以外,通过“selenium”的函数组合,你就能模拟浏览器的绝大部分操作。我也希望你能够在学完这节课后,想一想你的工作当中是否也使用了浏览器进行重复的工作,希望你能通过对本讲的学习,把它们改为自动化。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,最后我来为你留一道思考题,你能否利用今天讲的模拟浏览器的方法,自动登录Github,并以“Python”作为关键字进行搜索,然后把搜索的结果保存到一个文件呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
395
专栏/Python自动化办公实战课/20不同文件混在一起,怎么快速分类?.md
Normal file
395
专栏/Python自动化办公实战课/20不同文件混在一起,怎么快速分类?.md
Normal file
@ -0,0 +1,395 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
20 不同文件混在一起,怎么快速分类?
|
||||
你好,我是尹会生。
|
||||
|
||||
今天我们的内容要从一碗香喷喷的蛋炒饭开始。要想做一份传说中的蛋炒饭,肯定要放胡萝卜、黄瓜、火腿肠还有葱花等好多种类的食材。
|
||||
|
||||
这是不是像你的桌面一样,为了完成某一项目,需要将音频、视频、文档、图片等各种格式组合在一起。但你在你完成了项目之后,想要将它们进行整理的时候,会发现各类文件堆满了桌面,要想从桌面找都某个文件就像从蛋炒饭里将所有的葱花挑出来一样困难。
|
||||
|
||||
对于这种情况,我们可以采用Python按照扩展名,分门别类地整理到不同的目录,方法虽然找到了,但是在你动手写代码的时候发现也不容易,就像从蛋炒饭中把鸡蛋、米饭、胡萝卜丁、火腿肠等食材挑出来,分类型放在不同的盘子中。这无疑会让你非常头痛。
|
||||
|
||||
所以在今天这节课中,我就带你来学习一下,怎么用我们之前学习过的自定义函数、队列,来实现按照扩展名对文件的自动分类。
|
||||
|
||||
批量分类的方法与思路
|
||||
|
||||
在带你学习代码之前,我要先为你讲解一下解决这类问题的思路,因为像自动分类这种场景,可以被拆解成判断类型逻辑和移动逻辑,而这两个逻辑有前后顺序,还有前后依赖关系。这一大类问题,你在工作中会经常遇到,当你学会了这类问题的解决思路之后,再遇到同类问题,就能非常容易的想到处理逻辑,那再通过代码将你的思路实现出来,也就不在话下了。
|
||||
|
||||
要想实现自动分类,就要设计好分类的规则****,以及按照规则对每一个文件分类的通用模式。我们先来学习设计分类规则。
|
||||
|
||||
怎样设计合理的数据类型
|
||||
|
||||
分类规则是指将扩展名和要移动的目录建立对应关系,而想要保存对应关系,就必须设计一个合理的数据类型,既能保存这种对应关系,又不能太复杂,为自己编码带来困扰。由此来看,分类规则的核心就是设计合理的数据类型。
|
||||
|
||||
这么说,你可能有点难以理解。我先把代码提供给你,然后我来带着你分析,我们为什么要设计数据类型。代码比较长,为了让你有更好的学习效果,我把分类前和分类后的文件目录结构都提供给你。通过对照分析分类前后文件所在的目录,帮你理解自动分类代码的实现思路。
|
||||
|
||||
分类前的目录和文件结构如下:
|
||||
|
||||
$ ls -R files
|
||||
dir1 a.mp4 c.rm d.avi b.mp3
|
||||
|
||||
files/dir1:
|
||||
aa.exe bb.bat
|
||||
|
||||
|
||||
分类后的目录和文件结构如下:
|
||||
|
||||
$ ls -R files
|
||||
dir1 execute movie music
|
||||
|
||||
files/dir1:
|
||||
|
||||
files/execute:
|
||||
aa.exe bb.bat
|
||||
|
||||
files/movie:
|
||||
a.mp4 c.rm d.avi
|
||||
|
||||
files/music:
|
||||
b.mp3
|
||||
|
||||
|
||||
对比分类前后的目录和文件结构,可以看到,我并没有把每一种扩展名保存在一个独立的文件夹中,而是把这些文件按照音乐、视频和可执行文件的方式进行了分类,然后再把同一类型的文件放在相同目录中。
|
||||
|
||||
这样的实现方式为工作中查找文件带来了便利,但是不可避免地,会增加我们编码工作的复杂度,因为你不能通过循环遍历一次文件来实现分类了。
|
||||
|
||||
我这么表述,你可能还不太理解它的难度具体在哪里,我们还是回到蛋炒饭的例子。如果把每个扩展名都放在一个目录中,就类似把蛋炒饭中的每种原材料都放在一个碗里。你只要准备和原材料类型相同数量的碗,去分类就好了。
|
||||
|
||||
而分类方式如果变成了只有三个碗,我们此时需要把材料要把主食、素菜、荤菜分别放在三个碗中,那你在遍历蛋炒饭的时候,就需要二次分类。
|
||||
|
||||
对于这样的需求,你在编写代码前需要设计合理的数据类型,把碗的数量和蛋炒饭的原料对应关系事先确定好。而确定的这一对应关系在编程语言中就被称作设计数据类型。
|
||||
|
||||
那怎样来设计合理的数据类型呢?让我们来看看文件自动分类中的“碗和原材料”。
|
||||
|
||||
文件自动分类功能中的“碗”是多个文件夹。但是在Python中,表示多个文件夹的时候,我们会采用字符串,方便和文件夹名称建立对应关系。而且你还可以通过创建文件夹的库,把字符串作为文件夹名字,实现字符串到文件夹的对应关系。
|
||||
|
||||
文件自动分类功能中的“原材料”是扩展名。扩展名也要使用字符串类型。那么每组文件夹到扩展名对应关系都是一个字符串对应多个字符串,相信你一定想到了这种对应关系应该使用字典保存它们的映射关系了吧,那作为字典值的多个扩展名,由于在运行程序前指定好就不会再修改了,所以我将扩展名字符串组成元组,将元组作为字典的值。
|
||||
|
||||
那么根据我们的分析,我把扩展名和文件类型定义为如下字典,如果你的工作场景使用到了更多的扩展名,或者使用了和我不同的分类,也可以修改这个字典。
|
||||
|
||||
# 定义文件类型和它的扩展名
|
||||
file_type = {
|
||||
"music": ("mp3", "wav"),
|
||||
"movie": ("mp4", "rmvb", "rm", "avi"),
|
||||
"execute": ("exe", "bat")
|
||||
}
|
||||
|
||||
|
||||
通过刚才对字典的定义,我们给扩展名自动分类制定好了分类的规则。那接下来我们继续设计程序,按照这一分类规则,进行文件的读取和分类。
|
||||
|
||||
怎样设计生产者消费者模式
|
||||
|
||||
文件的读取和分类是两个不同的功能,你在编写代码时可以把它们编写成两个不同的函数。
|
||||
|
||||
但是由于今天的程序比以往要复杂,所以实现这两个函数的思路也会比较多。比如:
|
||||
|
||||
|
||||
你可以把所有的文件全部读取之后,再按照分类规则移动到新的目录;
|
||||
也可以读取一个紧接着将它移动到一个新的文件夹里;
|
||||
当然还可以读取一部分文件之后,将一部分文件进行移动,然后继续读取第二批文件。
|
||||
|
||||
|
||||
到底选择哪种方案才是最佳实践呢?
|
||||
|
||||
在这种情况下,你最希望的是能够向有丰富开发经验的开发人员请教,看他是怎么实现类似需求的,然后按照他的实现逻辑来编写你的代码。这也是专业软件开发人员面对这一问题时的通常做法:去寻找和当前场景相似的“设计模式”。因为设计模式是众多软件开发人员经过相当长的时间,对某一场景进行大量试错总结出来的经验。那我们可以利用它,来解决我们当前场景下的问题。
|
||||
|
||||
我们当前的场景刚好和设计模式中的“生产者消费者模式”比较吻合。生产者消费者模式的描述是这样的:有两个进程共用一个缓冲区,两个进程分别是生产数据和消费数据的。而缓冲区,用于存放生产进程产生的数据,并让消费进程从缓冲区读取数据进行消费。
|
||||
|
||||
使用生产者消费者模式刚好能解决文件读取和文件分离的逻辑。我把读取当前文件名称和路径函数作为生产者,把分类和移动文件的逻辑作为消费者。在生产者消费者中间,我再使用队列作为它们中间的缓冲区。
|
||||
|
||||
可以看到,使用生产消费者模式,我主要是增加了一个队列,而不是从生产者直接把数据交给消费者。这样做主要有三个好处:
|
||||
|
||||
|
||||
如果生产者比消费者快,可以把多余的生产数据放在缓冲区中,确保生产者可以继续生产数据。
|
||||
如果生产者比消费者慢,消费者处理完缓冲区中所有数据后,会自动进入到阻塞状态,等待继续处理任务。
|
||||
缓冲区会被设置为一定的大小,当生产者的速度远远超过消费者,生产者数据填满缓冲区后,生产者也会进入到阻塞状态,直到缓冲区中的数据被消费后,生产者才可以继续写入。而当消费性能不足时,可以等待消费者运行,减少生产者和消费者在进度上相互依赖的情况。
|
||||
|
||||
|
||||
通过分析我们发现,可以采用生产者消费者模式来编写文件的读取和分类代码。
|
||||
|
||||
考虑到是你初次接触设计模式,为了不让你产生较大的学习心理负担,我把其中的多线程并发访问缓冲区简化成单线程版本,这样你能根据代码的执行过程,先学会简单的生产者和消费者模式。
|
||||
|
||||
在分类规则的“file_type”字典之后,我增加了以下代码,实现了单线程版本的生产者消费者设计模式。如下:
|
||||
|
||||
from queue import Queue
|
||||
# 建立新的文件夹
|
||||
make_new_dir(source_dir, file_type)
|
||||
|
||||
# 定义一个用于记录扩展名放在指定目录的队列
|
||||
filename_q = Queue()
|
||||
|
||||
# 遍历目录并存入队列
|
||||
write_to_q(source_dir, filename_q)
|
||||
|
||||
# 将队列的文件名分类并写入新的文件夹
|
||||
classify_from_q(filename_q, file_type)
|
||||
|
||||
|
||||
上面的代码实现了从定义队列到文件处理的完整函数调用。在后续(第22节、28节)我为你讲完面向对象、类和多线程后,我会带你再实现一个多线程版本的生产者消费者模型,让你完全掌握这一设计模式,并应用到更多的场景中。
|
||||
|
||||
在确定了分类规则用到的数据模型,以及分类流程用到的设计模式之后,接下来就到了具体实现代码的环节了。
|
||||
|
||||
在生产者消费模式下,我通过定义三个函数来分别实现三个功能,如下:
|
||||
|
||||
|
||||
定义函数make_new_dir(),实现新建分类文件夹的功能;
|
||||
定义函数write_to_q(),实现写入当前文件路径到队列的功能;
|
||||
定义函数classify_from_q(),实现把队列中的文件分类并移动的功能。
|
||||
|
||||
|
||||
接下来,我带你依次学习一下它们各自的实现代码。
|
||||
|
||||
如何实现分类
|
||||
|
||||
要想实现分类,首先要先创建分类需要的文件夹。这里需要注意,创建文件夹的操作要在批量分类前完成,否则在每次移动文件前,你还得对要移动的文件夹进行判断,这会影响程序的运行效率。
|
||||
|
||||
我们来看一下怎样利用分类规则的字典“file_type”,以及make_new_dir()函数来批量分类文件夹。
|
||||
|
||||
如何建立分类文件夹
|
||||
|
||||
批量建立文件夹操作的前提是建立哪几个文件夹,以及在哪个目录下建立它。基于这样的考虑,我为make_new_dir()函数增加了两个参数:
|
||||
|
||||
|
||||
使用dir指定建立文件夹的目录;
|
||||
使用type_dir指定按照哪个字典建立。
|
||||
|
||||
|
||||
而建立文件夹,可以使用我们学习过的os模块,通过os.mkdirs()函数建立一个新的文件夹。代码如下:
|
||||
|
||||
import os
|
||||
# 定义文件类型和它的扩展名
|
||||
file_type = {
|
||||
"music": ("mp3", "wav"),
|
||||
"movie": ("mp4", "rmvb", "rm", "avi"),
|
||||
"execute": ("exe", "bat")
|
||||
}
|
||||
|
||||
source_dir = "/Users/user1/Desktop/files
|
||||
|
||||
def make_new_dir(dir, type_dir):
|
||||
for td in type_dir:
|
||||
new_td = os.path.join(dir, td)
|
||||
if not os.path.isdir(new_td):
|
||||
os.makedirs(new_td)
|
||||
|
||||
# 建立新的文件夹
|
||||
make_new_dir(source_dir, file_type)
|
||||
|
||||
|
||||
这段代码把字典的key作为文件夹名称,通过遍历字典来批量创建文件夹。这里还有两个你需要注意的技巧。
|
||||
|
||||
第一个是文件路径的拼接。代码中要新建的文件夹路径,是由“source_dir”和遍历字典得到的“字典的key”两部分连接组成的。如果你使用字符串的连接函数“join()”函数来连接这两部分,你需要增加路径连接符号”/“,而如果你的操作系统从mac换成windows,则需要使用反斜线”“,这时候你就要再修改代码,把斜线改为正确的路径分隔符。
|
||||
|
||||
因此我采用了“os.path.join()”函数,这个函数会自动判断操作系统并增加斜线”/“,它还避免了你为已经有“/”的路径重复添加的问题。
|
||||
|
||||
另一个小技巧是判断目录是否存在。我在创建目录前,使用了os.path.isdir()函数,判断了目录是否存在,这样做的好处是避免重复创建目录。
|
||||
|
||||
另外,我还想教给你和它功能相近的两个函数,它们分别是os.path.isfile()和os.path.isexist()。
|
||||
|
||||
|
||||
前者用来判断该路径是否存在,并且是否是同一个文件类型。
|
||||
后者用来判断路径是否存在,并且这个路径可以是文件也可以是目录。
|
||||
|
||||
|
||||
结合代码中出现的isdir()函数,你就可以对一个目录到底是文件还是目录,以及是否存在进行判断了。
|
||||
|
||||
创建目录之后,我们就要开始对当前的文件进行遍历,并存入缓冲区中。
|
||||
|
||||
怎样遍历目录并写入队列
|
||||
|
||||
我先把遍历目录的代码写在下面,然后再为你详细讲解它。
|
||||
|
||||
from queue import Queue
|
||||
|
||||
# 遍历目录并存入队列
|
||||
def write_to_q(path_to_write, q: Queue):
|
||||
for full_path, dirs, files in os.walk(path_to_write):
|
||||
# 如果目录下没有文件,就跳过该目录
|
||||
if not files:
|
||||
continue
|
||||
else:
|
||||
q.put(f"{full_path}::{files}")
|
||||
|
||||
#########
|
||||
source_dir = "/Users/user1/Desktop/files
|
||||
|
||||
# 定义一个用于记录扩展名放在指定目录的队列
|
||||
filename_q = Queue()
|
||||
|
||||
# 遍历目录并存入队列
|
||||
write_to_q(source_dir, filename_q)
|
||||
|
||||
|
||||
这段代码实现了定义队列,并把指定目录下所有的文件名称和路径写入到队列中的功能。在这里有两个关键的知识点需要你掌握,它们分别是如何遍历目录,以及如何写入队列。
|
||||
|
||||
先来看如何遍历目录的函数。它在代码的第5行,叫做os.walk()函数,和之前我们学习过的pathlib()函数一样,都能实现对目录的遍历,但是它的返回值值得你学习一下。
|
||||
|
||||
我使用for循环遍历walk()函时,分别使用了full_path、dirs和files三个变量,因此walk()函数的返回值有三个。这三个变量分别对应每次遍历的文件的完整路径、文件所在的目录,以及该目录下所有文件名称的列表。
|
||||
|
||||
你可以根据你的工作场景灵活组合这三个变量,由于我在移动的场景需要文件的完整路径和文件名,所以我只使用了第一个参数full_path和第三个参数files。
|
||||
|
||||
此外,我在实现遍历时,也像创建目录一样增加了容错。如果某一目录下没有文件,就不需要对该目录进行移动了,所以我使用了“if not files” 来判断files列表的值。
|
||||
|
||||
由于我增加了not关键字,if的判断条件就从列表中包含文件,变成了列表中没包含任何一个文件。当条件成立时,则执行continue语句,跳过当前这次循环。而else语句中,是当files列表中包含了文件名称的处理过程,在这种情况下,我会将文件的完整路径和该路径下的文件列表放到缓冲区中。
|
||||
|
||||
在当前代码,我把队列这一数据类型作为缓冲区,它和我们之前学习过的多进程通信的队列功能和用法完全相同,区别则是我们导入的库名称不同。
|
||||
|
||||
要想把对象存入队列,可以使用put()函数。从队列取出数据,则可以使用get()函数。我把循环遍历得到的路径和文件名称均使用了put()函数存放到队列中,实现了生产者这一角色。
|
||||
|
||||
接下来,我们来学习消费者这一角色实现的代码,学习如何实现分类并将文件移动到新的文件夹的。
|
||||
|
||||
分类并移动到新的文件夹
|
||||
|
||||
同样的,我先把代码写在下面,然后再为你详细分析如何实现从队列取出文件名并进行分类的功能。
|
||||
|
||||
|
||||
# 移动文件到新的目录
|
||||
def move_to_newdir(filename_withext, file_in_path, type_to_newpath):
|
||||
# 取得文件的扩展名
|
||||
filename_withext = filename_withext.strip(" \'")
|
||||
ext = filename_withext.split(".")[1]
|
||||
|
||||
for new_path in type_to_newpath:
|
||||
if ext in type_to_newpath[new_path]:
|
||||
oldfile = os.path.join(file_in_path, filename_withext)
|
||||
newfile = os.path.join(source_dir, new_path, filename_withext)
|
||||
shutil.move(oldfile, newfile)
|
||||
|
||||
# 将队列的文件名分类并写入新的文件夹
|
||||
def classify_from_q(q: Queue, type_to_classify):
|
||||
while not q.empty():
|
||||
# 从队列里取目录和文件名
|
||||
item = q.get()
|
||||
|
||||
# 将路径和文件分开
|
||||
filepath, files = item.split("::")
|
||||
|
||||
# 剔除文件名字符串出现的"[" "]",并用","做分隔转换为列表
|
||||
files = files.strip("[]").split(",")
|
||||
# 对每个文件进行处理
|
||||
for filename in files:
|
||||
# 将文件移动到新的目录
|
||||
move_to_newdir(filename, filepath, type_to_classify)
|
||||
|
||||
|
||||
在这段代码中,我实现了从队列取出文件名称和目录,并根据分类将文件移动到新的目录。
|
||||
|
||||
由于消费者的逻辑是从队列读取内容和移动文件两个功能组成的,所以我把消费者拆分成了两个函数进行编写:
|
||||
|
||||
|
||||
classify_from_q()函数,用来实现从队列读取文件列表,并遍历列表,得到每一个文件名称;
|
||||
move_to_newdir()函数,把文件名称、路径、分类规则作为参数,真正实现移动。
|
||||
|
||||
|
||||
相应的,如果你在编写包含多个功能的程序时,也要尽量保持每个功能的独立性,把每一个功能尽量放在一个函数中,这样能有效提升你的代码的可读性。
|
||||
|
||||
这两个函数虽然比较长,但是大部分都是我们学过的内容,我想为你重点讲解一下第一次接触到的两个知识点,一个是in操作,一个是利用shutil库的move()函数实现的重命名。
|
||||
|
||||
in操作叫做成员操作符,它能支持目前我们学习过的所有基础数据类型,用来判断一个值是否是列表、元组、字典等基础数据类型中的一员。如果这个值是基础类型的成员之一就会直接返回True,如果不是成员之一返回的就是False。有了in操作符,你就不用手动遍历基础数据类型,再使用“==”逐个去判断某个值和数据类型中的成员是否相等了。
|
||||
|
||||
我举个例子,你会更容易理解。我在代码中使用了这样一行代码:“if ext in type_to_newpath[new_path]” :
|
||||
|
||||
|
||||
“ext” 就是文件的扩展名,就像是“a.mp3”的扩展名是“mp3”;
|
||||
“type_to_newpath[new_path]”是字典“type_to_newpath”中,以“new_path”作为key的值,就像是“type_to_newpath = { “music”: (“mp3”, “wav”) }”的“(“mp3”, “wav”)”。-
|
||||
我把变量改成具体的变量值,那这行代码就变成了下面的样子:
|
||||
|
||||
|
||||
"mp3" in ("mp3", "wav")
|
||||
|
||||
|
||||
如果扩展名在元组中,那么if条件的返回结果就是True,就可以进行文件的移动了,如果结果是False则从字典中继续取下一个key,直到所有的key遍历完成之后,仍然没有匹配的扩展名,就把文件保持在原地,不做任何移动操作。
|
||||
|
||||
还有一个我们第一次接触到的函数是shutil库的move()函数,这个函数是直接对系统上的文件进行操作的,所以你需要注意移动以后的文件名不要和已有的文件名冲突,这样会导致重名覆盖已有的文件,从而丢失文件。因此在你没有十足的把握之前,建议你在移动前增加一个判断功能,判断移动的文件是否存在,如果存在则提示使用脚本的人此情况,或移动前将文件进行改名。
|
||||
|
||||
以上就是如何对混在一起的多个扩展名的文件,进行自动分类的完整过程。这节课的完整代码比较长,我一并贴在了下方,帮你理解多个函数之间的调用关系和执行顺序。
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from queue import Queue
|
||||
|
||||
# 建立新的目录
|
||||
def make_new_dir(dir, type_dir):
|
||||
for td in type_dir:
|
||||
new_td = os.path.join(dir, td)
|
||||
if not os.path.isdir(new_td):
|
||||
os.makedirs(new_td)
|
||||
|
||||
# 遍历目录并存入队列
|
||||
def write_to_q(path_to_write, q: Queue):
|
||||
for full_path, dirs, files in os.walk(path_to_write):
|
||||
# 如果目录下没有文件,就跳过该目录
|
||||
if not files:
|
||||
continue
|
||||
else:
|
||||
q.put(f"{full_path}::{files}")
|
||||
|
||||
# 移动文件到新的目录
|
||||
def move_to_newdir(filename_withext, file_in_path, type_to_newpath):
|
||||
# 取得文件的扩展名
|
||||
filename_withext = filename_withext.strip(" \'")
|
||||
ext = filename_withext.split(".")[1]
|
||||
|
||||
for new_path in type_to_newpath:
|
||||
if ext in type_to_newpath[new_path]:
|
||||
oldfile = os.path.join(file_in_path, filename_withext)
|
||||
newfile = os.path.join(source_dir, new_path, filename_withext)
|
||||
shutil.move(oldfile, newfile)
|
||||
|
||||
# 将队列的文件名分类并写入新的文件夹
|
||||
def classify_from_q(q: Queue, type_to_classify):
|
||||
while not q.empty():
|
||||
item = q.get()
|
||||
|
||||
# 将路径和文件分开
|
||||
filepath, files = item.split("::")
|
||||
|
||||
files = files.strip("[]").split(",")
|
||||
# 对每个文件进行处理
|
||||
for filename in files:
|
||||
# 将文件移动到新的目录
|
||||
move_to_newdir(filename, filepath, type_to_classify)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 定义要对哪个目录进行文件扩展名分类
|
||||
source_dir = "/Users/edz/Desktop/files"
|
||||
|
||||
# 定义文件类型和它的扩展名
|
||||
file_type = {
|
||||
"music": ("mp3", "wav"),
|
||||
"movie": ("mp4", "rmvb", "rm", "avi"),
|
||||
"execute": ("exe", "bat")
|
||||
}
|
||||
|
||||
# 建立新的文件夹
|
||||
make_new_dir(source_dir, file_type)
|
||||
|
||||
# 定义一个用于记录扩展名放在指定目录的队列
|
||||
filename_q = Queue()
|
||||
|
||||
# 遍历目录并存入队列
|
||||
write_to_q(source_dir, filename_q)
|
||||
|
||||
# 将队列的文件名分类并写入新的文件夹
|
||||
classify_from_q(filename_q, file_type)
|
||||
|
||||
|
||||
|
||||
小结
|
||||
|
||||
最后让我来为你做个总结,实现文件自动分类是目前我们编写代码量最多的一讲。面对功能复杂、代码量增多时,你就需要通过函数设计合理的功能封装,还要考虑如何使用参数进行函数的通信。
|
||||
|
||||
当你的多个函数之间的工作流程也可以进行多种组合时,你可以借助开发高手的代码经验–设计模式,来实现工作逻辑上的函数组合。在本讲中我为你介绍的这种普遍应用于产品生产、销售的生产者消费者模式就是设计模式中最常用的一种。
|
||||
|
||||
希望你能在掌握如何使用Python提高工作效率的同时也能掌握设计模式、函数这些编写Python的思路。这样,你在面对更庞大的需求时,也会更快地设计出结构清晰、逻辑清楚的代码。高效编程也是高效办公的一部分!
|
||||
|
||||
思考题
|
||||
|
||||
我来为你留一道思考题,如果我按照文件的大小对文件分成三类,将“大于1GB”“1GB到100MB”“小于100MB”三类的文件名和大小,依次显示在屏幕上,你会怎样实现呢?
|
||||
|
||||
|
||||
|
||||
|
283
专栏/Python自动化办公实战课/21SQLite文本数据库:如何进行数据管理(上)?.md
Normal file
283
专栏/Python自动化办公实战课/21SQLite文本数据库:如何进行数据管理(上)?.md
Normal file
@ -0,0 +1,283 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
21 SQLite文本数据库:如何进行数据管理(上)?
|
||||
你好,我是尹会生。
|
||||
|
||||
你在办公中,一定遇到过需要数据持久化的问题。数据持久化,简单来说,就是当你关闭程序的时候,数据依然可以完整地保存在电脑中。你可能会想到用文本文件、Excel来存储这些数据,文本呢,没有办法按列读写数据,Excel呢,支持的默认API无法进行复杂查询。所以我今天要给你介绍一个功能强大,但编写代码又简单的数据库SQLite。
|
||||
|
||||
你可以用SQLite存储结构化的数据,把程序的处理结果保存到电脑中,便于下次或使用新的程序对这些数据进行访问。
|
||||
|
||||
用SQLite存储结构化的数据,包括增删改查这些操作。所以今天这节课,我就教你怎么来使用函数封装SQL语句,实现数据的读取和写入,下一节课我们再来学习如何通过类实现复杂的SQL语句的封装,以及如何更新和删除数据。
|
||||
|
||||
在讲解这些之前,考虑到SQLite在持久化数据存储的重要性,我想要先给你介绍SQLite的优势。
|
||||
|
||||
使用SQLite代替文本和Excel有哪些优势
|
||||
|
||||
也许你对SQLite这个名字还很陌生,但其实你早就在手机、智能电视、机顶盒等电子设备中用到过它了,比如手机上的通讯录,就是使用SQLite存储联系人的。
|
||||
|
||||
SQLite中存储的内容是结构化数据,像是通讯录、企业ERP数据、财务数据等这些存储和排列很有规律的数据,就被称作结构化数据。类似Excel的格式一样,分为“行”和“列”。以存储通讯录为例,每一列会提前指定好哪一列存放姓名、哪那一列存放电话号码,而每一行就是一个联系人的姓名和电话的具体记录。
|
||||
|
||||
在自动化办公中,你可以把结构化数据存放到SQLite中,它的处理速度和文件体积上要比文本文件和Excel更有优势。它会比文本文件的处理速度更快,比Excel需要的空间更少。甚至有人将SQLite应用到了每天点击量在10万次的网站上,足见它的稳定和高效的处理效率。
|
||||
|
||||
SQLite和你听说过的大型数据库Oracle、MySQL数据库不同,它更多是面向办公场景和移动设备的,所以它有大型数据库的稳定、高效、支持SQL语言的特性,但是也要比大型数据库学习起来更加简单。
|
||||
|
||||
正是由于它拥有数据库的特性,所以从SQLite中查找数据要比从文本文件中更快。而且它的数据还满足数据库必需的“增删改查”,但不支持设置字体、字号,所以存储一条数据所使用的空间会比Excel更小。这些特性叠加起来就刚好形成了SQLite的特有优势:
|
||||
|
||||
|
||||
数据查询速度快;
|
||||
存放数据的空间占用少;
|
||||
实现了一般数据库能够支持的(基于SQL语言的)增删改查。
|
||||
|
||||
|
||||
总结来说,就是如果你需要存放结构化的数据,而且只关注数据读取的效率,不关注数据的样式,而且还需要编程语言来进行数据访问的话,使用SQLite要比文本文件、Excel更适合你的办公场景。
|
||||
|
||||
我为你介绍了SQLite这么多好处,那赶快带着你把它用起来吧。
|
||||
|
||||
为SQLite建立数据表
|
||||
|
||||
要想使用SQLite数据库,光有文件还不够,你还要为它建立数据表,类似你新建了一个Excel工作簿的文件,与此同时你还要建立一个工作表,把数据写在工作表上, 再将多张工作表放在工作簿上面。
|
||||
|
||||
因此,要想实现对数据库的操作,我也需要为SQLite创建一张工作表,接下来我就用一个创建手机通讯录数据表的脚本,为你演示一下我是如何为SQLite数据库创建它的“工作表”的。
|
||||
|
||||
SQLite建立数据表的一般流程
|
||||
|
||||
为了能够让你更具体地学习数据表是怎样创建和使用的,我先把创建数据表的代码写出来给你,对照代码我来为你讲解,SQLite建立数据表的流程。
|
||||
|
||||
import sqlite3
|
||||
import pathlib
|
||||
|
||||
# 数据库文件的路径和文件名称
|
||||
dir = pathlib.PurePath(__file__).parent
|
||||
db = pathlib.PurePath(dir, "contents.db")
|
||||
|
||||
# 创建连接
|
||||
conn = sqlite3.connect(db)
|
||||
|
||||
# 创建游标
|
||||
cur = conn.cursor()
|
||||
|
||||
# 定义要执行的SQL语句
|
||||
sql = '''CREATE TABLE address_book(
|
||||
id INT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
phone INT NOT NULL
|
||||
)'''
|
||||
|
||||
# 执行SQL
|
||||
try:
|
||||
cur.execute(sql)
|
||||
print("创建成功")
|
||||
except Exception as e:
|
||||
print("创建失败")
|
||||
print(f"失败原因是:{e}")
|
||||
finally:
|
||||
# 关闭游标
|
||||
cur.close()
|
||||
# 关闭连接
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
这段代码实现了连接SQLite数据库,并创建通讯录数据表的功能。要想使用SQLite数据库,你需要掌握四个步骤,我来带你依次学习。
|
||||
|
||||
第一步,连接数据库文件。
|
||||
|
||||
连接数据库文件有两种情况,数据库文件不存在和数据库文件已经存在。如果数据库文件不存在,那么第一次连接时就会建立一个空的数据库文件,你需要注意的是,创建时要确保Python程序对操作数据库文件的目录有写入权限,否则会因权限不足而创建失败。另一种情况,如果数据库文件已经存在,则会直接打开数据库文件,相应的数据库文件中已经建立的表和数据,也可以直接进行操作。
|
||||
|
||||
我在代码中演示的是数据库文件不存在的情况。为了便于你观察数据库是否被创建成功,我在代码中使用了之前学习过的“file”变量,将数据库文件contents.db和脚本放在了相同的目录。
|
||||
|
||||
第二步,创建游标。
|
||||
|
||||
游标,它的官方定义叫做操作行指针。它是第一次接触数据库的人,最难理解的概念,其实你可以想象一下,当你只能按行操作Excel时,你可以一次选中一行,也可以一次选中多行。而游标就是记录你当前选中的到底是哪一行。
|
||||
|
||||
在计算机中记录当前选中的行,是需要占用内存的,因此,你必须先声明一个游标对象,利用这一对象申请计算机内存来记录你当前的行数,用于后续对选中行继续操作。
|
||||
|
||||
创建游标的函数是cursor(),创建之后,一般会将它赋值给一个变量,后续操作可以利用变量来再次引用创建好的游标。
|
||||
|
||||
第三步,执行SQL语句。
|
||||
|
||||
SQL语句是和SQLite数据库“打交道”的主要形式,你需要通过SQL语句来操纵数据库。在上面的代码中,我就在代码的15行定义了一个创建表的SQL语句;在代码的第23行通过“excute()”函数运行该语句,运行之后就可以为SQLite创建表了。
|
||||
|
||||
第四步,关闭游标和连接。
|
||||
|
||||
当你完成对数据库的操作后,就需要手动关闭游标和连接,来正确退出数据库,这样可以避免在内存中数据还未来得及写入磁盘时,由于突然关闭进程导致数据丢失。
|
||||
|
||||
以上四个步骤是创建数据表的基本操作,也是SQLite乃至其他数据库使用SQL语句的通用步骤,你需要记住这种模式,这样你在使用Python来操作各种数据库时,保证数据的持久化。
|
||||
|
||||
我在代码中还使用了一个小技巧,叫做异常捕获。通过异常捕获你可以在程序运行过程出现问题时,及时对问题进行处理。
|
||||
|
||||
捕获异常就像是你在森里中捕猎,猎物就是森林中你无法预知什么时候会出现的小动物。而捕获它们的方法,就是在猎物必经之路上设置陷阱。如果没有出现猎物,一切照常;如果出现了猎物,就会被陷阱捕获到。
|
||||
|
||||
例如我在代码中设置的陷阱就是“try”语句块,当这一语句块中的“excute()”在执行时如果出错,那么它后面的语句不会再继续执行,而是直接转向except语句。并且你会得到你的“猎物”–产生异常的原因“Exception”类。你可以像我一样将它重新命名为“e”,并输出到终端,帮你定位这一错误产生的原因。
|
||||
|
||||
相信你还记得我在讲操作SQLite四个必要步骤时提到的最后一个步骤,你不能因为出现错误,而“忘记”关闭游标和数据库,所以在“try”“except”语句中还有一个和它们配合使用的“finally”语句,它的作用是无论是否抛出异常,“finally”语句块下的代码都会被执行。
|
||||
|
||||
你看,增加了异常处理的数据库处理流程就要比原始的代码更加健壮。因此我建议你在进行数据库操作时,务必要为执行SQL语句的部分增加异常处理,因为它关系到你的数据是否能够安全的被保存到硬盘中。
|
||||
|
||||
建立数据表的SQL语句
|
||||
|
||||
在你了解了SQLite的一般流程之后,我再为你讲解一下建立数据表的SQL语句。
|
||||
|
||||
SQL语句,类似你使用“open()”函数打开文件之后,对文件内容执行的各种操作。根据不同的标准,它有不同的语法格式。SQLite能够支持的是大部分的SQL92标准(触发器和可写视图不被支持),因此,你可以使用符合SQL92标准的语句创建、删除表,并对数据进行增删改查的基本操作。
|
||||
|
||||
创建一张数据表,使用的SQL语句是“CREATE TABLE 表名称 (包括表的字段、字段类型和约束条件)。我还是以创建通讯录的表为例:
|
||||
|
||||
CREATE TABLE address_book(
|
||||
id INT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
phone INT NOT NULL
|
||||
)
|
||||
|
||||
|
||||
这条语句的“address_book”是表名称,它有三个字段(也可以称作是三列数据),分别为id、name和phone。而每个字段都由字段名称、字段类型和可选的约束条件三部分组成。
|
||||
|
||||
|
||||
字段名称,是用于操作表时方便进行列操作的名字。
|
||||
字段类型,会限制你对该字段写入数据的类型,如果类型不正确会报错或被填入空值。SQLite能支持的类型非常多,我在示例中使用了最常见的整数和字符串,其他类型你可以通过官方文档来了解。
|
||||
约束条件,是对该列强制执行的规则。例如我使用了“NOT NULL”约束,确保在数据写入时,该字段不能为空值。同时,我还使用了“PRIMARY KEY”,确保了字段唯一性,确保该字段的值不会重复。更多的约束条件,你同样可以根据官方文档来了解。
|
||||
|
||||
|
||||
通过创建SQLite的数据表,我为你讲解了操作SQLite的基本流程中已经用到的SQL语句,以及如何使用异常捕获来使程序更加健壮,特别是像操作数据的程序,你要保证它在运行时出现的任何错误能在第一时间发现,这样才可以尽可能保证你的数据完整。
|
||||
|
||||
为了对数据实现操作,你还要在掌握创建表的基础上实现数据的增删改查,而其中的写入和查询是实际工作场景中对数据库最多的操作,接下来我就来带你学习一下写入和查询操作需要用到的SQL语句。
|
||||
|
||||
使用SQL实现对SQLite的写入和查询
|
||||
|
||||
对一个数据库的各种操作,查询一般会占到70%,而且大部分对数据的修改操作其实都隐含着查询。所以说查询是数据库中最重要的操作也不为过。
|
||||
|
||||
对SQLite数据库的表,实现写入和查询分别要使用“INSERT”和“SELECT” SQL语句,接下来我还是以通讯录的数据表为例,先为你插入通讯录汇总的联系人,然后通过查询的“SELECT”语句取出表中的联系人信息,通过这样的例子来给你演示一下怎么为SQLite数据库进行数据的写入和查询。
|
||||
|
||||
数据的写入
|
||||
|
||||
首先,我为通讯录添加一个联系人Tom,它的实现代码如下:
|
||||
|
||||
import sqlite3
|
||||
import pathlib
|
||||
|
||||
# 数据库文件的路径和文件名称
|
||||
dir = pathlib.PurePath(__file__).parent
|
||||
db = pathlib.PurePath(dir, "contents.db")
|
||||
|
||||
# 创建连接
|
||||
conn = sqlite3.connect(db)
|
||||
|
||||
# 创建游标
|
||||
cur = conn.cursor()
|
||||
|
||||
# 定义要执行的SQL语句
|
||||
sql1 = '''INSERT INTO address_book
|
||||
VALUES (?, ?, ?)'''
|
||||
v = (1, "Tom", 12377778888)
|
||||
|
||||
# 执行SQL
|
||||
try:
|
||||
cur.execute(sql1, v)
|
||||
conn.commit()
|
||||
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
finally:
|
||||
# 关闭游标
|
||||
cur.close()
|
||||
# 关闭连接
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
通过执行这段代码,你可以给通讯录增加一个用户“Tom”,以及他的电话号码“12377778888”。在这段数据写入的代码中,有两点需要你注意,分别是SQL语句和数据的拆分,以及写入后的提交功能。
|
||||
|
||||
SQL语句和数据的拆分,是指用于写入数据的SQL语句“INSERT”语句与真正要写入表中的数据需要保存在不同的变量中。当它们在真正执行SQL语句的那一刻,才会由excute ()函数进行合并。
|
||||
|
||||
写入两个变量的好处在于,数据往往是经过计算或用户输入得到的,而SQL语句是可以复用的,把数值脱离SQL语句可以更方便地在Python中进行处理。所以我也建议你在进行数据写入时,都采用这种拆分数据和SQL语句的方法,这样会比一条SQL语句中既包含INSERT语句,又包含数据,灵活性上要提高很多。
|
||||
|
||||
另一个需要注意的是在执行SQL语句写入表之后,需要使用commit()函数进行数据提交。这是SQLite的事务机制导致的,因为使用事务可以避免数据不一致的情况。
|
||||
|
||||
事务其实是个比较大的话题,也是一个比较系统的工程,要想把事务的细节讲清楚,那就能写一本书了。不过由于SQLite没有支持较为完整的事务以及办公自动化场景,所以今天这节课我就不展开为你讲解事务的概念了,这也并不影响你对今天这节课的理解。
|
||||
|
||||
数据的查询
|
||||
|
||||
在实现了数据写入之后,我们继续来看一下如何使用SQL实现数据的读取。从SQLite读取数据的代码如下:
|
||||
|
||||
import sqlite3
|
||||
import pathlib
|
||||
|
||||
# 数据库文件的路径和文件名称
|
||||
dir = pathlib.PurePath(__file__).parent
|
||||
db = pathlib.PurePath(dir, "contents.db")
|
||||
|
||||
# 创建连接
|
||||
conn = sqlite3.connect(db)
|
||||
|
||||
# 创建游标
|
||||
cur = conn.cursor()
|
||||
|
||||
# 定义要执行的SQL语句
|
||||
sql2 = '''SELECT phone FROM address_book WHERE name = "Tom" '''
|
||||
|
||||
# 执行SQL
|
||||
try:
|
||||
result = cur.execute(sql2)
|
||||
print(result.fetchone())
|
||||
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
finally:
|
||||
# 关闭游标
|
||||
cur.close()
|
||||
# 关闭连接
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
这段代码使用了“SELECT”SQL语句实现了联系人“Tom”的手机号码读取。“SELECT”语句执行后,会把结果放到“result”变量中。由于取出的结果是一个对象,因此还需要通过fetchone() 函数,把对象中的一行取出,得到用户的手机号码。
|
||||
|
||||
那在这一查找流程中,我们要重点关注的就是“SELECT”语句,它是实现读取内容的语句。在任何一个数据库中,它都是最常用的SQL语句。我们从它的结构开始分析。
|
||||
|
||||
SELECT phone FROM address_book WHERE name = "Tom"
|
||||
|
||||
|
||||
这条语句的执行顺序和书写顺序是不同的,它的执行顺序是从“FROM”到“WHERE”,再到“SELECT”关键字。它们三个的含义分别是:
|
||||
|
||||
|
||||
FROM:从一张表中查找数据,用于来指定查询的表。
|
||||
WHERE:表示查询的条件是什么。在这个例子中,查询条件是name字段的值为“Tom”字符串。
|
||||
SELECT:显示哪一列。在这个例子中,只显示phone这一字段的值。
|
||||
|
||||
|
||||
“SELECT”语句按照代码中的顺序实现了电话号码的查找。当然了,这只是个非常简单的查找,而你在工作中肯定会遇到更加复杂的查找,我也会在下一讲教你怎样使用Python来为更加复杂的查询编写高效的代码。如果你想要更加深入的学习SELECT语句,可以参考官方文档。
|
||||
|
||||
官方文档中会有“SELECT”中其他语句的执行顺序和功能介绍,并且还采用了流程图方式来为你介绍它的子句之间是如何进行组合的。如下图:
|
||||
|
||||
|
||||
|
||||
小结
|
||||
|
||||
最后让我来为你总结一下,在本讲中,我使用了SQLite数据库为你讲解了数据库对比文本文件和Excel的优势,它在存储结构化数据方面,已经作为了业界的标准解决方案。
|
||||
|
||||
想要用好SQLite,你还要掌握数据库的四个操作步骤,那就是:
|
||||
|
||||
|
||||
连接数据库文件
|
||||
创建游标
|
||||
执行SQL语句
|
||||
关闭游标和连接
|
||||
|
||||
|
||||
这四个步骤是实现一切数据库操作的基础,在掌握四个操作步骤的基础上实现数据操作的SQL语句,包括增删改查四种操作,在本讲中我为你讲解了如何使用SQL实现SQLite的读写,下一讲将为你讲解更加复杂的SQL语句以及修改和删除的SQL语句。
|
||||
|
||||
查找和写入作为最常用的SQL语句,是需要你重点掌握的。除了存储通讯录数据,像是我们从网络批量下载的数据,以及工作中的员工信息表等数据都可以采用SQLite存放。它将是以后你存储结构化数据最实用的工具。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,我为你出一道思考题,如果使用SELECT语句查询到的结果不止一条,使用fetchone()函数得到的是什么样的呢?你能否通过官方文档再找到获得多条查询结果的函数呢?
|
||||
|
||||
欢迎把你的想法和思考写在留言区,我们一起交流讨论。如果这节课在数据保存上帮你提高了办公效率,那也欢迎你把课程分享给你的朋友或同事,我们一起做职场上的效率人。
|
||||
|
||||
|
||||
|
||||
|
387
专栏/Python自动化办公实战课/22SQLite文本数据库:如何进行数据管理(下)?.md
Normal file
387
专栏/Python自动化办公实战课/22SQLite文本数据库:如何进行数据管理(下)?.md
Normal file
@ -0,0 +1,387 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
22 SQLite文本数据库:如何进行数据管理(下)?
|
||||
你好,我是尹会生。
|
||||
|
||||
在上节课,我提到了使用比较简单的SQL来操作SQLite,并为你讲解了数据库的基本操作步骤。
|
||||
|
||||
不过当你的程序功能越来越强大的时候,随之而来的就是代码的复杂度越来越高。像是上一讲,我们在进行SQLite数据库搜索的时候,你需要建立连接、申请游标对象,才能进行查询。而这些准备工作,我们更希望在程序运行的时候就准备好,这样就不必多次重复编写。
|
||||
|
||||
而且对数据库进行增删改查能够通过尽可能少的SQL来实现数据库的操作。那么能实现这一功能的就是类。
|
||||
|
||||
通过类,你可以为越来越复杂的程序编写结构更清晰的代码。同时也能更好地把SQLite的增删改查封装成一个独立的对象,便于你调用数据库时能进行数据持久化。
|
||||
|
||||
那么今天这节课,我就带你使用类来实现SQLite数据的读取和写入。与此同时,我会继续以通讯录为例,来给你讲解,如果使用了比较复杂的SQL来操作SQLite时,怎么合理组织代码结构,让你更优雅地书写代码。
|
||||
|
||||
使用类实现SQLite的读写
|
||||
|
||||
由于类这个概念比较抽象,我还是采用老办法帮你理解它,我将使用“类”对SQLite的读写SQL操作进行封装,并将类进行实例化以后进行调用,得到SQLite中的通讯录数据。我先把代码贴出来,供你参考:
|
||||
|
||||
import sqlite3
|
||||
import pathlib
|
||||
|
||||
class OptSqlite(object):
|
||||
def __init__(self, dbname = "new.db"):
|
||||
"""
|
||||
:param dbname 数据库名称
|
||||
"""
|
||||
self.dir = pathlib.PurePath(__file__).parent
|
||||
self.db = pathlib.PurePath(self.dir, dbname)
|
||||
self.conn = sqlite3.connect(self.db)
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭连接
|
||||
"""
|
||||
self.cur.close()
|
||||
self.conn.close()
|
||||
|
||||
def get_one_phone(self, username):
|
||||
"""
|
||||
获取一个联系人的电话
|
||||
"""
|
||||
|
||||
self.get_user_phone_sql = f"""
|
||||
SELECT phone FROM address_book WHERE name = "{username}" """
|
||||
try:
|
||||
self.result = self.cur.execute(self.get_user_phone_sql)
|
||||
return self.result.fetchone()
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
def set_one_phone(self, name, phone):
|
||||
"""
|
||||
增加一个联系人
|
||||
"""
|
||||
self.set_user_phone_sql = '''INSERT INTO address_book
|
||||
VALUES (?, ?, ?)'''
|
||||
self.v = (2, str(name), int(phone))
|
||||
try:
|
||||
self.cur.execute(self.set_user_phone_sql, self.v)
|
||||
self.conn.commit()
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
my_query = OptSqlite("contents.db")
|
||||
|
||||
my_query.set_one_phone("Jerry","12344445555")
|
||||
|
||||
phone = my_query.get_one_phone("Tom")
|
||||
phone2 = my_query.get_one_phone("Jerry")
|
||||
|
||||
my_query.close()
|
||||
|
||||
print(phone)
|
||||
print(phone2)
|
||||
|
||||
# 输出结果
|
||||
# (12377778888,)
|
||||
# (12344445555,)
|
||||
|
||||
|
||||
在这段代码中,我使用类实现了两个连续操作:添加新的联系人“Jerry”,并取出联系人“Tom”和“Jerry”的手机号码。
|
||||
|
||||
通过代码,你会发现类的实现思路和语法,跟函数有非常大的区别,因此在你第一次使用类代替函数实现通讯录时,我要通过实现方式和语法方面来为你做个详细的对比,并且为你讲解类的初始化函数,在类实例化时是如何实现接收参数并自动初始化的。
|
||||
|
||||
总体来说,与使用函数实现数据库操作相比,类的最大优势就是完善的封装。
|
||||
|
||||
在使用类实现“SELECT”和“INSERT”这两个SQL操作的时候,你只需进行了一次初始化和关闭连接,后续的SQL操作都可以复用这次的连接,类能有效减少重复建立连接和重复初始化的工作。
|
||||
|
||||
因此在类似数据库封装这种功能复杂的代码中,你会看到更多的人选择用类代替自定义函数,实现开发需求。
|
||||
|
||||
从具体来讲,对比函数,类除了在封装方式上不同、语法和调用方式都不相同,我还是基于通讯录代码的封装和调用,为你讲解一下它和自定义函数的三个主要区别。
|
||||
|
||||
类和自定义函数的区别
|
||||
|
||||
首先,类和函数第一点区别就在于它们的对代码的封装方式上不同。
|
||||
|
||||
编写自定义函数,它的实现思路是通过函数去描述程序运行的过程,比如:代码的下一步需要做什么、需要什么参数。
|
||||
|
||||
而编写基于类的程序,它的实现思路更多要关注相同的一类数据,都有哪些属性和相同的动作。比如在代码中,我把数据库作为了一个类,因为类具有数据库名称这一属性,也具有查询和写入数据两个动作。而类在语法层面上,对属性和动作的封装要比函数更加完善。
|
||||
|
||||
在我工作中对建立数据库连接,以及执行查询、关闭数据库连接上都做过运行时间的测试,最终得出的结论是频繁地建立、关闭会给数据库带来较大的资源开销。因此,我在工作中会经常使用类把建立连接和关闭分别封装在多个查询动作之前和之后,确保这两个动作在多次查询时只执行一次,减少资源开销。
|
||||
|
||||
其次它们的语法结构也不同。函数是通过“def”关键字定义的,而类是通过“class”关键字定义的。
|
||||
|
||||
在编写一个新的类时,Python语法还强制要求它必须继承父类,例如,我在编写的数据库类“OptSqlite”,就继承了父类“object”。继承父类意味这你可以在当前类中执行父类定义过的方法,而不需要再重新去编写一个定义过的方法。那如果你不需要继承其他类呢?这时候你就可以使用object作为你自定义类的父类使用。
|
||||
|
||||
同时,object的关键字可以和定义类语法的“()”一起省略掉,因此你会看到其他人的代码出现,会有下面两种不同的写法,但含义却(在Python3.x版本)是完全相同的。我将两种写法写在下面供你参考。
|
||||
|
||||
class OptSqlite(object):
|
||||
class OptSqlite:
|
||||
|
||||
|
||||
最后它们的调用方式也不同。这一点主要表现在各自成员能否被访问和运行方式两方面。
|
||||
|
||||
类的定义中,可以定义当前类的属性和方法。属性就是类具有的数据状态,方法就是类对数据可以执行哪些操作。
|
||||
|
||||
在类中,可以设置哪些属性和方法能够被类以外的代码访问到,比如:我定一个了“鸟”类。并且定义了它的属性是黄色,它的动作是可以飞、可以叫。那么你可以借用变量这种形式来实现鸟类的属性,借用函数的形式实现鸟类能飞、能叫的动作。
|
||||
|
||||
此外,在定义属性和方法时,你还能限制它们的访问范围。像函数的调用,你只能访问它的函数名称和参数、中间的变量是不能被函数外的程序访问的。
|
||||
|
||||
是否能访问,在计算机中也被称作作用范围。在这一方面,类要比函数拥有更灵活的作用范围控制。
|
||||
|
||||
那在执行方式,类也和函数不同。函数执行时可以直接使用函数名+括号的方式调用它,如果需要多次执行可以使用变量存放多次执行的结果。
|
||||
|
||||
而类在执行时,一般要进行实例化。例如鸟类,在需要使用时,会实例化为一个对象“鸟001”,对象就具有类的所有属性和方法。当你需要多次使用鸟类时,可以多次将鸟类实例化成不同的小鸟。
|
||||
|
||||
再回到通讯录的代码。类似的在通讯录的代码中,我将SQLite数据库定义为类以后,如果你的工作需要一个通讯录,就实例化一次。实例化之后的代码我单独拎了出来,如下:
|
||||
|
||||
my_query = OptSqlite("contents.db")
|
||||
|
||||
|
||||
如果需要多个通讯录,就把它实例化多次,并指定不同的SQLite数据库即可。每个数据库实例,都会有一个“get_one_phone()”方法和一个“set_one_phone()”方法,来实现通讯录中联系人的读取和写入。
|
||||
|
||||
而为了表示属性和方法是在实例化中使用的,你还需要对它增加self关键字,即:使用实例的属性时,要用“self.属性”的写法。使用方法时,要是将实例的方法第一个参数设置为self,代码为“方法(self)”。
|
||||
|
||||
类能够在封装和调用上提供比函数更灵活的方式,因此你会发现当功能复杂,代码数量增多了以后,很多软件都采用了类方式实现代码的设计。
|
||||
|
||||
类中的特殊方法“init”
|
||||
|
||||
在类中,有一个内置的方法叫做“init”,它叫做类的初始化方法,能实现类在执行的时候接收参数,还能为类预先执行变量赋值、初始化等,实现在类一运行就需要完成的工作。
|
||||
|
||||
“init()”方法的作用有两个,分别是:
|
||||
|
||||
|
||||
为实例接收参数;
|
||||
实例化时立即运行该方法中的代码。
|
||||
|
||||
|
||||
当一个类实例化时,它可以像函数调用一样,接收参数。类实例化时,它的后面需要增加括号“()”,括号中可以指定实例化的参数。这个参数将交给“init()”方法,作为“init()”方法的参数,进行使用。
|
||||
|
||||
我来为你举个例子,来说明类是如何实现接收参数的。例如我在通讯录的例子中,实例化一个SQLite的“OptSqlite”类,实例化的代码如下:
|
||||
|
||||
my_query = OptSqlite("contents.db")
|
||||
|
||||
|
||||
这段代码中的“OptSqlite”就是类的名称,而“contents.db”是该类初始化时,输入的参数,也是SQLite数据库文件的名称。
|
||||
|
||||
要想实现“my_query”实例在“OptSqlite”类实例化时获得参数,就需要在类中使用初始化方法:
|
||||
|
||||
def __init__(self, dbname = "new.db"):
|
||||
|
||||
|
||||
在这段代码中,我定义了“init()”方法,并指定它的参数“dbname”之后,那么实例“my_query”就能够得到参数dbname变量的值“contents.db”了。
|
||||
|
||||
这就是一个实例化一个类,并如何在第一时间获得参数的完整过程。不过获得参数之后,你还要对参数继续使用和处理,以及需要在实例化之后就立即运行一些代码,这些功能就可以写在“init()”方法中来实现。
|
||||
|
||||
例如我就将数据库文件的路径处理、初始化连接、初始化游标的代码写入到了初始化函数。代码如下:
|
||||
|
||||
class OptSqlite(object):
|
||||
def __init__(self, dbname = "new.db"):
|
||||
"""
|
||||
:param dbname 数据库名称
|
||||
"""
|
||||
self.dir = pathlib.PurePath(__file__).parent
|
||||
self.db = pathlib.PurePath(self.dir, dbname)
|
||||
self.conn = sqlite3.connect(self.db)
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
|
||||
通过上面的写法,实例不但能够接受参数,还能在初始化时做很多主要逻辑前的预备操作。这些初始化操作让实例被调用时的主要逻辑更加清晰。
|
||||
|
||||
为了能够让你对类有更深刻的理解,也为了能让你将数据库的代码直接拿来在工作中使用,我们在对数据库的写入和读取基础上,再增加修改和删除功能,这样,SQLite的类就能完整实现数据库的增删改查功能了。
|
||||
|
||||
使用类实现完整的SQLite增删改查
|
||||
|
||||
SQLite的增删改查,都需要依赖SQL语句完成,在编写代码前,我们先来学习一些更新和删除的SQL,在掌握增删改查SQL基础上,你会更好地理解我编写操作SQLite类的代码逻辑。
|
||||
|
||||
更新和删除记录的SQL语句
|
||||
|
||||
首先,我先来带你学习一些更新的SQL语句。更新一般是对单个记录进行操作,因此更新的SQL语句会带有筛选条件的关键字“WHERE”。以更新“Tom”手机号码的SQL语句为例,我将更新需要用到的SQL语句,单独写出来供你参考:
|
||||
|
||||
UPDATE address_book SET phone=12300001111 WHERE id=1;
|
||||
|
||||
|
||||
在这条SQL语句中:
|
||||
|
||||
|
||||
“UPDATE”是指即将更新的数据表。
|
||||
“WHERE”是指更新的条件,由于“id”的主键约束条件限制,它的值在这张表中是唯一的,因此通过“WHERE id=1”会读取该表的“id”字段,得到唯一的一条记录。
|
||||
“SET”用于指定记录中的“phone”字段将被更新的具体值。
|
||||
|
||||
|
||||
这就是更新语句的各关键字的作用,那我们再来看看删除操作的SQL语句。例如我希望删除通讯录中的“Jerry”用户,就可以使用如下的SQL语句。
|
||||
|
||||
DELETE FROM address_book WHERE id=1;
|
||||
|
||||
|
||||
在这条SQL语句中,“DELETE FROM”用于指定表,“WHERE”用于指定过滤条件。
|
||||
|
||||
我想你肯定还发现了,无论更新还是删除操作中,都包含了“WHERE”关键字。使用了“WHERE”关键字,也就意味这“UPDATE和DELETE”也读取了数据库。因此,我们将插入和删除也称作是“使用SQL语句对数据库执行了一次查询”。当你为以后工作中编写复杂的“UPDATE和DELETE”语句时,如果遇到它们的性能达不到你预期的要求,可以从“查询”方面先对你的SQL语句进行优化。
|
||||
|
||||
在你对SQL语句不熟练的时候,我有一个建议提供给你,由于UPDATE和DELETE语句在没有指定条件时,会将整张表都进行更新和删除,所以我建议你在编写代码时,先通过SELECT得到要操作的数据,再将SELECT改写为UPDATE或DELETE语句,避免因手动操作失误导致数据发生丢失。
|
||||
|
||||
接下来我们就把修改和删除功能也加入到“OptSqlite”类中,实现对数据库的增删改查操作。
|
||||
|
||||
实现增删改查的类
|
||||
|
||||
实现了增删改查的“OptSqlite”类代码如下:
|
||||
|
||||
import sqlite3
|
||||
import pathlib
|
||||
|
||||
class OptSqlite(object):
|
||||
def __init__(self, dbname = "new.db"):
|
||||
"""
|
||||
:param dbname 数据库名称
|
||||
"""
|
||||
self.dir = pathlib.PurePath(__file__).parent
|
||||
self.db = pathlib.PurePath(self.dir, dbname)
|
||||
self.conn = sqlite3.connect(self.db)
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭连接
|
||||
"""
|
||||
self.cur.close()
|
||||
self.conn.close()
|
||||
|
||||
def new_table(self, table_name):
|
||||
"""
|
||||
新建联系人表
|
||||
"""
|
||||
|
||||
sql = f'''CREATE TABLE {table_name}(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
phone INT NOT NULL
|
||||
)'''
|
||||
|
||||
try:
|
||||
self.cur.execute(sql)
|
||||
print("创建表成功")
|
||||
except Exception as e:
|
||||
print("创建表失败")
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
def get_one_phone(self, username):
|
||||
"""
|
||||
获取一个联系人的电话
|
||||
"""
|
||||
|
||||
self.get_user_phone_sql = f"""
|
||||
SELECT phone FROM address_book WHERE name = "{username}" """
|
||||
try:
|
||||
self.result = self.cur.execute(self.get_user_phone_sql)
|
||||
return self.result.fetchone()
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
def get_all_contents(self):
|
||||
"""
|
||||
取得所有的联系人
|
||||
"""
|
||||
try:
|
||||
self.result = self.cur.execute("SELECT * FROM address_book")
|
||||
return self.result.fetchall()
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
def set_one_phone(self, name, phone):
|
||||
"""
|
||||
增加或修改一个联系人的电话
|
||||
"""
|
||||
if self.get_one_phone(name):
|
||||
self.set_user_phone_sql = '''UPDATE address_book
|
||||
SET phone= ? WHERE name=?'''
|
||||
self.v = (int(phone), str(name))
|
||||
else:
|
||||
self.set_user_phone_sql = '''INSERT INTO address_book
|
||||
VALUES (?, ?, ?)'''
|
||||
self.v = (None, str(name), int(phone))
|
||||
try:
|
||||
self.cur.execute(self.set_user_phone_sql, self.v)
|
||||
self.conn.commit()
|
||||
except Exception as e:
|
||||
print(f"失败原因是:{e}")
|
||||
|
||||
def delete_one_content(self, name):
|
||||
"""
|
||||
删除一个联系人的电话
|
||||
"""
|
||||
self.delete_user_sql = f'''DELETE FROM address_book
|
||||
WHERE name="{name}"'''
|
||||
|
||||
try:
|
||||
self.cur.execute(self.delete_user_sql)
|
||||
self.conn.commit()
|
||||
except Exception as e:
|
||||
print(f"删除失败原因是:{e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# 实例化
|
||||
my_query = OptSqlite("contents.db")
|
||||
|
||||
# 创建一张表
|
||||
# my_query.new_table("address_book")
|
||||
|
||||
# 增加或修改一个联系人的电话
|
||||
my_query.set_one_phone("Jerry","12344445556")
|
||||
|
||||
# 查询一个联系人的电话
|
||||
phone = my_query.get_one_phone("Jerry")
|
||||
print(phone)
|
||||
|
||||
# 查询所有人的电话
|
||||
contents = my_query.get_all_contents()
|
||||
print(contents)
|
||||
|
||||
# 删除一个联系人
|
||||
my_query.delete_one_content("Jerry")
|
||||
|
||||
contents = my_query.get_all_contents()
|
||||
print(contents)
|
||||
|
||||
# 关闭连接
|
||||
my_query.close()
|
||||
|
||||
|
||||
在这段代码中,实现的主要逻辑,是将代码的相似功能尽量封装成一个方法,将数据库初始化连接放在“init()”方法,并尽量复用这个连接。为此,我编写类“OptSqlite”实现通讯录操作的时候,使用了四个方法,我按照这四个方法在代码里的定义顺序依次为你分析一下。
|
||||
|
||||
第一个方法是创建通讯录的数据表。我把创建通讯录数据表的功能定义成类的一个方法。定义类的方法我刚才已经教过你了,它是借用函数的语法格式来定义的。
|
||||
|
||||
不过我在定义通讯录表的时候,还对id这个主键增加了一个新的修饰条件,叫做自增“AUTOINCREMENT”,它的用途是每插入一条记录,它的值就会自动+1。“SQL92标准”中规定自增只能修饰整数类型的主键,所以我把id的类型改为“INTEGER” ,否则在创建表时,SQLite会提示类型不符合要求而报错。
|
||||
|
||||
第二个方法是查看通讯录所有的联系人。这和我们学习过的查看单个联系人时,使用的“SELECT 某个字段”在SQL语句是有区别的。当你需要匹配所有字段时,不用把所有字段逐一写在“SELECT”SQL语句后面,你可以使用“*”来代替所有的字段,这样实现起来更便捷。
|
||||
|
||||
此外,在查询结果上面,由于fetchone()函数只返回多个结果中的第一条,因此我把它改为fetchall()函数,这样就能把查询到的所有联系人都显示出来。
|
||||
|
||||
而且Python比较友好的一点是,它会把整个通讯录显示为一个列表,每个联系人显示为元组,联系人的各种属性都放在相同的元组中,方便你能对取出来的数据再次处理。它的执行结果是:
|
||||
|
||||
[(1, 'Tom', 12344445555), (2, 'Jerry', 12344445556)]
|
||||
|
||||
|
||||
第三个方法是更新用户手机号码,由于更新操作的UPDATE语句和新增操作INSERT语句,对通讯录这一场景,实现起来非常相似。因此我没为它们两个功能编写两个方法,而是都放在了同一个方法–“set_one_phone()”方法中了。
|
||||
|
||||
这样做的好处是,使用“set_one_phone()”方法的人不用区分联系人是否存在,如果用户不存在,则通过条件判断语句,使用“INSERT”语句新建一个联系人。如果联系人存在,则改用“UPDATE”语句更新联系人的手机号码。
|
||||
|
||||
第四个方法是删除某个联系人,使用的是“DELETE”SQL语句。由于这里的SQL语句拼接比较简单,我没有单独使用一个变量v来保存,而是使用了f-string字符串把变量直接替换到字符串中,拼接为一个SQL语句。
|
||||
|
||||
对于以后工作中遇到的简单的字符串替换,你也可以采用这种方式,会对代码阅读上带来比较流畅的阅读体验。
|
||||
|
||||
通过这四个方法,我实现了“OptSqlite”类的增删改查功能。实例化“OptSqlite”类之后,你只需了解每个方法的名称和参数,就能利用我编写的四个方法实现通讯录的完整操作。这也是采用类替代了函数实现更完善的封装,最大的优势。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下本讲的主要内容。我们在这节课第一次编写了基于类的代码。通过对比类和函数的差别,我们了解到类的编写方法。这些差别体现在如何定义类、类中的成员属性和方法、以及一个用于接收参数、在实例化类时完成初始化的特殊方法“init()”。当你接触更多的其他人编写的Python代码时,就会慢慢发现代码量较大的程序,都会采用基于类的方式封装代码。也希望你在掌握类之后能够通过读懂其他人的代码,对自己的编码能力进行提升。
|
||||
|
||||
此外,我还用类重新封装了基于SQLit的通讯录的基本功能,其中就包括增删改查。相信你在掌握了对数据库的封装之后,可以把原有需要用SQL与数据库打交道的接口,封装为类的方法,这样也有助于你能够把SQLite更多的应用于自己的办公优化中来。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,最后我来为你留一道思考题,在本讲的代码中,我使用“INSERT”增加联系人之前没有判断该联系人是否存在。你能否利用判断语句实现增加联系人前对联系人是否存在进行判断,并提示用户对重复联系人进行合并操作呢?
|
||||
|
||||
欢迎把你的思考和想法放在留言区,我们一起交流讨论。如果这节课学习的数据透视表对你的工作有帮助,也欢迎你把课程推荐给你的朋友或同事,一起做职场中的效率人。
|
||||
|
||||
|
||||
|
||||
|
139
专栏/Python自动化办公实战课/23怎么用数据透视表更直观地展示汇报成果?.md
Normal file
139
专栏/Python自动化办公实战课/23怎么用数据透视表更直观地展示汇报成果?.md
Normal file
@ -0,0 +1,139 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
23 怎么用数据透视表更直观地展示汇报成果?
|
||||
你好, 我是尹会生。
|
||||
|
||||
从这节课开始,我们将进入课程的最后一个模块“输出模块”的学习了。在这一模块中,我们要把“控制、存储等模块”的数据,以更加智能化的方式展现到你的面前。比如用图表、图形代替数字进行工作成果汇报;利用压缩包、PDF把工作成果定时发送邮件等。这些都是你在输出模块能够学习到的自动化办公技巧。
|
||||
|
||||
通过对这些技巧的掌握,能够为你的工作成果输出带来更高效的处理方式,也能让你把工作成果输出这件事做得更出色。
|
||||
|
||||
那今天这节课,我想和你聊一聊怎样把你的工作成果更好地向领导汇报的问题。
|
||||
|
||||
你在工作中肯定遇到过这样的情况:精心处理了好几天的数据,需要把数据汇报给你的领导时,往往不知道采用什么样的格式更合适。比如只提交了整理过的原始数据,很容易被领导误认为工作只完成了半成品。而提交了最终结果时,领导又可能会提出合理的疑问,比如如果按月汇总、按某项汇总会得到哪些不同的结果等问题。这个时候,你就不得不再进行一次统计结果汇报。
|
||||
|
||||
那么今天这节课,我就要给你介绍一款Excel中的插件,叫做“Power Pivot”,这个插件可以根据需要随时调整数据处理结果。而你可以利用“Power Pivot”生成自己需要的数据模型,还能基于模型生成更灵活的、可随时调整的数据透视表。
|
||||
|
||||
等下次再遇到需要随时调整数据汇总结果的时候,你就可以直接拖拽想要的列,按月、按年、或按照任意你想要的字段进行数据聚合了。
|
||||
|
||||
不过在讲解之前,考虑到你可能初次使用数据透视表,我想先为你介绍一下如何将数据转换为数据透视表的操作步骤,这样你才能更好的理解数据透视表展示的表格和数据之间的对应关系。在掌握了数据透视表的操作基础之上,我再教你怎样使用“Power Pivot”插件来生成支持多表和自定义排序的数据透视表。
|
||||
|
||||
利用数据透视表进行灵活的数据分析
|
||||
|
||||
数据透视表能用来做什么呢?一句话来描述它,数据透视表是一种能够随意对数据明细表进行分类汇总,并能随时调整汇总结果的交互式报表。
|
||||
|
||||
可能这样描述它,你仍然觉得不够直观,还不能对数据透视表的作用有具体的感知。那么我就用一个Excel文件,来给你演示一下怎么把数据表生成数据透视表,并进行数据的统计和分析。
|
||||
|
||||
为了让你对数据透视表有个直观的印象,我先把原始的数据表和制作成透视表的表格一起提供给你,原始的数据表记录了5台计算机硬件负载情况的明细数据,透视表按照每台计算机每种硬件在每小时的最大利用率进行展示。两张表格内容如下:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
同时,我也把生成数据透视表后的截图,给你放在下方,供你参考:
|
||||
|
||||
|
||||
|
||||
数据表中包含了1点-4点,一共5台计算机的CPU、内存、磁盘利用率,如果把数据交给你,你能从中分析出哪些有趣的结果呢?
|
||||
|
||||
那么我在这里用数据透视表提供两种将数据组合起来有意义的结果,它们是每台电脑每小时各种资源利用率的最大值,以及每台电脑每天资源利用率的平均值。
|
||||
|
||||
当然你还能用数据组合出更多有用且有趣的结果。那接下来我们就看看怎样把明细数据转换为数据透视表、怎样组合行和列,才能得到我刚才提到的两个有意义的结果。
|
||||
|
||||
使用数据透视表实现数据统计
|
||||
|
||||
使用Excel的数据透视表来实现数据统计,有三个简单的步骤,分别是数据预处理、生成透视表和调整透视表结果。让我们逐个来学习一下它们吧!
|
||||
|
||||
首先是数据预处理,它是输出数据的基础。数据预处理的目标就是把每一类数据放在单独的一列,因为数据透视表只能支持这种格式的数据,这样才能输出有意义的透视结果。另外,你可以使用第13讲的“Power Query”对表格进行调整,也可以使用咱们学习过的Python对Excel的数据内容进行调整。
|
||||
|
||||
我给你举一个调整好格式的例子,帮你理解数据透视表能支持的数据明细的格式。比如刚才的计算机利用率的表格,假设“pc-1”这台计算机的利用率明细表中包含“CPU”、“磁盘”和“内存”的利用率数据,如果你需要使用数据透视表,则必须为它建立“类型和利用率”两个列,并把“类型和利用率”作为表头。
|
||||
|
||||
与此同时,在类型列中把“CPU、内存、磁盘”作为数据,在利用率中分别写入每种类型不同时间各自对应的利用率,而不能把“CPU、内存、磁盘”作为三列,把利用率作为每一列的数据。
|
||||
|
||||
如果你还是无法理解需要把数据处理成什么样的格式,才能让数据透视表正常工作,那我再给你提供一个既简便又不会出错的方法:把第一列指定为时间,而其他的列,每一列存放一个类型的数据。把需要透视的数据,按照时间递增依次填入到Excel中。
|
||||
|
||||
这种数据有一个专有的名称,叫做时序数据。数据预处理完成后,如果你的透视表不需要时间字段,可以将时间这一列的数据再删掉。
|
||||
|
||||
接下来是生成透视表。数据准备好之后,下一步就是在新的工作表中插入数据透视表了。插入数据透视表的命令在“插入”菜单栏的“表格”命令组,你只要找到“数据透视表”按钮,点击后就会打开创建数据透视表的菜单了。
|
||||
|
||||
在这一步骤,有两个选项你需要格外关注一下。
|
||||
|
||||
|
||||
一个是“选择要分析的数据”选项,Excel会自动为你选择当前表中的所有数据,如果数据表中的数据都是你要分析的内容,那你可以不做修改。如果你只希望将表格部分数据制作成数据透视表,可以根据自己的需要选取数据明细表中的具体数据范围。
|
||||
另一个是“请选择放置数据透视表的位置”选项,我们可以把透视表放在“新工作表”中。可以保持原始数据不被破坏外,便于你再次利用原始数据做其他的数据处理。
|
||||
|
||||
|
||||
这两个选项一般保持默认即可。同时,我也把创建透视表的截图放在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
最后是调整透视表结果。创建好默认的数据透视表之后,你会发现它的内容是空的,这时只需要把“报表的字段”拖动到透视表区域就行了。同样的,我先给你看一下截图,帮你更直观地感知数据透视表的操作。
|
||||
|
||||
|
||||
|
||||
在截图的左侧是透视表的结果,而右侧就是透视表的控制区域,你需要根据自己的分析需求,把字段拖入到右下角的“透视表区域”,包括“行、列、值”三个方框。例如我希望得到每台电脑的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”加载多张表的方式是使用“添加到数据模型”按钮。点击按钮后会弹出“创建表”窗口。这个窗口可以选择两张表中的其中一张,先作为数据模型。如图所示:
|
||||
|
||||
|
||||
|
||||
图片中“创建表”窗口会默认把当前表全部选中,点击“确定”后,会弹出“PP”的工作窗口。在这一窗口下,你可以对数据进行筛选和处理。
|
||||
|
||||
接下来我再导入另一个工作表。导入更多表的操作方法和导入当前工作表是相同的。这样,在“PP”工作窗口的左下角,就会出现两张要进行数据透视的表格。需要说明的是,“PP”还能支持从其他数据源导入数据,例如可以从数据库或“Access”导入数据,和Excel中的数据一起进行数据统计。
|
||||
|
||||
最后,生成由“PP”制作的数据透视表。你在“PP”窗口的任意一张表点击“数据透视表”按钮之后,就可以创建新的数据透视表了。这时你会发现,数据透视表字段的右上方出现了两张表,你可以把这两张表按照需求拖入到右下方的行、列和值,从而实现多表的数据统计。我把截图放在下方供你参考。
|
||||
|
||||
|
||||
|
||||
通过使用“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表格,才能通过数据透视表进行数据分析呢?
|
||||
|
||||
欢迎把你的思考和想法放在留言区,我们一起交流讨论。如果这节课学习的数据透视表对你的工作有帮助,也欢迎你把课程推荐给你的朋友或同事,一起做职场中的效率人。
|
||||
|
||||
|
||||
|
||||
|
176
专栏/Python自动化办公实战课/24条形、饼状、柱状图最适合用在什么场景下?.md
Normal file
176
专栏/Python自动化办公实战课/24条形、饼状、柱状图最适合用在什么场景下?.md
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
24 条形、饼状、柱状图最适合用在什么场景下?
|
||||
你好,我是尹会生。
|
||||
|
||||
提起图表,你一定会想到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,我就再为你提供一个示例数据的镜像站。除了鸢尾花数据外,镜像站这里还包括房产价格预测等经典示例数据,你可以利用它们来学习不同的图表。我把示例数据的地址放在这里,另外,我把部分示例数据也贴在下方,供你参考:
|
||||
|
||||
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()函数进行图形的绘制,那么鸢尾花数据的散点图绘制结果如下:
|
||||
|
||||
|
||||
|
||||
在截图中,基于花的四个属性,我采用了不同的维度进行绘图。同时你会发现,在某一维度下,其中一种颜色和其他颜色有明显的分界,非常容易把其中一种和另外两种花区分开。
|
||||
|
||||
通过观察散点图,你会得到这样一个结论,使用合理的图形,能够帮你更好地解释某个晦涩难懂的概念,也能更容易从数据中发现规律。那既然不同的图表能带来不同的价值,接下来,我就来为你讲解一下,如何使用seaborn生成其他类型的图表,比如可以通过histplot()函数生成柱状图、heatmmap()生成热力图、kdeplot()生成核密度图等等。
|
||||
|
||||
用seaborn生成不同类型的图表
|
||||
|
||||
要想使用seaborn生成其他类型的图表,你需要学会如何使用官方文档。我以折线图为例,为你讲解一下官方文档的正确用法。
|
||||
|
||||
在seaborn的官方文档地址API页面下,所有的图表都先按照不同的用途进行了分类,折线图在表示关系的分类中,你可以参考如下截图。
|
||||
|
||||
|
||||
|
||||
当你需要绘制折线图时,可以点击“lineplot”,进入折线图的函数解释网页。它的网页采用了和Python官方文档风格一致的API解释方法,即函数定义、一般场景案例和特殊场景案例。
|
||||
|
||||
如果你是第一次使用折线图,那你可以按照一般场景案例、函数定义和特殊场景案例的顺序来学习这个函数。如果你对折线图已经有了较多的使用经验,可以从函数定义,按照网页顺序阅读官方文档。为什么要按照这样的方式来学习呢?
|
||||
|
||||
在你对某一图表有了初步的使用经验后,会对该图形的样子有一个感性的认识,这时候再通过函数的定义、参数去学习它们,会比通过一般场景案例来学习的效率更高。而且通过学习函数的参数,能够了解哪些技术点会影响图形的展示。
|
||||
|
||||
而对于第一次使用某一图表的话,你没法通过图表的名字想象出这类图形的优缺点,因此我会建议你对初次使用的图形,先按照一般场景案例把图形展示出来,有个直观的印象。
|
||||
|
||||
不只是seaborn的文档,在学习其他库甚至Python语言,或其他任何编程语言,都需要通过阅读官方文档来掌握扩展知识。而阅读官方文档最佳的时机,是当你掌握了该软件的基本应用之后,例如在你掌握seaborn的散点图,以及它的基本运行过程之后,这时你就需要通过官方文档的学习来掌握更多的图表。当你掌握足够多的图表后,用seaborn绘图才能更加得心应手。
|
||||
|
||||
为不同的应用场景选择合适的图表
|
||||
|
||||
由于seaborn支持的图表非常丰富,在有经验的开发工程师进行图表选择时,绝不会逐个尝试。他们会根据图表的应用场景来选择适合的种类,再通过适合的种类再细化到图表的具体样式。
|
||||
|
||||
但是你可能并没有使用过seaborn的图表,甚至也不了解图表会有多少种类型、每种类型里包含着哪些具体的图表。因此根据是否有图表的使用经验,你可以按照我给你提供的两种方法来根据工作场景,找到最适合你的图表。这两种解决办法总结来说就是参考图例和参考分类。
|
||||
|
||||
第一种解决办法是参考图例,我把这种情况称作是“手中有剑、心中无剑”,“手中有剑”代表着你能看到图表一共有哪些,但是心中还不清楚哪种更适合你的场景。在seaborn的官方文档中,列举了各种图例,它的地址和截图如下:
|
||||
|
||||
|
||||
|
||||
你可以根据截图,找到离你的工作场景最相近的图表,通过点击图表之后,你就可以得到官方网站的演示代码了。演示代码就是你的“宝剑”。通过修改演示代码来完善你的工作场景的图表。
|
||||
|
||||
第二种解决办法是参考分类,我把这种情况称作“心中有剑,手中无剑”,“心中有剑”也就意味着你在心里已经*把*应用场景锁定在某一个图表的大类中,但是这一大类里包含了哪些具体的图表,要看seaborn能否支持。
|
||||
|
||||
这时候,你就要根据你的业务场景,分析出它都对应了以下四个分类中的哪一类,再按照分类通过官方文档API页面找到具体的图表函数。四个分类如下。
|
||||
|
||||
|
||||
关系类,用于展示数据集中多个变量之间的关系,relplot()、scatterplot()、lineplot()都属于关系类。
|
||||
分布类,用于展示数据集中多个变量的分布情况,displot()、kdeplot()是这一类经常使用的图表类型。
|
||||
线性关系类,是把多个变量联系起来,观察每个采样的线性变化趋势。regplot() 和 lmplot()经常用于表示线性关系。
|
||||
结构化多图,用于把多种方式的分析数据放在一起进行展示。例如我们分析鸢尾花就使用了散点图+柱状图的方式,但是散点图更能体现出它的各种属性之间的关系。
|
||||
|
||||
|
||||
你在心中掌握的图表分类就是“宝剑”,通过分类能够更快找到特定的图表类型。
|
||||
|
||||
这两种方式是基于不同场景,快速选择图表的方法。因为选择图表最核心的思路还是要基于场景,而不能基于个人的喜好或结果的美观性来选择图表,避免以偏概全。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下这一讲的主要内容。在本讲中,我通过seaborn生成图表的过程,为你讲解了如何在Python中使用图表。相对于其他软件,Python的图表样式由参数组成,你可以为多次产生图表指定相同的样式、也能为不同的数据重复使用图表来提高绘制图表的效率。
|
||||
|
||||
在为你讲解了散点图之外,我还为你讲解了如何基于场景选择合适的图表,你可以基于目前对图表掌握的深度,选择更适合你的图表深入学习路线。
|
||||
|
||||
同时,我还着重为你强调了文档的重要性,它也是很多专业从事开发的工程师必需掌握的技能之一。如果你希望更加深入学习seaborn以及更加深入学习Python,你应该从现在开始阅读官方文档,它会是你未来编写代码最权威的参考资料。
|
||||
|
||||
思考题
|
||||
|
||||
我来为你留一道思考题,如果我的工作场景需要展示当前地区的房价走势,你会选择什么样的图表进行展示呢?你能否用seaborn将这一图表绘制出来呢?
|
||||
|
||||
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。
|
||||
|
||||
|
||||
|
||||
|
245
专栏/Python自动化办公实战课/25图表库:想要生成动态图表,用Echarts就够了.md
Normal file
245
专栏/Python自动化办公实战课/25图表库:想要生成动态图表,用Echarts就够了.md
Normal file
@ -0,0 +1,245 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
25 图表库:想要生成动态图表,用Echarts就够了
|
||||
你好,我是尹会生。
|
||||
|
||||
在上一讲中,我们学习了怎么使用Seaborn来生成图片格式的图表。事实上,图片格式的图表也被称作静态图表,它能通过数据来更直观地展示结果。
|
||||
|
||||
不过很多时候,我们不仅要通过图片直观地展示数据,还要让图片容纳更多种类、更丰富的数据信息。这个时候,静态图表能展示的结果就十分有限了。比如你希望能给领导和同事在会议上演示数据的分析结果时,需要通过一张图来容纳更多的数据。
|
||||
|
||||
别担心,这时候我们可以采用动态图表的方法,来增强图片的表现力。因为动态图表展示的结果,相当于静态图表和数据这两者的混合,所以容纳的内容信息也就更丰富。
|
||||
|
||||
举个例子,我希望用一张图片来展示全国新冠确诊病例的分布。如果采用动态图,我就可以把鼠标移动到我需要查看的省份上面,显示该地区的确诊人数等相关信息。
|
||||
|
||||
就像下面这张截图一样。这张分布图不但基于颜色深浅显示了确诊人数的变化,还能通过鼠标悬停来显示具体的数据。使用起来是不是很方便?
|
||||
|
||||
|
||||
|
||||
这张动态图表是使用HTML网页文件格式来展示的。同时,它也采用了Python的库pyecharts进行了绘制,其中的图形、数据都可以基于你的需要进行调整。最重要的是,绘制这样一张图片,操作起来和seaborn生成静态图表一样简单。
|
||||
|
||||
那接下来,我就为你具体讲解一下怎么使用pyecharts来绘制疫情实时地图,并以此为例,让你掌握怎么通过pyecharts来绘制其他的动态图。
|
||||
|
||||
要想使用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()”函数把列表类型转换为元组。
|
||||
|
||||
确定数据格式之后,我们还需要一个数据来源,为了确保数据的准确性和实时性,我们得从腾讯新闻的网站引入外部数据,数据地址的链接我先贴出来,接下来我再给你讲解一下怎么得到数据地址。
|
||||
|
||||
为了得到数据的地址,我分析了网站加载数据的过程,找到了数据接口的地址。这个分析方法你学会以后,也可以应用到其他需要抓取网页数据的工作中。我把抓取的步骤分成四步,分别是开启浏览器调试、请求网页、确认接口和确认返回数据。
|
||||
|
||||
先看第一步,开启浏览器调试。这一步骤是为了在请求过程中,能记录网页都请求了哪些数据接口。
|
||||
|
||||
以Chrome浏览器为例,使用快捷键F12可以打开调试模式,把选项卡调整至“Network”,调整后就进入接口的监听状态了。
|
||||
|
||||
第二步,请求网页。以腾讯疫情实时网页为例,可以在地址栏输入“https://news.qq.com/zt2020/page/feiyan.htm#/”,输入后,调试界面会显示该网页都请求了哪些地址。我把调试页面放在下方供你参考。
|
||||
|
||||
|
||||
|
||||
截图的左侧就是请求的所有数据接口的地址,从最开始请求的地址向下找,除了JS、JPG等网页图片和样式数据外,其他的请求接口就是我们要重点查看的接口。
|
||||
|
||||
第三步,确认接。由于网页中包含了多次从“getOnsInfo”接口取得数据,所以要逐个查看接口的返回数据。你可以通过鼠标点击接口的链接,然后再点击“Response”按钮,最后根据返回接口的返回内容,查看是否为“疫情实时数据”。
|
||||
|
||||
第四部,确认返回数据。当你从接口初步确认了该数据是“疫情实时数据”后,可以把该地址复制到浏览器中,进行访问。访问的具体办法就是通过模拟网页请求接口,这样就可以得到以下数据了。我将数据放在截图中,供你参考。
|
||||
|
||||
|
||||
|
||||
截图中的数据,可以通过查找省份和数据来确认数据的正确性。如果数据不正确,你需要回到第二步,再重新找下一个接口。
|
||||
|
||||
调整数据格式
|
||||
|
||||
当你确认了数据格式和数据源之后,接下来就需要把数据源的格式转换为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的官方网站。
|
||||
|
||||
Echarts的图表指定函数和pyecharts相同,找到你需要的图例函数之后,就可以拿到pyecharts中直接使用。
|
||||
|
||||
那针对老手的图表分类和API可以参考这个地址。以最常用的图表,折线图为例,你可以打开地址,其中会包括图表的完整调用代码、测试数据和图例,通过参考示例可以让你掌握更多类型的图表。折线图的截图如下:-
|
||||
|
||||
|
||||
再让我们回到疫情地图的案例中,由于我们需要绘制中国地图,因此直接使用pyecharts库的Map()类,它是绘制动态地图的类。它的官方网站链接我贴在这里。
|
||||
|
||||
通过参考官方网站的案例代码,我们可知在将Map()类实例化之后,进行绘图时,调用了add()、set_global_opts()和render()三个方法,它们分别是增加数据、设置样式和渲染。我们来依次学习一下。
|
||||
|
||||
|
||||
add()方法用来为图表增加图表名称、加载数据、指定地图的区域,其中加载数据的参数我使用了tuple()函数,可以直接把列表转换为元组,这样在查找效率上会比列表更高。
|
||||
|
||||
set_global_opts()方法用来指定图表的样式和格式,这里我做了两个格式的调整操作,它们是:图表的标题和图表的颜色。图表的标题部分,我将“数据最后更新时间”作为图表的标题,是通过字典“alldata[‘lastUpdateTime’]”实现的。图表的颜色是通过pieces参数实现的,根据不同省份确诊人数在什么数量范围,显示该省份的颜色深浅。颜色越深,表示当前周期采集到的确诊人数越多。
|
||||
|
||||
render()方法,用来指定图表保存的网页路径和名称。这里需要注意的是,add()和set_global_opts()方法如果出现错误并不会马上报错,只有在调用render()方法时才会出现错误。所以一旦出现错误,你应该从导入数据的格式、add()和set_global_opts()方法参数中检查。
|
||||
|
||||
|
||||
执行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绘制的图表需要每天更新时,如何自动删除上一个生成的文件?那有没有办法让网页自动更新呢?
|
||||
|
||||
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。
|
||||
|
||||
|
||||
|
||||
|
245
专栏/Python自动化办公实战课/26快速提取图片中的色块,模仿一张大师的照片.md
Normal file
245
专栏/Python自动化办公实战课/26快速提取图片中的色块,模仿一张大师的照片.md
Normal file
@ -0,0 +1,245 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
26 快速提取图片中的色块,模仿一张大师的照片
|
||||
你好,我是尹会生。
|
||||
|
||||
当你进行工作汇报使用各种图表时,除了要考虑精确的数据、正确的结论外,合理的配色方案也是精确表达数据的重要部分。
|
||||
|
||||
正确的配色不但能让用户更容易get到你演示产品的商业用途,还能为你的数据增光添彩。而不合理的配色,不但会降低用户对你演示产品的兴趣,还容易产生误解。
|
||||
|
||||
因此你要为你的数据搭配上合适的色彩方案,来增加数据的表现力。那么你要不要找专门的美工或自己分析那些商业模版用了什么配色呢?其实这两点都不需要,有很多设计高手和名画大师已经为我们提供了太多经典的作品了,我们只需要使用相似的配色,将这些配色应用到你的演示图片和文稿中,就能实现非常好的配色方案了。
|
||||
|
||||
我今天就来教你一种利用Pillow库自动分析图片中出现最多的颜色,并自动提取出来的方法。当你再遇到那些为配色发愁的工作场景,就可以找一个你喜欢的商业模版进行模仿了。
|
||||
|
||||
在本节课,我打算使用一幅莫奈的名画《日出·印象》来教你,如何使用Pillow库提取图片中被用到最多的五种颜色并将它们作为色块标注在图片上。下面的截图就是我们要实现的最终效果,接下来我就教你如何来实现它。
|
||||
|
||||
|
||||
|
||||
安装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位图像处理起来方便:
|
||||
|
||||
|
||||
在计算准确性上,24位图像保存的数据多,所以在进行像素与像素之间的计算中,会产生浮点数,出现计算数据溢出的情况,并因此导致计算的结果异常。
|
||||
|
||||
在计算性能上,8位深度图像和24位表示颜色的方式不同。因为在8位深度的图像中,有一个“调色板(palette)”的概念,这一概念只在8位图像才有,16位以上就都把图像颜色记录到图片自身的数据中了。所以8位图像要想表示一个具体的颜色,需要通过调色板中记录的颜色模版和图片中的模版索引计算之后才能得到,*而*针对*24位图像*,就可以直接把具体的颜色写入到了图片中。
|
||||
|
||||
|
||||
我给你举个例子,例如我把调色板保存在一个列表中,那么由于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值的对话框贴在下面,供你参考。
|
||||
|
||||
|
||||
|
||||
查表取出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颜色元组。
|
||||
|
||||
代码最后两行是对新生成的图片进行保持保存,以及在当前运行界面显示图片的操作。通过观察图片,我们发现提取的五个颜色刚好和主色调吻合。
|
||||
|
||||
我把合并好的图片和完整代码放在下面,供你参考。
|
||||
|
||||
|
||||
|
||||
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
专栏/Python自动化办公实战课/27zipfile压缩库:如何给数据压缩&加密备份?.md
Normal file
0
专栏/Python自动化办公实战课/27zipfile压缩库:如何给数据压缩&加密备份?.md
Normal file
332
专栏/Python自动化办公实战课/28Celery库:让计算机定时执行任务,解放人力.md
Normal file
332
专栏/Python自动化办公实战课/28Celery库:让计算机定时执行任务,解放人力.md
Normal file
@ -0,0 +1,332 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
28 Celery库:让计算机定时执行任务,解放人力
|
||||
你好,我是尹会生。
|
||||
|
||||
上一讲我教你怎么把一个文件夹自动打包成压缩包,学会之后,你肯定会想到可以利用这个功能,把每日工作报告的文件夹制作成压缩包,作为邮件的附件发送给领导。
|
||||
|
||||
那像是每日发送邮件、每周定时提醒、每月填写报表等等,类似这些按照某一周期完成的重复性任务,你肯定希望也能用Python来自动化完成它们,再也不用设置闹钟,提醒自己手动发送邮件和手动提交周报了。
|
||||
|
||||
那么今天我就教你如何使用Windows任务计划和Python的Celery库实现周期任务的自动执行。
|
||||
|
||||
定时执行任务的注意事项
|
||||
|
||||
说起计算机执行任务的方式,我们一般分为两种,分别是手动执行和定时执行,
|
||||
|
||||
定时执行任务,在自动化上的要求要比手动执行任务更高,所以两者在程序执行方式和输出结果上有较大的区别。为了让你能正确运行定时任务,我先来带你区分两种执行方式上有什么差别。
|
||||
|
||||
首先,执行任务的对象不同。这一点很好理解。手动执行的任务往往是人为操作的,而定时执行的任务启动往往是计算机按照我们设定的时间自动发起的。
|
||||
|
||||
其次,基于不同的执行对象,执行过程也有不同的要求。手动执行一个任务时,那么任务弹出的对话框、提示信息等中断任务执行过程的消息,可以人工处理。
|
||||
|
||||
而定时运行的任务,由于是由计算机发起的,因此不会有人工来处理。所以定时执行的任务一定要把代码编写成从任务开始到结束都不需要和“人”进行交互,简而言之,定时任务运行的程序必须是“非交互程序”。
|
||||
|
||||
最后,定时运行的程序是不支持把任务的结果输出到字符终端的,因此如果需要定时执行任务,并输出执行结果的话,需要把输出结果保存在文件中,否则定时任务会执行失败,导致该定时任务不会被计算机执行。
|
||||
|
||||
以上是手动执行任务和定时执行任务的差别,由于我们一般接触的都是手动执行,那么第一次接触定时执行任务时,就要避免把“交互式的程序,需要将执行结果输出到终端的程序”,直接放在定时任务中运行。
|
||||
|
||||
在你了解了定时执行任务的方式和注意事项后,我就来带你学习怎么基于Windows的任务计划和Python的Celery库实现定时任务。
|
||||
|
||||
需要说明的是,Windows的任务计划只能支持在Windows操作系统且在当前计算机定时运行任务,而且采用了图形界面配置定时任务的时间,比其他的任务计划工具操作更简。
|
||||
|
||||
但是如果你要是有多个计算机需要同时配置定时任务,又不想逐个计算机手动配置,那么使用Celery会比Windows任务计划更适合。
|
||||
|
||||
使用Windows任务计划,执行定时任务
|
||||
|
||||
首先,我先带你学习如何使用“Windows 任务计划”,实现定时任务。
|
||||
|
||||
当你的定时任务只需要在一台计算机运行,我建议你使用Windows系统自带的“任务计划”实现定时任务,步骤简单、直观,通过三个操作步骤就可以完成。
|
||||
|
||||
第一步,自然是启动任务计划程序了。你可以在两个地方找到它的启动入口:
|
||||
|
||||
|
||||
一个是“管理工具” 里的“任务计划程序”图标;
|
||||
另一个是“控制面板”的“计划任务”图标。
|
||||
|
||||
|
||||
启动以后的界面如下:
|
||||
|
||||
|
||||
|
||||
界面显示了当前已经在运行的定时任务,你可以点击“名称”来修改它们被执行的时间。如果你需要新建一个定时任务,还可以点击界面右侧的“创建基本任务”按钮。
|
||||
|
||||
第二步,“创建基本任务”,指的是设置任务的名称、执行时间,以及要执行的是哪一类程序。
|
||||
|
||||
其中“任务名称”是用来在“任务计划程序”界面区分多个任务的,如上图。而设定任务在什么时间执行的功能,叫做“触发器”,它可以按照你期望程序重复的周期,设置为“每天、每周、每月”以及更详细的时间运行。
|
||||
|
||||
我把“触发器”的设置截图放在下方,帮你更直观地理解定时任务的设置方式。
|
||||
|
||||
-
|
||||
除了“每天、每月、每周”等比较宽泛的设置,你还可以通过点击“下一步”进行更具体的执行时间设置,比如我希望每周一至周五的00:09:01执行任务,就可以按照下图的方式进行设置。
|
||||
|
||||
-
|
||||
在截图中,我勾选了周一至周五的复选框,并把开始时间设置为0点09分01秒。
|
||||
|
||||
第三步,设置指定时间运行的任务。定时执行的任务可以是一个可执行程序,也可以是终端的一条命令。你只需输入可执行文件或命令的完整路径,以及要执行的文件名称就可以了。点击确认后,就可以按你设定的时间运行了。我把截图贴在下面供你参考。
|
||||
|
||||
|
||||
|
||||
由于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”地址下载,根据提示多次执行“下一步”即可安装成功。安装成功后,打开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四个组件。-
|
||||
-
|
||||
前三个是Celery用于执行任务的组件,由Celery软件自身实现。其中Broker是中间件,用来连接Beat和多个Worker组件,Beat作为任务的生产者、Worker作为任务的消费者,可以通过Broker进行通信,从而定时运行Worker组件的多个任务。
|
||||
|
||||
最后一个是Celery的数据存储组件,基于Redis实现。在定时任务场景中,Redis用于存放用户添加的任务计划到数据库中,并把数据库文件保存到磁盘,这样前三个组件所在的进程即使遇到意外故障,重新运行,也不会造成定时任务的丢失。
|
||||
|
||||
此外,Celery和Redis数据还是通过TCP网络协议连接的,所以你可以给多个定时任务指定一个Redis数据库,以此来保证多个计算机上的定时任务需要进行修改时,只修改一次就可以实现自动数据同步了。
|
||||
|
||||
根据对Celery执行定时任务的工作过程分析,相信你已经对它的四个组件有所了解了。那接下来我就以每周六22点01分定时备份“c:\data”文件夹为例,给你讲解一下怎么通过编写Celery四个组件的代码来定时备份数据。
|
||||
|
||||
定时备份文件夹
|
||||
|
||||
实现文件夹的定时备份功能,需要编写代码的组件主要是Worker组件和Beat组件。
|
||||
|
||||
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来为你举例,它可以有四种写法:
|
||||
|
||||
|
||||
如果minute为默认的“*”,表示每分钟执行一次。
|
||||
如果minute为具体的数字,例如10,表示将会在第10分钟运行任务。不过这里要注意的是,当你使用具体数字时需要配合其他参数一起生效。假设小时的参数为“*”,则表示每小时的第10分钟运行任务,假设小时的参数为5,则表示5点10分运行任务。
|
||||
如果minute参数为“*/2” ,表示每2分钟运行一次任务。
|
||||
如果minute参数为“1,3,5”,表示每小时第1、3、5分钟运行一次任务。
|
||||
|
||||
|
||||
四种写法也同样可以应用于参数hour(小时)、day_of_week(星期)、day_of_month(月)、month_of_year(年),因此其他位置的参数我就不再一一为你进行讲解了。
|
||||
|
||||
最后一个参数是args,它的参数是元组, 用来指定定时运行任务时,为该任务对应的函数传递哪些参数,如果不需要传递参数,则需要保留该值为空元组“()”格式。
|
||||
|
||||
设置好定时任务的日期之后,需要继续编写该日期所需要运行的任务函数。定时运行的任务函数需要按照“task”的字典value保存在“tasks.jobs”目录中,我把目录结构的截图放在下方,帮你更直观地找到每个功能所在的文件目录。
|
||||
|
||||
|
||||
|
||||
在截图中,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
|
||||
|
||||
-------------- [email protected] 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()函数呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
181
专栏/Python自动化办公实战课/29网络和邮件库:定时收发邮件,减少手动操作.md
Normal file
181
专栏/Python自动化办公实战课/29网络和邮件库:定时收发邮件,减少手动操作.md
Normal file
@ -0,0 +1,181 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
29 网络和邮件库:定时收发邮件,减少手动操作
|
||||
你好,我是尹会生。
|
||||
|
||||
相信在你的日常办公工作当中,对邮件肯定早就不陌生了。我们通过邮件既可以发送和接收正式的公文,也能够利用邮件编写周报、月报,以及订阅定期发布的新闻或者期刊,等等。
|
||||
|
||||
在这么多的应用场景当中,你会发现有些收发邮件的工作是周期性的。那我们就可以利用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邮箱的网页端登陆成功后,通过设置-账号-IMAP服务,打开IMAP/SMTP服务。打开功能后,可以参考官方文档将IMAP服务器的地址指定为:“imap.qq.com”,“使用SSL”保证数据传输过程的安全,并将连接IMAP服务器的端口指定为“993”。打开IMAP服务的截图和官方文档的截图如下。
|
||||
|
||||
-
|
||||
-
|
||||
第二步是使用用户名密码登录。当服务器允许你从远程使用IMAP协议登陆服务器接收邮件后,就可以使用Python的imaplib库进行连接和登陆了。
|
||||
|
||||
imaplib库是Python的内置库,连接服务器可以使用IMAP4_SSL()函数,登录可以使用login()函数,连接和登陆的代码如下:
|
||||
|
||||
import imaplib
|
||||
conn = imaplib.IMAP4_SSL(host="imap.qq.com", port = 993)
|
||||
conn.login("[email protected]","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="[email protected]",
|
||||
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("[email protected]", "主题", body, "one.jpg")
|
||||
|
||||
|
||||
|
||||
|
||||
这就是利用yagmail实现自动发送邮件的完整过程,掌握之后,你可以把上节课学习的定时任务,以及自动生成图形的matplotlib库结合起来使用,将发送邮件功能定义为一个函数,从而实现周报和月报的自动发送功能。
|
||||
|
||||
小结
|
||||
|
||||
最后,我来为你总结一下这一讲的主要内容。在本讲,我使用了yagmail库、imaplib库以及email库实现了邮件自动收发的功能。与Foxmail和Outlook比起来,使用Python实现的邮件客户端,能够在收取邮件后对主题等元素自行判断,并与正则表达式、IM通知等其他工具组合,实现更加自动化的功能。
|
||||
|
||||
利用yagmail自动发邮件前,你还可以为你的周期发送的邮件指定模版,通过Celery实现定时发送和周期发送邮件。
|
||||
|
||||
除了可以自动收发邮件外,我还为你详细讲解了接收邮件的处理过程,这一过程遵循了IMAP的协议规范,决定了代码编写的先后顺序,如果邮件接收或发送是你自动化工作中主要优化的工具,那么我建议你用更多的时间来掌握IMAP与SMTP协议规范。
|
||||
|
||||
当你掌握了IMAP协议能够支持哪些操作以及不能支持哪些操作之后,才能更好地进行邮件API的学习。对于IMAP支持的功能,你可以参考官方文档,找到函数及其参数,对于没有支持的功能,你需要自己实现自定义的函数。
|
||||
|
||||
最后的最后,除了微信、钉钉外,邮件可以说是我们职场中使用最广泛的通讯工具了。并且也是我们工作中最正式的通讯工具。因此我建议你能够多练习怎么通过Python更加熟练地自动化收发邮件,相信我,这会为你的工作带来更高效的输出。
|
||||
|
||||
思考题
|
||||
|
||||
按照惯例,我来为你留一道思考题,如果我希望每周六10点整,能够自动的将C盘上的一个目录作为邮件的附件发送到一个指定邮箱,你会使用哪些库来实现,你能否将实现的思路用自己的语言讲出来呢?
|
||||
|
||||
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!
|
||||
|
||||
|
||||
|
||||
|
209
专栏/Python自动化办公实战课/30怎么快速把任意文件格式转成PDF,并批量加水印?.md
Normal file
209
专栏/Python自动化办公实战课/30怎么快速把任意文件格式转成PDF,并批量加水印?.md
Normal file
@ -0,0 +1,209 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
30 怎么快速把任意文件格式转成PDF,并批量加水印?
|
||||
你好,我是尹会生。
|
||||
|
||||
在办公场景中,我们打交道最多的软件,要数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文件,并继续存储在该目录中。如果你手动进行格式转换,只是需要以下四个步骤:
|
||||
|
||||
|
||||
进入到C:\data文件夹;
|
||||
使用Office的Word组件打开 a.doc文件;
|
||||
使用“文件另存为”功能,保存为PDF格式,并指定保存目录;
|
||||
保存并关闭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)。当你增加新的功能时,可以通过网页的内容来获得更多功能。
|
||||
|
||||
将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转换上,有两个主要函数的参数不同。
|
||||
|
||||
|
||||
DispatchEx()函数的参数,这里使用了“Excel.Application”字符串作为该函数的参数,“Excel.Application”作为DispatchEx()参数的目的是,让pywin32库启动Excel进程,并让它读取“a.xls”文件。
|
||||
ExportAsFixedFormat函数的第一个参数从pdf路径变为保存的类型,你可以参考Office的Excel VBA官方文档,从中学习函数的每个参数。
|
||||
|
||||
|
||||
将PowerPoint幻灯片转换为PDF
|
||||
|
||||
在你学习了Word文档和Excel表格转换PDF文件的基础上,我再来带你你对比学习一下如何将PowerPoint的默认保存文件ppt格式转换为PDF。参考word2pdf()和excel2pdf()两个函数,我直接将PowerPoint的幻灯片转换PDF文件的代码提供给你,你可以通过官方文档(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代码块以外进行编写。
|
||||
|
||||
为了让你更直接地感知到增加水印后的结果,我把增加水印后的结果贴在下方,供你参考。
|
||||
|
||||
|
||||
|
||||
以上就是我使用PyPDF2库为PDF增加水印的全部流程。不过除了增加水印外,你还能使用pdfWriter对象,来实现很多实用的功能,比如为PDF文件设置密码、插入新的页面、旋转PDF页面等等。
|
||||
|
||||
此外,由于pyPDF2库封装得非常好,所以它的调用很简单,你只需一个函数就能实现我刚才提到的这些功能了。我将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文件呢?
|
||||
|
||||
|
||||
|
||||
|
32
专栏/Python自动化办公实战课/春节特别放送1实体水果店转线上销售的数据统计问题.md
Normal file
32
专栏/Python自动化办公实战课/春节特别放送1实体水果店转线上销售的数据统计问题.md
Normal file
@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
春节特别放送1 实体水果店转线上销售的数据统计问题
|
||||
你好,我是尹会生。今天是大年三十儿,先祝你春节快乐。同时我也要为假期还在坚持学习的你点赞。
|
||||
|
||||
春节期间,我为你准备了一个项目作业,帮助你检验第一模块的学习成果。这个作业由3个部分组成,今天,我会介绍项目背景,给出你操作题目,2月13号,我会给出参考思路,在2月16号,我会公布答案。希望你跟着更新节奏,完整地参与进来,巩固你之前的学习成果。
|
||||
|
||||
接下来,我先介绍下项目情况。
|
||||
|
||||
这个题目来自我身边的一个真实案例。我先给你简单介绍下背景:一家实体水果店上了多多买菜后,店里的销售管理系统都没法直接使用实体店的那一套。而且像多多买菜、美团社群现在都只能下载Excel数据表。
|
||||
|
||||
题目是:F水果店由于疫情原因,2020年由实体店改为了线上销售。老板为了进行年终数据统计,需要从在线管理软件下载Excel格式的销售数据,并对销售数据进行处理,分析各个种类的水果受欢迎的程度和库存量,最后录入销售管理系统。不过老板在分析Excel的过程中,遇到了这样一个问题。
|
||||
|
||||
将线上的数据转换为线下数据的时候,由于数据不兼容,必须进行统计和一定格式的调整。首先需要合并Excel,统计全年水果销售量。在Excel中,每个sheet记录了当日的销售数据,每个Excel文件记录了当月的销售数据。图表如下:
|
||||
|
||||
|
||||
|
||||
我给你留两道题目:
|
||||
|
||||
|
||||
统计出全年每种水果的总销售额。
|
||||
统计出每月销售数量排名Top 3的水果。
|
||||
|
||||
|
||||
欢迎你在留言区分享你对这道题的想法和思考,我们一起交流、讨论。在2月13日,也就是大年初二这一天,我会把解题思路给你分享出来,供你参考。
|
||||
|
||||
|
||||
|
||||
|
60
专栏/Python自动化办公实战课/春节特别放送2用自顶至底的思路解决数据统计问题.md
Normal file
60
专栏/Python自动化办公实战课/春节特别放送2用自顶至底的思路解决数据统计问题.md
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
春节特别放送2 用自顶至底的思路解决数据统计问题
|
||||
你好,我是尹会生。今天是大年初二,先祝你春节快乐。
|
||||
|
||||
上节课我策划了一个项目作业,你完成得怎么样了呢?如果你对第一模块的内容掌握了60%,其实这道题你就能完成得差不多了,或者说至少会有一个思路。那么今天这节课,我也来给你提供一些我的解题思路和解题方法。
|
||||
|
||||
我们再来回顾下两道题目:
|
||||
|
||||
|
||||
统计出全年每种水果的总销售额。
|
||||
统计出每月销售数量排名Top 3的水果。
|
||||
|
||||
|
||||
因为这两道题目都是需要在统计数据的前提下进行的,一个是统计之后进行求和,一个是统计之后进行排序。所以,我们优先要做的就是统计全部文件所有sheet里的单元格数据。
|
||||
|
||||
以第一题为例,可以按照自顶至底的思路拆分问题。
|
||||
|
||||
什么是自顶至底呢?意思是分解问题的时候,先关注总体,再解决具体问题。我给你举个例子你会更容易理解。这是一个你熟悉的问题:怎样把大象装进冰箱?
|
||||
|
||||
你会马上想到,把大象装进冰箱分成三个步骤。你看,你不会关注冰箱是什么品牌,大象是什么品种,冰箱的容积如何,大象的体积有多大等等。这样思考的好处是你可以先对高层次的问题进行定义、设计、编码,再细节的问题,再其中的子任务或下一层次去解决。这样逐层去进行设计和编码能够让你的程序有更好的可读性。
|
||||
|
||||
那统计销售总数,我也可以把问题拆分成三个步骤。
|
||||
|
||||
|
||||
第一步,拆解Excel,通过第二讲的循环功能,取得每日销售数据。
|
||||
第二步,读取每天里的每次销售金额数据。
|
||||
第三步,将数据按照自己的算法(这里就是简单的累加)进行汇总。
|
||||
|
||||
|
||||
接下来我带你具体看下每个步骤需要做什么。
|
||||
|
||||
首先,我们来取出每日销售数据。
|
||||
|
||||
你可以先从总销售额开始入手,思考如何拆解问题,。总销售额可以拆分成按月销售额的累加,每月的销售数据可以存放到单独的文件中。月销售额可以拆分成日销售额的累加,日销售额放在每个sheet中。
|
||||
|
||||
如果用python来实现总销售额的统计,实现思路应该为:通过pathlib库和for循环遍历文件,读取每个Excel文件,再通过xlrd库读取每个Excel里的每个sheet,每个sheet里记录了每一天的销售数据,这样可以把每天的销售数据读取出来。
|
||||
|
||||
接着,我们来读取每一次销售数据。
|
||||
|
||||
由于Excel中除了销售金额,还有日期等其他噪声数据,我们只需要读取“水果名称”“销售金额”这两列必须数据,就能实现统计销售总额的功能了。因此通过这两个数据所在的列取出“水果名称”“销售金额”这两列。
|
||||
|
||||
用python实现的话,就是:使用for循环遍历每一行的水果,再采用“字典”数据类型临时存放到Python的一种数据结构中。为什么要使用“字典”呢?在第三讲我给你讲解过,“字典”适合记录有映射关系的一对或多对数据。
|
||||
|
||||
最后,将数据汇总在一起。
|
||||
|
||||
根据需求,销售的金额只需要记录汇总的金额就可以实现最终要求,也就是统计全年销售额的任务了。所以这一步具体做法就是:把字典中可以对每种水果的每次销售金额累加存放,就可以得到当日每种水果销售总金额了。
|
||||
|
||||
第二道题,那如何来实现Top3功能呢?
|
||||
|
||||
这两道问题的处理思路非常相似,都是需要处理每个月的销售额,但是第一个问题,统计全年销售额,需要将12个月的销售数据进行累加。第二个问题,需要对每个月的销售数据进行从大到小的排序,前三位的就是“水果销售Top3”了。
|
||||
|
||||
你的思路和想法是什么呢?可以在留言区分享一下,我们一起交流、讨论。在2月16日,也就是大年初五这一天,我会把完整答案给你分享出来,供你参考。
|
||||
|
||||
|
||||
|
||||
|
180
专栏/Python自动化办公实战课/春节特别放送3揭晓项目作业的答案.md
Normal file
180
专栏/Python自动化办公实战课/春节特别放送3揭晓项目作业的答案.md
Normal file
@ -0,0 +1,180 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
春节特别放送3 揭晓项目作业的答案
|
||||
你好,我是尹会生。
|
||||
|
||||
前两节课,我给你策划了一个项目作业,也给你提供了解题思路和方法,那么今天,就是揭晓答案的时候了。不管你有没有完成作业,或者完成了多少,我都建议你在学习今天这节课的时候,一边看我的讲解,一边把你的答案与我讲解的答案进行对照。
|
||||
|
||||
不过我也要提醒你,因为现在你才学到了第三节课,所以答案对你来说并不是最重要的。重要的是你对前三节课的内容掌握了多少,以及这个项目作业的解题思路和方法。
|
||||
|
||||
话不多说,我们现在就开始吧。
|
||||
|
||||
第一题
|
||||
|
||||
我们先看第一道题中关于订单统计的处理思路。
|
||||
|
||||
首先我们要明确要解决的问题。
|
||||
|
||||
我们希望对多个Excel进行统计,并取得每个Excel文件里的sheet。然后再从每一个sheet当中取出水果的名称和它的每一次销售金额,最终要将多个Excel里的所有的数据进行合并。而合并之后就是我们想要的全年水果销售金额统计结果。
|
||||
|
||||
所以解决这个问题的关键点有两个。
|
||||
|
||||
第一个关键知识点是遍历。这个知识点在我们的第1讲、第2讲中都有提到。通过遍历的方式,我们能依次取到每一个文件里的每一个sheet,甚至能从每个sheet取出每一行的内容。
|
||||
|
||||
第二个关键的知识点就是多个元素的累加操作。主要就是把相同名称的水果销售金额进行计算。不过这里涉及到的计算很简单,就是累加操作,通过循环就可以实现多个元素的依次累加了。由于累加是针对每一行数据的,所以我们要把累加操作写在遍历每一行的循环当中。
|
||||
|
||||
在搞清楚了两个关键知识点之后,接下来我来带你去分析一下我实现全年销售统计的代码。
|
||||
|
||||
import xlrd
|
||||
from pathlib import Path, PurePath
|
||||
from collections import defaultdict
|
||||
|
||||
# 订单路径
|
||||
download_path = '/Users/edz/Desktop/效率专栏/新年特辑/订单'
|
||||
|
||||
# 取得该目录下所有的xlsx格式文件
|
||||
p = Path(download_path)
|
||||
files = [x for x in p.iterdir() if PurePath(x).match('*.xlsx')]
|
||||
|
||||
# 定义字典用于结果统计
|
||||
total = defaultdict(int)
|
||||
|
||||
# 中文做字典的key会有问题,做两个简单的翻译函数
|
||||
tran_dict = {
|
||||
"dragon_fruit":"火龙果",
|
||||
"coconut":"椰子",
|
||||
"watermelon":"西瓜"
|
||||
}
|
||||
# 中文翻译成英文
|
||||
def dict_trans_chi2eng(value):
|
||||
return [k for k,v in tran_dict.items() if v == value]
|
||||
|
||||
# 英文翻译成中文
|
||||
def dict_name_eng2chi(key):
|
||||
return tran_dict[key]
|
||||
|
||||
# 遍历文件
|
||||
for file in files:
|
||||
sheet = xlrd.open_workbook(file)
|
||||
|
||||
# 遍历表格
|
||||
for table in sheet.sheets():
|
||||
# 从第二行遍历内容
|
||||
for line in range(1,table.nrows):
|
||||
fruit = table.row_values(rowx=line, start_colx=0, end_colx=None)
|
||||
# 第一列水果名称, 倒数第二列销售金额
|
||||
# print(f'{fruit[0]},{fruit[-2]}')
|
||||
# 火龙果,15.0
|
||||
# 椰子,16.0
|
||||
# 西瓜,10.5
|
||||
|
||||
# 统计每种水果的销售额
|
||||
fruit_name = dict_trans_chi2eng(fruit[0])[0]
|
||||
total[fruit_name] = total[fruit_name] + fruit[-2]
|
||||
|
||||
for fruit_name in total:
|
||||
print(f'水果: {dict_name_eng2chi(fruit_name)} , 总金额: {total[fruit_name]}')
|
||||
|
||||
# 水果: 火龙果 , 总金额: 135.0
|
||||
# 水果: 椰子 , 总金额: 144.0
|
||||
# 水果: 西瓜 , 总金额: 94.5
|
||||
|
||||
|
||||
我们来看代码的前3行。第1行是读取Excel文件的库,第2行是用来去处理路径的库,这两个库在我们课程的第一讲都已经为你重点讲解过。读取Excel使用的是xlrd库, 处理路径需要使用pathlib。之后的课程你也会多次看到它们,所以没有记住的话一定要再去复习。
|
||||
|
||||
我重点讲解一下第3行出现的库。第3行的库是一个叫做defaultdict的特殊字典。它特殊在哪里呢?和字典相比**,defaultdict可以在*字典*初始化的时候*得到一个默认的*值。在我的代码中,就使用defaultdict记录了水果数量,默认把水果数量设置为0,那就不用像默认字典一样,设置了字典之后还要手动把字典的值赋值为0。
|
||||
|
||||
这三行都是程序引入的库,接下来我们来看具体的代码逻辑。
|
||||
|
||||
首先我们需要获得Excel文件所在的路径。第9、10行代码是用来读取目录下所有的Excel文件的,获取路径使用的是第2讲的pathlib库,我在第2讲也为你详细剖析过它的逻辑,这里就不再赘述了。
|
||||
|
||||
获得路径之后,接着就要去处理每一个文件了。从第30行开始就是我们程序的核心逻辑,像咱们的第二讲一样,我在这里使用了三个for循环。
|
||||
|
||||
|
||||
第1个循环用来遍历所有的Excel文件。
|
||||
第2个循环用来遍历所有的sheet。
|
||||
第3个循环用来读取文件当中的,第2行到最后一行。
|
||||
|
||||
|
||||
通过这些循环我就可以按行进行处理。那再接着需要处理的就是如何进行水果销售金额的累加。
|
||||
|
||||
如果你仔细观察代码就会发现,我并没有马上去进行累加,而是在这里做了一个把中文转换成英文的额外处理,这个额外的处理使用了第三讲学过的自定义函数功能。
|
||||
|
||||
你可能会问,为什么要进行中文转换英文呢?因为在Python程序当中,字典这个数据类型对中文支持并不友好,使用中文作为字典的key进行操作时会报错。
|
||||
|
||||
所以为了避免字典在处理中文的时候出现报错,我在这里做了一个中文英文映射和转换的一个小功能。这个功能被我写在文件当中的第15到第27行,你会看到这里有一个被定义为translate_dict的一个映射关系的字典。
|
||||
|
||||
在这个字典当中,Key就是英文单词,value就是中文汉字。如果在你编写的程序当中需要支持更多的水果种类,那你就可以按照我的格式在这个字典中继续去扩展中英文的映射关系。
|
||||
|
||||
那中英文转换又是怎么实现的呢?我在字典之后又自定义了两个函数,分别被我命名为Chinese to English和English to Chinese。
|
||||
|
||||
在有了中英文转换功能,那我在进行水果的累加之前,“Chinese to English”函数就可以把字典的“key”从中文的水果名称转换成英文。转换成英文,就意味着英文的水果名称可以作为统计总金额的字典total的key。那么:
|
||||
|
||||
|
||||
使用total[fruit_name]方法就可以取得已经统计的水果金额。
|
||||
使用fruit[-2]取得通过当前得到的水果金额。
|
||||
使用total[fruit_name] = total[fruit_name] + fruit[-2] 方式,就可以实现全年的水果销售总金额统计功能。
|
||||
|
||||
|
||||
最后,在第49行把销售金额统计结果进行输出。由于水果名称在处理过程被处理成英文,为了让你查看的结果更加友好,我还需要再做两件事情。
|
||||
|
||||
|
||||
第一个是我在输出前把英文的水果名称使用“English to Chinese”函数转换成了中文。
|
||||
第二个是使用了字符串连接功能,将输出的内容进行对齐并增加必要的提示信息,这样你就可以看到每种水果今年的销售总额了。
|
||||
|
||||
|
||||
以上就是我在实现全年水果销售统计的问题上,是如何编码来实现的。
|
||||
|
||||
第二题
|
||||
|
||||
接下来我们再看第二个问题。第二个问题要求你按月进行水果销售额Top3的统计信息,并且取出当月销售数量前三名的水果名称。
|
||||
|
||||
针对这道题,在进行编码之前我们要思考一下,按月统计数据和全年统计数据有什么区别?怎么才能取出销售数量的前三名呢?
|
||||
|
||||
咱们首先来解决按月统计数据和全年统计数据有什么区别这个问题。
|
||||
|
||||
回顾一下我们在解决全年统计问题的过程。我们先取得了每次销售数据。之后又使用了total字典类型,统计了所有行的结果,通过循环,将所有行的结果汇总成日数据和月数据,又继续通过循环将月数据统计为全年销售总额。
|
||||
|
||||
如果需要统计水果销售额Top3,我们也需要先将数据统计为月销售数据,之后不能将月数据合并到一起,而是需要对每个月的数据进行排序,取出销售最多的前三个水果销售额,所以统计月销售数据以前的程序逻辑是相同的,区别是统计月销售数据以后,一个是进行合并一个是进行排序操作。
|
||||
|
||||
那第二个问题,怎样去取前三名呢?我们要想取出前几名,前提是要先进行数据的排序工作。因为排序之后才能从大到小取出销售量最高的前三名。为了实现排序这一功能,我们必须要掌握Python的排序和截取前三个元素的程序编写方法。
|
||||
|
||||
所以接下来我们要学习的就是具体编码。由于和统计总金额的代码功能非常相似,我们就直接复用统计总金额的获取文件路径,统计月销售额的代码。由于每一个文件代表一个月份的销售数据,所以我在第48行用输出文件名的方式告诉你当前在统计的文件是哪一个月份的。
|
||||
|
||||
通过上面的逻辑方法,我们实现了按月统计销售额和输出当前月份的功能。接下来就可以进行后续的排序,并且取出字典的value排前3的数据了。你可以通过比较传统的sort函数进行排序,并采用复循环三次取出前3水果名称和销售数量。
|
||||
|
||||
不过我觉得采用这样的方式,代码其实不够简洁。所以我们还有另外一种简洁的方法。
|
||||
|
||||
在程序中,我为了避免自己需要编程实现代码逻辑,就直接引入了collections标准库的Counter包实现排序并提取前n个数的功能。Counter包支持对列表和字典进行排序、数量统计、取前n个数的功能,非常强大。
|
||||
|
||||
那这里就用它来实现基于字典的value从大到小进行排序,排序之后取前三个元素的功能来实现,从而取得水果销售额Top3。
|
||||
|
||||
在代码的第53行,就是我使用了Counter包实现对total变量进行排序的具体实现代码。
|
||||
|
||||
在排序之后,接下来的问题就是如何取出前三个元素了。由于提取前N个元素的问题,Counter包已经内置了该功能,所以在第58行,我可以再通过most_common()函数,为函数增加数字“3”作为参数,从而实现提取销量Top3的功能。
|
||||
|
||||
# 使用Counter函数排序和统计数量
|
||||
sorted_total = Counter(total)
|
||||
|
||||
# 清空本月统计数据
|
||||
total = defaultdict(int)
|
||||
|
||||
# 通过most_commnon函数排序取出Top3
|
||||
print(sorted_total.most_common(3))
|
||||
|
||||
|
||||
|
||||
在这个程序当中,我们也有一个要注意的事项。因为每个月份统计好的数据都会放到total变量当中,因此在进行下一个月统计的时候,total变量会把本月统计结果累加上去,这样就会导致下一个月统计的数量不准确。
|
||||
|
||||
所以我们在统计完每一个月的统计数据之后,需要把total变量重新初始化。为了能够让你注意这件事情,我特意把它写在了第56行并详细进行了标注。这也是在进行数据统计时极容易犯错误的地方,希望你能注意它的用法。
|
||||
|
||||
以上就是我实现所有水果的金额统计和按月统计的实现方法,希望你能通过这两个代码理解如何去处理大批量重复的Excel工作。
|
||||
|
||||
好了,我们的春节策划到这节课就结束了,你的答案和我的一样吗?希望你能借助这个项目,查漏补缺,多多巩固前面的内容。下节课,我们继续来自动化办公的方法,我们下次见。
|
||||
|
||||
|
||||
|
||||
|
96
专栏/Python自动化办公实战课/结束语和我一起成为10X效率职场人.md
Normal file
96
专栏/Python自动化办公实战课/结束语和我一起成为10X效率职场人.md
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
|
||||
因收到Google相关通知,网站将会择期关闭。相关通知内容
|
||||
|
||||
|
||||
结束语 和我一起成为10X效率职场人
|
||||
你好,我是尹会生。
|
||||
|
||||
这是咱们的最后一节课了,在前面的课程中,你跟着我学了30种提高办公效率的方式,那么现在回过头来,我想请你再思考一下:你认为什么样的办公方式效率是更高的呢?可以把你的想法写在评论区,我们一起互动交流。
|
||||
|
||||
我先来分享一下我的想法,聊聊我在这么多年的职场工作中,对办公效率的一些思考。
|
||||
|
||||
什么样的办公方式效率更高?
|
||||
|
||||
我对办公效率的理解经过了两个阶段,也可以称之为两个层面。
|
||||
|
||||
第一个阶段是多写非交互程序,让计算机能在没有人工干预的情况下,运行主要工作。
|
||||
|
||||
那时,我在一线做运维工程师,大部分时间我都在和服务器打交道,而且是海量的服务器。为了能批量控制服务器上的软件,我就要用各种编程技巧来让脚本批量自动运行。
|
||||
|
||||
所以,在这个阶段,我所理解的效率呢,就是多编写脚本。这样原本需要人工来完成的工作,我都可以编写一个定时任务,让它们自动运行,从而让流程可控不出错,工作高效还省心。
|
||||
|
||||
但是,在我开始接触更深入的研发工作后,我对效率的认知就进入到了第二个阶段。只有提升项目全路径和全流程的效率,才能真正把办公效率提升10倍。
|
||||
|
||||
因为我发现,不同的软件架构有不同的业务特点,而它们又是相互依赖的,那么如果只针对某个业务进行提效,还是不能大幅度提升一个完整研发项目的落地效率。
|
||||
|
||||
换句话说,当你学会用某个编程技巧写出一个编程脚本时,你的工作效率会有所提升。但是如果项目的其他部分还是手动操作,那么对于整个项目来说,效率的提升是很微不足道的。
|
||||
|
||||
那你肯定会问了,怎样才能优化整个项目,来进一步提升办公效率呢?答案是用计算机思维。“思维”这个词可能乍一听,你会感觉很高级,学起来很难。其实不然。
|
||||
|
||||
我说的计算机思维,指的就是:当我们解决编程或计算机相关项目的时候,你可以将一个完整的任务,拆解成计算机可操作的基本单元。而这些基本单元,其实就是我在开篇词中所说的输入、运算、控制、存储、输出五个部分。当我们能够根据项目任务进行拆分,让一整套工作都能实现自动化的时候,工作效率也就能提升10倍,我们也就能成为10X效率的职场人了。
|
||||
|
||||
像我在第29节课讲用自动化发送周报时,提醒你可以把定时发送功能、编写周报模版等知识串联起来。这其实就是一个计算机思维的简单应用。
|
||||
|
||||
那你肯定会接着问了:那怎么才能有计算机思维呢?这正是我接下来想要分享的,计算机思维不是一朝一夕可以修炼成的,你需要走过三个阶段,这也正是从1X效率,到10X效率职场人的三个阶段。
|
||||
|
||||
成为10X效率职场人的三个阶段
|
||||
|
||||
在我看来,通过编程提高效率有三个阶段,那就是:先模仿,再理解,最后连接。
|
||||
|
||||
这么说可能有点抽象,我想结合一个运营的案例,来给你讲一讲每个阶段应该具体怎么去做。
|
||||
|
||||
在运营方法论中,有一个“漏斗模型”。假如我现在要用漏斗模型来分析一个游戏玩家的留存率,那怎么将这个过程进行自动化呢?
|
||||
|
||||
我们先来分析一下。在这个过程中,我需要采用漏斗模型来依次分析用户点击广告、用户下载游戏、用户登陆游戏、7日留存和付费的情况。这就要用到咱们在课程中学过的利用Python来进行数据提取、数据处理和图形展示。
|
||||
|
||||
现在,我们来看怎么将整个过程自动化。
|
||||
|
||||
首先是第一个阶段:模仿,模仿高手的代码。
|
||||
|
||||
在模仿阶段,你可以模仿我在课程中讲过的代码。比如咱们学习过的利用SQL语句从数据库提取数据,存入Excel中;接着,可以用Python代替Excel处理人工观察数据,统计结果;最后,你可以把结果用Seaborn进行绘图并展示。
|
||||
|
||||
第二个阶段是理解,理解代码的底层逻辑。
|
||||
|
||||
在理解阶段,你需要通过计算机的原理,把数据提取、处理和展示,按照计算机的输入、处理、输出逻辑,拆解为这样几个步骤:
|
||||
|
||||
|
||||
从数据库读取数据,这是输入;
|
||||
在内存中处理数据,这是运算;
|
||||
最后通过图形整理好数据,并按照字典、列表等数据类型输出成图表,这是输出。
|
||||
|
||||
|
||||
你看,虽然这个场景我在课程中并没有讲过,但是通过拆解计算机的基本单元,你会发现内容都是相似的,也就可以举一反三了。
|
||||
|
||||
第三个阶段是连接,将手工的工作转变为由代码执行。
|
||||
|
||||
在连接阶段,你需要把工作中的需求在拆解计算机的逻辑基础上,再将它们转化为代码,通过编码来实现一整套工作的自动化。
|
||||
|
||||
就像我们刚才拆分的读取数据、处理数据和整体数据这样几步,我们可以将这些内容都整合在一起,转化为代码,让一整套工作都自动化。
|
||||
|
||||
看到这儿你肯定就明白了,提高办公效率,根本的解决方法不在于学习多少小技巧或小软件,而在于理解底层逻辑,加快人和计算机的交互过程,这样自然就能解决大部分的效率问题。
|
||||
|
||||
所以我提出这三个阶段,是希望你不仅能学会这30节课,还能掌握其中的思想和方法。问题千变万化,但计算机思维是不变的,这样你才能触类旁通,应对你遇到的绝大部分办公效率问题。
|
||||
|
||||
当然,这个目标不可能一蹴而就,你需要不断地学习、应用。我也想再给你分享两条学习这门课程的建议。
|
||||
|
||||
首先,如果你有时间,或者基础比较薄弱的话,我建议你去抄写每一段代码。要知道,看一节课和把看到的知识写出来的感觉是完全不一样的。
|
||||
|
||||
其次,在结课后,你还要学以致用。
|
||||
|
||||
“圣人之道无异于百姓之日用。”你在这门课中学到那么多东西,如果不用起来,那么最后的价值还是零。所以你需要把学会的知识、方法、代码,用到你的办公场景中,在提升工作效率的同时,也能锻炼提升效率的思维。
|
||||
|
||||
当你用得多了之后,再面对这些问题,你就会形成一种肌肉反应。这就像你虽然不记得键盘上从D到P中间都有哪些字母,但是你能在用到这些字母时,熟练地将它们输入进去一样。
|
||||
|
||||
有人说这是凭借经验不假,但是更本质的是,你需要有更洞察的思维。这是需要你把拼音、五笔烂熟于心才能做到的,而绝不是用什么样的指法敲击得更快。
|
||||
|
||||
我记得在第19节讲了怎么利用Python领取京东金豆时,有同学在评论区分享到,学会之后就可以去研究自动签到打卡了。你看,这就是一个很好的应用计算机思维的例子。
|
||||
|
||||
当你把自己的时间从重复的劳动解脱出来时,你会发现,自己的技术和问题处理能力都上了一大台阶,职场进阶速度会更快。此外,你也会更有时间、有心情去提升自己其他方面的能力,以及享受生活。提升工作效率,也就是在提升你的职场进阶效率,提高你生活的幸福指数。
|
||||
|
||||
最后,我也为你准备了一份毕业问卷,想听一下你对这门课的想法和建议。题目不多,希望你可以花两分钟填一下。十分期待你的反馈。-
|
||||
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user