
Hoe analyseer ik een PDF in Python? Stap voor stap tutorial
Het kan voorkomen dat je als bijvoorbeeld data analist moet werken met een dataset vanuit een PDF-bestand. Idealiter probeer je altijd de data alsnog in een gestructureerd formaat te verkrijgen, zoals in CSV- of JSON-formaat. Maar het kan zijn dat dit niet mogelijk is. Denk aan data die verkregen is via een export vanuit een programma of systeem. Waarbij er ofwel geen andere exportopties zijn, of hier niet voor gekozen is. Dit kan bijvoorbeeld een PDF zijn met hierin een grote tabel met financiële transacties. Om weer met de data in tabelvorm te kunnen werken, zul je enkele handelingen moeten verrichten.
In deze blog laten we vanuit een voorbeeld zien hoe je stap voor stap een PDF met hierin data in tabelvorm uitleest met programmeertaal Python. Om deze vervolgens verder te kunnen gebruiken.
1. Voorbeeld PDF: financiële transacties
In deze blog gaan we met een voorbeeld PDF-bestand met hierin een tabel met financiële transacties, verdeeld over 28 pagina's. De eerste pagina ziet er als volgt uit:

De tweede pagina:

En de laatste (28e) pagina:

We zien hierin dat de eerste pagina algemene gegevens bevat, en kolomnamen. Vervolgens een verzameling rijen met financiële transacties. De tweede pagina tot en met de laatste pagina bevatten geen algemene gegevens, maar alleen rijen met financiële transacties.
In deze blog gaan we de rijen met financiële transacties vanuit alle pagina's met Python uitlezen naar een pandas DataFrame. Om het vervolgens op te kunnen slaan in een gestructureerd formaat, zoals een CSV-bestand. Ook voeren we een simpele analyse uit met de data.
2. Uitlezen van een PDF in Python
Python is een generieke programmeertaal die voor allerlei toepassingen kan worden gebruikt. Zo ook voor het uitlezen en analyseren van een PDF. We gaan met verschillende Python packages aan de slag om dit te doen. Doordat Python een open source programmeertaal is met een rijk ecosysteem, zijn er diverse packages die voor deze toepassingen te gebruiken zijn.
In deze blog gebruiken we de volgende packages:
- pandas: voor het werken met data in tabelvorm.
- PyPDF2: voor het uitlezen van een PDF.
- tabula-py: voor het uitlezen van tabellen in een PDF naar een pandas DataFrame.
We gebruiken specifiek deze packages omdat de PDF de data direct bevat. Het kan ook voorkomen dat een PDF afbeeldingen van data bevat. Denk aan ingescande documenten. Ook hier zijn mogelijkheden voor met bijvoorbeeld zogeheten OCR-tools (Optical Character Recognition). Hier staan we in deze blog niet verder bij stil.
3. Importeren van packages en definiëren van constanten
Allereerst importeren we in Python de te gebruiken packages:
import sys
import PyPDF2
import tabula
import pandas as pd
print("Python:", sys.version)
print("PyPDF2:", PyPDF2.__version__)
print("tabula:", tabula.__version__)
print("pandas:", pd.__version__)Output:
Python: 3.8.9 (tags/v3.8.9:a743f81, Apr 2 2021, 11:10:41) [MSC v.1928 64 bit (AMD64)]
PyPDF2: 3.0.1
tabula: 2.9.3
pandas: 1.3.3Vervolgens stellen we de referentie in naar het bestandspad van de PDF waarmee we gaan werken:
FILEPATH_TRANSACTIONS_PDF = "data/analyze_pdf_in_python/transactions_20251231_landscape.pdf"Dit hergebruiken we later.
3. PDF uitlezen met PyPDF2
Met package PyPDF2 kun je allerlei handelingen met PDF-bestanden vanuit Python uitvoeren. Denk naast uitlezen aan inkorten of samenvoegen van PDF-bestanden.
In onderstaande code gebruiken we PyPDF2 om de PDF te openen en het aantal pagina's uit te lezen. We verwijzen hierbij naar het bestandspad van de PDF.
reader = PyPDF2.PdfReader(FILEPATH_TRANSACTIONS_PDF)
count_pages = len(reader.pages)
print(count_pages)Output:
28We zien dat de PDF is uitgelezen, en inderdaad bestaat uit 28 pagina's.
4. Tabel op PDF-pagina uitlezen met tabula-py
We hebben in de screenshots van de PDF gezien dat iedere pagina data in tabelvorm bevat. Met package tabula-py kunnen we dit uitlezen.
Met onderstaande code doen we een poging om de data uit de eerste pagina uit te lezen:
- Met de (
read_pdf()) functie lezen we een pagina uit de PDF uit. - Met
input_pathverwijzen we naar de bestandslocatie van de PDF. - Met
pagesgeven we het paginanummer op.
Hierbij krijgen we (indien aanwezig) de gevonden tabellen van de PDF-pagina terug. Dit kunnen er ook meerdere zijn.
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=1,
)
print("Tables count on PDF page:", len(tables))
print("Shape of table:", tables[0].shape)
tables[0]Output:
Tables count on PDF page: 1
Shape of table: (26, 5)| Unnamed: 0 | Unnamed: 1 | Unnamed: 2 | Rekeningafschrift | Unnamed: 3 | |
|---|---|---|---|---|---|
| 0 | nan | nan | Rekeningnummer | Datum afschrift | Aantal transacties |
| 1 | ONDERNEMERSREKENING | nan | 12.34.56.789 | 31/12/2025 | 834 |
| 2 | Volgnummer | Boekdatum | Omschrijving | Bedrag af (debet) | Bedrag bij (credit) |
| 3 | 1 | 02/01/2024 | Factuur ZL342560 | nan | 603,9 |
| 4 | 2 | 02/01/2024 | Factuur ZL797708 | 943,3 | nan |
| 5 | 3 | 02/01/2024 | Factuur ZL634455 | 237,3 | nan |
| 6 | 4 | 03/01/2024 | Factuur ZL146909 | 603 | nan |
| 7 | 5 | 03/01/2024 | Factuur ZL951417 | nan | 703,4 |
| 8 | 6 | 05/01/2024 | Factuur ZL675929 | nan | 426,3 |
| 9 | 7 | 05/01/2024 | Factuur ZL946947 | 776,8 | nan |
We zien vanuit de output het volgende:
- Er is één tabel uit de eerste pagina van de PDF uitgelezen.
- Deze tabel is direct bruikbaar als pandas DataFrame.
- Echter, de tabel bevat ook de algemene gegevens, die niet direct bij de tabel met financiële gegevens horen.
- Hierdoor kloppen bijvoorbeeld ook de kolomnamen niet.
Als volgende stap gaan we diverse experimenten uitvoeren om met tabula-py precies de juiste gegevens te verkrijgen.
5. Verdieping: tabel op PDF-pagina uitlezen met tabula-py
We kunnen divese aanpassingen doen om met tabula-py de data op de gewenste manier uit te kunnen lezen. Dit bekijken we voor verschillende pagina's.
5.1. Uitlezen van de eerste pagina
We hebben gezien dat we de data van de eerste pagina in tabelvorm uit kunnen lezen, maar dat dit nog niet helemaal goed gaat.
Met onderstaande code gebruiken we het area argument om op de eerste pagina slechts een deel van de pagina uit te lezen. Hierin lezen we de hele pagina uit, maar pas vanaf 25% van de bovenkant:
top = 25
left = 0
bottom = 100
right = 100
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=1,
area=[top, left, bottom, right],
relative_area=True,
)
print("Shape of table:", tables[0].shape)
tables[0]Output:
Shape of table: (23, 7)| Volgnummer | Boekdatum | Omschrijving | Bedrag af (debet) | Unnamed: 0 | Bedrag bij (credit) | Unnamed: 1 | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 02/01/2024 | Factuur ZL342560 | nan | nan | nan | 603,9 |
| 1 | 2 | 02/01/2024 | Factuur ZL797708 | nan | 943,3 | nan | nan |
| 2 | 3 | 02/01/2024 | Factuur ZL634455 | nan | 237,3 | nan | nan |
| 3 | 4 | 03/01/2024 | Factuur ZL146909 | nan | 603 | nan | nan |
| 4 | 5 | 03/01/2024 | Factuur ZL951417 | nan | nan | nan | 703,4 |
We zien het volgende:
- De algemene informatie wordt niet meer uitgelezen: dat gaat goed.
- Er ontstaan alleen extra, lege, kolommen, wat niet gewenst is.
Een andere manier voor het overslaan van de algemene informatie laten we met onderstaande code zien. Daarbij gebruiken we selecties met iloc[] (integer-location based indexing), om alleen de gewenste data te verkrijgen.
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=1,
)
df = tables[0].iloc[3:]
df.columns = tables[0].iloc[2]
dfOutput:
Shape of table: (23, 5)| Volgnummer | Boekdatum | Omschrijving | Bedrag af (debet) | Bedrag bij (credit) | |
|---|---|---|---|---|---|
| 3 | 1 | 02/01/2024 | Factuur ZL342560 | nan | 603,9 |
| 4 | 2 | 02/01/2024 | Factuur ZL797708 | 943,3 | nan |
| 5 | 3 | 02/01/2024 | Factuur ZL634455 | 237,3 | nan |
| 6 | 4 | 03/01/2024 | Factuur ZL146909 | 603 | nan |
| 7 | 5 | 03/01/2024 | Factuur ZL951417 | nan | 703,4 |
Dit ziet er goed uit. Deze methode kunnen we gebruiken voor data van de eerste pagina
5.2. Uitlezen van pagina's 2 en verder
De pagina's vanaf pagina 2 bevatten alleen rijen met financiële transacties, geen kolomnamen of andere details.
Met onderstaande code lezen we de tweede pagina uit.
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=2,
)
print("Shape of table:", tables[0].shape)
tables[0]Output:
Shape of table: (30, 5)| 24 | 21/01/2024 | Factuur ZL831783 | 832,3 | Unnamed: 0 | |
|---|---|---|---|---|---|
| 0 | 25 | 21/01/2024 | Factuur ZL171775 | nan | 77,9 |
| 1 | 26 | 22/01/2024 | Factuur ZL742540 | nan | 738,3 |
| 2 | 27 | 23/01/2024 | Factuur ZL468213 | 984,4 | nan |
| 3 | 28 | 23/01/2024 | Factuur ZL831750 | 334,7 | nan |
| 4 | 29 | 26/01/2024 | Factuur ZL801904 | 848,9 | nan |
Hierin zien we dat het uitlezen goed gaat, behalve de eerste rij. Deze wordt gezien gebruikt voor de kolomnamen. Dit corrigeren we met onderstaande code:
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=2,
pandas_options={'header': None}
)
print("Shape of table:", tables[0].shape)
tables[0]Output:
Shape of table: (31, 5)| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 24 | 21/01/2024 | Factuur ZL831783 | 832,3 | nan |
| 1 | 25 | 21/01/2024 | Factuur ZL171775 | nan | 77,9 |
| 2 | 26 | 22/01/2024 | Factuur ZL742540 | nan | 738,3 |
| 3 | 27 | 23/01/2024 | Factuur ZL468213 | 984,4 | nan |
| 4 | 28 | 23/01/2024 | Factuur ZL831750 | 334,7 | nan |
Hiermee voorkomen we dat de eerste rij op een pagina als kolomnamen wordt gebruikt.
Wanneer we deze code nu echter hergebruiken om de rijen van de laatste pagina (28) uit te lezen, zien we het volgende:
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=28,
pandas_options={'header': None}
)
print("Shape of table:", tables[0].shape)
tables[0]Output:
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_8420/2191584453.py in <module>
5 )
6
----> 7 print("Shape of table:", tables[0].shape)
8 tables[0]
IndexError: list index out of rangeWe krijgen een foutmelding. Kennelijk kan er op de laatste pagina geen tabel gevonden worden. Ondanks dat deze er weldegelijk staat. Dit kunnen we oplossen door van het eerder gebruikte area argument gebruik te maken:
top = 0
left = 0
bottom = 100
right = 100
tables = tabula.read_pdf(
input_path=FILEPATH_TRANSACTIONS_PDF,
pages=28,
area=[top, left, bottom, right],
relative_area=True,
pandas_options={'header': None}
)
print("Shape of table:", tables[0].shape)
tables[0]Output:
Shape of table: (5, 5)| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| 0 | 830 | 29/12/2025 | Factuur ZL113965 | nan | 77,1 |
| 1 | 831 | 29/12/2025 | Factuur ZL592938 | nan | 819,8 |
| 2 | 832 | 31/12/2025 | Factuur ZL642481 | 128 | nan |
| 3 | 833 | 31/12/2025 | Factuur ZL139523 | nan | 853,3 |
| 4 | 834 | 31/12/2025 | Factuur ZL624240 | 315,8 | nan |
Door deze aanpassing kunnen we alsnog de tabel op de laatste pagina uitlezen.
6. Samenvoegen van code
We hebben tot nu toe code ontwikkeld voor:
- Uitlezen van de PDF en het bepalen van het aantal pagina's
- Uitlezen van de tabel met financiële transacties op de eerste pagina
- Uitlezen van de tabel met financiële transacties op pagina's 2 en verder
Met onderstaande code hergebruiken we dit, om de transacties van alle pagina's om te kunnen zetten naar één pandas DataFrame. Daarin doen we het volgende:
- Uitlezen van het aantal pagina's
- Aanmaken van een leeg DataFrame voor de combinatie van alle data.
- Met een
[for-loop](https://datasciencepartners.nl/python-for-loop/ "for-loop")elk van de pagina's uitlezen en omzetten naar een DataFrame. - Toevoegen van elke tabel aan het eerder aangemaakte DataFrame.
# Read PDF pages count
reader = PyPDF2.PdfReader(FILEPATH_TRANSACTIONS_PDF)
count_pages = len(reader.pages)
# Create empty dataframe
df_all_pages = pd.DataFrame()
# Define PDF page area
top = 0
left = 0
bottom = 100
right = 100
# Iterate over all pages in PDF and read table content
for page_number in range(1, count_pages + 1):
# Read tables on specific page
tables = tabula.read_pdf(
FILEPATH_TRANSACTIONS_PDF,
pages=page_number,
area=[top, left, bottom, right],
relative_area=True,
pandas_options={'header': None}
)
# First page is different
if page_number == 1:
df_current_page = tables[0].iloc[4:]
column_names = tables[0].iloc[3]
else:
df_current_page = tables[0]
# Set column names
df_current_page.columns = column_names
# Concatenate tables
df_all_pages = pd.concat([df_all_pages, df_current_page], ignore_index=True)
# Show result
print("Shape of table:", df_all_pages.shape)
df_all_pagesOutput:
Shape of table: (834, 5)| Volgnummer | Boekdatum | Omschrijving | Bedrag af (debet) | Bedrag bij (credit) | |
|---|---|---|---|---|---|
| 0 | 1 | 02/01/2024 | Factuur ZL342560 | nan | 603,9 |
| 1 | 2 | 02/01/2024 | Factuur ZL797708 | 943,3 | nan |
| 2 | 3 | 02/01/2024 | Factuur ZL634455 | 237,3 | nan |
| 3 | 4 | 03/01/2024 | Factuur ZL146909 | 603 | nan |
| 4 | 5 | 03/01/2024 | Factuur ZL951417 | nan | 703,4 |
Het resultaat ziet er goed uit: we hebben alle 834 rijen vanuit de 28 pagina's uit de PDF uitgelezen.
7. Analyseren van de data
We hebben de data uitgelezen en het is nu beschikbaar vanuit een pandas DataFrame. Daarmee kunnen er gemakkelijk analyses op uitgevoerd worden. Ook kan het daarmee gemakkelijk worden opgeslagen in een ander formaar. Bijvoorbeeld met de to_csv() methode voor een CSV-bestand, of de to_sql() methode voor opslag in een SQL database.
We gaan een simpele analyse uitvoeren en een visualisatie maken. Hiervoor voeren we met onderstaande code eerst enkele voorbewerkingen uit.
- We stellen voor enkele kolommnen de juiste datatypes in.
- Voegen een kolom toe die het jaar en de maand van de boekdatum bevat.
# Set column data types
float_columns = [
"Bedrag af (debet)",
"Bedrag bij (credit)",
]
date_columns = [
"Boekdatum",
]
for float_column in float_columns:
df_all_pages[float_column] = df_all_pages[float_column].str.replace(pat=",", repl=".")
df_all_pages[float_column] = df_all_pages[float_column].astype(float)
for date_column in date_columns:
df_all_pages[date_column] = pd.to_datetime(df_all_pages[date_column])
# Add calculated columns
df_all_pages["Jaar-Maand"] = df_all_pages["Boekdatum"].dt.strftime("%Y-%m")Vervolgens maken we voor de analyse een visualisatie aan. We willen de af- of bijgeboekte bedragen per maand inzichtelijk maken. Dit doen we met onderstaande code.
- We groeperen per maand en aggregeren de boekingen.
- We maken een staafgrafiek.
# Group by month and aggregate
df_by_month = (
df_all_pages
.groupby("Jaar-Maand")
.agg(
{
"Bedrag af (debet)": "sum",
"Bedrag bij (credit)": "sum",
}
)
)
# Plot result
df_by_month.plot(
title="Bedrag af en bij per maand",
kind="bar",
)Output:

Met dit voorbeeld hebben we laten zien dat de uit de PDF uitgelezen transacties gemakkelijk zijn te analyseren en/of visualiseren met package pandas.
Om te onthouden
We hebben gezien dat we met Python en diverse packages PDF-bestanden met daarin data in tabelvorm uit kunnen lezen. Het is daarbij altijd wenselijk om voordat je daarmee aan de slag gaat te onderzoeken of je de data niet alsnog in een ander, gestructureerder, formaat kunt verkrijgen. Zoals een CSV- of JSON-bestand. Want zoals we ook hebben gezien kunnen er allerlei issues zijn die opgelost moeten worden. Bijvoorbeeld het ontbreken van kolomnamen, of pagina's die niet direct goed uitgelezen konden worden. Maar met wat uitzoek is voor ieder issue een oplossing te vinden, waardoor je alsnog gebruik kunt maken van data uit een PDF-bestand.
Wil je nog veel meer leren over visualisaties met Python?
Schrijf je dan in voor onze Python cursus voor data science, Python advanced training, onze machine learning cursus, of voor onze data science opleiding en leer met vertrouwen te programmeren en visualiseren in Python. Nadat je een van onze trainingen hebt gevolgd kun je zelfstandig verder aan de slag. Je kunt ook altijd even contact opnemen als je een vraag hebt.
Download één van onze opleidingsbrochures voor meer informatie











