Python:使用pypdf2合并、分割、加密pdf檔案。__Python

來源:互聯網
上載者:User

朋友需要對一個pdf檔案進行分割,在網上查了查發現這個pypdf2可以完成這些操作,所以就研究了下這個庫,並做一些記錄。首先pypdf2是python3版本的,在之前的2版本有一個對應pypdf庫。

可以使用pip直接安裝:

pip install pypdf2

官方文檔:https://pythonhosted.org/PyPDF2/

裡面主要有這幾個類:

PdfFileReader 。

該類主要提供了對pdf檔案的讀操作,其構造方法為:

PdfFileReader(stream, strict=True, warndest=None, overwriteWarnings=True)

第一個參數可以傳入一個檔案流,或者一個檔案路徑。後面三個參數都是用來設定警告的處理方式,直接使用預設的即可。

得到執行個體之後,就可以對pdf進行一些操作了。主要的有以下幾個操作:

decrypt(password):如果pdf檔案加密的話,可以使用該方法對其解密。

getDocumentInfo():檢索pdf檔案的一些資訊。其傳回值為一個DocumentInformation 類型,直接輸出的話會得到類似下面的資訊:

{‘/ModDate’: “D:20150310202949-07’00’”, ‘/Title’: ”, ‘/Creator’: ‘LaTeX with hyperref package’, ‘/CreationDate’: “D:20150310202949-07’00’”, ‘/PTEX.Fullbanner’: ‘This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014/MacPorts 2014_6) kpathsea version 6.2.0’, ‘/Producer’: ‘pdfTeX-1.40.15’, ‘/Keywords’: ”, ‘/Trapped’: ‘/False’, ‘/Author’: ”, ‘/Subject’: ”}

getNumPages():這個會pdf檔案中的頁數。

getPage(pageNumber):會得到pdf檔案中對應的pageNumber頁數的頁面對象,傳回值為PageObject執行個體。在得到PageObject執行個體之後就可以將其加添、插入等操作。

getPageNumber(page):與上面的方法對立,可以傳入PageObject執行個體,然後得到該執行個體是pdf檔案中第幾頁的。

getOutlines(node=None, outlines=None):檢索文檔中出現的文檔大綱。

isEncrypted:記錄該pdf是否加密。如果檔案本身加密,即使在使用解密decrypt方法之後,還是會返回true。

numPages:pdf總共的頁數,相當於訪問getNumPages()的唯讀屬性。 PdfFileWriter 。

該類支援對pdf檔案進行寫操作,通常是使用PdfFileReader讀取一些pdf資料,然後使用該類進行一些操作。

建立該類的執行個體時不需要參數。

其主要的方法有:

addAttachment(fname, fdata):向pdf添加檔案。

addBlankPage(width=None, height=None):給pdf添加一個空白頁到最後,如果沒有指定大小就使用當前Weiter中pdf最後一頁的大小。

addPage(page):添加page到pdf中,通常這個page是由上面的Reader擷取的。

appendPagesFromReader(reader, after_page_append=None):將reader中的資料拷貝到當前的Writer執行個體中,並且如果指定after_page_append的話,最後還有回掉該函數並且將writer中的資料傳入其中。

encrypt(user_pwd, owner_pwd=None, use_128bit=True):將pdf進行加密,其中官方說userpwd是允許使用者使用一些限制的許可權開啟pdf檔案,也就是使用該密碼的話可能會有一些限制,但是本人並沒有在文檔中找到設定許可權的內容。而ownerpwd則是允許使用者無限制的使用。第三個參數是是否使用128位加密。

getNumPages():得到pdf頁數。

getPage(pageNumber):得到對應頁數的Page,是一個PageObject對象,可以使用上面的addPage方法將page進行添加。

insertPage(page, index=0):將page添加到pdf中,index指定的是被插入的位置。

write(stream):將該Writer中的內容寫入到檔案中。 PdfFileMerger。

該類用來合并pdf檔案,該類的構造方法有一個參數:PdfFileMerger(strict=True),注意這裡的參數後面會介紹:

常用方法:

addBookmark(title, pagenum, parent=None):給pdf添加一個書籤,title是書籤的標題,pagenum是該書籤指向的頁面。

append(fileobj, bookmark=None, pages=None, import_bookmarks=True):將指定的fileobj檔案添加到檔案的末尾,bookmark是贖前,pages可以使用(start, stop[, step])或者一個 Page Range來設定將fileobj中的指定範圍的頁面進行添加。

merge(position, fileobj, bookmark=None, pages=None, import_bookmarks=True):與append方法類似,不過可以使用position參數指定添加的位置。

write(fileobj):將資料寫入到檔案中。

使用的時候可以建立一個PdfFileMerger執行個體,然後使用append或者merge將想要融合的pdf檔案依次添加進去,最後使用write儲存即可。

