63

Problem
I'm trying to determine what type a document is (e.g. pleading, correspondence, subpoena, etc) by searching through its text, preferably using python. All PDFs are searchable, but I haven't found a solution to parsing it with python and applying a script to search it (short of converting it to a text file first, but that could be resource-intensive for n documents).

What I've done so far
I've looked into pypdf, pdfminer, adobe pdf documentation, and any questions here I could find (though none seemed to directly solve this issue). PDFminer seems to have the most potential, but after reading through the documentation I'm not even sure where to begin.

Is there a simple, effective method for reading PDF text, either by page, line, or the entire document? Or any other workarounds?

Insarov
  • 971
  • 1
  • 10
  • 15
  • 1
    I was looking for the same solution. The problem is that PDF documents are notorious for breaking up text into chunks that are difficult to reassemble. It depends on the program that wrote the PDF. I ended up using PDFminer and a lot of "elif" code to parse PDFs. – Brent Washburne Jun 13 '13 at 23:44
  • 1
    Just a thought, maybe not practical... If you are desperate to find a workaround, you could try calling pdfgrep (http://pdfgrep.sourceforge.net/) to do the searching. – Brian Z Dec 08 '13 at 21:17

11 Answers11

55

This is called PDF mining, and is very hard because:

  • PDF is a document format designed to be printed, not to be parsed. Inside a PDF document, text is in no particular order (unless order is important for printing), most of the time the original text structure is lost (letters may not be grouped as words and words may not be grouped in sentences, and the order they are placed in the paper is often random).
  • There are tons of software generating PDFs, many are defective.

Tools like PDFminer use heuristics to group letters and words again based on their position in the page. I agree, the interface is pretty low level, but it makes more sense when you know what problem they are trying to solve (in the end, what matters is choosing how close from the neighbors a letter/word/line has to be in order to be considered part of a paragraph).

An expensive alternative (in terms of time/computer power) is generating images for each page and feeding them to OCR, may be worth a try if you have a very good OCR.

So my answer is no, there is no such thing as a simple, effective method for extracting text from PDF files - if your documents have a known structure, you can fine-tune the rules and get good results, but it is always a gambling.

I would really like to be proven wrong.

[update]

The answer has not changed but recently I was involved with two projects: one of them is using computer vision in order to extract data from scanned hospital forms. The other extracts data from court records. What I learned is:

  1. Computer vision is at reach of mere mortals in 2018. If you have a good sample of already classified documents you can use OpenCV or SciKit-Image in order to extract features and train a machine learning classifier to determine what type a document is.

  2. If the PDF you are analyzing is "searchable", you can get very far extracting all the text using a software like pdftotext and a Bayesian filter (same kind of algorithm used to classify SPAM).

So there is no reliable and effective method for extracting text from PDF files but you may not need one in order to solve the problem at hand (document type classification).

Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
  • All of the documents were scanned in as pdfs and OCR'ed to become searchable--is that different than what you're describing? – Insarov Jun 14 '13 at 16:12
  • 1
    @Insarov: Exactly what I'm talking about, any OCR worth its salary will have the option to output a pure text file along with the PDF file. – Paulo Scardine Jun 14 '13 at 22:23
  • If there is a sizable sample of document in relatively consistent narrative context (not necessarily in format), can we train a AI to understand it, so it can be used to read the text of the PDF files outsided the sample? – xappppp Jun 28 '19 at 19:22
  • 1
    @xappppp given enough time and resources, almost anything is possible. – Paulo Scardine Jun 28 '19 at 20:49
51

I am totally a green hand, but this script works for me:

# import packages
import PyPDF2
import re

# open the pdf file
reader = PyPDF2.PdfReader("test.pdf")

# get number of pages
num_pages = len(reader.pages)

# define key terms
string = "Social"

# extract text and do the search
for page in reader.pages:
    text = page.extract_text() 
    # print(text)
    res_search = re.search(string, text)
    print(res_search)
Emma
  • 643
  • 6
  • 7
  • 2
    Hi Amey, just change the "Social" to any text you want to search! – Emma Apr 17 '19 at 19:40
  • Hi Emma, searching is not a problem, but I need to replace this word with some other word. ex, replace the word "Social" with "friend". – Amey P Naik Apr 18 '19 at 09:06
  • 5
    @AmeyPNaik That would be modifying a PDF, not just reading/searching a PDF. Modifying an existing PDF programmatically without lay-out and formatting problems is even more complicated. – Mast Apr 15 '21 at 12:54
  • Note: on plenty of PDF pages this will only read the header and footer, not the rest of the pages. – Mast Apr 19 '21 at 11:10
  • 1
    this code helped me thank you ! – Fuad Ak Apr 01 '23 at 14:28
  • Change from rext = page.extract_text() to text = page.extract_text(), thanks for your answer – Noel Moses Mwadende Apr 09 '23 at 07:48
  • @NoelMosesMwadende Thanks for your comment. This is odd - my original answer was "Text = PageObj.extractText()". Looks like someone changed my answer... I changed to your recommendation anyway. – Emma Apr 10 '23 at 19:47
17

I've written extensive systems for the company I work for to convert PDF's into data for processing (invoices, settlements, scanned tickets, etc.), and @Paulo Scardine is correct--there is no completely reliable and easy way to do this. That said, the fastest, most reliable, and least-intensive way is to use pdftotext, part of the xpdf set of tools. This tool will quickly convert searchable PDF's to a text file, which you can read and parse with Python. Hint: Use the -layout argument. And by the way, not all PDF's are searchable, only those that contain text. Some PDF's contain only images with no text at all.

MikeHunter
  • 4,144
  • 1
  • 19
  • 14
  • 1
    why it's the fastest and most reliable way? Any proofs? – Hugo Moreno Jun 14 '13 at 02:31
  • 3
    If there's a way to convert a PDF to a text file, is there a way to do it without writing an actual new file? Something like reading a document into memory? (At least, in a way that's as straight forward as converting it?). – Insarov Jun 14 '13 at 16:09
  • 1
    @Insarov, I don't think so, not with pdftotext. But I may be wrong on this, you'll have to check the docs. You can do that with pyPdf and pdfminer, but they are a lot slower than pdftotext, even with pdftotext writing to the file. – MikeHunter Jun 14 '13 at 16:49
  • 2
    @Insarov From the pdftotext docs, "If text-file is '-', the text is sent to stdout." So you could pipe this to a search with `grep` or similar. – dragon788 Jul 28 '20 at 01:47
