PDF 和 Word 文档是二进制文件,比文本复杂的多,要特定程序才能打开它们,利用Python可以让我们很方便的处理这些文件。

安装包

1
pip install python-docx
1
2
Installing collected packages: python-docx
Successfully installed python-docx-0.8.10
1
pip install pyPDF2
1
2
Installing collected packages: pyPDF2
Successfully installed pyPDF2-1.26.0
1
pip install pdfminer.six
1
2
3
Installing collected packages: pycryptodome, sortedcontainers, pdfminer.six
Successfully installed pdfminer.six-20200517 pycryptodome-3.9.8 sortedcontainers
-2.2.2

PDF

PDF 表示 Portable Document Format,使用.pdf 文件扩展名。

PDF 读取文本内容

PyPDF2 没有办法从 PDF 文档中提取图像、图表或其他媒体,但它可以提取文本,并将文本返回为 Python 字符串。

pdf_meeting.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
## 导入 PyPDF2 模块。然后以读二进制模式打开 meetingminutes.pdf,并将它保存在 pdfFileObj
import PyPDF2
pdfFileObj = open('meetingminutes.pdf', 'rb')
pdfReader = PyPDF2.PdfFileReader(pdfFileObj)
pdfReader.numPages ## 文档的总页数
# 19
## 只提取第一页的文本
pageObj = pdfReader.getPage(0)
## extractText()方法,返回该页文本的字符串
pageObj.extractText()
# 'OOFFFFIICCIIAALL BBOOAARRDD MMIINNUUTTEESS Meeting of \nMarch 7\n, 2014\n \n The Board of Elementary and Secondary Education shall provide leadership and \ncreate policies for education that expand opportunities for children, empower \nfamilies and communities, and advance Louisiana in an increasingly \ncompetitive glob\nal market.\n BOARD \n of ELEMENTARY\n and \n SECONDARY\n EDUCATION\n '

## 该PDF中的文本 Charles E.“Chas”Roemer, President,在函数返回的字符串中消失了,而且空格有时候也会没有。但其实已经足够了。

加密/解密 PDF

某些 PDF 文档有加密功能,以防止别人阅读,只有在打开文档时提供口令才能阅读。

1
2
3
4
5
6
7
8
9
import PyPDF2 
pdfReader = PyPDF2.PdfFileReader(open('encrypted.pdf', 'rb'))
## 判断是否加密
pdfReader.isEncrypted
# True
pdfReader.getPage(0)
# Traceback (most recent call last):
# --snip--
# PyPDF2.utils.PdfReadError: file has not been decrypted

我们用一个encrypted.pdf,它已经用口令 rosebud 加密,就无法正常打开,此时我们可以用pdfReader.decrypt输入正确口令后打开。

1
2
3
pdfReader.decrypt('rosebud') 
# 1
pageObj = pdfReader.getPage(0)

使用pdfWriter.encrypt('swordfish')即可加密pdf。

从已有的文档生成新的 PDF

PyPDF2中,与 PdfFileReader 对象相对的是 PdfFileWriter 对象,它可以创建一个新的 PDF 文件。但 PyPDF2 不具备直接写入PDF的能力,仅可从其他 PDF 中拷贝页面、旋转页面、重叠页面和加密文件。

拷贝页面

可以利用 PyPDF2,从一个 PDF 文档拷贝页面到另一个 PDF 文档。这让你能够组合多个 PDF 文件,去除不想要的页面,或调整页面的次序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import PyPDF2 
pdf1File = open('meetingminutes.pdf', 'rb')
pdf2File = open('meetingminutes2.pdf', 'rb')
pdf1Reader = PyPDF2.PdfFileReader(pdf1File)
pdf2Reader = PyPDF2.PdfFileReader(pdf2File)
## 创建一个新的 PdfFileWriter对象,表示一个空白的PDF文档
pdfWriter = PyPDF2.PdfFileWriter()

## 从两个源 PDF 拷贝所有的页面,将它们添加到 PdfFileWriter 对象。
for pageNum in range(pdf1Reader.numPages):
pageObj = pdf1Reader.getPage(pageNum)
pdfWriter.addPage(pageObj)

