专栏首页Python进阶之路超实用!Office三件套批量转PDF以及PDF书签读写与加水印

超实用!Office三件套批量转PDF以及PDF书签读写与加水印

日常工作中,我们经常需要将 office 三件套,Word、Excel和PPT转换成PDF。当然办公软件自身都带有这样的功能,但当我们需要一次性转换大量的office文件时,还是用程序批量处理比较方便。

其实这类代码有其他作者写过,但是呢,要么每个组件用一个库,用么代码没法正常跑。今天呢,我将带大家完全只使用 win32 调用 VBA 的 API 来完成这个转换。

另外,将完成 PDF 书签的写入和提取操作以及批量加水印的操作。关于水印我们可以加背景底图水印或悬浮文字水印。

本文目录:

文章目录

  • office三件套转换为 PDF 格式
    • 将 Word 文档转换为 PDF
    • 将 Excel 表格转换为 PDF
    • 将 PowerPoint 幻灯片转换为 PDF
    • 批量转换成PDF
  • PDF书签的提取与写入
    • PDF书签提取
    • PDF书签保存到文件
    • 从文件读取PDF书签数据
    • 向PDF写入书签数据
  • 给PDF加水印
    • 生成水印PDF文件
    • PyPDF2库批量加水印
    • 拷贝书签
    • 加水印同时复制书签
    • PyMuPDF给PDF加文字水印
    • PyPDF2库压缩PDF

office三件套转换为 PDF 格式

office三件套包括Word、Excel、PowerPoint,为了调用 office 程序自身的 API 需要先确保已经安装pywin32,没有安装可以使用以下命令安装:

pip install pywin32 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

下面我们逐个查询 API 来测试:

将 Word 文档转换为 PDF

Document对象有个 ExportAsFixedFormat 方法:

https://docs.microsoft.com/zh-cn/office/vba/api/word.document.exportasfixedformat

所使用到的几个重要参数如下:

下面我们测试一下:

word_app = win32.gencache.EnsureDispatch('Word.Application')
file = word_app.Documents.Open(os.path.abspath("成立100周年大会讲话.doc"), ReadOnly=1)
file.ExportAsFixedFormat(os.path.abspath("成立100周年大会讲话.pdf"),
                         ExportFormat=17, Item=7, CreateBookmarks=1)
file.Close()
word_app.Quit() 

可以看到转换效成功。

将 Excel 表格转换为 PDF

对于Excel主要有两个API:

https://docs.microsoft.com/zh-cn/office/vba/api/excel.workbook.exportasfixedformat

https://docs.microsoft.com/zh-cn/office/vba/api/excel.worksheet.exportasfixedformat

分别针对整个Excel文件和单个工作表。

我们默认都认为要转换所有工作表,所以只用workbook的导出API。

第一个参数是 XlFixedFormatType 枚举类型,0表示PDF。其他参数可以根据实际需要微调。

测试一下:

excel_app = win32.gencache.EnsureDispatch('Excel.Application')
file = excel_app.Workbooks.Open(os.path.abspath("nsheet.xlsx"), ReadOnly=1)
file.ExportAsFixedFormat(0, os.path.abspath("nsheet.pdf"))
file.Close()
excel_app.Quit() 

可以看到每一张工作表都导入到 PDF 文件的一页中。

将 PowerPoint 转换为 PDF

对于PPT,官方虽然提供了导出API:Presentation.ExportAsFixedFormat 方法。但经过实测发现会爆出The Python instance can not be converted to a COM object的类型错误。

这是因为PPT的saveAs保存API提供了直接另存为PDF的方法,详解:

https://docs.microsoft.com/zh-cn/office/vba/api/powerpoint.presentation.saveas

ppSaveAsPDF常量的值为32,可以在https://docs.microsoft.com/zh-cn/office/vba/api/powerpoint.ppsaveasfiletype中查询到。

下面测试一下:

ppt_app = win32.gencache.EnsureDispatch('PowerPoint.Application')
file = ppt_app.Presentations.Open(os.path.abspath("第1章 机器学习和统计学习.pptx"), ReadOnly=1)
file.SaveAs(os.path.abspath("第1章 机器学习和统计学习.pdf"), 32)
file.Close()
ppt_app.Quit() 

