Extract Invoice Data From PDF With Python

To extract invoice data from a PDF with Python you read the text out of the file, then pull named fields out of that text. Two steps, maybe forty lines of code, and it works beautifully on the first invoice you try it on. Then supplier number seven moves their invoice number three lines up, and your parser starts returning None at two in the morning.

That second part is the real subject of this article. The reading is easy. The staying-correct is the job.

Key takeaways

  • Python extracts invoice text in a few lines. Keeping it correct across hundreds of supplier layouts is what actually costs you.
  • A PDF is not a data format. It is a typographic description of a printed page, which is why a regex written against one supplier's layout is a fragile thing.
  • Regular expressions and per-supplier templates scale linearly with your supplier list. Vision models do not, because they read the page instead of the string.
  • Whatever extracts the data, your own code has to check the arithmetic. Line items that do not sum to the subtotal are the cheapest bug detector you will ever write.
  • The extraction is rarely what makes people stop building. The exception queue, the vendor matching and the accounting integration are.

The PDF format

The PDF format is versatile, enabling the accurate representation of paper documents, like invoices, without limiting their design. It comes from the world of the paper print and is designed to be a digital representation of a printed page. This flexibility offers significant freedom, allowing PDF creators to express themselves and adhere to various standards and regulations.

However, the challenge arises when data is locked within a PDF. The format's free-form and complex nature can conflict with the structured and consistent approach necessary for managing the vast data a company processes daily.

A screen capture of PDF file format layers
PDF file format layers

A PDF stores where each glyph sits on the page. It does not store the fact that the number in the bottom right is the total. That relationship lives in your head, and every extraction method in this article is an attempt to encode it.

What are the steps to extract data from an invoice?

An invoice is a document that usually comes in the PDF format. An invoice formalizes a transaction between a supplier and a customer, where a product or service is exchanged for a precise amount of money. Here are the steps required to extract data from this document:

  1. Define a schema for the data you want to extract from your invoices
  2. Convert your invoice from image to text
  3. Extract the text from your invoice according to your data schema
  4. Collect the extracted data

A screen capture of Invoice data extraction process
Invoice data extraction process

Define a schema for your invoice data

Invoices come from different suppliers and each supplier tend to customize the way their invoices look. Despite this real-life diversity in the form, the substance of all invoices is basically the same: you need a supplier, a customer, an invoice reference, a date and a list of items with an associated quantity, description and cost. A great way to start defining your invoice format would be your accounting software, as it's most probably where to store your extracted invoice data in the end, right? If you just want a data format that can cover all corner cases, let me recommend the schema.org website, which conveniently defines a series of industry-standard data formats for a lot of things, including invoices. Parseur defines a default data schema for your invoices, but you can change it to fit your use case by renaming the fields in your invoice mailbox, as explained here. Once your data format is defined, you can convert your invoice from image to text.

For example, you can define the following fields for your invoice using JSON Swagger format:

{
    "InvoiceNumber": {
        "type": "string",
        "description": "The invoice number"
    },
    "InvoiceIssueDate": {
        "type": "string",
        "description": "The invoice date"
    },
    "Items": {
        "type": "array",
        "description": "The list of items in the invoice",
        "items": {
            "type": "object",
            "properties": {
                "quantity": {
                    "type": "number",
                    "description": "The quantity of the item"
                },
                "description": {
                    "type": "string",
                    "description": "The description of the item"
                },
                "unit_price": {
                    "type": "number",
                    "description": "The unit price of the item"
                },
                "price": {
                    "type": "number",
                    "description": "The total price of the item"
                }
            }
        }
    }
}

Write this down before you write any parsing code. It is the contract every method below has to satisfy, and it is the thing you will hand to a vision model later.

Convert your invoice from image to text

A screen capture of an invoice taken from a smartphone
Picture of an invoice taken from a smartphone

