För att extrahera fakturadata från en PDF med Python läser du texten ur filen och plockar sedan ut namngivna fält ur den texten. Två steg, kanske fyrtio rader kod, och det fungerar vackert på den första fakturan du provar det på. Sedan flyttar leverantör nummer sju upp sitt fakturanummer tre rader, och din tolkare börjar returnera None klockan två på natten.
Den andra delen är det verkliga ämnet för den här artikeln. Läsningen är enkel. Att förbli korrekt är själva jobbet.
Viktiga slutsatser
- Python extraherar fakturatext på några få rader. Att hålla det korrekt över hundratals leverantörslayouter är det som faktiskt kostar dig.
- En PDF är inget dataformat. Det är en typografisk beskrivning av en tryckt sida, vilket är anledningen till att ett reguljärt uttryck skrivet mot en leverantörs layout är en ömtålig sak.
- Reguljära uttryck och mallar per leverantör skalar linjärt med din leverantörslista. Synmodeller gör det inte, eftersom de läser sidan istället för strängen.
- Oavsett vad som extraherar datan, måste din egen kod kontrollera matematiken. Rader som inte summeras till delsumman är den billigaste buggdetektor du någonsin kommer att skriva.
- Det är sällan extraktionen i sig som får folk att sluta bygga. Det är undantagskön, leverantörsmatchningen och bokföringsintegrationen.
PDF-formatet
PDF är ett flexibelt format som gör det möjligt att återge pappersdokument, som fakturor, exakt utan att begränsa deras design. Formatet har sina rötter i traditionell tryckproduktion och är skapat för att digitalt efterlikna en tryckt sida. Denna flexibilitet innebär stor frihet, vilket gör att PDF-skapare kan utforma dokumenten fritt och följa olika normer och regler.
Utmaningen uppstår dock när datan är inlåst i en PDF. Formatets fria och komplexa natur kan stå i konflikt med den strukturerade och konsekventa hantering som krävs för den enorma mängd data ett företag bearbetar dagligen.

En PDF lagrar var varje tecken (glyph) sitter på sidan. Den lagrar inte det faktum att siffran nere till höger är totalsumman. Den relationen lever i ditt huvud, och varje extraktionsmetod i den här artikeln är ett försök att koda just det.
Vilka steg krävs för att extrahera data från en faktura?
En faktura är ett dokument som oftast levereras i PDF-format. En faktura formellt dokumenterar en transaktion mellan en leverantör och en kund, där produkter eller tjänster byts mot ett visst belopp. Här är stegen som krävs för att extrahera data från detta dokument:
- Specificera ett dataschema för de uppgifter du vill ta ut från dina fakturor
- Konvertera din faktura från bild till text
- Extrahera texten från din faktura enligt ditt dataschema
- Samla in den extraherade datan

Definiera ett schema för din fakturadata
Fakturor kommer från olika leverantörer och varje leverantör brukar anpassa hur deras fakturor ser ut. Trots denna formmässiga mångfald i verkligheten, är själva innehållet i alla fakturor i grunden detsamma: du behöver en leverantör, en kund, ett fakturanummer, ett datum och en lista med artiklar med tillhörande antal, beskrivning och kostnad. En bra utgångspunkt för att definiera ditt fakturaformat är ditt bokföringssystem, eftersom det med största sannolikhet är där du till slut vill lagra din extraherade fakturadata, eller hur? Om du bara vill ha ett dataformat som kan täcka alla specialfall, rekommenderar vi webbplatsen schema.org, som praktiskt definierar en serie branschstandardiserade dataformat för många saker, inklusive fakturor. Parseur definierar ett standard-dataschema för dina fakturor, men du kan ändra det så att det passar ditt användningsområde genom att byta namn på fälten i din fakturamailbox, så här. När ditt dataformat är definierat kan du konvertera din faktura från bild till text.
Du kan till exempel definiera följande fält för din faktura med JSON Swagger-formatet:
{
"InvoiceNumber": {
"type": "string",
"description": "Fakturanumret"
},
"InvoiceIssueDate": {
"type": "string",
"description": "Fakturadatumet"
},
"Items": {
"type": "array",
"description": "Listan över artiklar i fakturan",
"items": {
"type": "object",
"properties": {
"quantity": {
"type": "number",
"description": "Antalet av artikeln"
},
"description": {
"type": "string",
"description": "Beskrivningen av artikeln"
},
"unit_price": {
"type": "number",
"description": "Styckpriset på artikeln"
},
"price": {
"type": "number",
"description": "Totala priset för artikeln"
}
}
}
}
}
Skriv ner detta innan du skriver någon kod för tolkning. Det är kontraktet som varje metod nedan måste uppfylla, och det är vad du senare kommer att ge till en synmodell (vision model).
Konvertera din faktura från bild till text

