2013-04-01 14 views
5

Próbuję przeanalizować tekst pliku pdf za pomocą pdfMiner, ale wyodrębniony tekst zostanie scalony. Używam pliku pdf z poniższego linku.Wyodrębnij tekst przy użyciu kolumn PdfMiner i PyPDF2 Scalanie

PDF File

jestem dobry z dowolnego typu wyjścia (plik/string). Oto kod, który zwraca wyodrębniony tekst jako ciąg znaków, ale z jakiegoś powodu kolumny są scalane.

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 

Próbowałem również PyPdf2, ale w obliczu tego samego problemu. Oto przykładowy kod dla 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") 

Próbowałem również pdf2txt.py ale w stanie uzyskać sformatowanego wyjścia.

+0

Czy ten pierwszy blok kodu powinien brzmieć' retstr = StringIO.StringIO() '? – Stedy

+0

Wiele kolumn jest naprawdę trudnym do odczytania z pliku PDF. W zależności od tego, co chcesz [k2pdfopt] (http://www.willus.com/k2pdfopt/) tworzy obrazy z każdej strony. – bobrobbob

Odpowiedz

1

Próbowałem swój pierwszy blok kodu i dostał kilka wyników, które wyglądają tak:

wielorodzinny AGARDEN COMPLEX 14945010314370 DO 372WILLOWRD W wielorodzinny AGARDEN COMPLEX 14945010314380 DO 384WILLOWRD W wielorodzinny AGARDEN COMPLEX 149450103141000 DO 1020WILLOWBROOKRD MULTIPLE DOM POMIESZCZENIA MIESZKANIEM 198787

Zgaduję, że znajdujesz się w podobnej sytuacji co ta answer i że wszystkie białe spacje są używane do pozycjonowania słów we właściwym miejscu, a nie jako rzeczywiste znaki do druku. Fakt, że próbowałeś z innymi bibliotekami pdf, sprawia, że ​​myślę, że może to być problem, który jest trudny do przeanalizowania przez bibliotekę pdf.

13

Niedawno zmagałem się z podobnym problemem, chociaż mój PDF miał nieco prostszą strukturę.

PDFMiner używa klas zwanych "urządzeniami" do analizowania stron w pliku pdf. Podstawową klasą urządzeń jest klasa PDFPageAggregator, która po prostu analizuje pola tekstowe w pliku. Klasy konwerterów, np. TextConverter, XMLConverter i HTMLConverter również wyprowadzają wynik w pliku (lub w strumieniu łańcuchów, jak w twoim przykładzie) i wykonują bardziej skomplikowane analizowanie zawartości.

Problem z TextConverter (i PDFPageAggregator) polega na tym, że nie rekompensują one wystarczająco głębokości struktury dokumentu, aby poprawnie wyodrębnić różne kolumny. Dwa pozostałe konwertery wymagają pewnych informacji o strukturze dokumentu do celów wyświetlania, aby gromadzić bardziej szczegółowe dane. W twoim przykładzie pdf oba uproszczone urządzenia analizują (w przybliżeniu) całe pole tekstowe zawierające kolumny, co uniemożliwia (lub przynajmniej bardzo trudne) prawidłowe rozdzielenie różnych wierszy. Rozwiązaniem tego, że znalazłem całkiem dobrze działa, to albo

  • utworzyć nową klasę, która dziedziczy PDFPageAggregator lub
  • Używaj XMLConverter i analizowania wynikowy dokument XML przy użyciu np Beautifulsoup

W obu przypadkach konieczne będzie połączenie różnych segmentów tekstu z wierszami przy użyciu współrzędnych y.

W przypadku nowej klasy urządzeń ("jest to bardziej wymowne, myślę) musiałbyś przesłonić metodę receive_layout, która jest wywoływana dla każdej strony podczas procesu renderowania. Ta metoda następnie rekurencyjnie analizuje elementy na każdej stronie.Na przykład coś takiego może Ci zacząć:

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 

w kodzie powyżej, każdy znalazł elementem LTTextLine jest przechowywany w uporządkowaną listę krotek zawierających numer strony, współrzędne obwiedni, a tekst zawarty w tym konkretnym elemencie. Można by wtedy zrobić coś podobnego do tego:

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) 

Zmienna device.rows zawiera uporządkowaną listę wszystkich linii tekstu umieszczone przy użyciu swojego numeru strony i Y współrzędnych. Możesz przechodzić przez linie tekstu i linie grupowe z tymi samymi współrzędnymi y, aby tworzyć wiersze, przechowywać dane kolumn itp.

Próbowałem przeanalizować plik pdf, używając powyższego kodu, a kolumny są w większości poprawnie przetwarzane. Jednak niektóre kolumny są tak blisko siebie, że domyślna heurystyka modułu PDFMiner nie może ich oddzielić na własne elementy. Prawdopodobnie można to obejść, modyfikując parametr "margin" (flaga -W w narzędziu wiersza poleceń pdf2text.py). W każdym razie możesz przeczytać (słabo udokumentowane) PDFMiner API, a także przejrzeć kod źródłowy PDFMiner, który można uzyskać z github. (Niestety, nie mogę wkleić linku, ponieważ nie mam wystarczającej liczby punktów rep: "<, ale możesz mieć nadzieję, że poprawne repo to go)

Powiązane problemy