Getting text and tables out of a PDF: what works, what needs OCR

A digital PDF gives up its text exactly; a scan gives up nothing. Learn to tell them apart in five seconds, extract tables to Excel, run the OCR route for scans including Hindi, and clean the result with pandas.

There are two kinds of PDF and only one of them will give you its text. A digital PDF — exported from Word, Tally, a bank's reporting system — stores actual characters, and extracting them is exact and instant. A scan stores a photograph of characters, and extraction returns nothing at all, not an approximation. Every frustrated hour spent on PDF extraction starts with not knowing which one is on screen, so test that first.

The five-second test

Open the file in any viewer and try to drag-select a line of text.

  • The selection highlights word by word and follows the line: digital PDF. Go straight to extraction.
  • The whole page highlights as one rectangle, or nothing highlights: scan. You need OCR.

Two confirmations if you want them. Press Ctrl+F and search for a word you can clearly see on the page; no match means no text layer. Or zoom to 400%: digital text stays sharp because the glyphs are redrawn at every zoom level, while a scan turns into visible pixels.

There is a third case. A scan that has already been through OCR, as many office scanners do automatically, carries an invisible text layer behind the image. Ctrl+F works and extraction returns text, but that text holds whatever mistakes the original OCR made, and nothing marks them. If a document that looks like a scan extracts with l where 1 should be, that is what happened.

Mixed files are common too: a 40-page digital contract with two scanned signature pages. Test the page you need.

Pulling out the text

PDF to Text reads the characters stored in the file and puts them in an editable box you can copy or download as .txt. For a digital PDF this is character-for-character accurate, because nothing is being recognised; the text is simply read out of the file.

The one setting that matters is Preserve column spacing. Leave it ticked and the tool inserts spaces that mirror where each word sits horizontally on the page, so a table or a two-column layout stays visually aligned in the output. Untick it when you want flowing sentences and the extra gaps are just noise, which is the case for contracts, circulars and anything you plan to paste into a document.

Three honest limits:

  • Reading order is the order objects were written into the file, which is usually the visual order but not always. Complex layouts with sidebars and pull quotes can interleave.
  • Ligatures and typographic substitutions come through as stored, so you may get fi as one character or soft hyphens mid-word.
  • Some digital PDFs have a broken character map. If a Hindi or Tamil PDF was produced with a legacy non-Unicode font and no ToUnicode table, the text layer exists but maps to meaningless code points, and you get garbage even though the document is not a scan. There is no fix from the extraction side; treat that file as a scan and OCR it.

Pulling out tables

No PDF contains a table. It contains words at coordinates, and sometimes lines drawn near them. PDF to Excel reconstructs the structure: it groups words into rows by their vertical position, then works out column boundaries from where words start and end horizontally, and writes the result to .xlsx or CSV. Each page becomes its own sheet unless you tick Put all pages in one sheet, which is what you want for a statement whose table runs across fourteen pages. Values that look numeric are written as real numbers, so SUM works immediately.

For a clean, machine-generated bank statement or invoice register, the output is usually 90% ready. For anything else, budget a few minutes of cleaning.

Why columns break

  • Wrapped cells. A two-line address or a long narration produces a second row with only one cell filled, which looks like a data row to the algorithm.
  • Two-line or merged headers. "Withdrawal / Amount (INR)" across two lines becomes two header rows.
  • Narrow adjacent numeric columns. Right-aligned debit and credit columns sitting close together can be clustered into one column, because their word positions overlap.
  • Ruling lines do not help. The grid you see is vector drawing instructions, stored separately from the text, so the visible table borders give the column detection nothing to work with.
  • Page headers and footers land in the middle of a multi-page table when you merge all pages into one sheet.
  • Rotated text, as in a landscape annexure inside a portrait document, comes out in the wrong place.

What helps: switch to CSV output and clean in pandas rather than fighting the spreadsheet, and tick the single-sheet option for continuing tables. What does not help: converting the PDF to something else first. Every route reads the same coordinates.

When there is no text layer: the OCR route

Two steps, both in the browser.

Step 1: render the pages as images. Open PDF to JPG, set the resolution to 300 DPI, and list the pages you need in the Pages box. Resolution is the decision that determines your accuracy: Tesseract wants roughly 20 pixels of lowercase letter height to work well, and at 150 DPI a 9 pt line gives it about 12. At 300 DPI it gets 25. If the page has fine print, or Devanagari with its matras and conjunct clusters, do not go below 300. For clean 12 pt text, 220 DPI is enough and renders faster. Where JPEG ringing around letter edges is visibly hurting the result, use PDF to PNG instead and accept files three to five times larger.