10

I recently started using ScraperWiki to do what you described.

Here's an example of using ScraperWiki to extract PDF data.

The scraperwiki.pdftoxml() function returns an XML structure.

You can then use BeautifulSoup to parse that into a navigatable tree.

Here's my code for -

import scraperwiki, urllib2
from bs4 import BeautifulSoup

def send_Request(url):
#Get content, regardless of whether an HTML, XML or PDF file
    pageContent = urllib2.urlopen(url)
    return pageContent

def process_PDF(fileLocation):
#Use this to get PDF, covert to XML
    pdfToProcess = send_Request(fileLocation)
    pdfToObject = scraperwiki.pdftoxml(pdfToProcess.read())
    return pdfToObject

def parse_HTML_tree(contentToParse):
#returns a navigatibale tree, which you can iterate through
    soup = BeautifulSoup(contentToParse)
    return soup

pdf = process_PDF('http://greenteapress.com/thinkstats/thinkstats.pdf')
pdfToSoup = parse_HTML_tree(pdf)
soupToArray = pdfToSoup.findAll('text')
for line in soupToArray:
    print line

This code is going to print a whole, big ugly pile of <text> tags. Each page is separated with a </page>, if that's any consolation.

If you want the content inside the <text> tags, which might include headings wrapped in <b> for example, use line.contents

If you only want each line of text, not including tags, use line.getText()

It's messy, and painful, but this will work for searchable PDF docs. So far I've found this to be accurate, but painful.

JasTonAChair
  • 1,948
  • 1
  • 19
  • 31
  • 2
    I tried using scraperwiki, I am getting The system cannot find the path specified error. @JasTonAChair any help appreciated. – user1211 Jan 11 '17 at 22:54
  • 1
    @JasTonAChair Am getting error :- BeautifulSoup([your markup]) to this: BeautifulSoup([your markup], "lxml") – venkat Jan 17 '18 at 10:03
7

Here is the solution that I found it comfortable for this issue. In the text variable you get the text from PDF in order to search in it. But I have kept also the idea of spiting the text in keywords as I found on this website: https://medium.com/@rqaiserr/how-to-convert-pdfs-into-searchable-key-words-with-python-85aab86c544f from were I took this solution, although making nltk was not very straightforward, it might be useful for further purposes:

import PyPDF2 
import textract

from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords

def searchInPDF(filename, key):
    occurrences = 0
    pdfFileObj = open(filename,'rb')
    pdfReader = PyPDF2.PdfFileReader(pdfFileObj)
    num_pages = pdfReader.numPages
    count = 0
    text = ""
    while count < num_pages:
        pageObj = pdfReader.getPage(count)
        count +=1
        text += pageObj.extractText()
    if text != "":
       text = text
    else:
       text = textract.process(filename, method='tesseract', language='eng')
    tokens = word_tokenize(text)
    punctuation = ['(',')',';',':','[',']',',']
    stop_words = stopwords.words('english')
    keywords = [word for word in tokens if not word in stop_words and  not word in punctuation]
    for k in keywords:
        if key == k: occurrences+=1
    return occurrences 

