T1:文件自动化处理&邮件批量处理
- 文件路径识别、处理、文件夹的操作理论学习
- 文件自动化处理实践
- 邮件自动发送理论学习,使用python发送邮件附带excel附件
1.1文件处理
1.1.1 文件与文件路径
文件的两个属性:“路径”和“文件名”,路径指明文件在计算机上的位置,文件名是指该位置的文件的名称。比如,我的电脑上,有个名字为Datawhale - 开源发展理论研究.pdf的文件,它的路径在D:\Datawhale。在windows中,路径中的D:\部分是“根文件夹”,Datawhale是文件夹名。注:Windows中文件夹名和文件名不区分大小写的。
1.1.2 当前工作目录
每个运行在计算机上的程序,都有一个“当前工作目录”。利用`os.getcwd()`函数,可以取得当前工作路径的字符串,并可以利用`os.chdir()`改变它。
1.1.3 路径操作
1.1.3.1 绝对路径和相对路径
“绝对路径”,总是从根文件夹开始。
“相对路径”,相对于程序的当前工作目录。
相对路径中,单个句点“.”表示当前目录的缩写,两个句点“..”表示父文件夹。
几个常用的绝对路径和相对路径处理函数:
- os.path.abspath(path):将相对路径转换为绝对路径,将返回参数的绝对路径的字符串。
- os.path.isabs(path):判断是否是绝对路径,是返回True,不是则返回False
1.1.3.2 路径操作
`os.path.relpath(path,start)`:返回从start路径到path的相对路径的字符串。如果没提供start,就使用当前工作目录作为开始路径。
`os.path.dirname(path)`: 返回当前路径的目录名称。
`os.path.basename(path)`:返回当前路径的文件名称。
如果同时需要一个路径的目录名称和基本名称,可以调用`os.path.split()`,获得者两个字符串的元组。
```python
caFilePath = 'D:\\Datawhale\\python办公自动化\\python课程画图.pptx'
os.path.split(caFilePath) #('D:\\Datawhale\\python办公自动化', 'python课程画图.pptx')
```
我们也可以调用os.path.dirname()和os.path.basename(),将它们的返回值放在一个元组中,从而得到同样的元组。
```python
(os.path.dirname(caFilePath),os.path.basename(caFilePath)) #('D:\\Datawhale\\python办公自动化', 'python课程画图.pptx')
```
如果我们想返回每个文件夹的字符串的列表。用`os.path.split()`无法得到,我们可以用`split()`字符串方法,并根据`os.path.sep` 中的字符串进行分割。`os.path.sep` 变量设置为正确的文件夹分割斜杠。
```python
caFilePath.split(os.path.sep) #['D:', 'Datawhale', 'python办公自动化', 'python课程画图.pptx']
```
1.1.3.3 路径有效性检查
如果提供的路径不存在,很多Python函数就会崩溃并报错。`os.path`模块提供了一些函数,用于检测给定的路径是否存在,以及判定是文件还是文件夹。
`os.path.exists(path)`:如果path参数所指的文件或文件夹存在,则返回True,否则返回False。
`os.path.isfile(path)`:如果path参数存在,并且是一个文件,则返回True,否则返回False。
`os.path.isdir(path)`:如果path参数存在,并且是一个文件夹,则返回True,否则返回False。
```python
os.path.exists('C:\\Windows')
```
```python
os.path.exists('C:\\else')
```
```python
os.path.isfile('D:\\Datawhale\\python办公自动化\\python课程画图.pptx')
```
```python
os.path.isdir('D:\\Datawhale\\python办公自动化\\python课程画图.pptx')
```
1.1.4 文件及文件夹操作
1.1.4.1 用os.makedirs()创建新文件夹
注:`os.makedirs()`可以创建所有必要的中间文件夹。
```python
import os
os.makedirs('D:\\Datawhale\\practice') #查看目录,已创建,若文件夹已存在,不会覆盖,会报错
```
1.1.4.2 查看文件大小和文件夹内容
我们已经可以处理文件路径,这是操作文件及文件夹的基础。接下来,我们可以搜集特定文件和文件夹的信息。`os.path`模块提供了一些函数,用于查看文件的字节数以及给定文件夹中的文件和子文件夹。
`os.path.getsize(path)`:返回path参数中文件的字节数。
`os.listdir(path)`:返回文件名字符串的列表,包含path参数中的每个文件。
```python
"""
注意这里你可以自己按照这个路径新建文件夹,并任意放入一个pptx文件,
并重命名为python课程画图.pptx。否则若不存在该文件将会报错,而非0字节
"""
os.path.getsize('D:\\Datawhale\\python办公自动化\\python课程画图.pptx')
```
```python
os.listdir('D:\\Datawhale\\python办公自动化')
```
如果想知道目录下所有文件的总字节数,可以同时使用`os.path.getsize()`和`os.listdir()`
```python
totalSize = 0
for filename in os.listdir('D:\\Datawhale\\python办公自动化'):
totalSize = totalSize + os.path.getsize(os.path.join('D:\\Datawhale\\python办公自动化',filename))
print(totalSize)
```
1.1.5 文件读写过程
读写文件3个步骤:
1.调用`open()`函数,返回一个File对象。
2.调用File对象的`read()`或`write()`方法。
3.调用File对象的`close()`方法,关闭该文件。
open函数中 常见的对象方法及其作用说明:
1.1.5.1 用open()函数打开文件
要用`open()`函数打开一个文件,就要向它传递一个字符串路径,表明希望打开的文件。这既可以是绝对路径,也可以是相对路径。`open()`函数返回一个File对象。
先用TextEdit创建一个文本文件,名为hello.txt。输入Hello World!作为该文本文件的内容,将它保存在你的用户文件夹中。
文件对象可以通过Python内置的open函数得到,完整的语法如下。
open(file,mode=r',buffering=-1,encoding=None,errors=None,newline=None,closefd=True,opener=None)
open函数有8个参数,常用前4个,除了file参数外,其他参数都有默认值。file指定了要打开的文件名称,应包含文件路径,不写路径则表示文件和当前py脚本在同一个文件夹。buffering用于指定打开文件所用的缓冲方式,默认值-1表示使用系统默认的缓冲机制。文件读写要与硬盘交互,设置缓冲区的目的是减少CPU操作磁盘的次数,延长硬盘使用寿命。encoding用于指定文件的编码方式,如GBK、UTF-8等,默认采用UTF-8,有时候打开一个文件全是乱码,这是因为编码参数和创建文件时采用的编码方式不一样。
mode指定了文件的打开模式。打开文件的基本模式包括r、w、a,对应读、写、追加写入。附加模式包括b、t、+,表示二进制模式、文本模式、读写模式,附加模式需要和基本模式组合才能使用,如“rb”表示以二进制只读模式打开文件,“rb+”表示以二进制读写模式打开文件。
要注意的是,凡是带w的模式,操作时都要非常谨慎,它首先会清空原文件,但不会有提示。凡是带r的文件必须先存在,否则会因找不到文件而报错。
```python
helloFile = open('D:\\Datawhale\\python办公自动化\\hello.txt')
print(helloFile)
```
可以看到,调用`open()`函数将会返回一个File对象。当你需要读取或写入该文件,就可以调用helloFile变量中的File对象的方法。
1.1.5.2 读取文件内容
有了File对象,我们就可以开始从它读取内容。
`read()`:读取文件内容。
`readlines()`:按行读取文件中的内容,取得一个字符串列表,列表中每个字符串是文本中的一行且以\n结束。
```python
helloContent = helloFile.read()
helloContent
```
```python
sonnetFile = open('D:\\Datawhale\\python办公自动化\\hello.txt')
sonnetFile.readlines()
```
1.1.5.3 写入文件
需要用“写模式”‘w’和“添加模式”'a'打开一个文件,而不能用读模式打开文件。
“写模式”将覆写原有的文件,从头开始。“添加模式”将在已有文件的末尾添加文本。
```python
baconFile = open('bacon.txt','w')
baconFile.write('Hello world!\n')
```
```python
baconFile.close() #注意,关闭后,才能完成写入,从txt文件中看到写入的内容。
```
```python
baconFile = open('bacon.txt','a')
baconFile.write('Bacon is not a vegetable.')
```
```python
baconFile.close()
```
```python
baconFile = open('bacon.txt')
content = baconFile.read()
baconFile.close()
print(content)
```
注意,`write()`方法不会像print()函数那样,在字符串的末尾自动添加换行字符。必须自己添加该字符。
- 案例:统计字母出现的频率
文件对象有iter、next方法,所以它是一个可迭代对象,可以用for循环遍历。我们可以遍历文件获得每一行字符,再遍历每一行,获得每个字符,将字符放入列表,然后统计每个字符出现的频率。
```python
from collections import Counter
my_list = []
punctuation=',.!?\,。!?、()【】<>《》=:+-*“”...\n'
with open('bacon.txt','r') as f:
for line in f:
for word in line:
if word not in punctuation:
my_list.append(word)
counter = Counter(my_list)
counter
```
1.1.5.4 保存变量
1)、shelve模块
用`shelve`模块,可以将Python中的变量保存到二进制的`shelf`文件中。这样,程序就可以从硬盘中恢复变量的数据。
```python
import shelve
shelfFile = shelve.open('mydata')
cats = ['Zonphie','Pooka','Simon']
shelfFile['cats'] = cats
shelfFile.close()
```
在Windows上运行前面的代码,我们会看到当前工作目录下有3个新文件:mydata.bak、mydata.dat和mydata.dir。在OS X上,只会创建一个mydata.db文件。
重新打开这些文件,取出数据。注意:`shelf`值不必用读模式或写模式打开,因为打开后,既能读又能写。
```python
shelfFile = shelve.open('mydata')
type(shelfFile)
```
```python
shelve.DbfilenameShelf
```
```python
shelfFile['cats']
```
```python
shelfFile.close()
```
就像字典一样,`shelf`值有`keys()`和`values()`方法,返回shelf中键和值的类似列表的值。但是这些方法返回类似列表的值,却不是真正的列表,所以应该将它们传递给`list()`函数,取得列表的形式。
```python
shelfFile = shelve.open('mydata')
list(shelfFile.keys())
```
```python
list(shelfFile.values())
```
```python
shelfFile.close()
```
2)、用`pprint.pformat()`函数保存变量
`pprint.pformat()`函数返回要打印的内容的文本字符串,这个字符串既易于阅读,也是语法上正确的Python代码。
假如,有一个字典,保存在一个变量中,希望保存这个变量和它的内容,以便将来使用。`pprint.pformat()`函数将提供一个字符串,我们可以将它写入.py文件。这个文件可以成为我们自己的模块,如果需要使用存储其中的变量,就可以导入它。
```python
import pprint
cats = [{'name':'Zophie','desc':'chubby'},{'name':'Pooka','desc':'fluffy'}]
pprint.pformat(cats)
```
```python
fileObj = open('myCats.py','w')
fileObj.write('cats = '+pprint.pformat(cats)+'\n')
```
```python
fileObj.close()
```
import语句导入的模块本身就是Python脚本。如果来自pprint.pformat()的字符串保存为一个.py文件,该文件就是一个可以导入的模块。
```python
import myCats
myCats.cats
```
```python
myCats.cats[0]
```
```python
myCats.cats[0]['name']
```
1.1.6 练习
1、如果已有的文件以写模式打开,会发生什么?
提示:
```
以写模式打开
r : 只读模式,文件不存在泽报错,默认模式(文件指针位于文件末尾)
w : 写入模式,文件不存在则自动报错,每次打开会覆盖原文件内容,文件不关闭则可以进行多次写入(只会在打开文件时清空文件内容)
```
2、`read()`和`readlines()`方法之间的区别是什么?
提示:
read():以原格式返回全部文本
readline(): 只返回第一行文本
readlines(): 以列表的格式返回全部文本,文本的第几行对应列表的第几个元素
综合练习:
一、生成随机的测验试卷文件
假如你是一位地理老师, 班上有 35 名学生, 你希望进行美国各州首府的一个
小测验。不妙的是,班里有几个坏蛋, 你无法确信学生不会作弊。你希望随机调整
问题的次序, 这样每份试卷都是独一无二的, 这让任何人都不能从其他人那里抄袭答案。当然,手工完成这件事又费时又无聊。 好在, 你懂一些 Python。
下面是程序所做的事:
• 创建 35 份不同的测验试卷。
• 为每份试卷创建 50 个多重选择题,次序随机。
• 为每个问题提供一个正确答案和 3 个随机的错误答案,次序随机。
• 将测验试卷写到 35 个文本文件中。
• 将答案写到 35 个文本文件中。
这意味着代码需要做下面的事:
• 将州和它们的首府保存在一个字典中。
• 针对测验文本文件和答案文本文件,调用 open()、 write()和 close()。
• 利用 random.shuffle()随机调整问题和多重选项的次序。
提示:
https://blog.csdn.net/liying_tt/article/details/117968373
1.1.7 组织文件
在上一节中,已经学习了如何使用Python创建并写入新文件。本节将介绍如何用程序组织硬盘上已经存在的文件。不知你是否经历过查找一个文件夹,里面有几十个、几百个、甚至上千个文件,需要手工进行复制、改名、移动或压缩。比如下列这样的任务:
• 在一个文件夹及其所有子文件夹中,复制所有的 pdf 文件(且只复制 pdf 文件)
• 针对一个文件夹中的所有文件,删除文件名中前导的零,该文件夹中有数百个文件,名为 spam001.txt、 spam002.txt、 spam003.txt 等。
• 将几个文件夹的内容压缩到一个 ZIP 文件中(这可能是一个简单的备份系统)
所有这种无聊的任务,正是在请求用 Python 实现自动化。通过对电脑编程来完成这些任务,你就把它变成了一个快速工作的文件职员,而且从不犯错。
1.1.1.7.1 shutil模块
`shutil`(或称为shell工具)模块中包含一些函数,可以在Python程序中复制、移动、改名和删除文件。要使用`shutil`的函数,首先需要`import shutil`
1.1.1.7.2 复制文件和文件夹
`shutil.copy(source, destination)`:将路径source处的文件复制到路径 destination处的文件夹(source 和 destination 都是字符串),并返回新复制文件绝对路径字符串。
其中destination可以是:
1)、一个文件的名称,则将source文件复制为新名称的destination
2)、一个文件夹,则将source文件复制到destination中
3)、若这个文件夹不存在,则将source目标文件内的内容复制到destination中,若destination文件夹不存在,则自动生成该文件。(慎用,因为会将source文件复制为一个没有扩展名的名字为destination的文件,这往往不是我们希望的)
```python
"""
这里如果路径下没有bacon.txt,可以从当前代码文件路径下找到bacon.txt,
将其移至指定路径学习使用
"""
import shutil
import os
shutil.copy('D:\\Datawhale\\python办公自动化\\bacon.txt', 'D:\\Datawhale\\practice')
```
- shutil.copytree(source, destination):将路径source处的文件夹,包括其包含的文件夹和文件,复制到路径destination处的文件夹,并返回新复制文件夹绝对路径字符串。
注:destination处的文件夹为新创建的文件夹,如已存在,则会报错
```python
import shutil
shutil.copytree('D:\\Datawhale\\python办公自动化','D:\\Datawhale\\practice')
```
```python
import shutil
shutil.copytree('D:\\Datawhale\\python办公自动化','D:\\Datawhale\\practice_unexist')
```
1.1.7.3 文件和文件夹的移动与改名
`shutil.move(source, destination)`:将路径 source 处的文件/文件夹移动到路径destination,并返回新位置的绝对路径的字符串。
1)、如果source和destination是文件夹,且destination已存在,则会将source文件夹下所有内容复制到destination文件夹中。移动。
2)、如果source是文件夹,destination不存在,则会将source文件夹下所有内容复制到destination文件夹中,source原文件夹名称将被替换为destination文件夹名。 移动+重命名
3)、如果source和destination是文件,source处的文件将被移动到destination处的位置,并以destination处的文件名进行命名,移动+重命名。
注意:如果destination中有原来已经存在同名文件,移动后,会被覆写,所以应当特别注意。
```python
import shutil
shutil.move('D:\\Datawhale\\practice','D:\\Datawhale\\docu')
```
1.1.7.4 永久删除文件和文件夹
`os.unlink(path)`: 删除path处的文件。
`os.rmdir(path)`: 删除path处的文件夹。该文件夹必须为空,其中没有任何文件和文件夹。
`shutil.rmtree(path)`:删除 path 处的文件夹,它包含的所有文件和文件夹都会被删除。
注意:使用时,需要非常小心,避免删错文件,一般在第一次运行时,注释掉这些程序,并加上`print()`函数来帮助查看是否是想要删除的文件。
```python
#建议先指定操作的文件夹,并查看
os.chdir('D:\\Datawhale\\docue')
os.getcwd()
```
```python
import os
for filename in os.listdir():
print(filename)
os.unlink(filename)
# 可以看到bacon.txt已经被删除
for filename in os.listdir():
print(filename)
```
1.1.7.5 用send2trash模块安全地删除
`shutil.rmtree(path)`会不可恢复的删除文件和文件夹,用起来会有危险。因此使用第三方的`send2trash`模块,可以将文件或文件夹发送到计算机的垃圾箱或回收站,而不是永久删除。因程序缺陷而用send2trash 删除的某些你不想删除的东西,稍后可以从垃圾箱恢复。
注意:使用时,需要非常小心,避免删错文件,一般在第一次运行时,注释掉这些程序,并加上`print()`函数来帮助查看是否是想要删除的文件。
```python
!pip install send2trash #安装send2trash模块
```
```python
import send2trash
send2trash.send2trash('bacon.txt')
```
1.1.8 遍历目录树
`os.walk(path)`:传入一个文件夹的路径,在for循环语句中使用`os.walk()`函数,遍历目录树,和range()函数遍历一个范围的数字类似。不同的是,`os.walk()`在循环的每次迭代中,返回三个值:
1)、当前文件夹称的字符串。
2)、当前文件夹中子文件夹的字符串的列表。
3)、当前文件夹中文件的字符串的列表。
注:当前文件夹,是指for循环当前迭代的文件夹。程序的当前工作目录,不会因为`os.walk()`而改变。
![2](.\png\2.png)
按照下图目录树,创建相应的文件。
```python
import os
for folderName, subFolders,fileNames in os.walk('D:\\animals'):
print('The current folder is ' + folderName)
for subFolder in subFolders:
print('Subfolder of ' + folderName+':'+subFolder)
for filename in fileNames:
print('File Inside ' + folderName+':'+filename)
print('')
```
1.1.9 用zipfile模块压缩文件
为方便传输,常常将文件打包成.zip格式文件。利用zipfile模块中的函数,Python程序可以创建和打开(或解压)zip文件。
1.1.9.1 创建和添加到zip文件
将上述章节中animals文件夹进行压缩。创建一个example.zip的zip文件,并向其中添加文件。
`zipfile.ZipFile('filename.zip', 'w')` :以写模式创建一个压缩文件
`ZipFile` 对象的 `write('filename','compress_type=zipfile.ZIP_DEFLATED')`方法:如果向`write()`方法中传入一个路径,Python 就会压缩该路径所指的文件, 将它加到 ZIP 文件中。 如果向`write()`方法中传入一个字符串,代表要添加的文件名。第二个参数是“压缩类型”参数,告诉计算机用怎样的算法来压缩文件。可以总是将这个值设置为 `zipfile.ZIP_DEFLATED`(这指定了 deflate 压缩算法,它对各种类型的数据都很有效)。
注意:写模式会擦除zip文件中所有原有的内容。如果只希望将文件添加到原有的zip文件中,就要向`zipfile.ZipFile()`传入'a'作为第二个参数,以添加模式打开 ZIP 文件。
```python
## 1 创建一个new.zip压缩文件,并向其中添加文件
import zipfile
newZip = zipfile.ZipFile('new.zip','w')
newZip.write('Miki.txt',compress_type=zipfile.ZIP_DEFLATED)
newZip.close()
```
```python
newZip = zipfile.ZipFile('new.zip','w')
newZip.write('D:\\animals\\dogs\\Taidi.txt',compress_type=zipfile.ZIP_DEFLATED)
newZip.close()
```
```python
## 2 创建一个example.zip的压缩文件,将animals文件夹下所有文件进行压缩。
import zipfile
import os
newZip = zipfile.ZipFile('example.zip','w')
for folderName, subFolders,fileNames in os.walk('D:\\animals'):
for filename in fileNames:
newZip.write(os.path.join(folderName,filename),compress_type=zipfile.ZIP_DEFLATED)
newZip.close()
```
1.1.9.2 读取zip文件
调用`zipfile.ZipFile(filename)`函数创建一个`ZipFile`对象(注意大写字母Z和F),filename是要读取zip文件的文件名。
`ZipFile`对象中的两个常用方法:
`namelis()`方法,返回zip文件中包含的所有文件和文件夹的字符串列表。
`getinfo()`方法,返回一个关于特定文件的`ZipInfo`对象。
`ZipInfo`对象的两个属性:`file_size`和`compress_size`,分别表示原来文件大小和压缩后文件大小。1.2.3.2 读取zip文件
```
import zipfile,os
exampleZip = zipfile.ZipFile('example.zip')
exampleZip.namelist()
```
```
catInfo = exampleZip.getinfo('animals/Miki.txt')
```
```
catInfo.file_size
```
```
catInfo.compress_size
```
```
print('Compressed file is %s x smaller!' %(round(catInfo.file_size/catInfo.compress_size,2)))
```
```
exampleZip.close()
```
1.1.9.3 从zip文件中解压缩
`ZipFile` 对象的 `extractall()`方法:从zip文件中解压缩所有文件和文件夹,放到当前工作目录中。也可以向`extractall()`传递的一个文件夹名称,它将文件解压缩到那个文件夹, 而不是当前工作目录。如果传递的文件夹名称不存在,就会被创建。
`ZipFile` 对象的 `extract()`方法:从zip文件中解压单个文件。也可以向 extract()传递第二个参数, 将文件解压缩到指定的文件夹, 而不是当前工作目录。如果第二个参数指定的文件夹不存在, Python 就会创建它。extract()的返回值是被压缩后文件的绝对路径。
```python
import zipfile, os
exampleZip = zipfile.ZipFile('example.zip')
exampleZip.extractall('.\zip')
exampleZip.close()
```
```python
exampleZip = zipfile.ZipFile('example.zip')
exampleZip.extract('animals/Miki.txt')
exampleZip.extract('animals/Miki.txt', 'D:\\animals\\folders')
exampleZip.close()
```
1.1.10 文件查找
对于文件操作,最需要熟练掌握的就是查找文件。前面介绍了使用os.listdir、os.walk方法可以批量列出当前工作目录的全部文件,下面介绍常用于查找特定文件的模块。
1.1.10.1 glob
glob是Python自带的一个文件操作相关模块,用它可以查找符合条件的文件。例如,我们要找到当前目录下全部的.txt文档,可以用下面的代码。
```python
import glob
glob.glob('*.txt')
```
这里主要是写匹配条件,“*”匹配任意个字符,“?”匹配单个字符,也可以用“[]”匹配指定范围内的字符,如[0-9]匹配数字。
- glob.glob('*[0-9]*.*')可以匹配当前目录下文件名中带有数字的文件。
- glob.glob(r'G:\*')可以获取G盘下的所有文件和文件夹,但是它不会进一步列明文件夹下的文件。也就是说,其返回的文件名只包括当前目录里的文件名,不包括子文件夹里的文件
1.1.10.2 fnmatch模块
fnmatch也是Python自带的库,是专门用来进行文件名匹配的模块,使用它可以完成更为复杂的文件名匹配。它有4个函数,分别是fnmatch、fnmatchcase、filter和translate,其中最常用的是fnmatch函数,其语法如下。
- fnmatch.fnmatch(filename,pattern)
pattern表示匹配条件,测试文件名filename是否符合匹配条件。
下面找出目标文件夹里所有结尾带数字的文件
```python
import os,fnmatch
path = os.getcwd() # 获取当前代码文件所在目录
for foldname, subfolders,filenames in os.walk(path):
for filename in filenames:
if fnmatch.fnmatch(filename,'*[0-9].*'):
print(filename)
```
fnmatchcase和fnmatch函数类似,只是fnmatchcase函数强制区分字母大小写。
以上两个函数都返回True或者False,filter函数则返回匹配的文件名列表,其语法如下:
- fnmatch.filter(filelist,pattern)
1.1.10.3 hashlib模块
随着计算机中文件越来越多,我们需要找出重复文件。重复文件可能有不同的文件名,不能简单用文件名和文件大小来判断。从科学角度,最简单的办法就是通过MD5来确定两个文件是不是一样的。
Python自带的hashlib库里提供了获取文件MD5值的方法。
```python
import hashlib
m = hashlib.md5()
f = open('bacon.txt','rb')
m.update(f.read())
f.close()
md5_value = m.hexdigest()
print(md5_value)
```
电子文件容易被篡改或者伪造,在出现纠纷时,怎么提供有力的证据来证明文件的真实性?一个可行的办法就是制作文件后对整个文件生成MD5值。一旦MD5值生成之后,文件发生过任何修改,MD5值都将改变,通过此方法可以确定文件是否被篡改过。
1.1.11 练习
1)、编写一个程序,遍历一个目录树,查找特定扩展名的文件(诸如.pdf 或.jpg)。不论这些文件的位置在哪里, 将它们拷贝到一个新的文件夹中。
2) 、一些不需要的、 巨大的文件或文件夹占据了硬盘的空间, 这并不少见。如果你试图释放计算机上的空间, 那么删除不想要的巨大文件效果最好。但首先你必须找到它们。编写一个程序, 遍历一个目录树, 查找特别大的文件或文件夹, 比方说, 超过100MB 的文件(回忆一下,要获得文件的大小,可以使用 os 模块的 `os.path.getsize()`)。将这些文件的绝对路径打印到屏幕上。
3)、编写一个程序, 在一个文件夹中, 找到所有带指定前缀的文件, 诸如 spam001.txt,spam002.txt 等,并定位缺失的编号(例如存在 spam001.txt 和 spam003.txt, 但不存在 spam002.txt)。让该程序对所有后面的文件改名, 消除缺失的编号。作为附加的挑战,编写另一个程序,在一些连续编号的文件中,空出一些编号,以便加入新的文件。
1.3 自动发送电子邮件
使用Python实现自动化邮件发送,可以让你摆脱繁琐的重复性业务,节省非常多的时间。
Python有两个内置库:`smtplib`和`email`,能够实现邮件功能,`smtplib`库负责发送邮件,`email`库负责构造邮件格式和内容。
邮件发送需要遵守**SMTP**协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
```python
#1 先导入相关的库和方法
import smtplib #导入库
from smtplib import SMTP_SSL #加密邮件内容,防止中途被截获
from email.mime.text import MIMEText #构造邮件的正文
from email.mime.image import MIMEImage #构造邮件的图片
from email.mime.multipart import MIMEMultipart #把邮件的各个部分装在一起,邮件的主体
from email.header import Header #邮件的文件头,标题,收件人
```
```python
#2 设置邮箱域名、发件人邮箱、邮箱授权码、收件人邮箱
host_server = 'smtp.163.com' #sina 邮箱smtp服务器 #smtp 服务器的地址
sender_163 = 'pythonauto_emai@163.com' #sender_163为发件人的邮箱
pwd = 'DYEPOGLZDZYLOMRI' #pwd为邮箱的授权码'DYEPOGLZDZYLOMRI'
#也可以自己注册个邮箱,邮箱授权码'DYEPOGLZDZYLOMRI' 获取方式可参考#http://help.163.com/14/0923/22/A6S1FMJD00754KNP.html
# 设置接受邮箱,换成自己的邮箱即可
receiver = '1121091694@qq.com'
```
```python
#3 构建MIMEMultipart对象代表邮件本身,可以往里面添加文本、图片、附件等
msg = MIMEMultipart() #邮件主体
```
```python
#4 设置邮件头部内容
mail_title = 'python办公自动化邮件' # 邮件标题
msg["Subject"] = Header(mail_title,'utf-8') #装入主体
msg["From"] = sender_163 #寄件人
msg["To"] = Header("测试邮箱",'utf-8') #标题
```
```python
#5 添加正文文本
mail_content = "您好,这是使用python登录163邮箱发送邮件的测试" #邮件的正文内容
message_text = MIMEText(mail_content,'plain','utf-8') #构造文本,参数1:正文内容,参数2:文本格式,参数3:编码方式
msg.attach(message_text) # 向MIMEMultipart对象中添加文本对象
```
```python
#6 添加图片
image_data = open('D:\\animals\\cats\\zophie.jpg','rb') # 二进制读取图片
message_image = MIMEImage(image_data.read()) # 设置读取获取的二进制数据
image_data.close() # 关闭刚才打开的文件
msg.attach(message_image) # 添加图片文件到邮件信息当中去
```
```python
# 7 添加附件(excel表格)
atta = MIMEText(open('D:\\animals\\cats\\cat.xlsx', 'rb').read(), 'base64', 'utf-8') # 构造附件
atta["Content-Disposition"] = 'attachment; filename="cat.xlsx"' # 设置附件信息
msg.attach(atta) ## 添加附件到邮件信息当中去
```
```python
#8 发送邮件
smtp = SMTP_SSL(host_server) #SSL登录 创建SMTP对象
smtp.login(sender_163,pwd) ## 登录邮箱,传递参数1:邮箱地址,参数2:邮箱授权码
smtp.sendmail(sender_163,receiver,msg.as_string()) # 发送邮件,传递参数1:发件人邮箱地址,参数2:收件人邮箱地址,参数3:把邮件内容格式改为str
print("邮件发送成功")
smtp.quit # 关闭SMTP对象
```
T2:Python Excel 自动化之 OpenPyXL
2.0 包的安装
打开 CMD/Terminal 进入到自己环境后,执行下面语句安装`openpyxl`模块。
```bash
pip3 install openpyxl
```
注:openpyxl可以读/写 .xlsx /.xlsm /.xltx /.xltm 的格式文件,但是不支持去读 /.xls 格式;读取 xls 格式,可以安装 **xlrd** 模块,`pip3 install xlrd`,本章节以 /.xlsx 格式为主。
2.1 Excel读取
- 2003年版本的是 xls 格式,2007和2007年之后的版本是 xlsx 格式。
- xlsx 格式通过 `openpyxl` 模块打开; xls 格式通过 `xlwt` 模块写,`xlrd` 模块读取。
- 本文以 xlsx 模式为例
2.1.1 读取Excel中的工作表
**关于路径:**
文件应在当前工作目录才可直接用相对路径引用,可导入`os`,使用函数`os.getcwd()`弄清楚当前工作目录是什么,可使用`os.chdir()`改变当前工作目录,具体可参考第一章节。(此处显现为相对路径)
```python
# 获取当前工作目录
import os
print(os.getcwd())
import warnings
warnings.filterwarnings('ignore')
root_path = './OpenPyXL_test/'
```
2.1.1.1. 读取Excel文件 `用户行为偏好.xlsx ` ,查看返回值属性
```python
# 导入模块,查看属性
import openpyxl
wb = openpyxl.load_workbook(root_path+'用户行为偏好.xlsx')
type(wb)
```
openpyxl.workbook.workbook.Workbook
【代码解释】
这里我们使用 openpyxl 中的 load_workbook 函数来加载指定的 xlsx 文件,。
- openpyxl.load_workbook(
filename,
read_only=False,
keep_vba=False,
data_only=False,
keep_links=True,
)
load_workbook 函数有五个参数,除 filename 外,其他参数都有默认值,各参数含义如下:
- `filename`: str 类型,表示要打开的文件的相对/绝对路径;
- `read_only`: bool 类型,是否以只读模式打开文件,默认值为 False,可读写;
- `keep_vba`: bool 类型,是否保留文件中的 vba 内容(即使保留了也不一定在代码中能使用),默认值为 False,不保留;
- `data_only`: bool 类型,如果单元格中是 excel 公式,是以公式计算后的值的形式显示还是以公式内容形式显示,默认值为 False,以公式内容形式展示;
- `keep_links`: bool 类型,是否保留单元格中的外链,默认值为 True,保留外链;
- 返回值类型: `openpyxl.workbook.Workbook`
如无特殊要求,我们只需要指定`filename`参数即可。
【小知识】
**import * 和from...import...**
`import *`和`from...import...`的区别
- `import`导入一个模块,相当于导入的是一个文件夹,相对路径。
- `from...import...`导入了一个模块中的一个函数,相当于文件夹中的文件,绝对路径。
2.1.1.2. 查看对应工作簿包含的 sheet(工作表) 的名称,读取活动表
```python
# 导入模块中的函数,查询对应表的名称
print(wb.sheetnames)
```
['订单时长分布', 'Sheet3']
【代码解释】
这里我们使用 `openpyxl.workbook.Workbook` 类对象的 `sheetnames` 属性来获取读取的工作簿中包含的 sheet(工作表) 的名称。
通过上述代码输出内容,我们可以知道 `用户行为偏好.xlsx` 中包含两个 sheet(工作表),分别是:订单时长分布、 Sheet3。
```python
# 读取工作簿的活动表
# 活动表是工作簿在 Excel 中打开时出现的工作表,在取得 Worksheet 对象后,可通过 title 属性取得它的名称。
active_sheet = wb.active
print(f'active_sheet对象: {active_sheet}')
print(f'active_sheet 名称: {active_sheet.title}')
```
active_sheet对象: <Worksheet "订单时长分布">
active_sheet 名称: 订单时长分布
【小知识】
活动表是可以修改的,在我们正常打开excel,完成修改后,保存excel,在关闭 excel 前显示的 sheet 就是活动表。
2.1.1.3. 查看指定sheet信息
```python
# 通过传递表名字符串读取表、类型和名称、内容占据的大小
sheet = wb.get_sheet_by_name('Sheet3')
print(f'sheet: {sheet}')
print(f'type(sheet): {type(sheet)}')
print(f'sheet.title: {sheet.title}')
print(f'sheet.dimensions: {sheet.dimensions}')
```
sheet: <Worksheet "Sheet3">
type(sheet): <class 'openpyxl.worksheet.worksheet.Worksheet'>
sheet.title: Sheet3
sheet.dimensions: A1:I17
【代码解释】
这里我们使用 `openpyxl.workbook.Workbook` 类对象的 `get_sheet_by_name` 方法,通过指定 sheetname 的方式来获取读取的工作簿中指定的 sheet(工作表) 对象。
并使用 `openpyxl.worksheet.worksheet.Worksheet` 类对象的一些属性来获取 sheet 的基本信息,比如 `Worksheet.title`获取 sheet 名称,`Worksheet.dimensions` 获取 sheet 中值的范围。
Workbook.get_sheet_by_name(name) 函数只有一个参数,就是:sheetname(工作表名称),功能是:通过 sheetname 获取到 Worksheet 对象,除了通过函数的方式获取到 Worksheet 对象,你还可以提过索引的方式,如:
```python
wb['Sheet3']
```
2.1.2 读取工作表中的单元格
**Cell(Excel单元格)**
- Cell 对象有一个 value 属性,包含这个单元格中保存的值。
- Cell 对象也有 row 、column 和 coordinate 属性,提供该单元格的位置信息。
- Excel 用字母指定列,在Z列之后,列开始使用两个字母:AA、AB等,所以在调用的 cell() 方法时,可传入整数作为 row 和 column 关键字参数,也可以得到一个单元格。
- 注:第一行或第一列的整数取1,而不是0.
```python
# 从表中取得单元格 在 2.1.1 中我们已经读取过工作簿了 返回结果存储变量为 wb
## 获取表格名称
print(f'sheetnames: {wb.sheetnames}')
```
sheetnames: ['订单时长分布', 'Sheet3']
```python
# 获取指定sheet
sheet = wb.get_sheet_by_name('订单时长分布')
# 通过单元格位置获取单元格对象,如:B1
a = sheet['B1']
print(f"sheet[B1']: {a}")
# 获取并打印 B1 单元格的文本内容
print(f"sheet[B1'].value: {a.value}")
# 获取并打印 B1 单元格所在行、列和数值
print(f'Row: {a.row}, Column: {a.column}')
# 获取并打印 B1 单元格坐标 和 值
print(f'Cell {a.coordinate} is {a.value}')
```
sheet[B1']: <Cell '订单时长分布'.B1>
sheet[B1'].value: 日期
Row: 1, Column: 2
Cell B1 is 日期
```python
# 获取并打印出 B列 前8行的奇数行单元格的值
for i in range(1,8,2):
print(i, sheet.cell(row=i,column=2).value)
```
1 日期
3 2020-07-24 00:00:00
5 2020-07-24 00:00:00
7 2020-07-24 00:00:00
```python
# 确定表格的最大行数和最大列数,即表的大小
print(f'sheet.max_row: {sheet.max_row}')
print(f'sheet.max_column: {sheet.max_column}')
```
sheet.max_row: 14
sheet.max_column: 4
2.1.3 读取多个单元格的值
```python
# 方法一:直接通过sheet索引,A1到C8区域的值
cells = sheet['A1:C8']
print(f'type(cells): {type(cells)} \n')
# 遍历元组 print每一个cell值
for rows in cells:
for cell in rows:
print(cell.value, end=" |")
print("\n")
```
type(cells): <class 'tuple'>
编号 |日期 |行为时长 |
71401.30952380953 |2020-07-24 00:00:00 |a |
71401.30952380953 |2020-07-24 00:00:00 |b |
71401.30952380953 |2020-07-24 00:00:00 |c |
71401.30952380953 |2020-07-24 00:00:00 |d |
71401.30952380953 |2020-07-24 00:00:00 |e |
71401.30952380953 |2020-07-24 00:00:00 |f |
71401.30952380953 |2020-07-24 00:00:00 |g |
```python
# 方法二:sheet.iter_rows函数 按行获取数据
rows = sheet.iter_rows(min_row=1, max_row=8, min_col=1, max_col=3)
# 遍历元组 print每一个cell值
for row in rows:
for cell in row:
print(cell.value, end=" |")
print("\n")
```
编号 |日期 |行为时长 |
71401.30952380953 |2020-07-24 00:00:00 |a |
71401.30952380953 |2020-07-24 00:00:00 |b |
71401.30952380953 |2020-07-24 00:00:00 |c |
71401.30952380953 |2020-07-24 00:00:00 |d |
71401.30952380953 |2020-07-24 00:00:00 |e |
71401.30952380953 |2020-07-24 00:00:00 |f |
71401.30952380953 |2020-07-24 00:00:00 |g |
```python
# 方法三:sheet.iter_cols函数 按列获取数据
cols = sheet.iter_cols(min_row=1, max_row=4, min_col=1, max_col=3)
# 遍历元组 print每一个cell值
for col in cols:
for cell in col:
print(cell.value, end=" |")
print("\n")
```
编号 |71401.30952380953 |71401.30952380953 |71401.30952380953 |
日期 |2020-07-24 00:00:00 |2020-07-24 00:00:00 |2020-07-24 00:00:00 |
行为时长 |a |b |c |
2.1.4 练习题
找出`用户行为偏好.xlsx`中 Sheet3 表中空着的格子,并输出这些格子的坐标
```python
from openpyxl import load_workbook
exl = load_workbook(root_path+'用户行为偏好.xlsx')
sheet3 = exl.get_sheet_by_name('Sheet3')
```
```python
sheet3.dimensions
```
'A1:I17'
```python
# 直接通过sheet索引,sheet3.dimensions获取sheet数据区域
cells = sheet3[sheet3.dimensions]
# 遍历元组 判断每一个cell值是否为空
for rows in cells:
for cell in rows:
if not cell.value:
print(f'{cell.coordinate} is None \n')
```
D3 is None
D8 is None
G10 is None
2.2 Excel写入
2.2.1 写入数据并保存
2.2.1.1. 原有工作簿中修改数据并保存
```python
# 1) 导入 openpyxl 中的 load_workbook 函数
from openpyxl import load_workbook
# 2) 获取指定 excel文件对象 Workbook
exl = load_workbook(filename=root_path+'用户行为偏好.xlsx')
# 3) 通过指定 sheetname 从 Workbook 中获取 sheet 对象 Worksheet
sheet = exl.get_sheet_by_name('Sheet3')
# 4) 通过索引方式获取指定 cell 值,并重新赋值
print(f"修改前 sheet['A1']: {sheet['A1'].value}")
sheet['A1'].value = 'hello world'
print(f"修改后 sheet['A1']: {sheet['A1'].value}")
# 5) 保存修改后的内容
# 如果 filename 和原文件同名,则是直接在原文件中修改;
# 否则会新建一个 excel 文件,并保存内容
exl.save(filename=root_path+'用户行为偏好_1.xlsx') # 保存到一个新文件中 新文件名称为:用户行为偏好_1.xlsx
```
修改前 sheet['A1']: 1
修改后 sheet['A1']: hello world
```python
# 验证保存修改内容是否成功
exl_1 = load_workbook(filename=root_path+'用户行为偏好_1.xlsx')
# 我们将原表中 Sheet3 中的 A1 值改为了 'hello world'
# 所以读取保存文件,查看对应值是否为 'hello world' 即可
a1 = exl_1['Sheet3']['A1'].value
if a1 == 'hello world':
print(f"修改保存成功啦~,exl_1['Sheet3']['A1'].value = {a1}")
else:
print(f"修改保存有问题,现在exl_1['Sheet3']['A1'].value = {a1}")
```
修改保存成功啦~,exl_1['Sheet3']['A1'].value = hello world
【代码解释】
从这里我们可以看到,我们只需要获取到 sheet 中的 cell 对象后,就可以通过改变 cell.value 的值来改变 对应单元格中的值,然后使用 Workbook 对象的 save 函数可以将修改后的工作簿内容保存起来。
#### 2. 创建新的表格写入数据并保存
```python
# 1) 导入 openpyxl 中的 Workbook 类
from openpyxl import Workbook
# 2) 初始化一个 Workbook 对象
wb = Workbook()
print(f'默认sheet:{wb.sheetnames}')
# 3) 通过 Workbook 对象的 create_sheet 函数创建一个 sheet
# title sheet 名称
# index sheet 位置,默认从0开始
sheet = wb.create_sheet(title='mysheet', index=0)
print(f'添加后sheet:{wb.sheetnames}')
# 4) 在新建的 sheet 中写入数据
# 比如 在 A1 单元格中写入 'this is test'
sheet['A1'].value = 'this is test'
print(f"sheet['A1'].value = {sheet['A1'].value}")
# 保存
wb.save(root_path+'creat_sheet_test.xlsx')
```
默认sheet:['Sheet']
添加后sheet:['mysheet', 'Sheet']
sheet['A1'].value = this is test
2.2.2 将公式写入单元格保存
```python
# 1) 导入 openpyxl 中的 load_workbook 函数
from openpyxl import load_workbook
# 2) 获取指定 excel文件对象 Workbook
exl_1 = load_workbook(filename=root_path+'用户行为偏好_1.xlsx')
# 3) 通过指定 sheetname 从 Workbook 中获取 sheet 对象 Worksheet
sheet = exl_1['订单时长分布']
print(f'订单时长分布 值范围: {sheet.dimensions}') #先查看原有表格的单元格范围,防止替代原有数据
```
订单时长分布 值范围: A1:D14
```python
# 单元格 A15 中写入 合计
sheet['A15'].value = '合计'
```
```python
# 单元格 D15 中写入求和公式:SUM(D2:D14)
sheet['D15'] = '=SUM(D2:D14)'
exl_1.save(filename='用户行为偏好_1.xlsx')
```
```python
# 使用 xlwings 打开 excel 文件然后保存 使写入的 公式生效
import xlwings as xw
# 打开工作簿
app = xw.App(visible=False, add_book=False)
wb = app.books.open('用户行为偏好_1.xlsx')
wb.save()
# 关闭工作簿
wb.close()
app.quit()
```
```python
# 验证写入是否成功
# 1) 获取指定 excel文件对象 Workbook,
# 并设置 data_only=True,表示读取的时候如果单元格内是公式的话,以公式计算后的值的形式显示
exl_2 = load_workbook(filename = '用户行为偏好_1.xlsx', data_only=True)
# 2) 打印相关信息
sheet = exl_2['订单时长分布']
print(f"sheet['A15']={sheet['A15'].value},sheet['D15']={sheet['D15'].value}")
print(f"{sheet['D1'].value} 求和值为SUM(D2:D14)={sheet['D15'].value}")
```
sheet['A15']=合计,sheet['D15']=4004.7261561561563
次数 求和值为SUM(D2:D14)=4004.7261561561563
【注意】
即使设置了 data_only=True,也不能立即获取到刚刚添加的公式计算后的结果,需要自己 手动/添加代码 打开下 对应excel表格,然后 ctrl s保存下,再运行上面代码才能获取到对应公式计算后的值。
你可以使用下面代码自动打开指定 excel 文件然后保存使写入的公式生效,使用前你需要安装 xlwings,输入`pip3 install xlwings`即可,再后面我们也会学习这个模块。
```python
# 使用 xlwings 打开 excel 文件然后保存 使写入的 公式生效
import xlwings as xw
# 打开工作簿
app = xw.App(visible=False, add_book=False)
wb = app.books.open('用户行为偏好_1.xlsx')
wb.save()
# 关闭工作簿
wb.close()
app.quit()
```
2.2.3 插入空列/行
```python
# 获取指定 sheet
sheet = exl_1['Sheet3']
# 插入列数据 insert_cols(idx,amount=1)
# idx是插入位置,amount是插入列数,默认是1
# idx=2第2列,第2列前插入一列
sheet.insert_cols(idx=2)
# 第2列前插入5
# sheet.insert_cols(idx=2, amount=5)
# 插入行数据 insert_rows(idx,amount=1)
# idx是插入位置,amount是插入行数,默认是1
# 在第二行前插入一行
sheet.insert_rows(idx=2)
# 第2行前插入5行
# sheet.insert_rows(idx=2, amount=5)
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.2.4 删除
```python
# 删除多列
sheet.delete_cols(idx=5, amount=2)
# 删除多行
sheet.delete_rows(idx=2, amount=5)
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.2.5 移动
当数字为正即向下或向右,为负即为向上或向左
```python
# 移动
# 当数字为正即向下或向右,为负即为向上或向左
sheet.move_range('B3:E16',rows=1,cols=-1)
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3 Excel 样式
2.3.1设置字体样式
2.3.1.1. 设置单个 cell(单元格) 字体样式
`Font(name字体名称,size大小,bold粗体,italic斜体,color颜色)`
```python
# 1) 导入 openpyxl 中的 load_workbook 函数
# 导入 openpyxl 中的 styles 模块中的 Font 类
from openpyxl import load_workbook
from openpyxl.styles import Font
# 2) 获取指定 excel文件对象 Workbook
exl_1 = load_workbook(filename=root_path+'用户行为偏好_1.xlsx')
# 3) 通过指定 sheetname 从 Workbook 中获取 sheet 对象 Worksheet
sheet = exl_1['订单时长分布']
```
```python
# 4) 获取到指定 cell 后,查看cell字体属性
cell = sheet['A1']
cell.font
```
<openpyxl.styles.fonts.Font object>
Parameters:
name='宋体', charset=134, family=3.0, b=True, i=False, strike=None, outline=None, shadow=None, condense=None, color=<openpyxl.styles.colors.Color object>
Parameters:
rgb=None, indexed=None, auto=None, theme=1, tint=0.0, type='theme', extend=None, sz=11.0, u=None, vertAlign=None, scheme='minor'
```python
# 5) 实例化一个 Font 对象,设置字体样式
# 字体改为:黑体 大小改为:20 设置为:加粗 斜体 红色
font = Font(name='黑体', size=20, bold=True, italic=True, color='FF0000')
cell.font = font
# 6) 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.1.2. 设置多个 cell 的字体样式
```python
# 上面我们已经获取到了 '用户行为偏好_1.xlsx' 中的 订单时长分布 工作表
# 我们处理了 单元格 A1 的字体样式,我们也可以通过遍历的形式,批量设置单元格字体样式
# 1) 获取要处理的单元格
# 通过 sheet 索引获取第二行 cell
# 获取列可以用 字母索引,如 sheet['A'] 获取第一列 cell
cells = sheet[2]
# 2) 实例化一个 Font 对象,设置字体样式
# 字体改为:黑体 大小改为:10 设置为:加粗 斜体 红色
font = Font(name='黑体', size=10, bold=True, italic=True, color='FF0000')
# 3) 遍历给每一个 cell 都设置上对应字体样式
for cell in cells:
cell.font = font
# 4) 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.2 设置边框样式
2.3.2.1. 设置单元格边框样式
`Side`:边线样式设置类,边线颜色等
Side(style=None, color=None, border_style=None)
- style:边线的样式,有以下值可选:double, mediumDashDotDot, slantDashDot, dashDotDot, dotted, hair, mediumDashed, dashed, dashDot, thin, mediumDashDot, medium, thick
- color:边线颜色
- border_style:style 的别名,必须设置,一般直接设置 border_style 就行,不用设置 style
`Border`:边框定位类,左右上下边线
Border常用参数解释:
- top bottom left right diagonal:上下左右和对角线的边线样式,为 Side 对象
- diagonalDown:对角线从左上角向右下角方向,默认为 False
- diagonalUp:对角线从右上角向左下角方向,默认为 False
```python
# 上面我们已经获取到了 '用户行为偏好_1.xlsx' 中的 订单时长分布 工作表 sheet
# 1) 导入 openpyxl 中的 styles 模块中的 Side, Border 类
from openpyxl.styles import Side, Border
# 2) 首先初始化一个边线对象(也可以设置多个)
side = Side(border_style='double', color='FF000000')
# 3) 通过 Border 去设置 整个单元格边框样式
border = Border(left=side, right=side, top=side, bottom=side, diagonal=side, diagonalDown=True, diagonalUp=True)
```
```python
# 4) 查看目前单元格边框样式
# 获取第一行 cells
cells = sheet[1]
# 取出一个 cell 看边框样式
cells[0].border
```
<openpyxl.styles.borders.Border object>
Parameters:
outline=True, diagonalUp=False, diagonalDown=False, start=None, end=None, left=<openpyxl.styles.borders.Side object>
Parameters:
style=None, color=None, right=<openpyxl.styles.borders.Side object>
Parameters:
style=None, color=None, top=<openpyxl.styles.borders.Side object>
Parameters:
style=None, color=None, bottom=<openpyxl.styles.borders.Side object>
Parameters:
style=None, color=None, diagonal=<openpyxl.styles.borders.Side object>
Parameters:
style=None, color=None, vertical=None, horizontal=None
```python
# 5) 修改边框样式,并保存修改
for cell in cells:
cell.border = border
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.3 设置单元格其他样式
2.3.3.1. 设置单元格背景色
```python
# 上面我们已经获取到了 '用户行为偏好_1.xlsx' 中的 订单时长分布 工作表 sheet
# 1) 从 openpyxl.styles 中导入 背景颜色设置类 PatternFill, GradientFill
from openpyxl.styles import PatternFill, GradientFill
# 2) 实例化 PatternFill 对象,fill_type 参数必须指定
pattern_fill = PatternFill(fill_type='solid',fgColor="DDDDDD")
# 3) 实例化 GradientFill 对象,填充类型 type 默认为 linear
gradient_fill = GradientFill(stop=('FFFFFF', '99ccff','000000'))
```
```python
# 4) 获取指定 cells 遍历填充
# 对第三行 PatternFill 模式设置背景色
cells = sheet[3]
for cell in cells:
cell.fill = pattern_fill
# 对第四行 GradientFill 模式设置背景色
cells = sheet[4]
for cell in cells:
cell.fill = gradient_fill
# 5) 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.3.2.设置水平居中
openpyxl.styles 中的 Alignment 类常用参数介绍:
- horizontal:水平对齐,常见值 `distributed, justify, center, left, fill, centerContinuous, right, general`
- vertical:垂直对齐,常见值 `bottom, distributed, justify, center, top`
- textRotation:文字旋转角度,数值:0-180
- wrapText:是否自动换行,bool值,默认 False
```python
# 上面我们已经获取到了 '用户行为偏好_1.xlsx' 中的 订单时长分布 工作表 sheet
# 1) 从 openpyxl.styles 中导入 对齐方式设置类 Alignment
from openpyxl.styles import Alignment
# 2) 实例化一个 Alignment 对象,设置水平、垂直居中
alignment = Alignment(horizontal='center', vertical='center')
# 3) 获取指定 cells 遍历填充
# 对第五行数据设置上面的对齐方式
cells = sheet[5]
for cell in cells:
cell.alignment = alignment
# 4) 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.3.3. 设置行高与列宽
```python
# 1) 设置行高,通过 row_dimensions 和 column_dimensions 来获取行和列对象
# 2) 设置第1行行高为 30
sheet.row_dimensions[1].height = 30
# 3) 设置第3列列款为 24
sheet.column_dimensions['C'].width = 24
# 4) 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.4 合并、取消合并单元格
```python
# 注意:合并后的单元格只会显示合并区域中最右上角的单元格的值,会导致其他单元格内容丢失
# 上面我们已经获取到了 '用户行为偏好_1.xlsx' 对象 exl_1,我们可以通过 exl_1 来索引获取自己想要的 sheet
# 1) 获取 Sheet3 这个工作表
sheet = exl_1['Sheet3']
# 合并指定区域单元格
sheet.merge_cells('A1:B2')
# sheet.merge_cells(start_row=1, start_column=3,
# end_row=2, end_column=4)
# 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
```python
# 解除合并
sheet.unmerge_cells('A1:B2')
# sheet.unmerge_cells(start_row=1, start_column=3,
# end_row=2, end_column=4)
# 保存修改
exl_1.save(filename=root_path+'用户行为偏好_1.xlsx')
```
2.3.5 练习题
打开 test.xlsx 文件,找出文件中购买数量 `buy_mount` 超过5的单元格,并对其标红、加粗、加上红色边框。
```python
# 1) 导入 openpyxl 相关函数和类
from openpyxl import load_workbook
from openpyxl.styles import Font, Side, Border
# 2) 读取 test.xlsx 文件,并筛选出 buy_mount 这一列
workbook = load_workbook(root_path+'test.xlsx')
sheet = workbook.active
buy_mount = sheet['B']
```
```python
# 3) 设置边框 文字样式
side = Side(style='thin', color='FF0000')
border = Border(left=side, right=side, top=side, bottom=side)
font = Font(bold=True, color='FF0000')
```
```python
# 4) 遍历判断 cell 值是否满足筛选条件
for cell in buy_mount:
if isinstance(cell.value, float) and cell.value > 5:
cell.font = font
cell.border = border
# 5) 修改内容另存为 new_test.xlsx
workbook.save(root_path+'new_test.xlsx')
```
2.4 综合练习
2.4.1 将 业务联系表.xlsx 拆分成以下两个 excel:
- 客户信息表:客户名称 客户地址 客户方负责人 性别 联系电话 对接业务经理编号
- 业务经理信息表:业务经理编号 所在分区 所在区域 业务经理姓名
```python
# 1) 导入 openpyxl 相关函数和类
from openpyxl import load_workbook, Workbook
# 2) 读取原表数据
wb = load_workbook(root_path+'业务联系表.xlsx')
# 3) 获取工作表
sheet = wb.active
```
```python
# 草稿纸
# 我们知道我们表格的实际列名在第二行
# 获取每列第二行的坐标和值
for i in sheet[2]:
print(i.coordinate, i.value)
```
A2 业务经理编号
B2 分区
C2 区域
D2 业务经理
E2 客户名称
F2 客户地址
G2 客户方负责人
H2 性别
I2 联系电话
J2 备注
```python
sheet.max_column, sheet.max_row
```
(10, 57)
```python
# 4) 筛选出需要的列
# 4.1) 客户信息表:客户名称 客户地址 客户方负责人 性别 联系电话 备注 对接业务经理编号
cust_info = {'业务经理编号': 'A', '客户名称': 'B', '客户地址': 'C', '客户方负责人': 'D', '性别': 'E', '联系电话': 'F', '备注': 'G'}
# 4.2) 新建一个工作簿,并将默认sheet名称改成 客户信息
cust_info_excel = Workbook()
cust_info_sh = cust_info_excel.active
cust_info_sh.title = '客户信息'
```
```python
# 4.3) 遍历筛选,如果是需要的表头,就将该列的值复制到新的工作簿中的 客户信息 工作表中
for i in sheet[2]:
if i.value in cust_info:
# 遍历将这一列中除了第一个cell外的所有cell值复制到新表
for cell in sheet[i.coordinate[0]]:
if cell.row == 1:
continue
cust_info_sh[f'{cust_info[i.value]}{cell.row-1}'].value = cell.value
```
```python
# 5) 筛选出需要的列
# 5.1) 业务经理信息表:业务经理编号 所在分区 所在区域 业务经理姓名
manager_info = {'业务经理编号': 'A', '分区': 'B', '区域': 'C', '业务经理': 'D'}
# 5.2) 新建一个工作簿,并将默认sheet名称改成 客户信息
manager_info_excel = Workbook()
manager_info_sh = manager_info_excel.active
manager_info_sh.title = '业务经理信息'
```
```python
# 5.3) 遍历筛选,如果是需要的表头,就将该列的值复制到新的工作簿中的 业务经理信息 工作表中
for i in sheet[2]:
if i.value in manager_info:
# 遍历将这一列中除了第一个cell外的所有cell值复制到新表
for cell in sheet[i.coordinate[0]]:
if cell.row == 1:
continue
manager_info_sh[f'{manager_info[i.value]}{cell.row-1}'].value = cell.value
```
```python
# 6.1 ) 保存 客户信息表 工作簿内容
cust_info_excel.save(root_path+'客户信息表_xl.xlsx')
# 6.2) 保存 业务经理信息表 工作簿内容
manager_info_excel.save(root_path+'业务经理信息表_xl.xlsx')
```
以上,虽然完成了数据拆分,但是对于进一步数据处理,继续使用 openpyxl 并不是很便捷,比如数据去重,筛选等,接下来我将给大家介绍如何使用 pandas 更便捷的处理 excel 数据。
```python
import pandas as pd
# 1) 读取数据
data = pd.read_excel(root_path+'业务联系表.xlsx', header=1)
```
```python
# 2) 数据筛选处理
# 2.1) 客户信息表
# 筛选出 客户信息表 需要的列
cust_info_pd = data[['业务经理编号', '客户名称', '客户地址', '客户方负责人', '性别', '联系电话', '备注']]
# 去除重复行
cust_info_pd.drop_duplicates(inplace=True)
# 打印出前三行
cust_info_pd.head(3)
```
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>业务经理编号</th>
<th>客户名称</th>
<th>客户地址</th>
<th>客户方负责人</th>
<th>性别</th>
<th>联系电话</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>1</td>
<td>尹承望</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>1</th>
<td>1</td>
<td>何茂材</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>2</th>
<td>1</td>
<td>徐新霁</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
</tbody>
</table>
</div>
```python
# 2.2) 业务经理信息表
# 筛选出 业务经理信息表 需要的列,并打印出前三行
manager_info_pd = data[['业务经理编号', '分区', '区域', '业务经理']]
# 去除重复行
manager_info_pd.drop_duplicates(inplace=True)
# 打印出前三行
manager_info_pd.head(3)
```
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>业务经理编号</th>
<th>分区</th>
<th>区域</th>
<th>业务经理</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
</tr>
<tr>
<th>5</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
</tr>
<tr>
<th>11</th>
<td>3</td>
<td>北区</td>
<td>河北</td>
<td>王一磊</td>
</tr>
</tbody>
</table>
</div>
```python
# 3) 数据保存
cust_info_pd.to_excel(root_path+'客户信息表_pd.xlsx', index=None)
manager_info_pd.to_excel(root_path+'业务经理信息表_pd.xlsx', index=None)
```
2.4.2 将 客户信息表.xlsx 和 客户关系表.xlsx 合并成一个excel
```python
# 接上面的,将 客户信息表.xlsx 和 客户关系表.xlsx 合并成一个excel
# 这里我们依然用 pandas 来处理
business_contact = pd.merge(manager_info_pd, cust_info_pd, on='业务经理编号')
# 查看合并后数据基本信息
business_contact.info()
```
<class 'pandas.core.frame.DataFrame'>
Int64Index: 55 entries, 0 to 54
Data columns (total 10 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 业务经理编号 55 non-null int64
1 分区 55 non-null object
2 区域 55 non-null object
3 业务经理 55 non-null object
4 客户名称 55 non-null object
5 客户地址 55 non-null object
6 客户方负责人 55 non-null object
7 性别 55 non-null object
8 联系电话 55 non-null object
9 备注 0 non-null float64
dtypes: float64(1), int64(1), object(8)
memory usage: 4.7+ KB
```python
# 查看前10条数据
business_contact.head(10)
```
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>业务经理编号</th>
<th>分区</th>
<th>区域</th>
<th>业务经理</th>
<th>客户名称</th>
<th>客户地址</th>
<th>客户方负责人</th>
<th>性别</th>
<th>联系电话</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
<td>尹承望</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>1</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
<td>何茂材</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>2</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
<td>徐新霁</td>
<td>*****-*****-****</td>
<td>孙康适</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>3</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
<td>郭承悦</td>
<td>*****-*****-****</td>
<td>邓翰翮</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>4</th>
<td>1</td>
<td>南区</td>
<td>贵州</td>
<td>占亮</td>
<td>梁浩思</td>
<td>*****-*****-****</td>
<td>邓翰翮</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>5</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
<td>毛英朗</td>
<td>*****-*****-****</td>
<td>邓翰翮</td>
<td>男</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>6</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
<td>侯俊美</td>
<td>*****-*****-****</td>
<td>任敏智</td>
<td>女</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>7</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
<td>许高轩</td>
<td>*****-*****-****</td>
<td>任敏智</td>
<td>女</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>8</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
<td>段英豪</td>
<td>*****-*****-****</td>
<td>任敏智</td>
<td>女</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
<tr>
<th>9</th>
<td>2</td>
<td>南区</td>
<td>贵州</td>
<td>李朝华</td>
<td>汤承福</td>
<td>*****-*****-****</td>
<td>任敏智</td>
<td>女</td>
<td>***-****-***</td>
<td>NaN</td>
</tr>
</tbody>
</table>
</div>
```python
# 数据保存
manager_info_pd.to_excel(root_path+'业务联系表_pd.xlsx', index=None)
```