def merge_pdf():    # 建立一個用來合并檔案的執行個體    pdf_merger = PdfFileMerger()    # 首先添加一個Week1_1.pdf檔案    pdf_merger.append('Week1_1.pdf')    # 然後在第0頁後面添加ex1.pdf檔案    pdf_merger.merge(0, 'ex1.pdf')    # 添加書籤    pdf_merger.addBookmark('這是一個書籤', 1)    # 將其寫入到檔案中    pdf_merger.write('merge_pdf.pdf')

下面看一下PdfFileMerger(strict=True)中的這個參數:

官方對這個參數的解釋:

strict (bool) – Determines whether user should be warned of all problems and also causes some correctable problems to be fatal. Defaults to True.

確定是否應該警告使用者所有問題,並且還會導致一些可糾正的問題。

剛開始感覺這個參數就是用來是否警告使用者一些錯誤的,直接使用預設即可,但是當本人嘗試合并帶中文的pdf時,出現了如下錯誤:

Traceback (most recent call last):  File "I:\python3.5\lib\site-packages\PyPDF2\generic.py", line 484, in readFromStream    return NameObject(name.decode('utf-8'))UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc8 in position 10: invalid continuation byteDuring handling of the above exception, another exception occurred:PyPDF2.utils.PdfReadError: Illegal character in Name Object

在源碼包中使用utf解碼的時候出錯了,嘗試修改此處源碼,讓其使用gbk,但是還出現了其他的錯誤。最後發現當把建構函式中的strict設定為False時,控制台會列印下面的錯誤:

PdfReadWarning: Illegal character in Name Object [generic.py:489]

但是兩個檔案成功的合并了,並且大概看了下合并後的檔案有時好又是壞,同樣的代碼運行多次,有時候能夠正常處理中文,但有時候中文亂碼。

除了列出的方法還有一些其他的方法,比如添加書籤、添加連結等等,可以參考官方文檔。 對pdf進行合并、分割、加密。

整合出來了加密、解密、合并、根據頁數進行分割、根據份數進行分割的範例:

使用注意:如果時中文檔案,運行結果可能會出現亂碼,但是多運行幾次,中間有正常顯示中文的問題。具體原因還不清楚,但就是這麼玄學。。。

代碼傳送門

