从加密PDF中提取Python数据

17

我是一名纯数学专业的毕业生,只学过基础编程课程。我正在实习,并有一个内部数据分析项目。我必须分析去年的内部PDF文件。这些PDF文件是“安全的”,也就是加密的。我们没有密码,而且我们不确定是否存在密码。但是,我们拥有所有这些文档,我们可以手动阅读它们。我们也可以打印它们。目标是使用Python读取它们,因为那是我们了解的编程语言。

首先,我尝试使用一些Python库来读取PDF文件。然而,我找到的Python库无法读取加密的PDF文件。那时,我也无法使用Adobe Reader导出信息。

其次,我决定解密PDF文件。我使用了Python库pykepdf成功地解密了PDF文件。Pykepdf非常好用!然而,使用前面提到的Python库(PyPDF2和Tabula)仍然无法读取已经解密的PDF文件。此时,我们已经有所进步,因为使用Adobe Reader,我可以从解密后的PDF文件中导出信息,但是我们的目标是完全使用Python来做。

我展示的代码在未加密的PDF文件上运行得很好,但对于加密的PDF文件则不行。它也无法处理使用pykepdf解密的PDF文件。

我没有编写这段代码。我在Python库Pykepdf和Tabula的文档中找到了它。PyPDF2解决方案是由Al Sweigart在他的书籍《用Python自动化无聊的事情》中编写的,我强烈推荐这本书。我还检查了代码是否运行良好,但前面提到的限制仍然存在。

第一个问题, 为什么我不能读取解密文件,如果程序可以处理从未加密过的文件?

第二个问题, 我们能用Python以某种方式读取已解密的文件吗?哪个库可以做到或不可能?所有解密的PDF文件都可提取吗?

感谢您的时间和帮助!!!

我是在使用Python 3.7,Windows 10,Jupiter Notebooks和Anaconda 2019.07时找到这些结果的。

Python

import pikepdf
with pikepdf.open("encrypted.pdf") as pdf:
  num_pages = len(pdf.pages)
  del pdf.pages[-1]
  pdf.save("decrypted.pdf")

import tabula
tabula.read_pdf("decrypted.pdf", stream=True)

import PyPDF2
pdfFileObj=open("decrypted.pdf", "rb")
pdfReader=PyPDF2.PdfFileReader(pdfFileObj)
pdfReader.numPages
pageObj=pdfReader.getPage(0)
pageObj.extractText()

使用Tabula时,我收到了“输出文件为空”的消息。

使用PyPDF2时,我只得到了'/n'。

更新于2019年10月3日 Pdfminer.six(版本为2018年11月)

我使用DuckPuncher发布的解决方案得到了更好的结果。对于已解密的文件,我得到了标签,但没有数据。加密文件也是一样的情况。对于从未加密过的文件,效果很好。由于我需要加密或解密文件的数据和标签,因此这段代码对我不起作用。为了进行分析,我使用了pdfminer.six,它是一个Python库,于2018年11月发布。Pdfminer.six包括一个名为pycryptodome的库。根据他们的文档“PyCryptodome是一种自包含的低级密码基元的Python软件包。”

代码在堆栈交换问题中:

使用Python中的PDFMiner从PDF文件提取文本?

如果您愿意重复我的实验,我会很高兴。以下是描述:

1)使用任何从未加密过的PDF运行此问题中提到的代码。

2)对于一个“安全”的PDF(这是Adobe使用的术语),我称之为加密PDF。使用您可以在Google上找到的通用表格。下载后,您需要填写字段。否则,您将仅检查标签而不是字段。数据在字段中。

3)使用Pykepdf解密加密的PDF。这将是已解密的PDF。

4)再次使用已解密的PDF运行代码。

更新于2019年10月4日 骆驼(版本为2019年7月)

我发现了Python库Camelot。请注意,您需要camelot-py 0.7.3

它非常强大,并且适用于Python 3.7。而且,它非常容易使用。首先,您还需要安装Ghostscript。否则,它将无法正常工作。 您还需要安装Pandas不要使用pip install camelot-py。而是使用pip install camelot-py[cv]

程序的作者是Vinayak Mehta。 Frank Du在YouTube视频“使用Python从PDF提取表格数据的骆驼”中分享了这段代码。

我检查了代码,可以使用未加密的文件工作。但是,它无法处理加密和解密的文件,而这就是我的目标

Camelot面向从PDF中获取表格数据。

以下是代码:

Python

import camelot
import pandas
name_table = camelot.read_pdf("uncrypted.pdf")
type(name_table)

#This is a Pandas dataframe
name_table[0]

first_table = name_table[0]   

#Translate camelot table object to a pandas dataframe
first_table.df

