深刻學習Python解析並解密PDF文件內容的方法

  前面學習瞭解析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介紹

  PyPDF2是源自pyPdf項目的純python PDF工具包。它目前由Phaseit,Inc。維護。PyPDF2能夠從PDF文件中提取數據,或者操縱現有的PDF來生成新文件。PyPDF2與Python版本2.6,2.7和3.2 - 3.5兼容。app

做爲PDF工具包構建的Pure-Python庫。它可以:函數

  • 提取文檔信息(標題,做者,......)
  • 逐頁拆分文檔
  • 逐頁合併文檔
  • 裁剪頁面
  • 將多個頁面合併爲一個頁面
  • 加密和解密PDF文件

  經過Pure-Python,它應該在任何Python平臺上運行,而不依賴於外部庫。它也能夠徹底在StringIO對象而不是文件流上工做,容許在內存中進行PDF操做。所以,它是管理或操做PDF的網站的有用工具。工具

  而本文主要學習加密解密PDF文件。佈局

二:PyPDF2安裝

2.1 下載

  在https://pypi.org/project/PyPDF2/ 中搜索PyPDF2 1.26.0能夠安裝包。

2.2  在Linux安裝壓縮包命令以下:

cd /data  && tar -xvf  PyPDF2-1.26.0.tar.gz

cd PyPDF2-1.26.0

python setup.py install

 

2.3 直接安裝

pip install pypdf2

 2.4   PyPDF的官方文檔:https://pythonhosted.org/PyPDF2/

 

三:PyPDF 的使用目的

  首先 我這裏有一個加密的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的理論介紹

  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)

 

五 :PdfFileReader類

class PyPDF2.PdfFileReader(stream,strict = True,warndest = None,
overwriteWarnings = True )

 

初始化PdfFileReader對象。此操做可能須要一些時間,由於PDF流的交叉引用表被讀入內存。

參數:

stream - File對象或支持相似於File對象的標準讀取和搜索方法的對象。也能夠是表示PDF文件路徑的字符串。

  • strictbool) - 肯定是否應該警告用戶全部問題而且還會致使一些可糾正的問題致命。默認爲True
  • warndest - 記錄警告的目的地(默認爲 sys.stderr)。
  • overwriteWarningsbool) - 肯定是否warnings.py使用自定義實現覆蓋Python的 模塊(默認爲 True)。

decrypt(密碼)

  使用帶有PDF標準加密處理程序的加密/安全PDF文件時,此功能將容許解密文件。它根據文檔的用戶密碼和全部者密碼檢查給定的密碼,若是密碼正確,則存儲生成的解密密鑰。

  哪一個密碼匹配可有可無。兩個密碼都提供了正確的解密密鑰,容許文檔與此庫一塊兒使用。

       參數:password(str) 要匹配的密碼

  返回0若是密碼失敗,1密碼是否與用戶密碼匹配,密碼2是否與全部者密碼匹配。

  返回類型: INT

  引起NotImplementedError:若是文檔使用不受支持的加密方法。

documentInfo

  訪問給定Destination對象的頁碼

getDestinationPageNumber(destination)

  檢索PDF文件的文檔信息字典(若是存在)。請注意,某些PDF文件使用元數據流而不是docinfo詞典,此功能不會訪問這些元數據流。

  返回:頁碼或者若是找不到頁面的話 則爲-1

  返回類型:INT

getDocumentInfo()

  檢索PDF文件的文檔信息字典(若是存在)。請注意,某些PDF文件使用元數據流而不是docinfo詞典,此功能不會訪問這些元數據流。

  返回:該PDF文件的文檔信息

  返回類型DocumentInformation或者None若是不存在。

getFieldstree = Noneretval = Nonefileobj = None 

  若是此PDF包括交互式表單字段,則提取字段數據,該樹和retval的參數是遞歸使用。

  參數:fileobj  用於在找到的全部交互式表單字段上寫入報告的文件對象(一般是文本文件)

  返回:一個字典,其中每一個鍵是一個字段名稱,每一個值都是一個個Field對象。默認狀況下,映射名稱用於鍵。

  返回類型:dict  或者None沒法找到表單數據。

getFormTextFields()

  使用文本數據從文檔中檢索表單域(輸入,下拉列表)

getNameDestinations(tree=None,retval=None)

  檢索文檔中存在的指定目標

  返回:將名稱映射到的字典 Destinations

  返回類型:字典

getNumPages()

  計算此PDF文件中的頁面。

  返回:頁面

  返回類型:INT

  引起PDFReadError:若是文件已加密且限制阻止此操做。

getOutlines(node=None,outlines=None)

  檢查文檔中存在的文檔大綱。

  返回:一個PageObject實例。

  返回類型PageObject

getPageLayout()

  獲取頁面佈局,有關setPageLayout() 有效佈局的說明,請參閱參考資料。

  返回:目前正在使用的頁面佈局

  返回類型:str None若是沒有指定。

getPageMode()

  獲取頁面佈局,有關setPageMode() 有效模式的說明,請參閱。

  返回:目前正在使用的頁面模式。

  返回類型strNone若是沒有指定。

getPageNumber()

  檢索給定PageObject的頁面。

  參數:page(PageObject) - 獲取頁碼的頁面。應該是一個實例PageObject

  返回:頁碼或若是找不到頁面,則爲-1

  返回類型:INT

getXmpMetadata()

  從PDF文檔跟目錄中檢索XMP(可擴展元數據平臺)數據。

  返回XmpInformation 可用於從文檔訪問XMP元數據的實例

  返回類型XmpInformation或者 None若是在文檔根目錄中未找到元數據。

isEncrypted

  只讀布爾屬性,顯示此PDF文件是否已加密。請注意,即便decrypt()調用該方法,此屬性(若是爲true)仍將保持爲true 。

namedDestinations

  訪問該getNamedDestinations()函數的只讀屬性 。

numPages

  訪問該getNumPages()函數的只讀屬性 。

outlines

  只讀屬性訪問 getOutlines() 功能。

pageLayout

  訪問該getPageLayout()方法的只讀屬性 。

pageMode

  訪問該getPageMode()方法的只讀屬性 。

pages

  只讀屬性,它根據getNumPages()和 getPage()方法模擬列表 。

xmpMetadata

訪問該getXmpMetadata()函數的只讀屬性 。

PDF讀取操做:

# 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)

 

 六: PDFFileWriter類

  這個類支持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 文件

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'))

 PageObject類

class PyPDF2.pdf.PageObject(pdf = None,indirectRef = None )

 

此類表示 PDF 文件中的單個頁面,一般這個對象是經過訪問 PdfFileReader 對象的 getPage() 方法來獲得的,也可使用 createBlankPage() 靜態方法建立一個空的頁面。

參數:

  • pdf : 頁面所屬的 PDF 文件。
  • indirectRef:將源對象的原始間接引用存儲在其源 PDF 中。

PageObject 對象的屬性和方法

屬性或方法 描述
static createBlankPage(pdf=None,width=None,height=None) 返回一個新的空白頁面
extractText() 找到全部文本繪圖命令,按照他們在內容流中提供的順序,並提取文本
getContents() 訪問頁面內容,返回 Contents 對象或 None
rotateClockwise(angle) 順時針旋轉 90 度
scale(sx,sy) 經過向其內容應用轉換矩陣並更新頁面大小

粗略讀取PDF文本內容

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
相關文章
相關標籤/搜索