Python办公自动化

T3:Python& PDF /Word

1)PDF操作
4.1. 相关介绍

Python 操作 PDF 会用到两个库,分别是:PyPDF2 和 pdfplumber

其中 **PyPDF2** 可以更好的读取、写入、分割、合并PDF文件,而 **pdfplumber** 可以更好的读取 PDF 文件中内容和提取 PDF 中的表格

> PyPDF2:https://pythonhosted.org/PyPDF2/

> pdfplumber:https://github.com/jsvine/pdfplumber

由于这两个库都不是 Python 的标准库,所以在使用之前都需要单独安装

 4.2. 批量拆分

将一个完整的 PDF 拆分成几个小的 PDF,因为主要涉及到 PDF 整体的操作,所以本小节需要用到 PyPDF2 这个库

拆分的大概思路如下:

- 读取 PDF 的整体信息、总页数等

- 遍历每一页内容,以每个 step 为间隔将 PDF 存成每一个小的文件块

- 将小的文件块重新保存为新的 PDF 文件

需要注意的是,在拆分的过程中,可以手动设置间隔,例如:每5页保存成一个小的 PDF 文件

拆分的代码如下:

```python

import os

from PyPDF2 import PdfFileWriter, PdfFileReader

def split_pdf(filename, filepath, save_dirpath, step=5):

    """

    拆分PDF为多个小的PDF文件,

    @param filename:文件名

    @param filepath:文件路径

    @param save_dirpath:保存小的PDF的文件路径

    @param step: 每step间隔的页面生成一个文件,例如step=5,表示0-4页、5-9页...为一个文件

    @return:

    """

    if not os.path.exists(save_dirpath):

        os.mkdir(save_dirpath)

    pdf_reader = PdfFileReader(filepath)

    读取每一页的数据

    pages = pdf_reader.getNumPages()

    for page in range(0, pages, step):

        pdf_writer = PdfFileWriter()

        # 拆分pdf,每 step 页的拆分为一个文件

        for index in range(page, page+step):

            if index < pages:

                pdf_writer.addPage(pdf_reader.getPage(index))

        保存拆分后的小文件

        save_path = os.path.join(save_dirpath, filename+str(int(page/step)+1)+'.pdf')

        print(save_path)

        with open(save_path, "wb") as out:

            pdf_writer.write(out)

    print("文件已成功拆分,保存路径为:"+save_dirpath)


filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

save_dirpath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告【拆分】')

split_pdf(filename, filepath, save_dirpath, step=5)

```

4.3. 批量合并

比起拆分来,合并的思路更加简单:

- 确定要合并的 **文件顺序**

- 循环追加到一个文件块中

- 保存成一个新的文件

对应的代码比较简单,基本不会出现问题:

```python

import os

from PyPDF2 import PdfFileReader, PdfFileWriter

def concat_pdf(filename, read_dirpath, save_filepath):

    """

    合并多个PDF文件

    @param filename:文件名

    @param read_dirpath:要合并的PDF目录

    @param save_filepath:合并后的PDF文件路径

    @return:

    """

    pdf_writer = PdfFileWriter()

     对文件名进行排序

    list_filename = os.listdir(read_dirpath)

    list_filename.sort(key=lambda x: int(x[:-4].replace(filename, "")))

    for filename in list_filename:

        print(filename)

        filepath = os.path.join(read_dirpath, filename)

        读取文件并获取文件的页数

        pdf_reader = PdfFileReader(filepath)

        pages = pdf_reader.getNumPages()

        逐页添加

        for page in range(pages):

            pdf_writer.addPage(pdf_reader.getPage(page))

    保存合并后的文件

    with open(save_filepath, "wb") as out:

        pdf_writer.write(out)

    print("文件已成功合并,保存路径为:"+save_filepath)

filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

read_dirpath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告【拆分】')

save_filepath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告-合并后.pdf')

concat_pdf(filename, read_dirpath, save_filepath)

```

4.4. 提取文字内容

涉及到具体的 PDF 内容 操作,本小节需要用到 pdfplumber 这个库

在进行文字提取的时候,主要用到 extract_text 这个函数

具体代码如下:

