first commit

This commit is contained in:
张乾
2024-10-16 06:37:41 +08:00
parent 633f45ea20
commit 206fad82a2
3590 changed files with 680090 additions and 0 deletions

View 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。

View 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。

View 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。

View 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操作的区别。比如下面这一段文字
简易流程——集团原则在每年15月、711月生产工作任务较重或考核时间较紧的情况下运用。
在Word文件中不但有文字内容还有加粗、红色等格式而且这些特殊的格式和文字内容是混合在一起的。
但如果用Python来读取Word文件这段文字会被分为纯文字、段落、字体、字号以及表格等更具体的部分而且每一个部分都对应着Python的变量和函数。
这样一来,你就可以根据自己的需求,只提取某一具体部分的内容。比如说,你看到哪一段文字的字体很好看,就可以直接读取之后套用到新的文字段落就行,非常便捷。
不过用Python读取文件时你需要记住很多个Python变量和函数。当然了针对这一点你也不用担心这些变量和函数在Python的Word扩展库官方文档可以查看。所以如果你需要某个功能但是不知道应该用什么变量和函数名称时可以在官方文档中找到它的名字和描述信息。
总之用Python读取文件的方式是非常有助于提高工作效率的。所以接下来我们用Python处理Word文件时就需要通过刚才介绍的变量和函数来替代手动操作。
接下来我先带你学习怎样用Python合并多个Word文件然后再讲怎么把Word文件与纯文本、图片和Excel进行合并。
如何合并多个Word文件只保留文字内容
先从合并两个Word文件说起。假设你现在有两个Word文件需要进行文件中的文字合并操作。两个文件的内容分别是:
文件一(内容包含字体、字号、颜色等额外信息):
1.简易流程——集团原则在每年15月、711月生产工作任务较重或考核时间较紧的情况下运用。
文件二(内容文字出现在表格中):
我把两个文件的信息总结如下:
第一个文件中,字体使用了黑体和宋体字,此外还有红色字体和加粗等格式。现在我需要只提取其中的文字内容,不带任何格式。
第二个文件中文字被放在了一张表格里。现在我需要用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。

View 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

View 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逻辑判断适合将一个范围替换成一个值。
除了灵活掌握不同的替换方式,我还建议你把字典和逻辑判断放入自定义函数当中,当你遇到类似需求的时候就可以直接复用代码。
思考题
通过将城市的拼音替换成汉字的功能,你是否能实现一个自己的自动多文件标点符号替换函数,将英文符号替换为中文符号呢?

View 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文件放入变量便于你合并新的视频的时候进行重新赋值。
所以你看相比直接使用FFmpegsubprocess调用FFmpeg的优势就在于两点一是不用记住复杂参数二是对批量转换视频非常有利。举两个例子。
如果你是视频剪辑的专业工作者肯定要大量使用FFmpeg更复杂的功能这些功能对应的参数一般都比较多而且参数很多都使用了简写和大小写, 很难记忆。但要是使用Python调用的话你可以直接更改要操作的文件路径就不必记录大量的参数。
另外需要进行视频的批量转换时,可以通过第一讲的循环操作对视频任务批量处理,这样就避免了手动逐个修改书写文件的操作,从而提高视频转换的效率。
小结
最后,我来为你总结一下这节课的主要内容。
通过对subprocess库的讲解你知道了怎样通过它实现Python加载外部可执行程序并且能够对程序执行的结果进行处理。
我也为你讲解了长图拼接和视频拆分合并的两个案例帮你更好地理解Python为什么会被称作“胶水”语言。
我还想强调一下通过Python调用可执行程序的用法非常常见特别是在多媒体处理、自然科学、AI等领域里。在这些专业领域为了加快计算速度通常会使用C++语言实现专业程序。
这些专业程序参数多、功能单一且使用命令行执行当你需要多次执行这些程序又不想背诵它们的参数的时候就可以利用Python的判断循环功能结合C++语言实现的专业程序,来实现批量执行和减少参数手动输入的工作,提高你的工作效率。
最后,我也把这节课的代码附上,你可以查看。本讲代码
思考题
在最后也请你思考一下你在工作当中是否会使用命令行工具呢它们能否用Python进行包装从而避免手写复杂参数呢
如果你觉得这节课有用,能解决你的办公效率问题,欢迎你点击“请朋友读”,分享给你的朋友或同事。