for pageNum in range(pdf2Reader.numPages):
pageObj = pdf2Reader.getPage(pageNum)
pdfWriter.addPage(pageObj)
## 写入一个新的 PDF 文档,名为 combinedminutes.pdf
pdfOutputFile = open('combinedminutes.pdf', 'wb')
pdfWriter.write(pdfOutputFile)
pdfOutputFile.close()
pdf1File.close()
pdf2File.close()

旋转页面

利用 rotateClockwise()和 rotateCounterClockwise()方法,PDF 文档的页面也可以旋转 90 度的整数倍。

以下代码旋转了第一页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import PyPDF2 
minutesFile = open('meetingminutes.pdf', 'rb')
pdfReader = PyPDF2.PdfFileReader(minutesFile)
page = pdfReader.getPage(0)
page.rotateClockwise(90)
# {'/Contents': [IndirectObject(961, 0), IndirectObject(962, 0),
#--snip--
# }
pdfWriter = PyPDF2.PdfFileWriter()
pdfWriter.addPage(page)
resultPdfFile = open('rotatedPage.pdf', 'wb')
pdfWriter.write(resultPdfFile)
resultPdfFile.close()
minutesFile.close()

叠加页面

PyPDF2 也可以将一页的内容叠加到另一页上,这可以用来在页面上添加公司标志、时间戳或水印。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import PyPDF2 
minutesFile = open('meetingminutes.pdf', 'rb')
pdfReader = PyPDF2.PdfFileReader(minutesFile)
minutesFirstPage = pdfReader.getPage(0)
pdfWatermarkReader = PyPDF2.PdfFileReader(open('watermark.pdf', 'rb'))
## 加入水印
minutesFirstPage.mergePage(pdfWatermarkReader.getPage(0))
pdfWriter = PyPDF2.PdfFileWriter()
## 加入加了水印的第一页
pdfWriter.addPage(minutesFirstPage)

for pageNum in range(1, pdfReader.numPages):
pageObj = pdfReader.getPage(pageNum)
pdfWriter.addPage(pageObj)
resultPdfFile = open('watermarkedCover.pdf', 'wb')
pdfWriter.write(resultPdfFile)
minutesFile.close()
resultPdfFile.close()

从多个 PDF 中合并选择的页面

目标:将所有PDF 文件合并成一个 PDF 文件,并去除每一个文件的第一个封面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import PyPDF2, os

## 返回当前工作目录中所有PDF文件的列表并简单整理排序
pdfFiles = []
for filename in os.listdir('.'):
if filename.endswith('.pdf'):
pdfFiles.append(filename)
pdfFiles.sort()

pdfWriter = PyPDF2.PdfFileWriter()

## 循环打开每个PDF 文件,并读取除第一页的酥油内容然后写入
for filename in pdfFiles:
pdfFileObj = open(filename, 'rb')
pdfReader = PyPDF2.PdfFileReader(pdfFileObj)

for pageNum in range(1, pdfReader.numPages):
pageObj = pdfReader.getPage(pageNum)
pdfWriter.addPage(pageObj)

## 以写二进制的模式打开输出 PDF 文件 allminutes.pdf并写入刚才的结果
pdfOutput = open('allminutes.pdf', 'wb')
pdfWriter.write(pdfOutput)
pdfOutput.close()

Docx

利用 python-docx 模块,Python 可以创建和修改.docx 的 Word 文档。

读取 Word 文档

Document 对象包含一个 Paragraph 对象的列表,表示文档中的段落, 每个 Paragraph 对象都包含一个 Run 对象的列表。

Docx.jpg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import docx 
doc = docx.Document('demo.docx')
len(doc.paragraphs)
# 7
doc.paragraphs[0].text
# 'Document Title'
doc.paragraphs[1].text
# 'A plain paragraph with some bold and some italic'
len(doc.paragraphs[1].runs)
# 4
doc.paragraphs[1].runs[0].text
# 'A plain paragraph with some '
doc.paragraphs[1].runs[1].text
# 'bold'
doc.paragraphs[1].runs[2].text
# ' and some '
doc.paragraphs[1].runs[3].text
# 'italic'

