利用python给pdf添加目录

利用tampermonkey知网下载助手脚本下载pdf格式论文时,发现论文缺少书签,而脚本可以下载一个txt格式的书签(目录),因此打算利用python将txt格式的目录添加到pdf中。

txt文件解析

txt 文件的读取

利用python读取txt文件时,使用的是python中的open方法,读取文件时最好加上文件的编码方式。不然有可能出现以下错误:

UnicodeDecodeError: 'gbk' codec can't decode byte 0xa6 in position 14: illegal multibyte sequence

这是因为再windows系统中python读取文件时的默认编码方式不是utf-8导致的。
实现代码如下:

txtpath = "D:/目录.txt"

with open(txtpath,'r',encoding='utf-8') as f:
    list_data = f.read()
    print(type(list_data)) # 打印文件读取得到变量的类型,输出结果为“str”,即文本类型
    print(list_data)

转义字符的原样打印

通过以上步骤已经可以读取到txt文件中的内容了,由于换行符、制表符在使用print输出时会可视化输出,而不是输出\t \n 这种转移字符,而在进行文本内容解析时需要对这些转义字符进行解析,所以现在需要将转义字符原样输出,其方法是将字符串放到数组中,然后打印数组。
实现代码如下:

str='致谢\t5\n摘要\t6\nABSTRACT\t8\n1 绪论\t16\n\t1.1 研究意义\t16\n\t'
print([str])

输出结果:

['致谢\t5\n摘要\t6\nABSTRACT\t8\n1 绪论\t16\n\t1.1 研究意义\t16\n\t']

txt文件按行读取

而通过分析txt中的文本可以发现,txt文件中的内容每行对应一条目录,因此可以采用按行读取的方式一行行的去对文本进行解析。
按行读取的代码如下:

with open(txtpath,'r',encoding='utf-8') as f:
    for line in f:
        print([line]) # 将行文本放在数组中打印是为了查看转移字符
        pass

书签文本信息解析

观察每行文本信息可以发现,书签的格式大致为

\t书签文字\t页码\n

因此解析文本时,可以先找到倒数第一个\t制表符,从而确定页码,然后就可以将页码以外的文本全部放到书签文本中了,而书签是几级书签,可以通过每行文字前面\t制表符的个数确定。
参考代码:

for line in f:
    line=line.replace('\n','') #去除\n
    i0=line.rfind('\t') #在find前加r的效果是从后往前搜索
    page_num=int(line[i0+1:])
    line=line[:i0]
    i0=0
    page_grade=0
    while(line.find('\t',i0,-1)!=-1):
        page_grade+=1
        i0 = line.find('\t',i0+1,-1) #更新i0,继续搜索
    page_text=line.replace('\t','')

需要注意的是,在查找倒数第一个\t的时候,使用的事rfind函数,而不是find函数,这两个函数的区别是,rfind函数从后往前找,find函数从前往后找。

为pdf文件添加书签

这里需要用到的是PyPDF2库,大概流程是用reader读取pdf,将reader读取到的pdf复制到writer中,然后给writer中的pdf添加标签,最后保存pdf即可。
实现的代码如下:

from PyPDF2 import PdfFileReader as reader,PdfFileWriter as writer

pdfpath = "D:/1.pdf"
pdf_in = reader(pdfpath)
pdf_out = writer()

# 将读取的pdf放到writer中
pageCount = pdf_in.getNumPages()
for iPage in range(pageCount):
    pdf_out.addPage(pdf_in.getPage(iPage))
    
parent0=pdf_out.addBookmark('父目录',0,parent = None) # 添加父目录
# 使用方法: addBookmark(书签文字,书签页码,书签的父目录),返回值是书签(可以作为其他书签的父目录)
parent1=pdf_out.addBookmark('子目录',0,parent = parent0) # 给父添加子目录

# 保存pdf
with open('D:/1-bookmark.pdf','wb') as fout:
	pdf_out.write(fout)