Step 2: recognise the text. Drop the images into AI OCR. It runs Tesseract as WebAssembly in your tab with fifteen languages available, including Hindi, Marathi, Bengali, Tamil, Telugu, Gujarati and Urdu.

  • Tap the language chips that match the page. English is on by default; for a page with English headings and Hindi body text, select both, which is what the multi-select is for.
  • Leave Enhance image before OCR ticked. It upscales and sharpens, and it usually helps on phone photos more than it hurts on clean scans.
  • Read the average confidence score under the output. It is a signal, not a guarantee, but a sudden drop on one page usually means that page is skewed or out of focus and is worth recapturing.

Output is text, not a table. Paste it into a sheet and use Data → Text to Columns with the space delimiter, or clean it in pandas as below.

What accuracy to actually expect

Rough ranges from typical documents. Your page will land somewhere near, not exactly on, these figures.

Source Realistic character accuracy The practical effect
300 DPI flatbed scan, printed English, 11 pt 97–99% a handful of errors per page
Straight phone photo, good light, printed English 92–97% a few dozen errors per page
Same photo, Hindi / Devanagari noticeably lower matras and conjuncts are where it fails
Ruled table, clean scan text mostly right row and column structure is lost
Thermal bill, faint carbon copy, dot-matrix print poor often faster to retype
Handwriting of any kind unusable the engine is trained on printed type

Convert that percentage into work before you rely on it. A page holds around 2,000 characters, so 98% accuracy means roughly 40 errors on that page. The errors cluster exactly where you least want them: 5 and 6, 0 and O, 1 and l and I, 8 and B. For a bank statement or an invoice register this means OCR saves you the typing but not the checking. Never let an OCR'd figure go into an accounting entry or a tax return without someone reading it against the original.

Cleaning the extracted table with pandas

Export CSV from PDF to Excel, then run this. It handles the three problems that turn up in almost every extraction: empty rows and columns, a header row left sitting in the data, and wrapped narration split across rows.

import pandas as pd

raw = pd.read_csv("statement.csv", header=None, dtype=str, keep_default_na=False)

# Drop what extraction left blank
raw = raw.replace("", pd.NA).dropna(how="all").dropna(axis=1, how="all")

# Promote the header row the tool wrote as data
raw.columns = [str(c).strip() for c in raw.iloc[0]]
df = raw.iloc[1:].reset_index(drop=True)

# Glue wrapped narration back onto the dated row above it
df["group"] = df["Date"].notna().cumsum()
df = (df.groupby("group")
        .agg(Date=("Date", "first"),
             Narration=("Narration", lambda s: " ".join(s.dropna())),
             Debit=("Debit", "first"),
             Credit=("Credit", "first"),
             Balance=("Balance", "last"))
        .reset_index(drop=True))

# "1,24,500.00 Dr" -> 124500.0
for col in ["Debit", "Credit", "Balance"]:
    df[col] = pd.to_numeric(df[col].str.replace(r"[^0-9.\-]", "", regex=True),
                            errors="coerce")

df["Date"] = pd.to_datetime(df["Date"], dayfirst=True, errors="coerce")
print(len(df), "rows;", df["Date"].isna().sum(), "dates failed to parse")

Rename the columns to match what your statement actually produced before running the groupby. Two lines carry the work. df["Date"].notna().cumsum() is the standard trick for reattaching wrapped text: a cumulative sum over a boolean gives every continuation row the same group number as the dated row above it, so grouping on it rejoins them. And errors="coerce" turns anything unparseable into NaN instead of raising, which makes df["Debit"].isna().sum() your error count rather than a stack trace. Check it before you trust the numbers.

If dayfirst=True looks unnecessary, it is not: 03/04/2026 is 3 April in an Indian statement and 4 March to pandas' default parser. More of this in the pandas course.

Frequently asked questions

PDF to Text returned nothing at all. What is wrong? The file is a scan, so there is no text layer to read. Render the pages with PDF to JPG at 300 DPI and run them through AI OCR.

Can I OCR a PDF in one step? Not here. Converting to images first is deliberate, because the rendering resolution is the single biggest factor in OCR accuracy and it should be your choice, not a hidden default.

Will OCR give me a spreadsheet from a scanned statement? No. OCR returns text in reading order and loses the column structure. Paste it into a sheet and split it with Data → Text to Columns, or clean it in pandas. Genuine table recognition from scans needs a paid service, and for a one-off statement it is usually faster to retype the figures than to fix a bad automated table.

Which is better for OCR input, JPG or PNG? PNG at 300 DPI is marginally better because there are no compression artefacts around letter edges, and it matters most on small text. JPG at 300 DPI is close enough for ordinary printed pages and produces much smaller files, which helps on a phone.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India

Related posts