web-dev-qa-db-ja.com

pyPDFを使用してドキュメントからページ番号を取得する

現在、私はいくつかのPDF pyPdfとのマージを検討していますが、入力が正しい順序になっていない場合があるため、各ページをスクレイピングしてそのページ番号を調べ、それが入る順序(たとえば、誰かが本を20枚の10ページのPDFに分割し、それらを一緒に戻したい場合)。

私には2つの質問があります。1。)[1243](150の10)のようにAdobeでレンダリングされるPDFを見たことがありますが、ページ番号がドキュメントデータのどこかに保存されていることがあります。この種のドキュメントはpyPDFになり、ページ番号を示す情報が見つかりません-これはどこに保存されますか?

2.)アベニュー#1が利用できない場合は、特定のページのオブジェクトを繰り返し処理してページ番号を見つけようと思います。おそらく、単一の番号を持つ独自のオブジェクトでしょう。しかし、オブジェクトの内容を判別する明確な方法を見つけることができないようです。実行した場合:

pdf.getPage(0).getContents()

これは通常、次のいずれかを返します。

{'/Filter': '/FlateDecode'}

または、IndirectObject(num、num)オブジェクトのリストを返します。私はこれらのどちらをどうすればいいのか本当にわかりませんし、私が知る限り、それに関する実際のドキュメントはありません。私を正しい方向に向けることができるこのようなことを知っている人はいますか?

18
SquidneyPoitier

完全なドキュメントについては、Adobeの978ページ PDFリファレンス を参照してください。 :-)

より具体的には、PDFファイルには、PDFの物理ページが論理ページ番号にマップされる方法とページ番号のフォーマット方法を示すメタデータが含まれています。ここで、正規の結果を得ることができます。例2- このページの は、これがPDFマークアップでどのように見えるかを示しています。それを釣り出し、解析して、マッピングを自分で実行する必要があります。

PyPDFで、この情報を取得するには、出発点として以下を試してください。

_pdf.trailer["/Root"]["/PageLabels"]["/Nums"]
_

ところで、IndirectObjectインスタンスを確認したら、そのgetObject()メソッドを呼び出して、ポイントされている実際のオブジェクトを取得できます。

代わりに、あなたが言うように、テキストオブジェクトをチェックして、どちらがページ番号であるかを理解しようとします。これにはページオブジェクトのextractText()を使用できますが、1つの文字列が返され、そこからページ番号を取得する必要があります。 (そしてもちろん、ページ番号は数字ではなくローマ字またはアルファベットであり、一部のページには番号が付いていない場合があります。)代わりに、extractText()が実際にどのように機能するかを確認してください。PyPDFはPythonで記述されています。結局のところ、それをページの各テキストオブジェクトを個別にチェックして、ページ番号のようなものかどうかを確認するルーチンの基礎として使用します。多数のページ番号がある目次/索引ページに注意してください。

7
kindall

以下は私のために働きました:

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

キンダーの答えはとても良いです。ただし、動作するコードサンプルが後で(夢想家によって)要求されたので、今日同じ問題が発生したため、いくつかメモを追加したいと思います。

  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仕様(関連セクション ここ )からわかるように、ページにラベルを付ける方法には多くの可能性があります。簡単な作業例として、少なくとも10進数(アラビア語)とローマ数字を処理する次のスクリプトを考えてみます。

脚本:

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)
4
0range

他の答えは、ファイル全体を読み取るように見えるPyPDF/PyPDF2を使用します。大きなファイルの場合、これには長い時間がかかります。

その間、実行にほとんど時間がかからない、速くて汚いものを書きました。それはシェル呼び出しを行いますが、私はそれを行う他の方法を知りませんでした。 5,000ページ以下のPDFのページ数をすばやく取得できます。

「pdfinfo」シェルコマンドを呼び出すだけで機能するため、おそらくLinuxでのみ機能します。これまでにubuntuでのみテストしました。

私が見た奇妙な動作の1つは、これを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
Bryant Kou