在复制pdf时没有使用cloneDocumentFromReader()方法,因为实际使用时发现使用了这个方法会在添加书签时报错,所以使用了一个折衷的方式,单页pdf复制。

ValueError: {'/Type': '/Outlines'} is not in list

为了让书签正确的添加到其父目录底下,程序在设计时引用了一个parent列表,实现方法如下:

parent=[]
curren_grade=0
parent.append(None)
with open(txtpath,'r',encoding='utf-8') as f:
    for line in f:
        line=line.replace('\n','') #去除\n
        i0=line.rfind('\t') #在find前加r的效果是从后往前搜索
        page_num=int(line[i0+1:])
        line=line[:i0]
        i0=0
        page_grade=0
        while(line.find('\t',i0,-1)!=-1):
            page_grade+=1
            i0 = line.find('\t',i0+1,-1) #更新i0,继续搜索
        page_text=line.replace('\t','')

		# 动态调整parent列表的长度
        if curren_grade<page_grade:
            parent.append(None)
            curren_grade=page_grade
            
        if page_grade==0:
            parent[0]=pdf_out.addBookmark(page_text,page_num-1,parent = None)
        else:
            parent[page_grade]=pdf_out.addBookmark(page_text,page_num-1,parent = parent[page_grade-1])

同时引用了一个curren_grade变量,让程序中的parent列表长度可以根据目录总的等级数量动态的调整。

完整代码

以下是程序的完整代码,程序中txtpath为书签文件的输入路径,pdfpath为pdf路径,添加书签后的pdf输出路径为源目录下pdf文件文件名后添加_bm
比如输入pdf路径为D:/1.pdf时,输出的路径就是D:/1_bm.pdf

import numpy as np
from PyPDF2 import PdfFileReader as reader,PdfFileWriter as writer
import os

def add_bookmarks(txtpath,pdfpath):
    pdf_in = reader(pdfpath)
    pdf_out = writer()
    # pdf_out.cloneDocumentFromReader(pdf_in,after_page_append=None)
    parent=[]
    pageCount = pdf_in.getNumPages()
    for iPage in range(pageCount):
        pdf_out.addPage(pdf_in.getPage(iPage))

    curren_grade=0
    parent.append(None)

    with open(txtpath,'r',encoding='utf-8') as f:

        for line in f:
            line=line.replace('\n','') #去除\n
            i0=line.rfind('\t') #在find前加r的效果是从后往前搜索
            page_num=int(line[i0+1:])
            line=line[:i0]
            i0=0
            page_grade=0
            while(line.find('\t',i0,-1)!=-1):
                page_grade+=1
                i0 = line.find('\t',i0+1,-1) #更新i0,继续搜索

            if curren_grade<page_grade:
                parent.append(None)
                curren_grade=page_grade


            page_text=line.replace('\t','')

            if page_grade==0:
                parent[0]=pdf_out.addBookmark(page_text,page_num-1,parent = None)
            else:
                parent[page_grade]=pdf_out.addBookmark(page_text,page_num-1,parent = parent[page_grade-1])

    outpath = pdfpath[:-4]+'_bm.pdf'
    with open(outpath,'wb') as fout:
        pdf_out.write(fout)

if __name__ == '__main__':
    txtpath = "D:/1_目录.txt"
    pdfpath = "D:/1.pdf"
    add_bookmarks(txtpath,pdfpath)

优化程序

接下来让程序自动的读取当前python脚本文件路径下的pdf文件和txt书签,然后匹配书签后再自动添加书签,避免手动输入路径的繁琐。
(未完待续)

参考

用Python为PDF文件批量添加书签 - 简书
用python合并pdf,并添加书签_BlowfishKing的博客-CSDN博客
python 反向查找_weixin_30596735的博客-CSDN博客
【python】读取和输出到txt_flora-CSDN博客
python遍历目录下所有文件 - 努力奋斗小青年 - 博客园

posted @ 2021-08-25 18:47  逸笔  阅读(1892)  评论(0编辑  收藏  举报