View 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库就能够根据每个词的词性对其进行标注。
我这里为你提供了一张paddlepaddle是百度开源的深度学习平台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 上,你可以点击这个链接查看。
思考题
我给你留一道思考题,我在最后一段代码分别统计了正向和负向评价的数量,你能否根据这段代码统计一段文字中包含了多少个动词、多少个名词和多少个形容词呢?欢迎你在课程评论区留言,和我一起讨论。

View 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()函数这一行的开头我还使用了一个关键字withwith关键字下面的代码是缩进形式和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丰富的数据类型可以让你更灵活的操作工作中的数据通过选择合适的数据类型也能使用各种数据类型自带的函数减少手动实现自定义函数的代码节省你的代码编码时间。
思考题
最后我想留一道思考题给你,在进行多个文件的中、英文和标点符号各自的数量统计时,你会选择哪种数据类型,用来存放每个文件的字数统计数据呢?欢迎你来说出自己的想法。

View File

@ -0,0 +1,179 @@
因收到Google相关通知网站将会择期关闭。相关通知内容
08 正则表达式:如何提高搜索内容的精确度?
你好,我是尹会生。
开始上课之前,我想先带你看两种常见的工作需求:
领导让你搜索出一个文档里所有的手机号码, 而你只知道手机号的模式是11位数字那该怎样搜索呢
你需要在一个文档中搜索一串带区号的电话号码比如010-12345678099-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次。{}还可以使用**{mn}的形式表示限定的次数从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()函数,实现身份证的匹配和提取功能呢?

View 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扩展名的文件呢?
欢迎把你的思考和想法分享在留言区,我们一起交流讨论。如果课程帮你解决了一些工作上的问题,也欢迎你把课程分享给你的朋友、同事。

View 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语言不将所有的数据类型都设计成默认有序的即存入数据时自动进行排序欢迎你说出自己的理由。
欢迎把你对问题的思考和想法分享在留言区,我们一起交流讨论。如果课程帮你解决了一些工作上的问题,也欢迎你把课程分享给你的朋友、同事。

View 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对比出逐个访问网页的时间是并行访问的几倍吗

View 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中你需要使用哪些函数做怎样的处理才能实现时间的对齐呢?
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以扫描课程详情页的二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。

View 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的主要流程。这七个步骤其实并不繁琐并且你要是再细心点儿就会发现我是把“转换”这一部分拆解成了五个步骤其他两部分并没有变。
总结来说相比较PythonPower 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编辑器中实现统计每个月的销售额以及如何实现每种产品名称半年的销售额统计功能。
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。

View 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 参考)改造批量打印脚本,让脚本实现包含关键字“汇总”,然后再打印报表?
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。

View 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”扩展名文件的批量删除请你大胆尝试一下。

View 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上供你学习参考。
思考题
最后我来为你留一道比较有趣的思考题,你能否通过命令行为代码指定两个参数,当这两个参数为整数时,脚本自动计算这两个参数的“和”和“差”,并将执行结果打印到屏幕上。
欢迎把你的思考和想法写在评论区,我们一起交流讨论。此外,你还可以点击课程详情页的“戳我进群”,然后扫描二维码,加入我们的课程读者群,我也会在群里为你解疑答惑。我们下节课再见!

View 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的代码呢
欢迎把你的思考和想法写在评论区,我们一起交流讨论。如果你学完有所收获,也欢迎你把课程分享给你的朋友、同事,一起提升办公效率。好了,那我们下节课再见!