A PDF file can contain an image. For example, your employee may snap a quick shot of an invoice with their smartphone camera. They then save it as PDF and send it to your accounting department. Your accounting team is in charge of extracting the data from this invoice and somehow get it into your accounting system without any mistake. The next step is to convert this image to text using an Optical Character Recognition system. One of the most popular OCR system is Tesseract. Tesseract is written in C and C++. In order to use Tesseract from our Python program, we'll need to use a binding such as PyTesseract. A binding is a way to call a software library (here, Tesseract) from a language it's not written in (here, Python). There exists many such systems and their results vary wildly depending on their underlying technology and the quality of the scan of the document they are working on. Parseur transparently detects if your document is an image and automatically converts it into text, internally. Once the document's data is in text form, it is ready to get extracted.

Extract the text from your invoice according to your data schema

Once your PDF is in text (or searchable) form, you can use the pdftotext Python library to get the data out of the PDF file, as text. Here is a code snippet to extract the text from a PDF file:

import pdftotext

# Load your invoice
with open("invoice.pdf", "rb") as file_handle:
    pdf = pdftotext.PDF(file_handle)

# Iterate over all the pages
for page in pdf:
    print(page)

Name this script convert_pdf_to_text.py and run it, you'll get the invoice as text to the standard output. If you want to redirect the output to a file, you can run:

$ python convert_pdf_to_text.py > invoice.txt

pdftotext gives you a flat string, which is fine for header fields and hopeless for tables. When you need the line items, reach for pdfplumber instead, because it keeps the coordinates of every word and can attempt the table itself:

import pdfplumber

with pdfplumber.open("invoice.pdf") as pdf:
    page = pdf.pages[0]

    # Words with their positions on the page
    for word in page.extract_words():
        print(word["text"], word["x0"], word["top"])

    # And an attempt at the line-item table
    table = page.extract_table()
    if table:
        for row in table:
            print(row)

Run that on a real supplier invoice and you will very often find table is None. That is not a bug. It is the first honest signal that this problem is harder than it looks, and we come back to it below.

Now that you have the invoice in text form, you can extract the data you want from it, using any combination of the following techniques:

  • You can use a regular expression to extract the data you want. Regular expressions are a powerful way to extract data from text, but they are also very brittle. If the invoice format changes, you'll need to update your regular expression. Also, regular expressions are not very good at extracting data from tables.
  • You can use a visual templating system, ideally leveraging Dynamic OCR and Zonal OCR. This is a more advanced way to extract data from text. It's more robust than regular expressions, but it's also more complex to implement.
  • You can hand the page to a vision model with a schema and let it read the document the way a person does. This is the approach that changed in the last two years, and it gets its own section below.

Let's extract data from your invoice with Python's regular expressions re module. Here is a code snippet to extract the invoice number from your invoice:

import re

# Load your invoice
with open("invoice.txt", "r") as file_handle:
    invoice = file_handle.read()

# Extract the invoice number
invoice_number = re.search(r"Invoice number: (\w+)", invoice).group(1)
print(invoice_number)

Name this script extract.py and run it, you'll get the invoice number to the standard output:

$ python extract.py

And you will get something like:

INV-1234

Why your regex invoice parser breaks

A regex matches a string. An invoice is a picture. Everything that goes wrong follows from that mismatch, and it goes wrong in a small number of predictable ways:

  • The label moved. Your pattern is anchored to Invoice number: and the new template says Invoice #, or puts the value on the next line instead of the same one.
  • extract_table returns None. Table extractors look for ruling lines. Most supplier invoices align their columns with whitespace and draw no borders at all.
  • The text comes out in the wrong order. Two-column layouts and floating address blocks interleave when the page is flattened into a string, so your line-item rows arrive shuffled.
  • The table spans pages. Rows two through nine are on page one, ten through fourteen on page two, with the column headers repeated in between and a subtotal line pretending to be an item.
  • Three numbers all look like the total. Subtotal, total, amount due, and balance forward. Picking the largest one is wrong on any invoice carrying a credit.
  • The scan is a photograph. OCR reads a smudged 8 as a 3 and nothing downstream notices, because 3 is a perfectly valid digit.
  • Merged cells and multi-line descriptions. One product description wraps onto three lines and your row-splitting logic turns it into three items with no prices.

None of these is fixable with a better regular expression. They are all layout problems wearing a string problem's clothes. This is the point where most people either start writing one template per supplier, which grows forever, or change approach.

The 2026 approach - a vision model and a schema

