8

pdfMiner を使用して pdf ファイルのテキストを解析しようとしていますが、抽出されたテキストがマージされます。以下のリンクのpdfファイルを使用しています。

PDFファイル

私はあらゆるタイプの出力 (ファイル/文字列) を得意としています。抽出されたテキストを文字列として返すコードを次に示しますが、何らかの理由で列がマージされます。

from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
import StringIO

def convert_pdf(filename):
    rsrcmgr = PDFResourceManager()
    retstr = StringIO()
    codec = 'utf-8'
    laparams = LAParams()
    device = TextConverter(rsrcmgr, retstr, codec=codec)

    fp = file(filename, 'rb')
    process_pdf(rsrcmgr, device, fp)
    fp.close()
    device.close()

    str = retstr.getvalue()
    retstr.close()
    return str

PyPdf2 も試しましたが、同じ問題に直面しました。PyPDF2 のサンプル コードは次のとおりです。

from PyPDF2.pdf import PdfFileReader
import StringIO
import time

def getDataUsingPyPdf2(filename):
    pdf = PdfFileReader(open(filename, "rb"))
    content = ""

    for i in range(0, pdf.getNumPages()):
        print str(i)
        extractedText = pdf.getPage(i).extractText()
        content +=  extractedText + "\n"

    content = " ".join(content.replace("\xa0", " ").strip().split())
    return content.encode("ascii", "ignore")

pdf2txt.pyも試しましたが、フォーマットされた出力を取得できません。

4

3 に答える 3

17

私は最近、同様の問題に苦労しましたが、pdf の構造は少し単純でした。

PDFMiner は、「デバイス」と呼ばれるクラスを使用して、PDF ファイル内のページを解析します。基本的なデバイス クラスは PDFPageAggregator クラスで、ファイル内のテキスト ボックスを単純に解析します。TextConverter、XMLConverter、HTMLConverter などのコンバーター クラスも、結果をファイル (または例のように文字列ストリーム) に出力し、コンテンツのより複雑な解析を行います。

TextConverter (および PDFPageAggregator) の問題は、さまざまな列を適切に抽出するためにドキュメントの構造を深く再帰しないことです。他の 2 つのコンバーターは、表示目的でドキュメントの構造に関する情報を必要とするため、より詳細なデータを収集します。あなたの例のpdfでは、両方の単純化されたデバイスは、列を含むテキストボックス全体を(大まかに)解析するだけであるため、異なる行を正しく分離することは不可能です(または少なくとも非常に困難です)。私が見つけたこれに対する解決策は、かなりうまくいくことです。

  • PDFPageAggregator から継承する新しいクラスを作成するか、
  • XMLConverter を使用し、Beautifulsoup などを使用して結果の XML ドキュメントを解析します

どちらの場合も、バウンディング ボックスの y 座標を使用して、さまざまなテキスト セグメントを行に結合する必要があります。

新しいデバイス クラスの場合 (もっと雄弁だと思います) receive_layout、レンダリング プロセス中に各ページに対して呼び出される get メソッドをオーバーライドする必要があります。このメソッドは、各ページの要素を再帰的に解析します。たとえば、次のようなものから始めることができます。

from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTPage, LTChar, LTAnno, LAParams, LTTextBox, LTTextLine

class PDFPageDetailedAggregator(PDFPageAggregator):
    def __init__(self, rsrcmgr, pageno=1, laparams=None):
        PDFPageAggregator.__init__(self, rsrcmgr, pageno=pageno, laparams=laparams)
        self.rows = []
        self.page_number = 0
    def receive_layout(self, ltpage):        
        def render(item, page_number):
            if isinstance(item, LTPage) or isinstance(item, LTTextBox):
                for child in item:
                    render(child, page_number)
            elif isinstance(item, LTTextLine):
                child_str = ''
                for child in item:
                    if isinstance(child, (LTChar, LTAnno)):
                        child_str += child.get_text()
                child_str = ' '.join(child_str.split()).strip()
                if child_str:
                    row = (page_number, item.bbox[0], item.bbox[1], item.bbox[2], item.bbox[3], child_str) # bbox == (x1, y1, x2, y2)
                    self.rows.append(row)
                for child in item:
                    render(child, page_number)
            return
        render(ltpage, self.page_number)
        self.page_number += 1
        self.rows = sorted(self.rows, key = lambda x: (x[0], -x[2]))
        self.result = ltpage

上記のコードでは、見つかった各 LTTextLine 要素が、ページ番号、境界ボックスの座標、およびその特定の要素に含まれるテキストを含むタプルの順序付きリストに格納されます。次に、次のようにします。

from pprint import pprint
from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.layout import LAParams

fp = open('pdf_doc.pdf', 'rb')
parser = PDFParser(fp)
doc = PDFDocument(parser)
doc.initialize('password') # leave empty for no password

rsrcmgr = PDFResourceManager()
laparams = LAParams()
device = PDFPageDetailedAggregator(rsrcmgr, laparams=laparams)
interpreter = PDFPageInterpreter(rsrcmgr, device)

for page in PDFPage.create_pages(doc):
    interpreter.process_page(page)
    # receive the LTPage object for this page
    device.get_result()

pprint(device.rows)

変数 device.rows には、ページ番号と y 座標を使用してすべてのテキスト行が配置された順序付きリストが含まれています。テキスト行をループし、同じ y 座標を持つ行をグループ化して、行を形成したり、列データを保存したりできます。

上記のコードを使用して pdf を解析しようとしましたが、列はほとんど正しく解析されています。ただし、一部の列は非常に接近しているため、デフォルトの PDFMiner ヒューリスティックではそれらを独自の要素に分離できません。word margin パラメーター (コマンド ライン ツール pdf2text.py の -W フラグ) を調整することで、おそらくこれを回避できます。いずれにせよ、(十分に文書化されていない) PDFMiner APIを読み、github から入手できる PDFMiner のソース コードを参照することをお勧めします。(残念ながら、十分な担当者ポイントがないため、リンクを貼り付けることができません:'<、しかし、正しいレポをグーグルで検索できることを願っています)

于 2013-10-04T10:33:02.857 に答える
2

最初のコード ブロックを試したところ、次のような結果が得られました。

MULTIPLE DWELLING AGARDEN COMPLEX 14945010314370 ~ 372WILLOWRD W MULTIPLE DWELLING AGARDEN COMPLEX 14945010314380 ~ 384WILLOWRD W MULTIPLE DWELLING AGARDEN COMPLEX 149450103141000 ~ 1020

あなたはこの回答と同様の立場にあり、実際の印刷可能なスペース文字としてではなく、すべての空白が適切な場所に単語を配置するために使用されていると思います。あなたが他のpdfライブラリで試したという事実は、これがどのpdfライブラリでも解析するのが難しい問題かもしれないと私に思わせます.

于 2013-04-01T06:09:28.960 に答える