first_table.to_excel("unencrypted.xlsx")
#This creates an excel file.
#Same can be done with csv, json, html, or sqlite.

#To get all the tables of the pdf you need to use this code.
for table in name_table:
   print(table.df)
更新于2019年10月7日 我找到了一个技巧。如果我使用Adobe Reader打开安全的PDF文件,然后使用Microsoft打印成PDF格式,并将其保存为PDF文件,我可以提取其中的数据。我还可以将PDF文件转换为JSON、Excel、SQLite、CSV、HTML和其他格式。这是解决我的问题的可能方法。然而,我仍在寻找一种不需要该技巧的选项,因为目标是只使用Python解决问题。我也担心,如果使用更好的加密方法,这个技巧可能不起作用。有时你需要多次使用Adobe Reader才能获得可提取的副本。

更新于2019年10月8日。第三个问题。 我现在有第三个问题。所有安全/加密的PDF文件都受密码保护吗?为什么pikepdf无法工作?我猜测当前版本的pikepdf可以破解某些类型的加密,但不是所有类型。 @constt提到PyPDF2可以破解某些类型的保护。然而,我回复他说,我发现一篇文章说PyPDF2可以破解使用Adobe Acrobat Pro 6.0进行加密的文件,但不能破解使用后续版本进行加密的文件。


2
我无法使用PyPDF2重现这些问题,一切都正常。我使用了pdftk以及在线服务来加密文件。你能否发布“有问题”的pdf文件的链接? - constt
1
@初学者 我猜测这可能与pykepdf使用的底层格式有关,用于编写未加密的PDF。 - Life is complex
2
所有安全/加密的pdf都需要密码保护吗?不是的。还有使用基于X509证书的公钥/私钥加密的pdf。 - mkl
1
更新于2019年10月7日--您能否提供更多有关如何通过Adobe Reader绕过安全PDF功能的技术细节?除非文件未启用保护,否则不应该出现这种情况。 - Life is complex
1
只是回答你的一个问题,确实可以加密PDF文件而不需要密码。这适用于具有功能限制(无打印、无重新保存、无编辑等)的PDF文件。 - yms
显示剩余8条评论
3个回答

12

最近更新于2019年10月11日

我不确定我完全理解您的问题。下面的代码可以进行优化,但它可以读取加密或未加密的PDF并提取文本。如果我误解了您的要求,请告诉我。

from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfpage import PDFPage
from io import StringIO

def extract_encrypted_pdf_text(path, encryption_true, decryption_password):

  output = StringIO()

  resource_manager = PDFResourceManager()
  laparams = LAParams()

  device = TextConverter(resource_manager, output, codec='utf-8', laparams=laparams)

  pdf_infile = open(path, 'rb')
  interpreter = PDFPageInterpreter(resource_manager, device)

  page_numbers = set()

  if encryption_true == False:
    for page in PDFPage.get_pages(pdf_infile, page_numbers, maxpages=0, caching=True, check_extractable=True):
      interpreter.process_page(page)

  elif encryption_true == True:
    for page in PDFPage.get_pages(pdf_infile, page_numbers, maxpages=0, password=decryption_password, caching=True, check_extractable=True):
      interpreter.process_page(page)

 text = output.getvalue()
 pdf_infile.close()
 device.close()
 output.close()
return text

results = extract_encrypted_pdf_text('encrypted.pdf', True, 'password')
print (results)

我注意到你的 pikepdf 代码用于打开加密的PDF时缺少密码,应该会出现以下错误信息:

pikepdf._qpdf.PasswordError: encrypted.pdf: invalid password

import pikepdf

with pikepdf.open("encrypted.pdf", password='password') as pdf:
num_pages = len(pdf.pages)
del pdf.pages[-1]
pdf.save("decrypted.pdf")

你可以使用{{tika}}从由{{pikepdf}}创建的解密.pdf中提取文本。
from tika import parser

parsedPDF = parser.from_file("decrypted.pdf")
pdf = parsedPDF["content"]
pdf = pdf.replace('\n\n', '\n')

另外,pikepdf目前未能实现文本提取,包括最新版本v1.6.4。
我决定使用各种加密的PDF文件进行测试。 我将所有加密文件命名为"encrypted.pdf",它们都使用相同的加密和解密密码。
  1. Adobe Acrobat 9.0及以上版本-加密等级256位AES

    • pikepdf能够解密此文件
    • PyPDF2无法正确提取文本
    • tika可以正确提取文本
  2. Adobe Acrobat 6.0及以上版本-加密等级128位RC4

    • pikepdf能够解密此文件
    • PyPDF2无法正确提取文本
    • tika可以正确提取文本
  3. Adobe Acrobat 3.0及以上版本-加密等级40位RC4

    • pikepdf能够解密此文件
    • PyPDF2无法正确提取文本
    • tika可以正确提取文本
  4. Adobe Acrobat 5.0及以上版本-加密等级128位RC4

    • 使用Microsoft Word创建
    • pikepdf能够解密此文件
    • PyPDF2可以正确提取文本
    • tika可以正确提取文本
  5. Adobe Acrobat 9.0及以上版本-加密等级256位AES

    • 使用pdfprotectfree创建
    • pikepdf能够解密此文件
    • PyPDF2可以正确提取文本
    • tika可以正确提取文本

