使用pyPDF从文档中检索页码

时间:2012-09-10 23:59:52

标签: python pypdf

目前我正在考虑使用pyPdf进行一些PDF合并,但有时输入的顺序不正确,所以我正在考虑抓每页的页码以确定它应该进入的顺序(例如,如果有人将一本书分成20页10页PDF,我想将它们重新组合在一起)。

我有两个问题 - 1.)我知道有时页码会存储在某个地方的文档数据中,因为我已经看到在Adobe上呈现的PDF类似于[1243](10 of 150),但我我已经将这类文件读入pyPDF,我找不到任何指示页码的信息 - 这是存储在哪里的?

2。)如果avenue#1不可用,我想我可以遍历给定页面上的对象以尝试查找页码 - 可能是它自己的对象中有一个数字。但是,我似乎无法找到任何明确的方法来确定对象的内容。如果我跑:

pdf.getPage(0).getContents()

这通常会返回:

{'/Filter': '/FlateDecode'}

或它返回IndirectObject(num,num)对象的列表。我真的不知道如何处理其中任何一个,据我所知,它没有真正的文档。熟悉这种事情的人是否能指出我正确的方向?

5 个答案:

答案 0 :(得分:26)

以下对我有用:

from PyPDF2 import PdfFileReader
pdf = PdfFileReader(open('path/to/file.pdf','rb'))
pdf.getNumPages()

答案 1 :(得分:6)

有关完整文档,请参阅Adobe的978页PDF Reference。 : - )

更具体地说,PDF文件包含指示PDF的物理页面如何映射到逻辑页码以及如何格式化页码的元数据。这是您获取规范结果的地方。示例2 of this page显示了这在PDF标记中的显示方式。你必须把它解决,解析它,然后自己进行映射。

在PyPDF中,要获取此信息,请尝试作为起点:

pdf.trailer["/Root"]["/PageLabels"]["/Nums"]

顺便提一下,当您看到IndirectObject实例时,可以调用其getObject()方法来检索指向的实际对象。

正如您所说,您的选择是检查文本对象并尝试确定哪个是页码。您可以使用页面对象的extractText(),但是您将获得一个字符串,并且必须尝试从中删除页码。 (当然,页码可能是罗马字母或字母而不是数字,有些页面可能没有编号。)相反,看看extractText()实际上如何完成其​​工作 - 毕竟PyPDF是用Python编写的,毕竟 - 并将其用作例程的基础,该例程单独检查页面上的每个文本对象以查看它是否像页码一样。警惕那些有很多页码的TOC /索引页面!

答案 2 :(得分:4)

