Python办公自动化

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)

```

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容