Python Invoice Parsing for PDF and Excel Files
Use pdfplumber, pandas and supplier templates to extract invoice fields reliably, with validation before anything is posted.

Build Python invoice parsing for PDF and Excel files with pdfplumber, pandas and supplier templates, plus validation checks. See a worked parser.
Supplier invoices arrive as PDFs and spreadsheets in every shape imaginable, and someone has to type the totals, dates and line items into the accounting system. When most invoices come from a known set of suppliers, Python invoice parsing is a practical, low-cost way to remove that typing. Libraries such as pdfplumber and pandas can pull text, tables and cells out of documents reliably, and a small set of rules per supplier turns that raw output into clean data. This guide explains when rule-based parsing is the right choice, how to structure the scripts, and a worked example that handles both PDF and Excel invoices.
When Python invoice parsing is the right approach
Rule-based scripts work best when:
- Most invoices come from a limited number of regular suppliers
- PDFs are generated digitally, so the text can be extracted, rather than scanned images
- Layouts change rarely, and you can update a template when they do
- You want predictable behaviour, low running costs and no external service
If you receive many layouts from many senders, or lots of scanned documents, a model-based approach is usually a better fit. Our guide to AI invoice data extraction covers that option. Many teams use both: rules for the regular suppliers, a model for the long tail.
The building blocks
pdfplumber for PDFs
pdfplumber extracts characters, words and tables from digital PDFs, with their positions on the page. That makes it possible to find a value by its label ("Invoice No") or by its location, and to read line-item tables.
pandas for Excel and CSV
Many suppliers send spreadsheets. pandas.read_excel reads them into data frames, after which locating the header row and the totals is straightforward.
Supplier templates
A template is a small configuration that tells the parser how to read one supplier's documents: patterns for the header fields, how to find the line-item table, and the date and number formats used.
Validation
Every parsed invoice is checked before it is used: totals should equal the sum of the lines plus tax, dates should be plausible, and the supplier's tax number should match the vendor record.
Structuring the parser
We keep the code in three layers so it stays maintainable as suppliers are added:
- Readers, which turn a PDF or spreadsheet into text and tables
- Templates, which map that raw content to fields for a specific supplier
- Normalisation and validation, which are shared by every supplier
Supplier detection runs first, usually by matching a tax number, email domain or a distinctive phrase, so the right template is chosen.
Worked example: parsing PDF and Excel invoices
The illustrative script below shows the pattern. Templates are plain dictionaries; in practice they would live in a configuration file.
import re
from decimal import Decimal
import pdfplumber
import pandas as pd
TEMPLATES = {
"acme": {
"detect": r"GSTIN:\s*27AAACA1234A1Z5",
"fields": {
"invoice_number": r"Invoice No[.:]?\s*([A-Z0-9/-]+)",
"invoice_date": r"Invoice Date[:]?\s*(\d{2}-\d{2}-\d{4})",
"total": r"Grand Total[:]?\s*([\d,]+\.\d{2})",
"tax": r"Total Tax[:]?\s*([\d,]+\.\d{2})",
},
"date_format": "%d-%m-%Y",
},
}
def money(text):
return Decimal(text.replace(",", ""))
def parse_pdf(path):
with pdfplumber.open(path) as pdf:
text = "\n".join(page.extract_text() or "" for page in pdf.pages)
tables = [t for page in pdf.pages for t in page.extract_tables()]
for name, tpl in TEMPLATES.items():
if re.search(tpl["detect"], text):
out = {"supplier": name}
for field, pattern in tpl["fields"].items():
m = re.search(pattern, text)
out[field] = m.group(1) if m else None
out["lines"] = tables[0][1:] if tables else []
return out
return {"supplier": None, "error": "No template matched"}
def parse_excel(path, header_label="Description"):
raw = pd.read_excel(path, header=None)
header_row = raw.index[raw.eq(header_label).any(axis=1)][0]
df = pd.read_excel(path, header=header_row).dropna(how="all")
return {"lines": df.to_dict("records")}
def validate(inv):
problems = []
if not inv.get("invoice_number"):
problems.append("Missing invoice number")
if inv.get("total") and inv.get("tax"):
if money(inv["tax"]) > money(inv["total"]):
problems.append("Tax greater than total")
return problems
The detection pattern, tax number and field labels above are invented for illustration. Each real supplier gets its own template, written after looking at a few sample invoices.
Fields and checks to capture
FieldNormalisationValidationInvoice numberTrim spaces, uppercaseNot already recorded for this supplierInvoice dateParse with the supplier's format, store as ISO dateNot in the future; not unreasonably oldSupplier tax numberRemove spacesMatches the vendor recordLine itemsConvert quantities and prices to decimalsQuantity times price equals line amountTax and totalDecimals, rounded to two placesLines plus tax equals total, within tolerancePurchase order numberTrim, uppercaseExists and is open
Handling the awkward cases
- Scanned PDFs have no text layer. Run optical character recognition first, or send them to the model-based route.
- Multi-page tables need rows merged across pages and repeated headers removed.
- Number formats differ: some suppliers use commas as decimal separators. Set the format in the template.
- Layouts change without warning. When a template stops matching, the invoice goes to a review queue and the template is updated.
- Credit notes look like invoices. Detect them by title and store them with a negative amount or a separate type.
Running it in production
A simple pipeline picks up files from a shared mailbox or folder, parses them, validates the result, and posts clean invoices to the accounting system as drafts. Anything that fails validation goes to a review list with the reason. Keep the original file linked to every record, and log the template version used, so you can trace any value back to its source.
Where to start
Do not try to cover every supplier on day one. Export a few months of invoice records and count invoices per supplier. A small number of suppliers usually accounts for a large share of the volume, and those are the templates worth writing first. For each one, collect several recent samples, including any credit notes and multi-page invoices, before writing the template.
Agree the target output with finance at the same time: which fields the accounting system needs, how line items map to accounts or items, and what should happen to invoices that fail validation. A clear target avoids building a parser that extracts data nobody uses. Start in report-only mode, where parsed results are compared with what the team entered by hand, and switch to creating draft bills once the comparison shows the templates are reliable.
Keeping invoice data secure
Invoices contain bank details, tax numbers and pricing. Store files and parsed data with restricted access, keep credentials for the accounting system in a secrets store rather than in scripts, and delete temporary files after processing.
Testing templates
Treat templates like code. For each supplier, keep a few sample invoices and the values you expect from them, and run these tests whenever a template or shared function changes. When a supplier changes its layout, add the new sample to the test set before updating the template. This takes little effort and prevents a fix for one supplier from quietly breaking another.
Measuring whether it works
Track how many invoices are parsed without any correction, how many go to review, and why. The review reasons tell you where to improve: a template that fails often needs attention, and a supplier that sends many scanned files may belong on the model-based route.
Once invoices are parsed, the next step is often entering related data elsewhere; see our guide to automating data entry into your CRM.
Frequently Asked Questions
Can Python extract tables from PDF invoices?
Yes. pdfplumber can extract tables from digitally generated PDFs. Results depend on the layout, so each supplier's template usually needs a little tuning.
Does this work with scanned invoices?
Not directly. Scanned images need optical character recognition first, or a model-based extraction approach.
How many supplier templates are practical?
Teams commonly maintain templates for their regular suppliers and route the rest to manual entry or a model. The right number depends on how often layouts change and who maintains them.
Is rule-based parsing better than AI extraction?
Neither is better in general. Rules are predictable and cheap for known layouts; models handle variety. Many businesses combine them.
Automate your invoice processing with us
We build invoice parsing pipelines in Python, with supplier templates, validation and review queues connected to your accounting system. See our AI automation services, or talk to us about your invoice workflow.
About the Author
Manav Sharma is the founder of Insyrge and a CRM solutions architect. He works across Zoho, Salesforce, HubSpot, Odoo and more, designing the CRM setups, integrations and automations that keep sales and operations teams working from the same data.
Need Help Implementing This in Your Business?
Our certified Zoho consultants and automation experts can help you design and deploy custom workflows tailored to your operations.
Book Free Consultation