En PDF-fil kan innehålla en bild. Till exempel kan en anställd ta ett snabbt foto av en faktura med sin smartphonekamera. De sparar den sedan som PDF och skickar den till ekonomiavdelningen. Ditt ekonomiteam har till uppgift att extrahera datan från denna faktura och på något sätt få in den i ert bokföringssystem utan några misstag. Nästa steg är att konvertera denna bild till text med hjälp av ett Optical Character Recognition-system (OCR). Ett av de populäraste OCR-systemen är Tesseract. Tesseract är skrivet i C och C++. För att använda Tesseract från vårt Python-program behöver vi använda en så kallad binding, som PyTesseract. En binding är ett sätt att anropa ett mjukvarubibliotek (här, Tesseract) från ett språk det inte är skrivet i (här, Python). Det finns många sådana system och deras resultat varierar kraftigt beroende på deras underliggande teknologi och kvaliteten på skanningen av dokumentet de arbetar med. Parseur upptäcker transparent om ditt dokument är en bild och konverterar det automatiskt till text, internt. När dokumentets data väl är i textform är det redo att extraheras.
Extrahera texten från din faktura enligt ditt dataschema
När din PDF är i text- (eller sökbar) form, kan du använda Python-biblioteket pdftotext för att hämta datan ur PDF-filen som text. Här är ett kodexempel för att extrahera texten från en PDF-fil:
import pdftotext
# Ladda din faktura
with open("invoice.pdf", "rb") as file_handle:
pdf = pdftotext.PDF(file_handle)
# Iterera igenom alla sidor
for page in pdf:
print(page)
Döp detta skript till convert_pdf_to_text.py och kör det, så får du fakturan som text till din standard output.
Om du vill omdirigera utdatan till en fil kan du köra:
$ python convert_pdf_to_text.py > invoice.txt
pdftotext ger dig en platt sträng, vilket fungerar utmärkt för huvudfält men är hopplöst för tabeller. När du behöver fakturaraderna, sträck dig istället efter pdfplumber, eftersom det behåller koordinaterna för varje ord och kan göra ett försök på själva tabellen:
import pdfplumber
with pdfplumber.open("invoice.pdf") as pdf:
page = pdf.pages[0]
# Ord med deras positioner på sidan
for word in page.extract_words():
print(word["text"], word["x0"], word["top"])
# Och ett försök att extrahera rad-tabellen
table = page.extract_table()
if table:
for row in table:
print(row)
Kör det på en riktig leverantörsfaktura och du kommer ofta att finna att table är None. Det är ingen bugg. Det är den första ärliga signalen om att det här problemet är svårare än det ser ut, och vi återkommer till det nedan.
Nu när du har fakturan i textform kan du extrahera den data du vill ha från den, med hjälp av valfri kombination av följande tekniker:
- Du kan använda ett reguljärt uttryck för att extrahera den data du vill ha. Reguljära uttryck är ett kraftfullt sätt att extrahera data från text, men de är också mycket sköra. Om fakturaformatet ändras, måste du uppdatera ditt reguljära uttryck. Reguljära uttryck är dessutom inte särskilt bra på att extrahera data från tabeller.
- Du kan använda ett visuellt mallsystem som idealiskt nyttjar Dynamisk OCR och Zonal OCR. Detta är ett mer avancerat sätt att extrahera data från text. Det är robustare än reguljära uttryck, men det är också mer komplext att implementera.
- Du kan ge sidan till en synmodell med ett schema och låta den läsa dokumentet på samma sätt som en person gör. Detta är tillvägagångssättet som har förändrats de senaste två åren, och det får ett eget avsnitt nedan.
Låt oss extrahera data från din faktura med Pythons re-modul för reguljära uttryck. Här är ett kodexempel för att extrahera fakturanumret från din faktura:
import re
# Ladda din faktura
with open("invoice.txt", "r") as file_handle:
invoice = file_handle.read()
# Extrahera fakturanumret
invoice_number = re.search(r"Invoice number: (\w+)", invoice).group(1)
print(invoice_number)
Döp detta skript till extract.py och kör det, så får du fakturanumret utskrivet till terminalen:
$ python extract.py
Och du kommer att få något i stil med:
INV-1234
Varför din regex-fakturatolkare går sönder
Ett regex matchar en sträng. En faktura är en bild. Allt som går snett följer av denna bristande överensstämmelse, och det går snett på ett litet antal förutsägbara sätt:
- Etiketten flyttades. Ditt mönster är förankrat i
Invoice number:men den nya mallen sägerInvoice #, eller så har värdet flyttats till nästa rad istället för att stå på samma. extract_tablereturnerarNone. Tabellextraherare letar efter rader och linjer. De flesta leverantörsfakturor justerar sina kolumner med mellanslag (whitespace) och ritar inga kantlinjer alls.- Texten kommer ut i fel ordning. Tvåkolumnslayouter och flytande adressblock flätas samman när sidan plattas ut till en sträng, så dina rader kommer fram blandade.
- Tabellen sträcker sig över flera sidor. Raderna två till nio är på sida ett, tio till fjorton på sida två, med kolumnrubrikerna upprepade emellan och en rad för delsumma som låtsas vara en artikel.
- Tre siffror ser alla ut som totalsumman. Delsumma, total, att betala och ingående balans. Att bara välja det största beloppet är fel på alla fakturor som innehåller en kredit.
- Skanningen är ett fotografi. OCR tolkar en suddig
8som en3och ingenting längre ner i kedjan märker det, eftersom3är en helt giltig siffra. - Sammanslagna celler och flerradiga beskrivningar. En produktbeskrivning bryts ner på tre rader, och din logik för att dela upp rader gör om det till tre olika artiklar utan priser.
Inget av detta går att fixa med ett bättre reguljärt uttryck. De är alla layoutproblem utklädda till strängproblem. Det är vid denna punkt som de flesta antingen börjar skriva en mall per leverantör, vilket växer i oändlighet, eller ändrar tillvägagångssätt.
2026-metoden – en synmodell och ett schema
Det användbara skiftet är att du inte längre behöver platta ut sidan till text innan du extraherar från den. En synmodell (vision model) tittar på den renderade fakturan, så när en leverantör flyttar sitt fakturanummer är det inte längre något som orsakar problem. Det du tillhandahåller är inget mönster, utan det schema du skrev i början av denna artikel.
Begränsa utdatan så att du får samma nycklar varje gång. Pydantic plus ett läge för strukturerad utdata, såsom OpenAIs strukturerade utdata, gör det åt dig:
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]
# Rendera PDF-sidan till en bild, skicka den till en synmodell,
# och kräv att svaret matchar Invoice-schemat.
# Modellen fyller i fälten. Den får inte uppfinna strukturen.
Detta löser i ärlighetens namn det mesta av extraktionsproblemet, och det är därför det här tillvägagångssättet har spridits så snabbt. Det introducerar dock också en ny felkälla som regex aldrig hade: ett regex som inte kan hitta fakturanumret returnerar None, medan en modell som inte hittar det ibland hittar på ett eget rimligt sådant. Regeln som håller dig säker är enkel. Modellen föreslår och din kod verifierar.
Om du hellre slipper köra modellen själv, säljs samma funktionalitet som en managerad tjänst av molnleverantörerna, i Azure AI Document Intelligence och Amazon Textract's AnalyzeExpense, vilka båda returnerar huvudfält och rader separat. Vi har beskrivit hur den bakomliggande metoden skiljer sig från regelbaserad tolkning i AI kontra regelbaserade PDF-tolkare, och hur det ser ut specifikt tillämpat på fakturor i fakturahantering med AI-seende.
Valideringsskiktet du måste skriva själv
Oavsett vad som skapade din JSON hör dessa kontroller hemma i din kod, inte i extraherarens konfidenspoäng. De är billiga, deterministiska, och det är de som fångar felen som kostar pengar:
delsumma + moms + frakt - rabattlandar exakt påtotalsumma(inom ett öre).- Radernas belopp summeras till delsumman. Om de inte gör det, har du tappat en rad eller uppfunnit en.
- Varje
antal * styckprisär lika med radensbelopp. - Datumet går att tolka, och det är inte i framtiden.
- Fakturanumret har inte redan betalats för den leverantören. Dubbla betalningar är den dyraste buggen i leverantörsreskontra.
- Leverantörens namn går att koppla till en post i ditt leverantörsregister.
- Bankuppgifterna för betalning stämmer överens med de som redan finns registrerade för den leverantören. En ändring här är en bedrägerikontroll, ingen datakontroll.
- Valutan är en som ni faktiskt handlar med.
- Inköpsordernumret finns, och dess antal och priser matchar ifall du kör tvåvägs- eller trevägsmatchning.
- Varje obligatoriskt fält är närvarande och inte tomt.
Allt som misslyckas skickas till en person, inte in i bokföringen:
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
Tio rader matematik kommer att fånga fler verkliga problem än aldrig så mycket finjustering av promptar (prompt tuning).
Hur är det med invoice2data och de andra biblioteken?
invoice2data förtjänar ett ärligt omnämnande, eftersom det är det första resultatet många landar på och det är en genuint bra programvara. Det är ett kommandoradsverktyg och Python-bibliotek som matchar fakturor mot YAML-mallar som du skriver per leverantör, med matchningsreglerna i versionshantering snarare än begravda i din kod. Om du har ett dussin leverantörer som aldrig ändrar sin layout, kommer det att tjäna dig väl i åratal.
Gränsen ligger i designen, inte i kvaliteten. En mall per leverantör innebär att ditt underhåll växer i takt med din leverantörslista, och mallarna går sönder av exakt de skäl som anges ovan. Någonstans mellan tjugo och trettio aktiva layouter lägger personen som underhåller mallarna mer tid på det än vad personen som brukade skriva in fakturorna manuellt gjorde.
Samma resonemang gäller för det bredare utbudet av bibliotek. pdfplumber, PyMuPDF, pdftotext och pytesseract är alla utmärkta på sina faktiska uppgifter, vilket är att få loss tecken och koordinater från en sida. Inget av dem var någonsin tänkt att veta vilken siffra som är totalsumman. Vi jämför en bredare uppsättning i vår genomgång av de bästa PDF-tolkarna och de bästa API:erna för dataextraktion.
Samla in den extraherade datan
Med Python kan du iterera över fakturafiler i en given mapp och extrahera datan från dem. Låt oss säga att vi extraherar fakturanummer och totalsumma, och skriver ut resultatet i CSV-format:
import os
import re
import pdftotext
# Iterera genom alla PDF-filer i mappen
for filename in os.listdir("invoices/"):
if not filename.endswith(".pdf"):
continue
# Ladda din faktura
with open("invoices/" + filename, "rb") as file_handle:
pdf = pdftotext.PDF(file_handle)
# Skriv ut CSV-kolumnrubriken
print("InvoiceNumber,TotalAmount")
# Iterera genom alla sidor
for page in pdf:
# Extrahera fakturanumret
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=",")
Döp detta skript till extract_to_csv.py och kör det, så får du fakturanummer och totalsumma till standard output,
som du kan omdirigera till en CSV-fil som du sedan kan öppna med ditt favoritkalkylprogram, som Excel:
$ python extract_to_csv.py > invoices.csv
En mapp med CSV-filer är där de flesta fakturaskript slutar, och det är också där det riktiga bokföringsarbetet börjar. Någon måste fortfarande importera dem, matcha leverantörerna, jaga rätt på raderna som bokföringssystemet avvisade, och komma på vad man ska göra med fakturan som misslyckades med valideringen klockan 2 på natten. Det arbetet syns inte i ditt skript och det syns inte i din faktura för tokens.
När det är dags att sluta bygga
Här är den del som de flesta leverantörer hoppar över, så vi säger den först. Om du har en handfull stabila leverantörer, en ingenjör som kan övervaka en pipeline och ingen som väntar på datan, skriv då skriptet. En synmodell plus ett schema plus de tio raderna av validering ovan kommer att ta dig långt för väldigt lite pengar, och du kommer att förstå varje del av det.
Notan för att bygga själv kommer senare, och aldrig i själva extraktionen. Den kommer i de delar ingen bygger prototyper för:
- Undantagskön. Ditt AP-team (leverantörsreskontra) behöver en skärm där ett klick på ett fält markerar det på fakturan så att de kan fixa det på fyra sekunder istället för fyrtio. Det är en produkt, inte ett skript.
- Leverantörsmatchning. "ACME Ltd", "Acme Limited" och "ACME LTD." är en och samma leverantör, och huvudboken accepterar inte tre olika.
- Dubblettdetektering. Samma faktura anländer som en e-postbilaga på tisdagen och som ett kontoutdrag i PDF-format på fredagen.
- Tillståndsmaskinen. Köer, återförsök, partiella misslyckanden, och att veta vilka av gårdagens 300 fakturor som faktiskt gick igenom.
- Granskningsspåret. Vad extraherades, vad ändrades av en människa, vem godkände det, och när. Ekonomi kommer att fråga, oftast i samband med en revision.
- Allt efter JSON. Fältmappning in i bokföringssystemet, bokföringskodning (GL-kodning), matchning mot inköpsordrar och raderna systemet avvisar.
De tydliga signalerna på att det är dags att köpa istället för att bygga är dessa. Du är uppe i mer än ungefär tjugo till trettio aktiva leverantörslayouter. Du behöver rader, inte bara huvudfält. Mer än en handfull av dina fakturor anländer som skannade kopior. Ditt AP-team, inte ditt ingenjörsteam, behöver kunna åtgärda felen. Misslyckade extraktioner försenar betalningar. Eller, som är allra vanligast, personen som underhåller tolkaren har slutat leverera någonting annat.
Hur Parseur hanterar det
Parseur är en dokumenttolkare som hanterar hela kedjan, inte bara extraktionssteget. Fakturor anländer till en dedikerad mailbox-adress, via API, eller från en övervakad mapp. AI-motorn för visuell tolkning (Vision AI engine) läser PDF:er, skannade dokument och fotografier, medan AI-textmotorn läser e-post och textdokument. Fält matas ut namngivna och typade, och fakturarader matas ut som rader. Det finns inga mallar att skriva och ingenting att underhålla när en leverantör designar om sin faktura.
Vad du får utöver själva JSON-datan är just den hälft som denna artikel har varnat dig för. Extraherad data kan granskas och korrigeras direkt på plats, så att en person på ekonomiavdelningen kan fixa en felläst totalsumma utan att behöva öppna en ärendeticket. Rättelser matas tillbaka in i extraktionen. Och datan skickas dit den ska via direkt webhook-integration, Make, Zapier eller Microsoft Power Automate, eller matas ut direkt via API:et som JSON.
Om du vill se vilka fält vi extraherar från fakturor som standard finns det dokumenterat på vår sida för faktura-OCR, och det bredare arbetsflödet täcks under fakturadatafångst (invoice data capture).
Slutsats
Att extrahera fakturadata från en PDF med Python är ett löst problem för hundra fakturor och ett olöst problem för tiotusen. Det är inte koden som förändras mellan de två siffrorna. Det som förändras är hur många leverantörslayouter du i tysthet åtar dig att underhålla, och vem som blir larmad när en av dem ändras.
Skriv skriptet. Det är genuint värt att göra det en gång, om så bara för att ta reda på exakt vilken av de sju felkällorna ovan som slår till först. Bestäm dig sedan ärligt för om de kommande sex månaderna av din tid är bäst spenderade på den åttonde felkällan. Om svaret är nej, har Parseur gjort detta sedan 2016 och tar gärna hand om den mappen åt dig.
Senast uppdaterad