# @Time    : 2018/3/26 23:48# @Author  : Leafage# @File    : handlePDF.py# @Software: PyCharm# @Describe: 對pdf檔案執行合并、分割、加密操作。from PyPDF2 import PdfFileReader, PdfFileMerger, PdfFileWriterdef get_reader(filename, password):    try:        old_file = open(filename, 'rb')    except IOError as err:        print('檔案開啟失敗。' + str(err))        return None    # 建立讀執行個體    pdf_reader = PdfFileReader(old_file, strict=False)    # 解密操作    if pdf_reader.isEncrypted:        if password is None:            print('%s檔案被加密,要求輸入密碼。' % filename)            return None        else:            if pdf_reader.decrypt(password) != 1:                print('%s密碼不正確。' % filename)                return None    if old_file in locals():        old_file.close()    return pdf_readerdef encrypt_pdf(filename, new_password, old_password=None, encrypted_filename=None):    """    對filename所對應的檔案進行加密,並產生一個新的檔案    :param filename: 檔案對應的路徑    :param new_password: 對檔案加密使用的密碼    :param old_password: 如果舊檔案進行了加密,要求輸入密碼    :param encrypted_filename: 加密之後的檔案名稱,省卻時使用filename_encrypted;    :return:    """    # 建立一個Reader執行個體    pdf_reader = get_reader(filename, old_password)    if pdf_reader is None:        return    # 建立一個寫操作的執行個體    pdf_writer = PdfFileWriter()    # 從之前Reader中將資料寫入到Writer中    pdf_writer.appendPagesFromReader(pdf_reader)    # 重新使用新密碼加密    pdf_writer.encrypt(new_password)    if encrypted_filename is None:        # 使用舊檔案名稱 + encrypted 作為新的檔案名稱        encrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'encrypted' + '.pdf'    pdf_writer.write(open(encrypted_filename, 'wb'))def decrypt_pdf(filename, password, decrypted_filename=None):    """    將加密的檔案及逆行解密,並產生一個無需密碼pdf檔案    :param filename: 原先加密的pdf檔案    :param password: 對應的密碼    :param decrypted_filename: 解密之後的檔案名稱    :return:    """    # 產生一個Reader和Writer    pdf_reader = get_reader(filename, password)    if pdf_reader is None:        return    if not pdf_reader.isEncrypted:        print('檔案沒有被加密,無需操作。')        return    pdf_writer = PdfFileWriter()    pdf_writer.appendPagesFromReader(pdf_reader)    if decrypted_filename is None:        decrypted_filename = "".join(filename.split('.')[:-1]) + '_' + 'decrypted' + '.pdf'    # 寫入新檔案    pdf_writer.write(open(decrypted_filename, 'wb'))def split_by_pages(filename, pages, password=None):    """    將檔案按照頁數進行平均分割    :param filename: 所要分割的檔案名稱    :param pages: 分割之後每個檔案對應的頁數    :param password: 如果檔案加密,需要進行解密操作    :return:    """    # 得到Reader    pdf_reader = get_reader(filename, password)    if pdf_reader is None:        return    # 得到總的頁數    pages_nums = pdf_reader.numPages    if pages <= 1:        print('每份檔案必須大於1頁。')        return    # 得到切分之後每個pdf檔案的頁數    pdf_num = pages_nums // pages + 1 if pages_nums % pages else int(pages_nums / pages)    print('pdf檔案被分為%d份,每份有%d頁。' % (pdf_num, pages))    # 依次產生pdf檔案    for cur_pdf_num in range(1, pdf_num + 1):        # 建立一個新的寫執行個體        pdf_writer = PdfFileWriter()        # 產生對應的檔案名稱        split_pdf_name = "".join(filename)[:-1] + '_' + str(cur_pdf_num) + '.pdf'        # 計算出當前開始的位置        start = pages * (cur_pdf_num - 1)        # 計算出結束的位置,如果是最後一份就直接返回最後的頁數,否則用每份頁數*已經分好的檔案數        end = pages * cur_pdf_num if cur_pdf_num != pdf_num else pages_nums        # print(str(start) + ',' + str(end))        # 依次讀取對應的頁數        for i in range(start, end):            pdf_writer.addPage(pdf_reader.getPage(i))        # 寫入檔案        pdf_writer.write(open(split_pdf_name, 'wb'))def split_by_num(filename, nums, password=None):    """    將pdf檔案分為nums份    :param filename: 檔案名稱    :param nums: 要分成的份數    :param password: 如果需要解密,輸入密碼    :return:    """    pdf_reader = get_reader(filename, password)    if not pdf_reader:        return    if nums < 2:        print('份數不能小於2。')        return    # 得到pdf的總頁數    pages = pdf_reader.numPages    if pages < nums:        print('份數不應該大於pdf總頁數。')        return    # 計算每份應該有多少頁    each_pdf = pages // nums    print('pdf共有%d頁,分為%d份,每份有%d頁。' % (pages, nums, each_pdf))    for num in range(1, nums + 1):        pdf_writer = PdfFileWriter()        # 產生對應的檔案名稱        split_pdf_name = "".join(filename)[:-1] + '_' + str(num) + '.pdf'        # 計算出當前開始的位置        start = each_pdf * (num - 1)        # 計算出結束的位置,如果是最後一份就直接返回最後的頁數,否則用每份頁數*已經分好的檔案數        end = each_pdf * num if num != nums else pages        print(str(start) + ',' + str(end))        for i in range(start, end):            pdf_writer.addPage(pdf_reader.getPage(i))        pdf_writer.write(open(split_pdf_name, 'wb'))def merger_pdf(filenames, merged_name, passwords=None):    """    傳進來一個檔案清單,將其依次融合起來    :param filenames: 檔案清單    :param passwords: 對應的密碼列表    :return:    """    # 計算共有多少檔案    filenums = len(filenames)    # 注意需要使用False 參數    pdf_merger = PdfFileMerger(False)    for i in range(filenums):        # 得到密碼        if passwords is None:            password = None        else:            password = passwords[i]        pdf_reader = get_reader(filenames[i], password)        if not pdf_reader:            return        # append預設添加到最後        pdf_merger.append(pdf_reader)    pdf_merger.write(open(merged_name, 'wb'))def insert_pdf(pdf1, pdf2, insert_num, merged_name, password1=None, password2=None):    """    將pdf2全部檔案插入到pdf1中第insert_num頁    :param pdf1: pdf1檔案名稱    :param pdf2: pdf2檔案名稱    :param insert_num: 插入的頁數    :param merged_name: 融合後的檔案名稱    :param password1: pdf1對應的密碼    :param password2: pdf2對應的密碼    :return:    """    pdf1_reader = get_reader(pdf1, password1)    pdf2_reader = get_reader(pdf2, password2)    # 如果有一個打不開就返回    if not pdf1_reader or not pdf2_reader:        return    # 得到pdf1的總頁數    pdf1_pages = pdf1_reader.numPages    if insert_num < 0 or insert_num > pdf1_pages:        print('插入位置異常,想要插入的頁數為:%d,pdf1檔案共有:%d頁。' % (insert_num, pdf1_pages))        return    # 注意需要使用False參數,可能會出現中文亂碼的情況    m_pdf = PdfFileMerger(False)    m_pdf.append(pdf1)    m_pdf.merge(insert_num, pdf2)    m_pdf.write(open(merged_name, 'wb'))if __name__ == '__main__':    # encrypt_pdf('ex1.pdf', 'leafage')    # decrypt_pdf('ex1123_encrypted.pdf', 'leafage')    # split_by_pages('ex1.pdf', 5)    split_by_num('ex2.pdf', 3)    # merger_pdf(['ex1.pdf', 'ex2.pdf'], 'merger.pdf')    # insert_pdf('ex1.pdf', 'ex2.pdf', 10, 'pdf12.pdf')
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.