1. 程式人生 > 其它 >利用python給pdf新增目錄

利用python給pdf新增目錄

利用tampermonkey的知網下載助手指令碼下載pdf格式論文時,發現論文缺少書籤,而指令碼可以下載一個txt格式的書籤(目錄),因此打算利用python將txt格式的目錄新增到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遍歷目錄下所有檔案 - 努力奮鬥小青年 - 部落格園