效果如下:

批量转换成PDF

下面我们将上面测试好的代码封装起来,让其能够对任何一个office三件套之一的文件都能转换PDF,程序员封装为在原文件相对目录下生成相同文件名的 PDF 文件(可以根据实际需求修改代码):

office_type = {
    "Word": [".doc", ".docx"],
    "Excel": [".xlsx", ".xls", ".xlsm"],
    "PowerPoint": [".pptx", ".ppt"]
}
cache = {}
for app_name, exts in office_type.items():
    for ext in exts:
        cache[ext] = app_name

def office_file2pdf(filename):
    filename_no_ext, ext = os.path.splitext(filename)
    app_name = cache.get(ext)
    if app_name == "Word":
        app = win32.gencache.EnsureDispatch('Word.Application')
        file = app.Documents.Open(os.path.abspath(filename), ReadOnly=1)
        try:
            file.ExportAsFixedFormat(os.path.abspath(f"{filename_no_ext}.pdf"),
                                     ExportFormat=17, Item=7, CreateBookmarks=1)
        finally:
            file.Close()
    elif app_name == "Excel":
        app = win32.gencache.EnsureDispatch('Excel.Application')
        file = app.Workbooks.Open(os.path.abspath(filename), ReadOnly=1)
        try:
            file.ExportAsFixedFormat(
                0, os.path.abspath(f"{filename_no_ext}.pdf"))
        finally:
            file.Close()
    elif app_name == "PowerPoint":
        app = win32.gencache.EnsureDispatch('PowerPoint.Application')
        file = app.Presentations.Open(os.path.abspath(filename), ReadOnly=1)
        try:
            file.SaveAs(os.path.abspath(f"{filename_no_ext}.pdf"), 32)
        finally:
            file.Close() 

开头先定义了各类文件所对应的格式,后面定义了一个同源。

下面批量转换一下指定文件夹:

from glob import glob

path = "E:\tmp\test"
for filename in glob(f"{path}/*"):
    office_file2pdf(filename)

win32.gencache.EnsureDispatch('Word.Application').Quit()
win32.gencache.EnsureDispatch('Excel.Application').Quit()
win32.gencache.EnsureDispatch('PowerPoint.Application').Quit() 

生成后:

PDF书签的提取与写入

后面我们打算使用 PyPDF2 来批量加水印,比较尴尬的是用这个库只能重新创建 PDF 文件,导致书签丢失,所以我们需要事先能提取标签并写入才行。顺便就可以写出一套可以给 PDF 加书签的方法。

PyPDF2库的安装如下:

pip install PyPDF2 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

PDF书签提取

from PyPDF2 import PdfFileReader

def get_pdf_Bookmark(filename):
    "作者CSDN:https://blog.csdn.net/as604049322"
    if isinstance(filename, str):
        pdf_reader = PdfFileReader(filename)
    else:
        pdf_reader = filename
    pagecount = pdf_reader.getNumPages()
    # 用保存每个标题id所对应的页码
    idnum2pagenum = {}
    for i in range(pagecount):
        page = pdf_reader.getPage(i)
        idnum2pagenum[page.indirectRef.idnum] = i

    # 保存每个标题对应的标签数据,包括层级,标题和页码索引(页码-1)
    bookmark = []
    def get_pdf_Bookmark_inter(outlines, tab=0):
        for outline in outlines:
            if isinstance(outline, list):
                get_pdf_Bookmark_inter(outline, tab+1)
            else:
                bookmark.append(
                    (tab, outline['/Title'], idnum2pagenum[outline.page.idnum]))
    outlines = pdf_reader.getOutlines()
    get_pdf_Bookmark_inter(outlines)
    return bookmark 

测试提取书签:

bookmark = get_pdf_Bookmark(filename='mysql.pdf')
bookmark 
[(0, '1. 数据库简介', 0),
 (1, '1.1. 概念', 0),
 (1, '1.2. 数据库分类', 0),
 (2, '1.2.1. 网络数据库', 0),
 (2, '1.2.2. 层级数据库', 0),
 (2, '1.2.3. 关系数据库', 0),
 (1, '1.3. 关系型数据库', 1),
 (2, '1.3.1. 基本概念', 1),
 (2, '1.3.2. 典型关系型数据库', 1),

PDF书签保存到文件

def write_bookmark2file(bookmark, filename="bookmark.txt"):
    with open(filename, "w") as f:
        for tab, title, pagenum in bookmark:
            prefix = "\t"*tab
            f.write(f"{prefix}{title}\t{pagenum+1}\n")

write_bookmark2file(bookmark) 

从文件读取PDF书签数据

有时我们希望自定义标签,所以可以从文件读取书签数据:

def read_bookmark_from_file(filename="bookmark.txt"):
    bookmark = []
    with open(filename) as f:
        for line in f:
            l2 = line.rfind("\t")
            l1 = line.rfind("\t", 0, l2)
            bookmark.append((l1+1, line[l1+1:l2], int(line[l2+1:-1])-1))
    return bookmark 

测试:

read_bookmark_from_file() 

读取结果与上面提取到的书签一致。

向PDF写入书签数据

下面我们测试从一个 PDF 读取书签后原本复制并保存。

先原样复制PDF:

from PyPDF2 import PdfFileReader, PdfFileWriter

filename = 'mysql.pdf'
pdf_reader = PdfFileReader(filename)
pdf_writer = PdfFileWriter()
for page in pdf_reader.pages:
    pdf_writer.addPage(page) 

读取书签并写入:

bookmark = read_bookmark_from_file()

last_cache = [None]*(max(bookmark, key=lambda x: x[0])[0]+1)
for tab, title, pagenum in bookmark:
    parent = last_cache[tab-1] if tab > 0 else None
    indirect_id = pdf_writer.addBookmark(title, pagenum, parent=parent)
    last_cache[tab] = indirect_id
pdf_writer.setPageMode("/UseOutlines") 

最后保存看看:

with open("tmp.pdf", "wb") as out:
        pdf_writer.write(out) 

可以看到书签已经完美保留:

给PDF加水印

对于给PDF加水印,个人还是推荐一些在线的免费工具或WPS。这些工具基本都支持加悬浮的透明水印。除非你确实有批量给 PDF 文件加水印的需求。

需要注意使用 Python 的 PyPDF2 库给 PDF 加水印,采用的是叠加模式,实际并不能算是加水印,而是加背景。

具体原理是用一张需要作为水印的 PDF 打底,然后将原本的 PDF 文件一页页叠加到上面。

首先我们需要生成水印PDF:

生成水印PDF文件

代码:

import math
from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageChops

def crop_image(im):
    '''裁剪图片边缘空白'''
    bg = Image.new(mode='RGBA', size=im.size)
    bbox = ImageChops.difference(im, bg).getbbox()
    if bbox:
        return im.crop(bbox)
    return im

def set_opacity(im, opacity):
    '''设置水印透明度'''
    assert 0 <= opacity <= 1
    alpha = im.split()[3]
    alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
    im.putalpha(alpha)
    return im

def get_mark_img(text, color="#8B8B1B", size=30, opacity=0.15):
    width = len(text) * size
    mark = Image.new(mode='RGBA', size=(width, size+20))
    ImageDraw.Draw(im=mark) \
        .text(xy=(0, 0),
              text=text,
              fill=color,
              font=ImageFont.truetype('msyhbd.ttc', size=size))
    mark = crop_image(mark)
    set_opacity(mark, opacity)
    return mark

def create_watermark_pdf(text, filename="watermark.pdf", img_size=(840, 1150), color="#8B8B1B", size=30, opacity=0.15, space=75, angle=30):
    mark = get_mark_img(text, color, size, opacity)
    im = Image.new(mode='RGB', size=img_size, color="white")
    w, h = img_size
    c = int(math.sqrt(w**2 + h**2))
    mark2 = Image.new(mode='RGBA', size=(c, c))
    y, idx = 0, 0
    mark_w, mark_h = mark.size
    while y < c:
        x = -int((mark_w + space)*0.5*idx)
        idx = (idx + 1) % 2
        while x < c:
            mark2.paste(mark, (x, y))
            x = x + mark_w + space
        y = y + mark_h + space
    mark2 = mark2.rotate(angle)
    im.paste(mark2, (int((w-c)/2), int((h-c)/2)),  # 坐标
             mask=mark2.split()[3])
    im.save(filename, "PDF", resolution=100.0, save_all=True)
    
create_watermark_pdf("小小明的CSDN:https://blog.csdn.net/as604049322") 

效果如下:

当然我们也可以借助 Word 或 WPS 直接操作生成这样的水印PDF。

然后我们就可以批量给每一页加水印了:

PyPDF2库批量加水印

import os
from PyPDF2 import PdfFileReader, PdfFileWriter
from copy import copy

def pdf_add2watermark(filename, save_filepath, watermark='watermark.pdf'):
    watermark = PdfFileReader(watermark).getPage(0)
    pdf_reader = PdfFileReader(filename)

    pdf_writer = PdfFileWriter()
    for page in pdf_reader.pages:
        new_page = copy(watermark)
        new_page.mergePage(page)
        new_page.compressContentStreams()
        pdf_writer.addPage(new_page)
    
    with open(save_filepath, "wb") as out:
        pdf_writer.write(out)

filename = 'mysql.pdf'
save_filepath = 'mysql【带水印】.pdf'
pdf_add2watermark(filename, save_filepath) 

可以看到水印已经成功的加上,就是缺少目录(书签)。

拷贝书签

下面我们将书签从原始文件拷贝到加过水印的 PDF 文件中:

from PyPDF2 import PdfFileReader, PdfFileWriter

def get_pdf_Bookmark(filename):
    "作者CSDN:https://blog.csdn.net/as604049322"
    if isinstance(filename, str):
        pdf_reader = PdfFileReader(filename)
    else:
        pdf_reader = filename
    pagecount = pdf_reader.getNumPages()
    # 用保存每个标题id所对应的页码
    idnum2pagenum = {}
    for i in range(pagecount):
        page = pdf_reader.getPage(i)
        idnum2pagenum[page.indirectRef.idnum] = i

    # 保存每个标题对应的标签数据,包括层级,标题和页码索引(页码-1)
    bookmark = []

    def get_pdf_Bookmark_inter(outlines, tab=0):
        for outline in outlines:
            if isinstance(outline, list):
                get_pdf_Bookmark_inter(outline, tab+1)
            else:
                bookmark.append((tab, outline['/Title'], idnum2pagenum[outline.page.idnum]))
    outlines = pdf_reader.getOutlines()
    get_pdf_Bookmark_inter(outlines)
    return bookmark

def copy_bookmark2pdf(srcfile, destfile):
    "作者CSDN:https://blog.csdn.net/as604049322"
    bookmark = get_pdf_Bookmark(srcfile)
    pdf_reader = PdfFileReader(destfile)
    pdf_writer = PdfFileWriter()
    for page in pdf_reader.pages:
        pdf_writer.addPage(page)
    last_cache = [None]*(max(bookmark, key=lambda x: x[0])[0]+1)
    for tab, title, pagenum in bookmark:
        parent = last_cache[tab-1] if tab > 0 else None
        indirect_id = pdf_writer.addBookmark(title, pagenum, parent=parent)
        last_cache[tab] = indirect_id
    pdf_writer.setPageMode("/UseOutlines")
    with open(destfile, "wb") as out:
        pdf_writer.write(out)

copy_bookmark2pdf('mysql.pdf', 'mysql【带水印】.pdf') 

成功实现既有水印又有书签。

上述代码涉及二次调用,而且涉及重复的磁盘读写操作,我们在一次读写磁盘时就直接把书签加上,现在重新封装一下:

加水印同时复制书签

将上述代码重新整理一下,并将递归转换为生成器调用:

from PyPDF2 import PdfFileReader, PdfFileWriter
from copy import copy

def get_pdf_Bookmark(pdf_file):
    "作者CSDN:https://blog.csdn.net/as604049322"
    if isinstance(pdf_file, str):
        pdf_reader = PdfFileReader(filename)
    else:
        pdf_reader = pdf_file
    pagecount = pdf_reader.getNumPages()
    # 用保存每个标题id所对应的页码
    idnum2pagenum = {}
    for i in range(pagecount):
        page = pdf_reader.getPage(i)
        idnum2pagenum[page.indirectRef.idnum] = i

    # 保存每个标题对应的标签数据,包括层级,标题和页码索引(页码-1)
    def get_pdf_Bookmark_inter(outlines, tab=0):
        for outline in outlines:
            if isinstance(outline, list):
                yield from get_pdf_Bookmark_inter(outline, tab+1)
            else:
                yield (tab, outline['/Title'], idnum2pagenum[outline.page.idnum])
    outlines = pdf_reader.getOutlines()
    return [_ for _ in get_pdf_Bookmark_inter(outlines)]

def write_bookmark2pdf(bookmark, pdf_file):
    "作者CSDN:https://blog.csdn.net/as604049322"
    if isinstance(pdf_file, str):
        pdf_writer = PdfFileWriter()
    else:
        pdf_writer = pdf_file
    last_cache = [None]*(max(bookmark, key=lambda x: x[0])[0]+1)
    for tab, title, pagenum in bookmark:
        parent = last_cache[tab-1] if tab > 0 else None
        indirect_id = pdf_writer.addBookmark(title, pagenum, parent=parent)
        last_cache[tab] = indirect_id
    pdf_writer.setPageMode("/UseOutlines")
    if isinstance(pdf_file, str):
        with open(pdf_file, "wb") as out:
            pdf_writer.write(out)

def pdf_add2watermark(filename, save_filepath, watermark='watermark.pdf'):
    watermark = PdfFileReader(watermark).getPage(0)
    pdf_reader = PdfFileReader(filename)
    pdf_writer = PdfFileWriter()
    for page in pdf_reader.pages:
        new_page = copy(watermark)
        new_page.mergePage(page)
        new_page.compressContentStreams()
        pdf_writer.addPage(new_page)
    bookmark = get_pdf_Bookmark(pdf_reader)
    write_bookmark2pdf(bookmark, pdf_writer)

    with open(save_filepath, "wb") as out:
        pdf_writer.write(out)

filename = 'mysql.pdf'
save_filepath = 'mysql【带水印】.pdf'
pdf_add2watermark(filename, save_filepath) 

可以看到经过压缩生成的加水印的 PDF 比原文件更小。

PyMuPDF给PDF加文字水印

前面我们使用PyPDF2库给PDF增加了背景底图性质的图片水印,那有什么方法可以给PDF增加文字型的水印呢?那就是通过PyPDF2库。

官方文档:https://pymupdf.readthedocs.io/en/latest/index.html

Github:https://github.com/pymupdf/PyMuPDF

安装:

pip install pymupdf -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

代码实现如下:

import fitz

with fitz.open("mysql【带水印】.pdf") as pdf_doc:
    for page in pdf_doc:
        page.insert_text((20, 40), "小小明的CSDN", fontsize=30, color=(1, 0, 0),
                         fontname="china-s", fill_opacity=0.2)
        page.insert_text((20, page.rect.height-20), "https://blog.csdn.net/as604049322",
                         color=(1, 1, 1, 1), fontsize=20, fill_opacity=0.2)
    pdf_doc.save('mysql【带水印】2.pdf') 

上述代码给 PDF 每一页都增加了两个悬浮文字,其中纯链接的文字点击还有跳转的效果:

当然上述代码只是一种抛砖引玉的写法,想要增加更复杂的文字水印还需各位读者认真阅读官方文档和 PyMuPDF 的源码。

我所参考的文档主要有:

  • https://pymupdf.readthedocs.io/en/latest/textwriter.html
  • https://pymupdf.readthedocs.io/en/latest/page.html#Page.write_text
  • https://pymupdf.readthedocs.io/en/latest/page.html#Page.insert_text

注意:Page.insert_text有个_rotate_参数可以对文字进行旋转,但该参数仅支持 90 度倍数的旋转。

如果直接给未经 PyPDF2 库压缩的 PDF 增加文字水印会导致文件大小增加较大,此时还可以使用 PyPDF2 库对 PDF进行压缩输出。

PyPDF2库压缩PDF

def compress_pdf(filename, save_filepath):
    pdf_reader = PdfFileReader(filename)
    pdf_writer = PdfFileWriter()
    for page in pdf_reader.pages:
        page.compressContentStreams()
        pdf_writer.addPage(page)
    bookmark = get_pdf_Bookmark(pdf_reader)
    write_bookmark2pdf(bookmark, pdf_writer)
    with open(save_filepath, "wb") as out:
        pdf_writer.write(out) 

结合前面 PyPDF2 读写书签的代码即可完成 PDF 的压缩。

本文转自 https://blog.csdn.net/as604049322/article/details/119047828。 作者:小小明(代码实体)

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:https://blog.csdn.net/fyfugoyfa复制
如有侵权,请联系 yunjia_community@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 别再问如何用Python提取PDF内容了!

    大家好,在之前的办公自动化系列文章中我们已经详细介绍了?如何使用Python批量处理PDF文件,包括合并、拆分、水印、加密等操作。

    刘早起
  • DearMob iPhone Manager Mac版一款功能强大的iPhone数据传输工具

    DearMob iPhone Manager 是Mac平台上一款功能强大的iPhone数据传输工具,无需iTunes即可完成数据传输。DearMob iPhon...

    Mac精选
  • 开发者把写了 12 年的软件,开源了!

    如果你这两天有关注 GitHub Trending,应该能注意到,一款名为「PDF 补丁丁」的软件多次反复出现在该榜单上。

    GitHubDaily
  • Flyingbee PDF to word for Mac(飞蜂pdf转word工具)

    飞蜂PDF转word Mac版可以将PDF文件导出到具有完全保留的原始文本,图像,布局,超链接,表格和贝塞尔曲线的完全格式化且可编辑的Microsoft Off...

    Mac精选
  • PDF常见问题总结

    Acrobat保存PDF文档报错“读取本文档时出现问题(135)” 用Acrobat修改PDF文档,保存的时候报错:“文档无法保存。读取本文档时出现问题(13...

    用户1221057
  • 130 个相见恨晚的神器网站

    猴哥yuri
  • MAC上很好的PDF阅读器+批注编辑软件——PDF Expert

    PDF虽然很常见的办公文档格式,各种电子书、资料不仅有时会有着非常复杂的排版、诸多的表格、嵌套的公式等,而且还有很多动辄几百 MB 的全彩高清扫描版 PDF 大...

    caikaili
  • 推荐 130 个令你眼前一亮的网站,总有一个用得着

    谷粉学术:https://gfsoso.99lb.net/scholar.html

    Java编程指南
  • 相见恨晚的超实用网站,一次性分享出来

    IT茂茂
  • 别光埋头写代码,这100个网站比涨工资都重要!

    终码一生
  • 太方便了!利用Python对批量Pdf转Word

    在wps或者office里面可以将pdf转word,不过只能免费转前面5页,超过5页就需要会员。今天教大家一个Python办公小技巧:批量Pdf转Word ,这...

    Python研究者
  • 推荐 | 6 款 Python 特殊文本格式处理库

    前言 以下是一些 Python 编写的用来解析和操作特殊文本格式的库,希望对大家有所帮助。 1 Tablib https://www.oschina.net/p...

    灯塔大数据
  • 努力成为一名合格的测试(附免费学习网址大全)

    时间过得挺快,从刚开始写文章都不敢标原创,觉得没自己的东西。到现在敢标原创,自己的原创文章也越来越多了。可喜可贺,没有失业,没有降薪。

    清菡
  • 爬虫系列:读取 CSV、PDF、Word 文档

    上一期我们讲解了使用 Python 读取文档编码的相关问题,本期我们讲解使用 Python 处理 CSV、PDF、Word 文档相关内容。

    太后
  • 如何套印防伪单证?

    如何批量打印防伪单证?批量生成个性化及智能化的纸质或者PDF单证-防伪单证智能输出系统通过表单设计工具,以快捷直观的方式创建包括但不限于公司徽标、图形及条码等复...

    用户6457849
  • 生物医学领域科研工作者的软件和工具清单-3.0版本

    微软的Office和金山的WPS,大家不陌生,应该都有。WPS免费,Office需要破解,这里提供破解版下载地址:

    DoubleHelix

扫码关注云+社区

领取腾讯云代金券