```python

import os

import pdfplumber

def extract_text_info(filepath):

    """

    提取PDF中的文字

    @param filepath:文件路径

    @return:

    """

    with pdfplumber.open(filepath) as pdf:

    获取第2页数据

        page = pdf.pages[1]

        print(page.extract_text())


filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

提取文字内容

extract_text_info(filepath)

```

可以看到,直接通过下标即可定位到相应的页码,从而通过 extract_text 函数提取该也的所有文字

而如果想要提取所有页的文字,只需要改成:

```python

with pdfplumber.open(filepath) as pdf:

获取全部数据

for page in pdf.pages

    print(page.extract_text())

```

>  拓展一下:此处可以结合前面 word 小节,将内容写入 word 文件中

4.5. 提取表格内容

同样的,本节是对具体内容的操作,所以也需要用到 pdfplumber 这个库

和提取文字十分类似的是,提取表格内容只是将 extract_text 函数换成了 extract_table 函数

对应的代码如下:

```python

import os

import pandas as pd

import pdfplumber

def extract_table_info(filepath):

    """

    提取PDF中的图表数据

    @param filepath:

    @return:

    """

    with pdfplumber.open(filepath) as pdf:

        获取第18页数据

        page = pdf.pages[17]

        如果一页有一个表格,设置表格的第一行为表头,其余为数据

        table_info = page.extract_table()

        df_table = pd.DataFrame(table_info[1:], columns=table_info[0])

        df_table.to_csv('dmeo.csv', index=False, encoding='gbk')


filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

# 提取表格内容

extract_table_info(filepath)

```

上面代码可以获取到第 18 页的第一个表格内容,并且将其保存为 csv 文件存在本地

> 但是,如果说第 18 页有多个表格内容呢?

因为读取的表格会被存成二维数组,而多个二维数组就组成一个三维数组

遍历这个三位数组,就可以得到该页的每一个表格数据,对应的将 extract_table 函数 改成 extract_tables 即可

具体代码如下:

```python

import os

import pandas as pd

import pdfplumber

def extract_table_info(filepath):

    """

    提取PDF中的图表数据

    @param filepath:

    @return:

    """

    with pdfplumber.open(filepath) as pdf:

       获取第7页数据

        page = pdf.pages[6]

       如果一页有多个表格,对应的数据是一个三维数组

        tables_info = page.extract_tables()

        for index in range(len(tables_info)):

        设置表格的第一行为表头,其余为数据

            df_table = pd.DataFrame(tables_info[index][1:], columns=tables_info[index][0])

            df_table.to_csv('dmeo.csv', index=False, encoding='gbk')


filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

# 提取表格内容

extract_table_info(filepath)

```

 4.6 提取图片内容

提取 PDF 中的图片和将 PDF 转存为图片是不一样的(下一小节),需要区分开。

提取图片:顾名思义,就是将内容中的图片都提取出来;转存为图片:则是将每一页的 PDF 内容存成一页一页的图片,下一小节会详细说明

转存为图片中,需要用到一个模块叫 fitz,fitz 的最新版 1.18.13,非最新版的在部分函数名称上存在差异,代码中会标记出来

使用 fitz 需要先安装 PyMuPDF 模块,安装方式如下:

> pip install PyMuPDF

提取图片的整体逻辑如下:

- 使用 fitz 打开文档,获取文档详细数据

- 遍历每一个元素,通过正则找到图片的索引位置

- 使用 Pixmap 将索引对应的元素生成图片

- 通过 size 函数过滤较小的图片

实现的具体代码如下:

```python

import os

import re

import fitz

def extract_pic_info(filepath, pic_dirpath):

    """

    提取PDF中的图片

    @param filepath:pdf文件路径

    @param pic_dirpath:要保存的图片目录路径

    @return:

    """

    if not os.path.exists(pic_dirpath):

        os.makedirs(pic_dirpath)

    # 使用正则表达式来查找图片

    check_XObject = r"/Type(?= */XObject)"

    check_Image = r"/Subtype(?= */Image)"

    img_count = 0

    """1. 打开pdf,打印相关信息"""

    pdf_info = fitz.open(filepath)

    # 1.16.8版本用法 xref_len = doc._getXrefLength()

    # 最新版本

    xref_len = pdf_info.xref_length()

    # 打印PDF的信息

    print("文件名:{}, 页数: {}, 对象: {}".format(filepath, len(pdf_info), xref_len-1))

    """2. 遍历PDF中的对象,遇到是图像才进行下一步,不然就continue"""

    for index in range(1, xref_len):

        # 1.16.8版本用法 text = doc._getXrefString(index)

        # 最新版本

        text = pdf_info.xref_object(index)

        is_XObject = re.search(check_XObject, text)

        is_Image = re.search(check_Image, text)

        # 如果不是对象也不是图片,则不操作

        if is_XObject or is_Image:

            img_count += 1

            # 根据索引生成图像

            pix = fitz.Pixmap(pdf_info, index)

            pic_filepath = os.path.join(pic_dirpath, 'img_' + str(img_count) + '.png')

            """pix.size 可以反映像素多少,简单的色素块该值较低,可以通过设置一个阈值过滤。以阈值 10000 为例过滤"""

            # if pix.size < 10000:

            #    continue

            """三、 将图像存为png格式"""

            if pix.n >= 5:

                # 先转换CMYK

                pix = fitz.Pixmap(fitz.csRGB, pix)

            # 存为PNG

            pix.writePNG(pic_filepath)


filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

pic_dirpath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告【文中图片】')

# 提取图片内容

extract_pic_info(filepath, pic_dirpath)

```

 4.7 转换为图片

转换为照片比较简单,就是将一页页的 PDF 转换为一张张的图片。大致过程如下:

 4.7.1 安装 pdf2image

首先需要安装对应的库,最新的 pdf2image 库版本应该是 1.14.0

它的 github地址 为:https://github.com/Belval/pdf2image ,感兴趣的可以自行了解

安装方式如下:

> pip install pdf2image

4.7.2 安装组件

对于不同的平台,需要安装相应的组件,这里以 windows 平台和 mac 平台为例:

**Windows 平台**

对于 windows 用户需要安装 poppler for Windows,安装链接是:http://blog.alivate.com.au/poppler-windows/

另外,还需要添加环境变量, 将 bin 文件夹的路径添加到环境变量 PATH 中

> 注意这里配置之后需要重启一下电脑才会生效,不然会报如下错误:

**Mac**

对于 mac 用户,需要安装 poppler for Mac,具体可以参考这个链接:http://macappstore.org/poppler/

详细代码如下:

```python

import os

from pdf2image import convert_from_path, convert_from_bytes

def convert_to_pic(filepath, pic_dirpath):

    """

    每一页的PDF转换成图片

    @param filepath:pdf文件路径

    @param pic_dirpath:图片目录路径

    @return:

    """

    print(filepath)

    if not os.path.exists(pic_dirpath):

        os.makedirs(pic_dirpath)

    images = convert_from_bytes(open(filepath, 'rb').read())

    # images = convert_from_path(filepath, dpi=200)

    for image in images:

        # 保存图片

        pic_filepath = os.path.join(pic_dirpath, 'img_'+str(images.index(image))+'.png')

        image.save(pic_filepath, 'PNG')


# PDF转换为图片

convert_to_pic(filepath, pic_dirpath)

filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

pic_dirpath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告【转换为图片】')

# PDF转换为图片

convert_to_pic(filepath, pic_dirpath)


```

 4.8. 添加水印

PDF 中添加水印,首先需要一个水印PDF文件,然后依次通过 mergePage 操作将每一页的 PDF 文件合并到水印文件上,据此,每一页的 PDF 文件将是一个带有水印的 PDF 文件

最后,将每一页的水印 PDF 合并成一个 PDF 文件即可

**生成水印**

生成水印的方式比较多,例如在图片添加水印,然后将图片插入到 word 中,最后将 word 保存成 PDF 文件即可

