前面學習瞭解析PDF文檔,並寫入文檔的知識,那篇文章的名字爲深刻學習Python解析並讀取PDF文件內容的方法。javascript
連接以下:https://www.cnblogs.com/wj-1314/p/9429816.html html
可是最近出現了一個新問題,就是上面使用pdfminer這個庫只能解析正常的PDF內容,然而在實際狀況中,公司的一些文檔多是加密的,那麼如何處理加密的PDF文件,就是本文學習的重點。java
在網上查找資料,發現pypdf2能夠實現對pdf文件進行加密,解密,因此就學習了一下這個庫,並留下筆記。node
首先說明pypdf2是Python3版本的,在以前的Python2版本有一個對應的pypdf庫,可是本文下載了pypdf2這個庫,在Python2 運行時沒有報錯的。python
注意:全部修改操做均沒法再原文件中操做,只能將修改的結果寫入新文件中。安全
PyPDF2是源自pyPdf項目的純python PDF工具包。它目前由Phaseit,Inc。維護。PyPDF2能夠從PDF文件中提取數據,或者操縱現有的PDF來生成新文件。PyPDF2與Python版本2.6,2.7和3.2 - 3.5兼容。app
做爲PDF工具包構建的Pure-Python庫。它可以:函數
經過Pure-Python,它應該在任何Python平臺上運行,而不依賴於外部庫。它也能夠徹底在StringIO對象而不是文件流上工做,容許在內存中進行PDF操做。所以,它是管理或操做PDF的網站的有用工具。工具
而本文主要學習加密解密PDF文件。佈局
在https://pypi.org/project/PyPDF2/ 中搜索PyPDF2 1.26.0能夠安裝包。
cd /data && tar -xvf PyPDF2-1.26.0.tar.gz cd PyPDF2-1.26.0 python setup.py install
pip install pypdf2
首先 我這裏有一個加密的PDF文件:
那麼我使用上一篇文章的代碼(以下):
#coding:utf-8 import importlib import sys import time importlib.reload(sys) time1 = time.time() from pdfminer.pdfparser import PDFParser, PDFDocument from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter from pdfminer.converter import PDFPageAggregator from pdfminer.layout import LTTextBoxHorizontal, LAParams from pdfminer.pdfinterp import PDFTextExtractionNotAllowed text_path = r'5b931164edc09a226b3a12c4.pdf' def parse(): '''解析PDF文本,並保存到TXT文件中''' fp = open(text_path, 'rb') # 用文件對象建立一個PDF文檔分析器 parser = PDFParser(fp) # 建立一個PDF文檔 doc = PDFDocument() # 鏈接分析器,與文檔對象 parser.set_document(doc) doc.set_parser(parser) # 提供初始化密碼,若是沒有密碼,就建立一個空的字符串 doc.initialize() # 檢測文檔是否提供txt轉換,不提供就忽略 if not doc.is_extractable: raise PDFTextExtractionNotAllowed else: # 建立PDF,資源管理器,來共享資源 rsrcmgr = PDFResourceManager() # 建立一個PDF設備對象 laparams = LAParams() device = PDFPageAggregator(rsrcmgr, laparams=laparams) # 建立一個PDF解釋其對象 interpreter = PDFPageInterpreter(rsrcmgr, device) # 循環遍歷列表,每次處理一個page內容 # doc.get_pages() 獲取page列表 for page in doc.get_pages(): interpreter.process_page(page) # 接受該頁面的LTPage對象 layout = device.get_result() # 這裏layout是一個LTPage對象 裏面存放着 這個page解析出的各類對象 # 通常包括LTTextBox, LTFigure, LTImage, LTTextBoxHorizontal 等等 # 想要獲取文本就得到對象的text屬性, for x in layout: if (isinstance(x, LTTextBoxHorizontal)): with open(r'2.txt', 'a') as f: results = x.get_text() print(results) f.write(results + "\n") if __name__ == '__main__': parse() time2 = time.time() print("總共消耗時間爲:", time2 - time1)
解析的時候,會主動觸發異常(以下):
那麼,打開文件,咱們會發現,實際狀況是這樣的:
既然文件已經加密,那麼正常渠道解析,確定會觸發異常,因此此時的重中之重就是解密PDF文件,而後再去解析便可。
如何解密呢? 話很少說,直接看代碼。
若是不知道密碼,最好設置爲空,這樣的話 大多數就能夠解析,代碼以下:
# coding:utf-8 import os from PyPDF2 import PdfFileReader from PyPDF2 import PdfFileWriter def get_reader(filename, password): try: old_file = open(filename, 'rb') print('run jiemi1') except Exception 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_reader def decrypt_pdf(filename, password, decrypted_filename=None): """ 將加密的文件及逆行解密,並生成一個無需密碼pdf文件 :param filename: 原先加密的pdf文件 :param password: 對應的密碼 :param decrypted_filename: 解密以後的文件名 :return: """ # 生成一個Reader和Writer print('run jiemi') 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')) decrypt_pdf(r'5b931164edc09a226b3a12c4.pdf', '')
運行結果以下:
新生成的文件以下:
打開是這樣的:
因此 ,這樣的話 就能夠打開了,也能夠解析了,下面繼續使用PDF解析文件解析,代碼是上面的,結果以下:
解析成功,那麼會保存爲txt格式。
可是這裏要注意,我給解密的代碼,把密碼設置爲abc,以下:
那麼會觸發異常,代碼結果表示以下:
# coding:utf-8 import os from PyPDF2 import PdfFileReader from PyPDF2 import PdfFileWriter def get_reader(filename, password): try: old_file = open(filename, 'rb') print('run jiemi1') except Exception 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_reader def decrypt_pdf(filename, password, decrypted_filename=None): """ 將加密的文件及逆行解密,並生成一個無需密碼pdf文件 :param filename: 原先加密的pdf文件 :param password: 對應的密碼 :param decrypted_filename: 解密以後的文件名 :return: """ # 生成一個Reader和Writer print('run jiemi') 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')) decrypt_pdf(r'5b931164edc09a226b3a12c4.pdf', 'abc')
PyPDF2 包含了 PdfFileReader PdfFileMerger PageObject PdfFileWriter 四個經常使用的主要 Class。
具體分析:
PyPDF2 將讀與寫分紅兩個類來操做:
from PyPDF2 import PdfFileWriter, PdfFileReader writer = PdfFileWriter() reader = PdfFileReader(open("document1.pdf", "rb"))
官方實例:
from PyPDF2 import PdfFileWriter, PdfFileReader output = PdfFileWriter() input1 = PdfFileReader(open("document1.pdf", "rb")) # print how many pages input1 has: print "document1.pdf has %d pages." % input1.getNumPages() # add page 1 from input1 to output document, unchanged output.addPage(input1.getPage(0)) # add page 2 from input1, but rotated clockwise 90 degrees output.addPage(input1.getPage(1).rotateClockwise(90)) # add page 3 from input1, rotated the other way: output.addPage(input1.getPage(2).rotateCounterClockwise(90)) # alt: output.addPage(input1.getPage(2).rotateClockwise(270)) # add page 4 from input1, but first add a watermark from another PDF: page4 = input1.getPage(3) watermark = PdfFileReader(open("watermark.pdf", "rb")) page4.mergePage(watermark.getPage(0)) output.addPage(page4) # add page 5 from input1, but crop it to half size: page5 = input1.getPage(4) page5.mediaBox.upperRight = ( page5.mediaBox.getUpperRight_x() / 2, page5.mediaBox.getUpperRight_y() / 2 ) output.addPage(page5) # add some Javascript to launch the print window on opening this PDF. # the password dialog may prevent the print dialog from being shown, # comment the the encription lines, if that's the case, to try this out output.addJS("this.print({bUI:true,bSilent:false,bShrinkToFit:true});") # encrypt your new PDF and add a password password = "secret" output.encrypt(password) # finally, write "output" to document-output.pdf outputStream = file("PyPDF2-output.pdf", "wb") output.write(outputStream)
class PyPDF2.PdfFileReader(stream,strict = True,warndest = None, overwriteWarnings = True )
初始化PdfFileReader對象。此操做可能須要一些時間,由於PDF流的交叉引用表被讀入內存。
stream - File對象或支持相似於File對象的標準讀取和搜索方法的對象。也能夠是表示PDF文件路徑的字符串。
True
。sys.stderr
)。warnings.py
使用自定義實現覆蓋Python的 模塊(默認爲 True
)。使用帶有PDF標準加密處理程序的加密/安全PDF文件時,此功能將容許解密文件。它根據文檔的用戶密碼和全部者密碼檢查給定的密碼,若是密碼正確,則存儲生成的解密密鑰。
哪一個密碼匹配可有可無。兩個密碼都提供了正確的解密密鑰,容許文檔與此庫一塊兒使用。
參數:password(str) 要匹配的密碼
返回:0
若是密碼失敗,1
密碼是否與用戶密碼匹配,密碼2
是否與全部者密碼匹配。
返回類型: INT
引起NotImplementedError:若是文檔使用不受支持的加密方法。
訪問給定Destination對象的頁碼
檢索PDF文件的文檔信息字典(若是存在)。請注意,某些PDF文件使用元數據流而不是docinfo詞典,此功能不會訪問這些元數據流。
返回:頁碼或者若是找不到頁面的話 則爲-1
返回類型:INT
檢索PDF文件的文檔信息字典(若是存在)。請注意,某些PDF文件使用元數據流而不是docinfo詞典,此功能不會訪問這些元數據流。
返回:該PDF文件的文檔信息
返回類型:DocumentInformation
或者None
若是不存在。
getFields
(tree = None,retval = None,fileobj = None )若是此PDF包括交互式表單字段,則提取字段數據,該樹和retval的參數是遞歸使用。
參數:fileobj 用於在找到的全部交互式表單字段上寫入報告的文件對象(一般是文本文件)
返回:一個字典,其中每一個鍵是一個字段名稱,每一個值都是一個個Field
對象。默認狀況下,映射名稱用於鍵。
返回類型:dict 或者None沒法找到表單數據。
使用文本數據從文檔中檢索表單域(輸入,下拉列表)
檢索文檔中存在的指定目標
返回:將名稱映射到的字典 Destinations
。
返回類型:字典
計算此PDF文件中的頁面。
返回:頁面
返回類型:INT
引起PDFReadError:若是文件已加密且限制阻止此操做。
檢查文檔中存在的文檔大綱。
返回:一個PageObject
實例。
返回類型:PageObject
獲取頁面佈局,有關setPageLayout()
有效佈局的說明,請參閱參考資料。
返回:目前正在使用的頁面佈局
返回類型:str None若是沒有指定。
獲取頁面佈局,有關setPageMode()
有效模式的說明,請參閱。
返回:目前正在使用的頁面模式。
返回類型:str
,None
若是沒有指定。
檢索給定PageObject的頁面。
參數:page(PageObject) - 獲取頁碼的頁面。應該是一個實例PageObject
返回:頁碼或若是找不到頁面,則爲-1
返回類型:INT
從PDF文檔跟目錄中檢索XMP(可擴展元數據平臺)數據。
返回:XmpInformation
可用於從文檔訪問XMP元數據的實例
返回類型:XmpInformation
或者 None
若是在文檔根目錄中未找到元數據。
只讀布爾屬性,顯示此PDF文件是否已加密。請注意,即便decrypt()
調用該方法,此屬性(若是爲true)仍將保持爲true 。
訪問該getNamedDestinations()
函數的只讀屬性 。
訪問該getNumPages()
函數的只讀屬性 。
getOutlines()
功能。
訪問該getPageLayout()
方法的只讀屬性 。
訪問該getPageMode()
方法的只讀屬性 。
只讀屬性,它根據getNumPages()
和 getPage()
方法模擬列表 。
訪問該getXmpMetadata()
函數的只讀屬性 。
# encoding:utf-8 from PyPDF2 import PdfFileReader, PdfFileWriter readFile = 'C:/ learn.pdf' # 獲取 PdfFileReader 對象 pdfFileReader = PdfFileReader(readFile) # 或者這個方式:pdfFileReader = PdfFileReader(open(readFile, 'rb')) # 獲取 PDF 文件的文檔信息 documentInfo = pdfFileReader.getDocumentInfo() print('documentInfo = %s' % documentInfo) # 獲取頁面佈局 pageLayout = pdfFileReader.getPageLayout() print('pageLayout = %s ' % pageLayout) # 獲取頁模式 pageMode = pdfFileReader.getPageMode() print('pageMode = %s' % pageMode) xmpMetadata = pdfFileReader.getXmpMetadata() print('xmpMetadata = %s ' % xmpMetadata) # 獲取 pdf 文件頁數 pageCount = pdfFileReader.getNumPages() print('pageCount = %s' % pageCount) for index in range(0, pageCount): # 返回指定頁編號的 pageObject pageObj = pdfFileReader.getPage(index) print('index = %d , pageObj = %s' % (index, type(pageObj))) # <class 'PyPDF2.pdf.PageObject'> # 獲取 pageObject 在 PDF 文檔中處於的頁碼 pageNumber = pdfFileReader.getPageNumber(pageObj) print('pageNumber = %s ' % pageNumber)
這個類支持PDF文件,給出其餘類生成的頁面。
屬性和方法 | 描述 |
---|---|
addAttachment(fname,fdata) | 在 PDF 中嵌入文件 |
addBlankPage(width= None,height=None) | 追加一個空白頁面到這個 PDF 文件並返回它 |
addBookmark(title,pagenum,parent=None, color=None,bold=False,italic=False,fit=’/fit,*args’) |
|
addJS(javascript) | 添加將在打開此 PDF 是啓動的 javascript |
addLink(pagenum,pagedest,rect,border=None,fit=’/fit’,*args) | 從一個矩形區域添加一個內部連接到指定的頁面 |
addPage(page) | 添加一個頁面到這個PDF 文件,該頁面一般從 PdfFileReader 實例獲取 |
getNumpages() | 頁數 |
getPage(pageNumber) | 從這個 PDF 文件中檢索一個編號的頁面 |
insertBlankPage(width=None,height=None,index=0) | 插入一個空白頁面到這個 PDF 文件並返回它,若是沒有指定頁面大小,就使用最後一頁的大小 |
insertPage(page,index=0) | 在這個 PDF 文件中插入一個頁面,該頁面一般從 PdfFileReader 實例獲取 |
removeLinks() | 從次數出中刪除鏈接盒註釋 |
removeText(ignoreByteStringObject = False) | 從這個輸出中刪除圖像 |
write(stream) | 將添加到此對象的頁面集合寫入 PDF 文件 |
def addBlankpage(): readFile = 'C:/study.pdf' outFile = 'C:/copy.pdf' pdfFileWriter = PdfFileWriter() # 獲取 PdfFileReader 對象 pdfFileReader = PdfFileReader(readFile) # 或者這個方式:pdfFileReader = PdfFileReader(open(readFile, 'rb')) numPages = pdfFileReader.getNumPages() for index in range(0, numPages): pageObj = pdfFileReader.getPage(index) pdfFileWriter.addPage(pageObj) # 根據每頁返回的 PageObject,寫入到文件 pdfFileWriter.write(open(outFile, 'wb')) pdfFileWriter.addBlankPage() # 在文件的最後一頁寫入一個空白頁,保存至文件中 pdfFileWriter.write(open(outFile,'wb'))
結果是:在寫入的 copy.pdf 文檔的最後最後一頁寫入了一個空白頁。
def splitPdf(): readFile = 'C:/learn.pdf' outFile = 'C://copy.pdf' pdfFileWriter = PdfFileWriter() # 獲取 PdfFileReader 對象 pdfFileReader = PdfFileReader(readFile) # 或者這個方式:pdfFileReader = PdfFileReader(open(readFile, 'rb')) # 文檔總頁數 numPages = pdfFileReader.getNumPages() if numPages > 5: # 從第五頁以後的頁面,輸出到一個新的文件中,即分割文檔 for index in range(5, numPages): pageObj = pdfFileReader.getPage(index) pdfFileWriter.addPage(pageObj) # 添加完每頁,再一塊兒保存至文件中 pdfFileWriter.write(open(outFile, 'wb'))
def mergePdf(inFileList, outFile): ''' 合併文檔 :param inFileList: 要合併的文檔的 list :param outFile: 合併後的輸出文件 :return: ''' pdfFileWriter = PdfFileWriter() for inFile in inFileList: # 依次循環打開要合併文件 pdfReader = PdfFileReader(open(inFile, 'rb')) numPages = pdfReader.getNumPages() for index in range(0, numPages): pageObj = pdfReader.getPage(index) pdfFileWriter.addPage(pageObj) # 最後,統一寫入到輸出文件中 pdfFileWriter.write(open(outFile, 'wb'))
class PyPDF2.pdf.PageObject(pdf = None,indirectRef = None )
此類表示 PDF 文件中的單個頁面,一般這個對象是經過訪問 PdfFileReader 對象的 getPage() 方法來獲得的,也可使用 createBlankPage() 靜態方法建立一個空的頁面。
參數:
屬性或方法 | 描述 |
---|---|
static createBlankPage(pdf=None,width=None,height=None) | 返回一個新的空白頁面 |
extractText() | 找到全部文本繪圖命令,按照他們在內容流中提供的順序,並提取文本 |
getContents() | 訪問頁面內容,返回 Contents 對象或 None |
rotateClockwise(angle) | 順時針旋轉 90 度 |
scale(sx,sy) | 經過向其內容應用轉換矩陣並更新頁面大小 |
def getPdfContent(filename): pdf = PdfFileReader(open(filename, "rb")) content = "" for i in range(0, pdf.getNumPages()): pageObj = pdf.getPage(i) extractedText = pageObj.extractText() content += extractedText + "\n" # return content.encode("ascii", "ignore") return content