PyPDF2能够从未使用Adobe Acrobat创建的解密PDF文件中提取文本。

我认为失败与Adobe Acrobat创建的PDF中嵌入的格式有关。需要进行更多测试以确认有关格式的猜测。

tika能够从使用pikepdf解密的所有文档中提取文本。


 import pikepdf
 with pikepdf.open("encrypted.pdf", password='password') as pdf:
    num_pages = len(pdf.pages)
    del pdf.pages[-1]
    pdf.save("decrypted.pdf")


 from PyPDF2 import PdfFileReader

 def text_extractor(path):
   with open(path, 'rb') as f:
     pdf = PdfFileReader(f)
     page = pdf.getPage(1)
     print('Page type: {}'.format(str(type(page))))
     text = page.extractText()
     print(text)

    text_extractor('decrypted.pdf')

PyPDF2无法解密Acrobat PDF文件 => 6.0

2015年9月15日以来,该问题已向模块所有者公开。与此问题相关的评论中并不清楚项目所有者何时会修复此问题。最后一次提交是在2018年6月25日。

PyPDF4解密问题

PyPDF4是PyPDF2的替代品。该模块也存在某些算法用于加密PDF文件的解密问题。

测试文件:Adobe Acrobat 9.0及更高版本-加密级别为256位AES

PyPDF2错误消息:仅支持算法代码1和2

PyPDF4错误消息:仅支持算法代码1和2。此PDF使用代码5


2019年10月11日更新

此部分是针对您于2019年10月7日和10月8日的更新。

在您的更新中,您提到可以使用Adobe Reader打开“安全pdf”并将文档打印到另一个PDF,从而删除“SECURED”标志。经过一些测试,我相信已经弄清楚了这种情况下发生了什么。

Adobe PDF的安全级别

Adobe PDF具有多种类型的安全控件,可以由文档的所有者启用。这些控件可以通过密码或证书进行强制实施。

  1. 文档加密(使用文档打开密码强制实施)

    • 加密所有文档内容(最常见)
    • 加密除元数据外的所有文档内容 => Acrobat 6.0
    • 仅加密文件附件 => Acrobat 7.0
  2. 限制编辑和打印(使用权限密码强制实施)

    • 允许打印
    • 允许更改

下图显示了一份采用256位AES加密的Adobe PDF文档。要打开或打印此PDF需要密码。当您在Adobe Reader中使用密码打开此文档时,标题将显示SECURED

password_level_encryption

这份文档需要密码才能使用本答案提到的Python模块打开。如果你试图使用Adobe Reader打开一个加密的PDF文件,你应该会看到这个:

password_prompt

如果您没有收到此警告,则文档未启用安全控件或仅启用了限制编辑和打印控件。
下面的图像显示在PDF文档中启用了带密码的限制编辑。请注意,已启用打印。要打开或打印此PDF,不需要密码。当您在Adobe Reader中无需密码打开此文档时,标题将显示已保护。这与使用密码打开的加密PDF相同。
将此文档打印到新的PDF时,已保护警告将被删除,因为已删除了限制编辑。

password_level_restrictive_editing

所有Adobe产品都会执行权限密码设置的限制。然而,如果第三方产品不支持这些设置,文档接收者可以绕过设置的一些或全部限制。

因此,我认为您要打印为PDF的文档启用了限制编辑,并且未启用需要密码才能打开的选项。

关于破解PDF加密

无论是PyPDF2还是PyPDF4,都不是为了破解PDF文档的打开密码功能而设计的。如果它们尝试打开一个受加密密码保护的PDF文件,则两个模块都会抛出以下错误。

PyPDF2.utils.PdfReadError: file has not been decrypted

加密PDF文件的打开密码功能可以通过多种方法绕过,但单个技术可能不起作用,有些方法由于包括密码复杂性在内的多种因素,可能不可接受。

PDF加密在内部使用40、128或256位的加密密钥,具体取决于PDF版本。二进制加密密钥来自用户提供的密码。密码受长度和编码约束。