生成一张 A4 纸大小的空白图片,参考这篇文章:[Python 批量加水印!轻松搞定!(https://mp.weixin.qq.com/s/_oJA6lbsdMlRRsBf6DPxsg) 给图片添加水印

然后将图片插入到 word 中并最终生成一个水印 PDF 文档

PDF 文档添加水印的主要代码如下:

```python

import os

from copy import copy

from PyPDF2 import PdfFileReader, PdfFileWriter

def add_watermark(filepath, save_filepath, watermark_filepath):

    """

    添加水印

    @param filepath:PDF文件路径

    @param save_filepath:最终的文件保存路径

    @param watermark_filepath:水印PDF文件路径

    @return:

    """

    """读取PDF水印文件"""

    # 可以先生成一个空白A4大小的png图片,通过 https://mp.weixin.qq.com/s/_oJA6lbsdMlRRsBf6DPxsg 教程的方式给图片加水印,将图片插入到word中并最终生成一个水印PDF文档

    watermark = PdfFileReader(watermark_filepath)

    watermark_page = watermark.getPage(0)

    pdf_reader = PdfFileReader(filepath)

    pdf_writer = PdfFileWriter()

    for page_index in range(pdf_reader.getNumPages()):

        current_page = pdf_reader.getPage(page_index)

        # 封面页不添加水印

        if page_index == 0:

            new_page = current_page

        else:

            new_page = copy(watermark_page)

            new_page.mergePage(current_page)

        pdf_writer.addPage(new_page)

    # 保存水印后的文件

    with open(save_filepath, "wb") as out:

        pdf_writer.write(out)

filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

save_filepath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告-水印.pdf')

watermark_filepath = os.path.join(os.getcwd(), 'watermark.pdf')

# 添加水印

add_watermark(filepath, save_filepath, watermark_filepath)

```

 4.9. 文档加密与解密

在对 PDF 文件加密需要使用 encrypt 函数,对应的加密代码也比较简单:

```python

import os

from PyPDF2 import PdfFileReader, PdfFileWriter

def encrypt_pdf(filepath, save_filepath, passwd='xiaoyi'):

    """

    PDF文档加密

    @param filepath:PDF文件路径

    @param save_filepath:加密后的文件保存路径

    @param passwd:密码

    @return:

    """

    pdf_reader = PdfFileReader(filepath)

    pdf_writer = PdfFileWriter()

    for page_index in range(pdf_reader.getNumPages()):

        pdf_writer.addPage(pdf_reader.getPage(page_index))

    # 添加密码

    pdf_writer.encrypt(passwd)

    with open(save_filepath, "wb") as out:

        pdf_writer.write(out)

filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

save_filepath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告-加密后.pdf')

# 文档加密

encrypt_pdf(filepath, save_filepath, passwd='xiaoyi')

```

代码执行成功后再次打开 PDF 文件则需要输入密码才行

根据这个思路,破解 PDF 也可以通过暴力求解实现,例如:通过本地密码本一个个去尝试,或者根据数字+字母的密码形式循环尝试,最终成功打开的密码就是破解密码

> 上述破解方法耗时耗力,不建议尝试

另外,针对已经加密的 PDF 文件,也可以使用 decrypt 函数进行解密操作

解密代码如下:

```python

def decrypt_pdf(filepath, save_filepath, passwd='xiaoyi'):

    """

    解密 PDF 文档并且保存为未加密的 PDF

    @param filepath:PDF文件路径

    @param save_filepath:解密后的文件保存路径

    @param passwd:密码

    @return:

    """

    pdf_reader = PdfFileReader(filepath)

    # PDF文档解密

    pdf_reader.decrypt('xiaoyi')

    pdf_writer = PdfFileWriter()

    for page_index in range(pdf_reader.getNumPages()):

        pdf_writer.addPage(pdf_reader.getPage(page_index))

    with open(save_filepath, "wb") as out:

        pdf_writer.write(out)

filename = '易方达中小盘混合型证券投资基金2020年中期报告-加密后.pdf'

filepath = os.path.join(os.getcwd(), filename)

save_filepath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告-解密后.pdf')

# 文档解密

decrypt_pdf(filepath, save_filepath, passwd='xiaoyi')


```

解密完成后的 PDF 文档打开后不再需要输入密码,如需加密可再次执行加密代码。

### 页面旋转

利用 rotateClockwise()和 rotateCounterClockwise()方法 可以对pdf页面进行90度及其倍数的旋转。

```python

import PyPDF2

filename = '易方达中小盘混合型证券投资基金2020年中期报告.pdf'

filepath = os.path.join(os.getcwd(), filename)

save_filepath = os.path.join(os.getcwd(), '易方达中小盘混合型证券投资基金2020年中期报告-旋转.pdf')

pdf_reader = PdfFileReader(filepath)

page = pdf_reader.getPage(0)

page.rotateClockwise(90)

pdf_writer = PdfFileWriter()

pdf_writer.addPage(page)

with open(save_filepath, "wb") as out:

        pdf_writer.write(out)

```

2)Word操作


3.0 课前准备

> python 处理 Word 需要用到 python-docx 库,需要注意的是pythonn-docx不支持doc文档,终端执行如下安装命令:

```pyhton

pip3 install python-docx

conda install python-docx

```

3.1.知识要点

项目难度:⭐

> 说明:

> 1. 通过小试牛刀初步认识docx,然后系统学习python对word的操作;

> 2. 预估每个知识点需要讲解的时间;

> 3. 研发逻辑就是讲解逻辑,一般从上往下,遵循:`What - Why - How` 或 `Why - What - How` 思路;

### 3.1.1 初步认识docx

相信同学们都进行过word的操作。话不多说,直接上python对word简单操作的代码,先有个直观的感觉,然后再系统学习!

```python

# 导入库

from docx import Document

# 新建空白文档

doc_1 = Document()

# 添加标题(0相当于文章的题目,默认级别是1,级别范围为0-9)

doc_1.add_heading('新建空白文档标题,级别为0',level = 0)

doc_1.add_heading('新建空白文档标题,级别为1',level = 1)

doc_1.add_heading('新建空白文档标题,级别为2',level = 2)

# 新增段落

paragraph_1 = doc_1.add_paragraph('这是第一段文字的开始\n请多多关照!')

# 加粗

paragraph_1.add_run('加粗字体').bold = True

paragraph_1.add_run('普通字体')

# 斜体

paragraph_1.add_run('斜体字体').italic =True

# 新段落(当前段落的下方)

paragraph_2 = doc_1.add_paragraph('新起的第二段文字。')

# 新段落(指定端的上方)

prior_paragraph = paragraph_1.insert_paragraph_before('在第一段文字前插入的段落')

# 添加分页符(可以进行灵活的排版)

doc_1.add_page_break()

# 新段落(指定端的上方)

paragraph_3 = doc_1.add_paragraph('这是第二页第一段文字!')

# 保存文件(当前目录下)

doc_1.save('doc_1.docx')

```

---

上节只是小试牛刀一下,接下来我们系统地学习python自动化之word操作。

在操作之前,我们需要了解 Word 文档的<font color=red>页面结构</font> :

- 文档 - Document

- 段落 - Paragraph

- 文字块 - Run

**`python-docx`**将整个文章看做是一个**`Document`**对象 ,其基本结构如下:

- 每个**`Document`**包含许多个代表“段落”的**`Paragraph`**对象,存放在**`document.paragraphs`**中。

- 每个**`Paragraph`**都有许多个代表"行内元素"的**`Run`**对象,存放在**`paragraph.runs`**中。

在**`python-docx`**中,**`run`**是最基本的单位,每个**`run`**对象内的文本样式都是一致的,也就是说,在从**`docx`**文件生成文档对象时,**`python-docx`**会根据样式的变化来将文本切分为一个个的`Run`对象。

### 3.1.2 整体页面结构介绍

我们以一个小案例为主线把文档,段落和文字块串一下:

```python

# 导入库

from docx import Document

from docx.shared import RGBColor, Pt,Inches,Cm

from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

from docx.oxml.ns import qn

# 新建文档(Datawhale)

doc_1 = Document()

# 字体设置(全局)

'''只更改font.name是不够的,还需要调用._element.rPr.rFonts的set()方法。'''

doc_1.styles['Normal'].font.name = u'宋体'

doc_1.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')

# 添加标题(0相当于文章的题目,默认级别是1,级别范围为0-9,0时候自动带下划线)

#WD_ALIGN_PARAGRAPH. LEFT:左对齐;

#WD_ALIGN_PARAGRAPH. CENTER:居中对其;

#WD_ALIGN_PARAGRAPH. RIGHT:右对齐;

#WD_ALIGN_PARAGRAPH. JUSTIFY:两端对齐;

heading_1 = doc_1.add_heading('周杰伦',level = 0)

heading_1.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER  #居中对齐,默认居左对齐

# 新增段落

paragraph_1 = doc_1.add_paragraph()

'''

设置段落格式:首行缩进0.75cm,居左,段后距离1.0英寸,1.5倍行距。

'''

paragraph_1.paragraph_format.first_line_indent = Cm(0.75)

paragraph_1.paragraph_format.alignment =  WD_PARAGRAPH_ALIGNMENT.LEFT

paragraph_1.paragraph_format.space_after =  Inches(1.0)

paragraph_1.paragraph_format.line_spacing =  1.5

text = '中国台湾华语流行歌手、' \

      '音乐创作家、作曲家、作词人、' \

      '制作人、杰威尔音乐公司老板之一、导演。' \

      '近年涉足电影行业。周杰伦是2000年后亚洲流行乐坛最具革命性与指标' \

      '性的创作歌手,有“亚洲流行天王”之称。他突破原有亚洲音乐的主题、形' \

      '式,融合多元的音乐素材,创造出多变的歌曲风格,尤以融合中西式曲风的嘻哈' \

      '或节奏蓝调最为著名,可说是开创华语流行音乐“中国风”的先声。周杰伦的' \

      '出现打破了亚洲流行乐坛长年停滞不前的局面,为亚洲流行乐坛翻开了新的一页!'


r_1 = paragraph_1.add_run(text)

r_1.font.size =Pt(10)    #字号

r_1.font.bold =True      #加粗

r_1.font.color.rgb =RGBColor(255,0,0)      #颜色

print(len(paragraph_1.runs))    # 查看段落拥有的run对象数量

print(paragraph_1.runs[0].text)  # 查看对应run对象的文本等属性

# 保存文件(当前目录下)

doc_1.save('周杰伦.docx')

```

通过上例我们可以看到,最小的操作对象为文字块,通过run的指定进行操作。比如字号,颜色等;而再上一个层级--段落是的格式是通过paragraph_format进行设置;

### 3.1.3 字体设置

通过(1),同学们已经注意到,字体的设置是全局变量。如果我想在不同的部分进行不同字体的设置,那该怎么办呢?这就需要在应用前操作设置一下。

```python

'''字体设置1.py'''

#导入库

from docx import Document

from docx.oxml.ns import qn

from docx.enum.style import WD_STYLE_TYPE

document = Document() # 新建docx文档

# 设置宋体字样式

style_font = document.styles.add_style('宋体', WD_STYLE_TYPE.CHARACTER)

style_font.font.name = '宋体'

document.styles['宋体']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')

# 设置楷体字样式

style_font = document.styles.add_style('楷体', WD_STYLE_TYPE.CHARACTER)

style_font.font.name = '楷体'

document.styles['楷体']._element.rPr.rFonts.set(qn('w:eastAsia'), u'楷体')

# 设置华文中宋字样式

style_font = document.styles.add_style('华文中宋', WD_STYLE_TYPE.CHARACTER)

style_font.font.name = '华文中宋'

document.styles['华文中宋']._element.rPr.rFonts.set(qn('w:eastAsia'), u'华文中宋')

paragraph1 = document.add_paragraph() # 添加段落

run = paragraph1.add_run(u'aBCDefg这是中文', style='宋体') # 设置宋体样式

font = run.font #设置字体

font.name = 'Cambira' # 设置西文字体

paragraph1.add_run(u'aBCDefg这是中文', style='楷体').font.name = 'Cambira'

paragraph1.add_run(u'aBCDefg这是中文', style='华文中宋').font.name = 'Cambira'

document.save('字体设置1.docx')

```

```python

'''字体设置2.py'''

#导入库

from docx import Document

from docx.oxml.ns import qn

from docx.enum.style import WD_STYLE_TYPE

#定义字体设置函数

def font_setting(doc,text,font_cn):

      style_add = doc.styles.add_style(font_cn, WD_STYLE_TYPE.CHARACTER)

      style_add.font.name = font_cn

      doc.styles[font_cn]._element.rPr.rFonts.set(qn('w:eastAsia'), font_cn)

      par = doc.add_paragraph()

      text = par.add_run(text, style=font_cn)

doc = Document()

a = '小朋友 你是否有很多问号'

b = '为什么 别人在那看漫画'

c = '我却在学画画 对着钢琴说话'

font_setting(doc,a,'宋体')

font_setting(doc,b,'华文中宋')

font_setting(doc,c,'黑体')

doc.save('字体设置2.docx')

```

我们很容易地看出来,字体设置1.py与字体设置2.py的区别在于是否为同一段落,同时字体设置2.py中自定义了一个函数。同学们可以在实际工作中看具体场景进行选择。

### 3.1.4 插入图片与表格

```python

#导入库

from docx import Document

from docx.shared import Inches

#打开文档

doc_1 = Document('周杰伦.docx')  #上面脚本存储的文档

#新增图片

doc_1.add_picture('周杰伦.jpg',width=Inches(1.0), height=Inches(1.0))

# 创建3行1列表格

table1 = doc_1.add_table(rows=2, cols=1)

table1.style='Medium Grid 1 Accent 1'  #表格样式很多种,如,Light Shading Accent 1等

# 修改第2行第3列单元格的内容为营口

table1.cell(0, 0).text = '营口'

# 修改第3行第4列单元格的内容为人民

table1.rows[1].cells[0].text = '人民'

# 在表格底部新增一行

row_cells = table1.add_row().cells

# 新增行的第一列添加内容

row_cells[0].text = '加油'

doc_1.save('周杰伦为营口加油.docx')

```

### 3.1.5 设置页眉页脚

在python-docx包中则要使用节(section)中的页眉(header)和页脚(footer)对象来具体设置。

```python

from docx import Document

from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

document = Document() # 新建文档

header = document.sections[0].header # 获取第一个节的页眉

print('页眉中默认段落数:', len(header.paragraphs))

paragraph = header.paragraphs[0] # 获取页眉的第一个段落

paragraph.add_run('这是第一节的页眉') # 添加页面内容

footer = document.sections[0].footer # 获取第一个节的页脚

paragraph = footer.paragraphs[0] # 获取页脚的第一个段落

paragraph.add_run('这是第一节的页脚') # 添加页脚内容

'''在docx文档中又添加了2个节,共计3个节,页面和页脚会显示了“与上一节相同”。

如果不使用上一节的内容和样式要将header.is_linked_to_previous的属性或footer.is_linked_to_previous的属性设置为False,

用于解除“链接上一节页眉”或者“链接上一节页脚”。'''

document.add_section() # 添加一个新的节

document.add_section() # 添加第3个节

header = document.sections[1].header # 获取第2个节的页眉

header.is_linked_to_previous = False # 不使用上节内容和样式

#对齐设置

header = document.sections[1].header # 获取第2个节的页眉

header.is_linked_to_previous = False # 不使用上节内容和样式

paragraph = header.paragraphs[0]

paragraph.add_run('这是第二节的页眉')

paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 设置页眉居中对齐

document.sections[1].footer.is_linked_to_previous = False

footer.paragraphs[0].add_run('这是第二节的页脚') # 添加第2节页脚内容

footer.paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 设置第2节页脚居中对齐

header = document.sections[2].header # 获取第3个节的页眉

header.is_linked_to_previous = False # 不使用上节的内容和样式

paragraph = header.paragraphs[0] # 获取页眉中的段落

paragraph.add_run('这是第三节的页眉')

paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 设置页眉右对齐

document.sections[2].footer.is_linked_to_previous = False

footer.paragraphs[0].add_run('这是第三节的页脚') # 添加第3节页脚内容

footer.paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT # 设置第3节页脚右对齐

document.save('页眉页脚1.docx') # 保存文档

```

### 3.1.6 代码延伸

```python

'''Document的其他常用方法'''

#添加无序列表

document.add_paragraph('前面是圆点', style='List Bullet')

#添加有序列表

document.add_paragraph('前面是数字', style='List Number')

#添加分页符

document.add_page_break()

#添加节

document.add_section()

#设置样式

document.styles.add_style('textstyle', WD_STYLE_TYPE. PARAGRAPH)

'''对齐设置'''

from docx.enum.text import WD_ALIGN_PARAGRAPH

#LEFT: 左对齐

#CENTER: 文字居中

#RIGHT: 右对齐

#JUSTIFY: 文本两端对齐

'''设置段落行距'''

from docx.shared import Length

# SINGLE :单倍行距(默认)

#ONE_POINT_FIVE : 1.5倍行距

# DOUBLE2 : 倍行距

#AT_LEAST : 最小值

#EXACTLY:固定值

# MULTIPLE : 多倍行距

paragraph.line_spacing_rule = WD_LINE_SPACING.EXACTLY #固定值

paragraph_format.line_spacing = Pt(18) # 固定值18磅

paragraph.line_spacing_rule = WD_LINE_SPACING.MULTIPLE #多倍行距

paragraph_format.line_spacing = 1.75 # 1.75倍行间距

'''设置字体属性'''

from docx.shared import RGBColor,Pt

#all_caps:全部大写字母

#bold:加粗

#color:字体颜色

#double_strike:双删除线

#hidden : 隐藏

#imprint : 印记

#italic : 斜体

#name  :字体

#shadow  :阴影

#strike  :  删除线

#subscript  :下标

#superscript  :上标

#underline  :下划线

```

3.2 项目实践

项目难度:⭐ ⭐ ⭐

### 3.2.1需求

>  你是公司的行政人员,对合作伙伴进行邀请,参加公司的会议;

**根据参会人名单,利用python批量生成邀请函。**

### 3.2.2需求分析

> 逻辑相对简单:

> - 获取 Excel 文件中每一行的信息,提取 参数;结合获取的参数设计邀请函样式并输出

> - 设计word段落及字体等样式。

### 3.2.3代码

```python

# 导入库

from openpyxl import load_workbook

from docx import Document

from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

from docx.shared import RGBColor, Pt,Inches,Cm

from docx.oxml.ns import qn

path = r'D:\idea\cloud_analyse_game_sentiment\word自动化'

# 路径为Excel 文件所在的位置,可按实际情况更改

workbook = load_workbook(path + r'\excel到word.xlsx')

sheet = workbook.active  #默认的WorkSheet

n = 0  #为了不遍历标题(excel的第一行)

for row in sheet.rows:

    if n:

        company = row[0].value

        office = row[1].value

        name = row[2].value

        date = str(row[3].value).split()[0]

        print(company, office, name, date)

        doc = Document()

        heading_1 = '邀 请 函'

        paragraph_1 = doc.add_heading(heading_1, level=1)

        # 居中对齐

        paragraph_1.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

        # 单独修改较大字号

        for run in paragraph_1.runs:

            run.font.size = Pt(17)

        greeting_word_1 = '尊敬的'

        greeting_word_2 = '公司'

        greeting_word_3 = ',您好:'

        paragraph_2 = doc.add_paragraph()

        paragraph_2.add_run(greeting_word_1)

        r_1 = paragraph_2.add_run(company)

        r_1.font.bold = True  # 加粗

        r_1.font.underline = True    #下划线

        paragraph_2.add_run(greeting_word_2)

        r_2 = paragraph_2.add_run(office)

        r_2.font.bold = True  # 加粗

        r_2.font.underline = True    #下划线

        r_3 = paragraph_2.add_run(name)

        r_3.font.bold = True  # 加粗

        r_3.font.underline = True    #下划线

        paragraph_2.add_run(greeting_word_3)

        paragraph_3 = doc.add_paragraph()

        paragraph_3.add_run('现诚挚的邀请您于2021年10月27日参加DataWhale主办的享受开源2050活动,地点在北京鸟巢,希望您届时莅临参加。')

        paragraph_3.paragraph_format.first_line_indent = Cm(0.75)

        paragraph_3.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT

        paragraph_3.paragraph_format.space_after = Inches(1.0)

        paragraph_3.paragraph_format.line_spacing = 1.5

        paragraph_4 = doc.add_paragraph()

        date_word_1 = '邀请时间:'

        paragraph_4.add_run(date_word_1)

        paragraph_4.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT

        sign_date = "{}年{}月{}日".format(date.split('-')[0], date.split('-')[1], date.split('-')[2])

        paragraph_4.add_run(sign_date).underline = True

        paragraph_4.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT


        #设置全文字体

        for paragraph in doc.paragraphs:

            for run in paragraph.runs:

                run.font.color.rgb = RGBColor(0, 0, 0)

                run.font.name = '楷体'

                r = run._element.rPr.rFonts

                r.set(qn('w:eastAsia'), '楷体')

        doc.save(path + "\{}-邀请函.docx".format(name))

    n = n + 1

```

## 3.3 后记

> 本案例也可适用于批量生产固定格式的word,如工资条,通知单等,面对这种相似且重复的任务,python的自动化运行能大幅提升当前的工作效率。

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

推荐阅读更多精彩内容