The useful shift is that you no longer have to flatten the page into text before you extract from it. A vision model looks at the rendered invoice, so a supplier moving their invoice number is not an event any more. What you supply is not a pattern, it is the schema you wrote at the top of this article.

Constrain the output so you get the same keys every time. Pydantic plus a structured-output mode, such as OpenAI's structured outputs, does that for you:

from typing import List
from pydantic import BaseModel

class LineItem(BaseModel):
    description: str
    quantity: float
    unit_price: float
    amount: float

class Invoice(BaseModel):
    vendor_name: str
    invoice_number: str
    invoice_date: str      # ISO 8601
    currency: str
    subtotal: float
    tax: float
    total: float
    line_items: List[LineItem]

# Render the PDF page to an image, send it to a vision model,
# and require the response to match the Invoice schema.
# The model fills the fields. It does not get to invent the shape.

That is genuinely most of the extraction problem solved, and it is why this approach spread so fast. It also introduces a new failure mode that regex never had: a regex that cannot find the invoice number returns None, while a model that cannot find it will sometimes write a plausible one. The rule that keeps you safe is simple. The model proposes and your code verifies.

If you would rather not run the model yourself, the same capability is sold as a managed service by the cloud providers, in Azure AI Document Intelligence and Amazon Textract's AnalyzeExpense, both of which return header fields and line items separately. We wrote up how the underlying approach differs from rule-based parsing in AI versus rule-based PDF parsers, and what it looks like applied to invoices specifically in vision AI invoice processing.

The validation layer you have to write yourself

Whatever produced your JSON, these checks belong in your code, not in the extractor's confidence score. They are cheap, they are deterministic, and they catch the errors that cost money:

  1. subtotal + tax + shipping - discount lands on total, within a cent.
  2. The line-item amounts sum to the subtotal. If they do not, you dropped a row or invented one.
  3. Every quantity * unit_price equals its own amount.
  4. The date parses, and it is not in the future.
  5. The invoice number has not already been paid for that vendor. Duplicate payments are the most expensive bug in accounts payable.
  6. The vendor name resolves to a record in your vendor master.
  7. The remit-to bank details match the ones already on file for that vendor. A change here is a fraud check, not a data check.
  8. The currency is one you actually trade in.
  9. The purchase order number exists, and its quantities and prices match if you run two-way or three-way matching.
  10. Every required field is present and non-empty.

Anything that fails goes to a person, not into the ledger:

def validate(invoice):
    errors = []

    if abs(invoice.subtotal + invoice.tax - invoice.total) > 0.01:
        errors.append("totals_do_not_add_up")

    line_sum = sum(item.amount for item in invoice.line_items)
    if invoice.line_items and abs(line_sum - invoice.subtotal) > 0.01:
        errors.append("line_items_do_not_sum_to_subtotal")

    if not invoice.invoice_number:
        errors.append("missing_invoice_number")

    return errors

Ten lines of arithmetic will catch more real problems than any amount of prompt tuning.

What about invoice2data and the other libraries?

invoice2data deserves an honest mention, because it is the first result many people land on and it is a genuinely good piece of software. It is a command line tool and Python library that matches invoices against YAML templates you write per supplier, with the matching rules in version control rather than buried in your code. If you have a dozen suppliers who never change their layout, it will serve you well for years.

The limit is in the design, not the quality. One template per supplier means your maintenance grows with your supplier list, and the templates break for exactly the reasons listed above. Somewhere between twenty and thirty active layouts, the person maintaining the templates is doing more work than the person who used to retype the invoices.

The same reasoning applies to the wider library shelf. pdfplumber, PyMuPDF, pdftotext and pytesseract are all excellent at their actual jobs, which is getting characters and coordinates off a page. None of them was ever meant to know which number is the total. We compare the broader set in our roundup of the best PDF parsers and the best data extraction APIs.

Collect the extracted data

With Python, you can iterate over the invoices files in a given folder and extract the data from them. Let's say we extract the invoice number and total amount, and output the result in CSV format:

import os
import re

import pdftotext