例如,PDF 1.7 Adobe Extension Level 3(Acrobat 9 - AES-256)引入了Unicode字符(65,536个可能字符),并将密码的UTF-8表示中的最大长度提高到127个字节。


以下代码将打开启用了限制编辑的PDF文件。它将保存此文件到一个新的PDF文件中,而不会添加SECURED警告。{{tika}}代码将解析新文件中的内容。
from tika import parser
import pikepdf

# opens a PDF with restrictive editing enabled, but that still 
# allows printing.
with pikepdf.open("restrictive_editing_enabled.pdf") as pdf:
  pdf.save("restrictive_editing_removed.pdf")

  # plain text output
  parsedPDF = parser.from_file("restrictive_editing_removed.pdf")

  # XHTML output
  # parsedPDF = parser.from_file("restrictive_editing_removed.pdf", xmlContent=True)

  pdf = parsedPDF["content"]
  pdf = pdf.replace('\n\n', '\n')
  print (pdf)

这段代码检查文件是否需要密码才能打开。这段代码可以优化并添加其他功能。还有一些其他特性可以添加,但是pikepdf的文档与代码库中的注释不匹配,因此需要进行更多的研究以改进此问题。
# this would be removed once logging is used
############################################
import sys
sys.tracebacklimit = 0
############################################

import pikepdf
from tika import parser

def create_pdf_copy(pdf_file_name):
  with pikepdf.open(pdf_file_name) as pdf:
    new_filename = f'copy_{pdf_file_name}'
    pdf.save(new_filename)
    return  new_filename

def extract_pdf_content(pdf_file_name):
  # plain text output
  # parsedPDF = parser.from_file("restrictive_editing_removed.pdf")

  # XHTML output
  parsedPDF = parser.from_file(pdf_file_name, xmlContent=True)

  pdf = parsedPDF["content"]
  pdf = pdf.replace('\n\n', '\n')
  return pdf

def password_required(pdf_file_name):
  try:
    pikepdf.open(pdf_file_name)

  except pikepdf.PasswordError as error:
    return ('password required')

  except pikepdf.PdfError as results:
    return ('cannot open file')


filename = 'decrypted.pdf'
password = password_required(filename)
if password != None:
  print (password)
elif password == None:
  pdf_file = create_pdf_copy(filename)
  results = extract_pdf_content(pdf_file)
  print (results)

2
你如何在不提供密码的情况下打开安全的PDF文件? - Life is complex
1
你是指仅限制性编辑保护吗? - Life is complex
1
回答已更新,附上可用于启用了编辑限制保护但允许打印的PDF文件的代码。 - Life is complex
1
你能使用XHTML吗? - Life is complex
1
我修改了答案以输出XHTML。JSON也是可能的,但它需要深入挖掘与tika解析器相关的github项目代码。 - Life is complex
显示剩余7条评论

1
对于tabula-py,您可以尝试使用read_pdf选项进行密码保护。这取决于tabula-java的功能,因此我不确定支持哪种加密方式。

1

当您未输入密码时打开这些文件时,您可以尝试处理这些文件产生的错误。

import pikepdf

def open_pdf(pdf_file_path, pdf_password=''):
    try:
        pdf_obj = pikepdf.Pdf.open(pdf_file_path)

    except pikepdf._qpdf.PasswordError:
        pdf_obj = pikepdf.Pdf.open(pdf_file_path, password=pdf_password)

    finally:
        return pdf_obj

您可以使用返回的pdf_obj进行解析工作。 此外,如果您有加密的PDF文件,也可以提供密码。

1
谢谢您的回答!我们正在尝试在没有密码的情况下读取它。目前,我们已经能够使用在我的2019年10月7日更新中解释的方法来实现。 - Beginner
这远远不能回答问题。看起来你没有完整地阅读问题。 - shoonya ek
1
这个处理那些安全的PDF文件,通常当密码的默认值为None时,pikepdf会失败。通过传递一个空字符串,它能够正确地打开和解析一个安全的PDF文档(在我运行的测试用例中)。 - Mahendra Singh
1
@初学者 在这种情况下,你不需要转换PDF文件。这只是基于我的经验告诉你安全的PDF文件通过提供空密码来工作。 - Mahendra Singh
我尝试运行你的代码,但它没有起作用。你能发布你的整个代码吗?我是一个初学者。我指的是在你的电脑上运行的代码。你能展示一下你可以生成哪些格式吗? - Beginner
1
@初学者 这是我的整个代码。它只返回pikepdf中的pdf_object。如果您想保存此PDF,请使用pdf_obj.save('your_file_path')保存返回的对象。之后,您可以使用此PDF来解析文本和其他对象。我使用一个叫做PdfPlumber的库进行文本提取。 - Mahendra Singh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接