善意的回答非常好。但是,由于稍后(由梦想家)请求工作代码示例,并且因为我今天遇到了同样的问题,所以我想添加一些注释。

  1. pdf结构不统一;您可以依赖的东西相当少,因此任何工作代码示例都不太可能适用于所有人。可以找到一个非常好的解释in this answer

  2. 正如所解释的那样,您很可能需要探索您正在处理的PDF格式。

  3. 像这样:

    import sys
    import PyPDF2 as pyPdf
    
    """Open your pdf"""
    pdf = pyPdf.PdfFileReader(open(sys.argv[1], "rb"))
    
    """Explore the /PageLabels (if it exists)"""
    
    try:
        page_label_type = pdf.trailer["/Root"]["/PageLabels"]
        print(page_label_type)
    except:
        print("No /PageLabel object")
    
    """Select the item that is most likely to contain the information you desire; e.g.
           {'/Nums': [0, IndirectObject(42, 0)]}
       here, we only have "/Num". """
    
    try:
        page_label_type = pdf.trailer["/Root"]["/PageLabels"]["/Nums"]
        print(page_label_type)
    except:
        print("No /PageLabel object")
    
    """If you see a list, like
           [0, IndirectObject(42, 0)]
       get the correct item from it"""
    
    try:
        page_label_type = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1]
        print(page_label_type)
    except:
        print("No /PageLabel object")
    
    """If you then have an indirect object, like
           IndirectObject(42, 0)
       use getObject()"""
    
    try:
        page_label_type = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1].getObject()
        print(page_label_type)
    except:
        print("No /PageLabel object")
    
    """Now we have e.g.
           {'/S': '/r', '/St': 21}
       meaning roman numerals, starting with page 21, i.e. xxi. We can now also obtain the two variables directly."""
    
    try:
        page_label_type = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1].getObject()["/S"]
        print(page_label_type)
        start_page = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1].getObject()["/St"]
        print(start_page)
    except:
        print("No /PageLabel object")
    
    1. 从ISO pdf 1.7规范(相关部分here)可以看出,如何标记页面有很多可能性。作为一个简单的工作示例,请考虑此脚本至少处理十进制(阿拉伯语)和罗马数字:
    2. 脚本:

      import sys
      import PyPDF2 as pyPdf
      
      def arabic_to_roman(arabic):
          roman = ''
          while arabic >= 1000:
            roman += 'm'
            arabic -= 1000
          diffs = [900, 500, 400, 300, 200, 100, 90, 50, 40, 30, 20, 10, 9, 5, 4, 3, 2, 1]
          digits = ['cm', 'd', 'cd', 'ccc', 'cc', 'c', 'xc', 'l', 'xl', 'xxx', 'xx', 'x', 'ix', 'v', 'iv', 'iii', 'ii', 'i']
          for i in range(len(diffs)):
            if arabic >= diffs[i]:
              roman += digits[i]
              arabic -= diffs[i]
          return(roman)
      
      def get_page_labels(pdf):
          try:
              page_label_type = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1].getObject()["/S"]
          except:
              page_label_type = "/D"
          try:
              page_start = pdf.trailer["/Root"]["/PageLabels"]["/Nums"][1].getObject()["/St"]
          except:
              page_start = 1
          page_count = pdf.getNumPages()
          ##or, if you feel fancy, do:
          #page_count = pdf.trailer["/Root"]["/Pages"]["/Count"]
          page_stop = page_start + page_count 
      
          if page_label_type == "/D":
              page_numbers = list(range(page_start, page_stop))
              for i in range(len(page_numbers)):
                  page_numbers[i] = str(page_numbers[i])
          elif page_label_type == '/r':
              page_numbers_arabic = range(page_start, page_stop)
              page_numbers = []
              for i in range(len(page_numbers_arabic)):
                  page_numbers.append(arabic_to_roman(page_numbers_arabic[i]))
      
          print(page_label_type)
          print(page_start)
          print(page_count)
          print(page_numbers)
      
      pdf = pyPdf.PdfFileReader(open(sys.argv[1], "rb"))
      get_page_labels(pdf)
      

答案 3 :(得分:4)

其他答案使用PyPDF / PyPDF2,它似乎读取整个文件。这对于大文件来说需要很长时间。

与此同时,我写了一些快速而又肮脏的东西,并不需要花费太长的时间。它执行shell调用,但我不知道有任何其他方法可以执行此操作。它可以非常快速地获得大约5000页的pdf页面。

它只需调用“pdfinfo”shell命令即可,所以它可能只适用于linux。到目前为止我只在ubuntu上测试过它。

我见过的一个奇怪的行为是,在try / except块中围绕这个行为不能捕获错误,除了subprocess.CalledProcessError之外你必须这样做。

from subprocess import check_output
def get_num_pages(pdf_path):
    output = check_output(["pdfinfo", pdf_path]).decode()
    pages_line = [line for line in output.splitlines() if "Pages:" in line][0]
    num_pages = int(pages_line.split(":")[1])
    return num_pages

答案 4 :(得分:0)

另一个选择是pymupdf: https://pymupdf.readthedocs.io/en/latest/tutorial.html

import fitz

doc = fitz.open('Path To File')
doc.pageCount

pip install pymupdf

对于大型文档,使用pypdf2时出现递归错误,因此这是另一种快速简便的方法。