# Iterate over all the PDF files in the folder
for filename in os.listdir("invoices/"):
    if not filename.endswith(".pdf"):
        continue

    # Load your invoice
    with open("invoices/" + filename, "rb") as file_handle:
        pdf = pdftotext.PDF(file_handle)

    # Print the CSV column header
    print("InvoiceNumber,TotalAmount")

    # Iterate over all the pages
    for page in pdf:
        # Extract the invoice number
        invoice_number = re.search(r"Invoice number: (\w+)", page).group(1)
        total_amount = re.search(r"Total amount: (\w+)", page).group(1)
        print(invoice_number, total_amount, sep=",")

Name this script extract_to_csv.py and run it, you'll get the invoice number and total amount to the standard output, that you can redirect to a CSV file that you can later open with your favorite spreadsheet software, like Excel:

$ python extract_to_csv.py > invoices.csv

A folder of CSV files is where most invoice scripts stop, and it is also where the honest accounting starts. Somebody still has to import them, match the vendors, chase the rows the accounting system rejected, and work out what to do with the invoice that failed validation at 2am. That work does not appear in your script and it does not appear in your token bill.

When to stop building

Here is the part most vendors skip, so we will say it first. If you have a handful of steady suppliers, an engineer who can watch a pipeline, and nobody waiting on the data, then write the script. A vision model plus a schema plus the ten lines of validation above will get you a long way for very little money, and you will understand every part of it.

The bill for building arrives later, and never in the extraction. It arrives in the parts nobody prototypes:

  • The exception queue. Your AP team needs a screen where clicking a field highlights it on the invoice so they can fix it in four seconds instead of forty. That is a product, not a script.
  • Vendor matching. "ACME Ltd", "Acme Limited" and "ACME LTD." are one supplier, and the ledger will not accept three.
  • Duplicate detection. The same invoice arrives as an email attachment on Tuesday and as a statement PDF on Friday.
  • The state machine. Queues, retries, partial failures, and knowing which of last night's 300 invoices actually made it through.
  • The audit trail. What was extracted, what a human changed, who approved it, and when. Finance will ask, usually during an audit.
  • Everything after the JSON. Field mapping into the accounting system, GL coding, purchase order matching, and the rows it rejects.

The clean signals that it is time to buy rather than build are these. You are past roughly twenty to thirty active supplier layouts. You need line items, not just header fields. More than a handful of your invoices arrive as scans. Your AP team, not your engineering team, needs to fix the mistakes. Failed extractions are delaying payments. Or, most commonly, the person maintaining the parser has stopped shipping anything else.

How Parseur handles it

Parseur is a document parser that does the whole loop, not just the extraction step. Invoices arrive at a dedicated mailbox address, through the API, or from a watched folder. The Vision AI engine reads PDFs, scans and photographs, and the Text AI engine reads emails and text documents. Fields come out named and typed, and line items come out as rows. There are no templates to write and nothing to maintain when a supplier redesigns their invoice.

What you get on top of the JSON is the half this article has been warning you about. Extracted data is reviewable and correctable in place, so an AP person fixes a misread total without opening a ticket. Corrections feed back into the extraction. And the data goes where it needs to go through direct webhook integration, Make, Zapier or Microsoft Power Automate, or straight out of the API as JSON.

If you want to see the field set we extract from invoices by default, it is documented on our invoice OCR page, and the wider workflow is covered in invoice data capture.

Sign up to Parseur for Free
Try out our powerful document processing tool for free.

Conclusion

Extracting invoice data from a PDF with Python is a solved problem for about a hundred invoices and an unsolved one for ten thousand. The code is not what changes between those two numbers. What changes is how many supplier layouts you are quietly signing up to maintain, and who gets paged when one of them moves.

Write the script. It is genuinely worth doing once, if only to find out exactly which of the seven failure modes above hits you first. Then decide honestly whether the next six months of your time are best spent on the eighth. If the answer is no, Parseur has been doing this since 2016 and will take the folder off your hands.

Last updated on

Get started

Ready to automate your
document data extraction?

Start free in minutes and see how Parseur fits into your workflow.

No model training required
Automates data entry from any document
Scales from point-and-click to API

Frequently Asked Questions

The questions developers actually ask once the first invoice script is running and the second supplier has broken it.