pdf_filename = '/home/florin/Downloads/python.pdf'
search_for = 'string'
print searchInPDF (pdf_filename,search_for)
florin.iliescu
  • 302
  • 4
  • 10
5

I agree with @Paulo PDF data-mining is a huge pain. But you might have success with pdftotext which is part of the Xpdf suite freely available here:

http://www.foolabs.com/xpdf/download.html

This should be sufficient for your purpose if you are just looking for single keywords.

pdftotext is a command line utility, but very straightforward to use. It will give you text files, which you may find easier to work with.

qwwqwwq
  • 6,999
  • 2
  • 26
  • 49
3

If you are on bash, There is a nice tool called pdfgrep, Since, This is in apt repository, You can install this with:

sudo apt install pdfgrep

It had served my requirements well.

Appaji Chintimi
  • 613
  • 2
  • 7
  • 19
1

A version using PyMuPDF. I find it to be more robust than PyPDF2.

import fitz
import re

filename = 'myfile.pdf'

# load document
doc = fitz.open(filename)

# define keyterms
String = "hours"

# get text, search for string and print count on page.
for page in doc:
    text = ''
    text += page.get_text()
    print(f'count on page {page.number +1} is: {len(re.findall(String, text))}')
Evilbyte
  • 7
  • 5
Cam
  • 1,263
  • 13
  • 22
  • Thanks so much, very helpfully, the output would be very nice if we put something like this `print(f'{word} is found {len(re.findall(String, text))} times on page {page.number +1}')` then our pout would be like this `statement is found 9 times on page 1` – Noel Moses Mwadende Apr 09 '23 at 08:09
0

Trying to pick through PDFs for keywords is not an easy thing to do. I tried to use the pdfminer library with very limited success. It’s basically because PDFs are pandemonium incarnate when it comes to structure. Everything in a PDF can stand on its own or be a part of a horizontal or vertical section, backwards or forwards. Pdfminer was having issues translating one page, not recognizing the font, so I tried another direction — optical character recognition of the document. That worked out almost perfectly.

Wand converts all the separate pages in the PDF into image blobs, then you run OCR over the image blobs. What I have as a BytesIO object is the content of the PDF file from the web request. BytesIO is a streaming object that simulates a file load as if the object was coming off of disk, which wand requires as the file parameter. This allows you to just take the data in memory instead of having to save the file to disk first and then load it.

Here’s a very basic code block that should be able to get you going. I can envision various functions that would loop through different URL / files, different keyword searches for each file, and different actions to take, possibly even per keyword and file.

# http://docs.wand-py.org/en/0.5.9/
# http://www.imagemagick.org/script/formats.php
# brew install freetype imagemagick
# brew install PIL
# brew install tesseract
# pip3 install wand
# pip3 install pyocr
import pyocr.builders
import requests
from io import BytesIO
from PIL import Image as PI
from wand.image import Image

if __name__ == '__main__':
    pdf_url = 'https://www.vbgov.com/government/departments/city-clerk/city-council/Documents/CurrentBriefAgenda.pdf'
    req = requests.get(pdf_url)
    content_type = req.headers['Content-Type']
    modified_date = req.headers['Last-Modified']
    content_buffer = BytesIO(req.content)
    search_text = 'tourism investment program'

    if content_type == 'application/pdf':
        tool = pyocr.get_available_tools()[0]
        lang = 'eng' if tool.get_available_languages().index('eng') >= 0 else None
        image_pdf = Image(file=content_buffer, format='pdf', resolution=600)
        image_jpeg = image_pdf.convert('jpeg')

        for img in image_jpeg.sequence:
            img_page = Image(image=img)
            txt = tool.image_to_string(
                PI.open(BytesIO(img_page.make_blob('jpeg'))),
                lang=lang,
                builder=pyocr.builders.TextBuilder()
            )
            if search_text in txt.lower():
                print('Alert! {} {} {}'.format(search_text, txt.lower().find(search_text),
                                               modified_date))

    req.close()
0

This answer follows @Emma Yu's:

If you want to print out all the matches of a string pattern on every page.
(Note that Emma's code prints a match per page):

import PyPDF2
import re

pattern = input("Enter string pattern to search: ")
fileName = input("Enter file path and name: ")

object = PyPDF2.PdfFileReader(fileName)
numPages = object.getNumPages()

for i in range(0, numPages):
    pageObj = object.getPage(i)
    text = pageObj.extractText()
   
    for match in re.finditer(pattern, text):
        print(f'Page no: {i} | Match: {match}')
thapa.k
  • 1
  • 2
0

Example with pdfminer.six

from pdfminer import high_level

with open('file.pdf', 'rb') as f:
    text = high_level.extract_text(f)
    print(text)

Compared to PyPDF2, it can work with cyrillic

Rugnar
  • 2,894
  • 3
  • 25
  • 29