View 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讲中我们实现了文件的批量改名功能你能否将批量改名功能也封装成一个函数结合今天的批量下载实现下载之后的文件自动改成你需要的文件名呢
欢迎把你的思考和想法分享在留言区,我们一起交流、讨论。也欢迎你把我们的课程分享给你的朋友、同事,一起做职场上的效率人。我们下节课再见!

View 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”作为关键字进行搜索然后把搜索的结果保存到一个文件呢
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!

View 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”三类的文件名和大小依次显示在屏幕上你会怎样实现呢

View 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()函数得到的是什么样的呢?你能否通过官方文档再找到获得多条查询结果的函数呢?
欢迎把你的想法和思考写在留言区,我们一起交流讨论。如果这节课在数据保存上帮你提高了办公效率,那也欢迎你把课程分享给你的朋友或同事,我们一起做职场上的效率人。

View 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”增加联系人之前没有判断该联系人是否存在。你能否利用判断语句实现增加联系人前对联系人是否存在进行判断并提示用户对重复联系人进行合并操作呢
欢迎把你的思考和想法放在留言区,我们一起交流讨论。如果这节课学习的数据透视表对你的工作有帮助,也欢迎你把课程推荐给你的朋友或同事,一起做职场中的效率人。

View 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表格才能通过数据透视表进行数据分析呢
欢迎把你的思考和想法放在留言区,我们一起交流讨论。如果这节课学习的数据透视表对你的工作有帮助,也欢迎你把课程推荐给你的朋友或同事,一起做职场中的效率人。

View 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将这一图表绘制出来呢
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。

View 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绘制的图表需要每天更新时如何自动删除上一个生成的文件那有没有办法让网页自动更新呢
欢迎你把思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。

View 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对图像操作外更想让你明白在计算机中对很多非数字化的元素也是可以进行计算的当然计算的前提是将它们转化成可量化的数字像是深度学习领域中对语音、文字、图像的处理都是以将它们数字化为前提进行的。因此当你遇到非数字化的对象需要进行自动化操作时也可以使用将非数字化转化为数字化的思路去解决办公难题。
思考题
按照惯例,我来为你留一道思考题,如果有多张图片需要提取其中的主要颜色,你怎样自动化实现它们的颜色提取和文件保存操作呢?
欢迎把你的思考和想法分享在留言区,我们一起交流讨论。如果今天的内容对你展示工作成果有帮助,也欢迎你把课程分享给你的同事和朋友,我们一起做职场上的效率人。

View 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参数为“135”表示每小时第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()函数呢?
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!

View 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盘上的一个目录作为邮件的附件发送到一个指定邮箱你会使用哪些库来实现你能否将实现的思路用自己的语言讲出来呢
欢迎把你的想法和思考分享在留言区,我们一起交流讨论。也欢迎你把课程分享给你的同事、朋友,我们一起做职场中的效率人。我们下节课再见!

View 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文件呢

View 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日也就是大年初二这一天我会把解题思路给你分享出来供你参考。

View 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日也就是大年初五这一天我会把完整答案给你分享出来供你参考。

View 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工作。
好了,我们的春节策划到这节课就结束了,你的答案和我的一样吗?希望你能借助这个项目,查漏补缺,多多巩固前面的内容。下节课,我们继续来自动化办公的方法,我们下次见。

View 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领取京东金豆时有同学在评论区分享到学会之后就可以去研究自动签到打卡了。你看这就是一个很好的应用计算机思维的例子。
当你把自己的时间从重复的劳动解脱出来时,你会发现,自己的技术和问题处理能力都上了一大台阶,职场进阶速度会更快。此外,你也会更有时间、有心情去提升自己其他方面的能力,以及享受生活。提升工作效率,也就是在提升你的职场进阶效率,提高你生活的幸福指数。
最后,我也为你准备了一份毕业问卷,想听一下你对这门课的想法和建议。题目不多,希望你可以花两分钟填一下。十分期待你的反馈。-