Получения номера страниц из документа с pyPDF

на данный момент я рассматриваю некоторые PDF-слияния с pyPdf, но иногда входные данные не в правильном порядке, поэтому я рассматриваю выскабливание каждой страницы для ее номера страницы, чтобы определить порядок, в котором она должна идти (например, если кто-то разделил книгу на 20 10-страничных PDF-файлов, и я хочу собрать их вместе).

у меня есть два вопроса-1.) Я знаю, что иногда номер страницы хранится в данных документа где-то, так как я видел PDF-файлы, которые отображаются в Adobe как что-то как [1243] (10 из 150), Но я читал документы такого рода в pyPDF, и я не могу найти никакой информации, указывающей номер страницы - где это хранится?

2.) Если avenue #1 недоступна, я думаю, что могу перебрать объекты на данной странице, чтобы попытаться найти номер страницы - вероятно, это будет его собственный объект, в котором есть один номер. Однако я не могу найти четкого способа определить содержимое объектов. Если я побегу:

pdf.getPage(0).getContents()

это обычно либо возвращается:

{'/Filter': '/FlateDecode'}

или возвращает список объектов IndirectObject (num, num). Я действительно не знаю, что делать с любым из них, и, насколько я могу судить, нет никакой реальной документации. Кто-нибудь знаком с такими вещами, которые могут указать мне в правильном направлении?

4 ответов


для получения полной документации см. Adobe 978-page PDF Reference. :-)

более конкретно, файл PDF содержит метаданные, которые указывают, как физические страницы PDF сопоставляются с логическими номерами страниц и как номера страниц должны быть отформатированы. Здесь вы идете за каноническими результатами. Пример 2 этой странице показывает, как это выглядит в разметке PDF. Вам придется выловить это, проанализировать и выполнить картографирование самостоятельно.

In PyPDF, чтобы получить эту информацию, попробуйте в качестве отправной точки:

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

кстати, когда вы видите IndirectObject экземпляр, вы можете вызвать его getObject() метод для извлечения фактического объекта, на который указывают.

ваша альтернатива, как вы говорите, проверить текстовые объекты и попытаться выяснить, какой номер страницы. Вы могли бы использовать extractText() объекта страницы для этого, но вы получите одну строку обратно и должны попытаться выудить номер страницы из этого. (И конечно номер страницы может быть римским или алфавитным, а не числовым, и некоторые страницы не могут быть пронумерованы.) Вместо этого посмотрите, как extractText() фактически выполняет свою работу-PyPDF написан на Python, в конце концов-и использует его в качестве основы процедуры, которая проверяет каждый текстовый объект на странице отдельно, чтобы увидеть, похож ли он на номер страницы. Будьте осторожны с TOC / индексными страницами, на которых есть много номеров страниц!


для меня сработало следующее:

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

другие ответы используют PyPDF / PyPDF2, который, кажется, читает весь файл. Для больших файлов это занимает много времени.

тем временем я написал что-то быстрое и грязное, что не занимает почти столько же времени. Он делает вызов оболочки, но я не знал другого способа сделать это. Он может очень быстро получить количество страниц для PDF-файлов, которые составляют ~5000 страниц.

он работает, просто вызывая команду оболочки "pdfinfo", поэтому он, вероятно, работает только в linux. Я только проверил это на ubuntu до сих пор.

одно странное поведение, которое я видел, заключается в том, что окружающее это в блоке try/except не ловит ошибки, вы должны кроме подпроцесса.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

ответ kindall очень хорош. Однако, поскольку образец рабочего кода был запрошен позже (dreamer) и поскольку у меня была та же проблема сегодня, я хотел бы добавить некоторые заметки.

  1. структура pdf неоднородна; есть довольно мало вещей, на которые вы можете положиться, поэтому любой образец рабочего кода вряд ли будет работать для всех. Очень хорошее объяснение можно найти в ответ.

  2. Как объяснил kindall, вы скорее всего, вам нужно будет изучить, с каким pdf вы имеете дело.

вот так:

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 (соответствующий раздел здесь) есть много возможностей, как пометить страницы. В качестве простого рабочего примера рассмотрим этот скрипт, который будет иметь дело как минимум с десятичными (арабскими) и римскими цифрами:

сценарий:

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)