Read the text out of the PDF with a library like pdfplumber or pdftotext, then pull named fields out of that text. For born-digital PDFs that is a two-step job. For scans you need an OCR pass first to turn the image into text. The part that decides whether it works in production is not the reading, it is how you get from a wall of text to invoice number, date, vendor and line items when every supplier lays them out differently.

invoice2data is an open-source command line tool and Python library that extracts fields from invoices using YAML templates you write per supplier. It is a good fit when you have a small, stable set of suppliers and you want the matching logic in version control rather than in your code. It stops being a good fit at the point where writing and maintaining one template per supplier costs more than the retyping it replaced.

Treat the line-item table as a separate extraction from the header fields, and reconstruct it across pages before you parse it. pdfplumber's extract_table works on clean ruled tables and returns nothing on borderless ones, which is most supplier invoices. The reliable pattern is to detect the column boundaries once per supplier layout, carry them across page breaks, drop repeated header rows, and then check that the rows you extracted sum to the subtotal. If they do not, you missed a row.

With deterministic checks in your own code, never with the extractor's confidence alone. The core set is arithmetic and identity. Confirm that subtotal plus tax plus shipping minus discount lands on the total, that the line items sum to the subtotal, that the date parses and is not in the future, that the invoice number has not already been paid for that vendor, that the vendor exists in your vendor master, and that the currency is one you expected. Anything that fails goes to a human instead of into the ledger.

When the extraction stops being the hard part. Calling a vision model is cheap and quick to prototype, so if you have a handful of steady suppliers and somebody who can watch the pipeline, build it. The bill arrives later, in the queue and retry logic, the exception review screen your AP team needs, the duplicate detection, the vendor matching, the audit trail, and the accounting integration. Count those before you compare per-page prices.

Yes, but a scan needs an OCR pass before any of the text libraries can see anything. A photographed or scanned invoice is an image wrapped in a PDF, so pdfplumber and pdftotext return an empty string on it. The usual open-source route is Tesseract through the pytesseract binding, after deskewing and cleaning the image. Modern vision models skip that step entirely and read the picture directly.

There is no invoice library, only PDF libraries plus your own logic. pdfplumber is the usual first choice because it exposes words with their coordinates and has a table extractor. PyMuPDF is faster on large batches. pdftotext is the smallest thing that works when you just need the raw text. pytesseract handles scans. Each one gives you text or boxes. None of them tells you which number is the total.

Because a regex matches a string and an invoice is a picture. Your pattern is anchored to a label, a line break, or a column position that the supplier's new template moved. Invoices are semi-structured visual documents, so the things that break you are layout problems rather than string problems. Tables that split across pages, repeated headers, merged cells, and the difference between subtotal, total and amount due are not fixable with a better regular expression.

Yes, and it is now the shortest path from a PDF to structured JSON, but only with a schema and a validator around it. A vision model reads the invoice as a human does, so a supplier changing their layout stops being an event. Constrain the output with a JSON schema, using something like OpenAI structured outputs or a Pydantic model, so you get the same keys every time. Then check the arithmetic yourself. A model that cannot find the invoice number will sometimes produce a plausible one.

Accuracy depends on the field and the scan, not on the product, so treat any single headline percentage as marketing. Printed totals and invoice numbers on a clean born-digital PDF come back near perfectly. The same fields on a photographed, skewed, low-contrast scan are where digits get confused and where a wrong character costs real money. The number worth measuring is not character accuracy but how many invoices reach your accounting system without a human touching them.

You stop writing anything per supplier. Any approach that needs a template, a regex set, or a coordinate map for each vendor grows linearly with your supplier list and shrinks nobody's workload after about twenty to thirty active layouts. A model that reads the document visually is format-independent by design, which is why layout drift stops being a maintenance event. What still needs your attention is the exception queue, and that is a workflow problem rather than an extraction one.

Writing a CSV is a few lines of Python and it is the easy half. The hard half is getting the data into the system that pays the bill, which means mapping your field names to its schema, matching vendors to existing records, handling the rows it rejects, and retrying the calls that fail. Plan for that half from the start, because a folder of CSV files that somebody still imports by hand has not saved anybody an afternoon.