利用 getText()函数,可从.docx 文件中取得完整的文本 。

1
2
3
4
5
6
7
8
9
import docx

def getText(filename):
doc = docx.Document(filename)
fullText = []
for para in doc.paragraphs:
fullText.append(para.text)
## 在段落间增加空行
return '\n\n'.join(fullText)

设置 Paragraph 和 Run 对象的样式

在 Windows 平台的 Word 中,你可以按下 Ctrl-Alt-Shift-S,显示样式窗口并查看样式。

在 OS X 上,可以点击 ViewStyles 菜单项,查看样式窗口。

对于 Word 文档,有 3 种类型的样式:段落样式可以应用于 Paragraph 对象,字符样式可以应用于 Run 对象,链接的样式可以应用于这两种对象。

UQGinJ.jpg

注意点:

  1. 在设置 style 属性时,不要在样式名称中使用空格。

  2. 如果对 Run 对象应用链接的样式,需要在样式名称末尾加上’Char’。

Run 属性

通过 text 属性,Run 可以进一步设置样式。每个属性都可以被设置为 3 个值之一:True(该属性总是启用,不论其他样式是否应用于该 Run)、False(该属性总是禁用)或 None(默认使用该 Run 被设置的任何属性)。

属性 描述
bold 文本以粗体出现
italic 文本以斜体出现
underline 文本带下划线
strike 文本带删除线
double_strike 文本带双删除线
all_caps 文本以大写首字母出现
small_caps 文本以大写首字母出现,小写字母小两个点
shadow 文本带阴影
outline 文本以轮廓线出现,而不是实心
rtl 文本从右至左书写
imprint 文本以刻入页面的方式出现
emboss 文本以凸出页面的方式出现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
doc = docx.Document('demo.docx')
doc.paragraphs[0].text
# 'Document Title'
doc.paragraphs[0].style
# 'Title'
doc.paragraphs[0].style = 'Normal'
## 将Document Title 设置为 Normal 样式

doc.paragraphs[1].text
'A plain paragraph with some bold and some italic'
(doc.paragraphs[1].runs[0].text, doc.paragraphs[1].runs[1].text, doc.
paragraphs[1].runs[2].text, doc.paragraphs[1].runs[3].text)
# ('A plain paragraph with some ', 'bold', ' and some ', 'italic')
doc.paragraphs[1].runs[0].style = 'QuoteChar'
doc.paragraphs[1].runs[1].underline = True
doc.paragraphs[1].runs[3].underline = True
doc.save('restyled.docx')

写入 Word 文档

这里我们直接引入官方的demo,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from docx import Document
from docx.shared import Inches
## 调用 docx.Document(),返回一个新的、空白的 Word Document 对象
document = Document()
## 添加一个段落,并使用一种标题样式0-4
document.add_heading('Document Title', 0)
## 添加新的段落文本
p = document.add_paragraph('A plain paragraph having some ')
p.add_run('bold').bold = True
p.add_run(' and some ')
p.add_run('italic.').italic = True

document.add_heading('Heading, level 1', level=1)
document.add_paragraph('Intense quote', style='Intense Quote')

document.add_paragraph(
'first item in unordered list', style='List Bullet'
)
document.add_paragraph(
'first item in ordered list', style='List Number'
)

document.add_picture('zophie.png', width=Inches(1.25))

records = (
(3, '101', 'Spam'),
(7, '422', 'Eggs'),
(4, '631', 'Spam, spam, eggs, and spam')
)

table = document.add_table(rows=1, cols=3)
hdr_cells = table.rows[0].cells
hdr_cells[0].text = 'Qty'
hdr_cells[1].text = 'Id'
hdr_cells[2].text = 'Desc'
for qty, id, desc in records:
row_cells = table.add_row().cells
row_cells[0].text = str(qty)
row_cells[1].text = id
row_cells[2].text = desc
## 添加换页符
document.add_page_break()

document.save('demo2.docx')

zophie.png

REFERENCES