post

Daten einlesen in Python: CSV, JSON, Excel und Datenbank

Ein Werkzeugkasten für jede Datenquelle: Wann die Standardbibliothek reicht, wann pandas lohnt, und wie man Encoding, Speicher und SQL-Injection nicht zum Verhängnis werden lässt.

Fast jedes Programm, das ich in Schulungen mit den Teilnehmenden baue, beginnt mit derselben Frage: Woher kommen die Daten? Selten sind es die schönen, aufgeräumten Objekte, die man im Kopf hat. Es ist eine CSV-Datei, die jemand aus einem Export gezogen hat. Eine JSON-Antwort von einer API. Eine Excel-Mappe mit drei Tabellenblättern und einer Kopfzeile, die erst in Zeile vier anfängt. Oder eine SQLite-Datei, die still im Projektordner liegt und darauf wartet, abgefragt zu werden.

Das Einlesen von Daten ist der unspektakuläre erste Schritt, an dem trotzdem erstaunlich viele Projekte hängenbleiben. Nicht, weil es schwer wäre – sondern weil Python für jedes Format einen anderen Weg anbietet und man leicht den Überblick verliert, welcher wann der richtige ist. In diesem Artikel baue ich mit dir einen kleinen Werkzeugkasten auf: die Standardbibliothek für den direkten, zeilenweisen Zugriff, pandas für die tabellarische Analysearbeit, und eine parametrisierte Datenbankabfrage, die dich nicht in die häufigste Sicherheitsfalle laufen lässt.

Die zwei Lager: Standardbibliothek oder pandas

Bevor wir Code schreiben, lohnt eine grobe Einordnung. Python bringt für die gängigsten Formate bereits alles mit, was du brauchst – csv, json, xml.etree und sqlite3 sind Teil der Standardbibliothek, ohne dass du etwas installieren müsstest. Diese Module arbeiten nah an den Rohdaten: Du bekommst Listen, Dictionaries, einzelne Zeilen. Das ist ideal, wenn du eine Datei zeilenweise verarbeiten willst, wenig Speicher zur Verfügung hast oder schlicht keine externe Abhängigkeit in dein Projekt holen möchtest.

pandas gehört ins andere Lager. Es ist eine zusätzliche Bibliothek, und es lohnt sich immer dann, wenn deine Daten tabellarisch sind und du sie analysieren, filtern oder aggregieren willst. Statt für jedes Format ein eigenes mentales Modell zu pflegen, landest du in pandas immer im selben DataFrame – egal, ob die Quelle eine CSV, eine Excel-Mappe, ein JSON-Dokument oder eine SQL-Abfrage war. read_csv, read_excel, read_json, read_sql: ein Reader je Format, ein gemeinsames Ziel. Dazu kommen Typinferenz und ein reichhaltiger Satz an Operationen.

Der folgende Überblick zeigt den Weg von der Quelle zur Zielstruktur:

flowchart LR
  CSV[CSV] --> STD[Standardbibliothek<br/>csv, json, etree, sqlite3]
  JSON[JSON] --> STD
  XML[XML] --> STD
  DB[(SQLite/DB)] --> STD
  CSV --> PD[pandas<br/>read_csv, read_excel, read_sql]
  XLSX[Excel .xlsx] --> PD
  DB --> PD
  STD --> DICTS[dicts / Listen]
  PD --> DF[DataFrame]

Die Faustregel, die ich in Schulungen weitergebe: Zeilenweise, klein, ohne Zusatzabhängigkeit – Standardbibliothek. Tabellarisch, analytisch, mit Filtern und Aggregaten – pandas. Und Excel im modernen .xlsx-Format bekommst du ohnehin nur über pandas (mit einer Hilfsbibliothek) sinnvoll gelesen, denn die Standardbibliothek kann das nicht. Wenn dich das Thema Werkzeugwahl grundsätzlich interessiert, habe ich das im Artikel Python für Entwickler, die Systeme verstehen wollen breiter ausgeführt.

CSV mit der Standardbibliothek: der Klassiker

CSV ist das Format, das dir am häufigsten begegnet, und gleichzeitig das mit den meisten kleinen Tücken. Die Standardbibliothek bietet zwei Wege: csv.reader liefert pro Zeile eine Liste, csv.DictReader liefert pro Zeile ein Dictionary und nutzt dafür die Kopfzeile automatisch als Schlüssel. In der Praxis greife ich fast immer zu DictReader, weil der Zugriff über sprechende Spaltennamen den Code lesbarer macht.

import csv

with open("customers.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)          # header row becomes dict keys
    rows = [row for row in reader]      # each row is a dict

for row in rows[:3]:
    print(row["name"], row["email"])

Zwei Details an dieser open-Zeile sind wichtiger, als sie aussehen. Erstens newline="": Das csv-Modul kümmert sich selbst um Zeilenumbrüche innerhalb von Feldern, und wenn du die Datei nicht mit leerem newline öffnest, kann es unter bestimmten Bedingungen zu zerrissenen Zeilen kommen. Das steht so in der Dokumentation, und es ist eine dieser Regeln, die man einmal lernt und dann nie wieder vergisst – meist nach dem ersten kaputten Datensatz.

Zweitens encoding="utf-8". Dazu gleich mehr, denn hier steckt der Fallstrick, der in meinen Schulungen die meiste Verwirrung stiftet.

Ein schöner Nebeneffekt von DictReader: Weil er die Datei als Iterator durchläuft, kannst du ihn auch für große Dateien nutzen, ohne alles auf einmal in den Speicher zu laden. Statt die Liste per Comprehension zu materialisieren, verarbeitest du dann Zeile für Zeile in der Schleife und behältst nur, was du wirklich brauchst.

JSON: von der Datei zum Python-Objekt

JSON ist in Python angenehm unkompliziert, weil sich die Struktur fast eins zu eins auf Dictionaries und Listen abbildet. json.load liest aus einem geöffneten Datei-Objekt, json.loads aus einer Zeichenkette. Das Ergebnis ist ein ganz normales Python-Objekt, mit dem du sofort weiterarbeiten kannst.

import json

with open("config.json", encoding="utf-8") as f:
    data = json.load(f)         # dict or list, depending on the document

print(data["version"])
for item in data["items"]:
    print(item["id"], item["title"])

Für das Zurückschreiben gibt es die Gegenstücke json.dump und json.dumps. Beim Schreiben lohnt sich meist ensure_ascii=False, damit Umlaute als echte Zeichen und nicht als \u-Escapes in der Datei stehen, sowie indent=2 für eine lesbare Formatierung. Beim Lesen brauchst du diese Optionen nicht – da ist JSON erfreulich anspruchslos.

Excel: hier führt der Weg über pandas

Sobald echte Tabellen ins Spiel kommen – mehrere Blätter, Zahlenformate, Kopfzeilen – wird pandas zum Mittel der Wahl. read_excel liest ein .xlsx in einen DataFrame, und du kannst gezielt ein Tabellenblatt über sheet_name auswählen.

import pandas as pd

df = pd.read_excel("report.xlsx", sheet_name="Sales")  # openpyxl engine by default
print(df.head())
print(df["revenue"].sum())

Ein Punkt, über den viele beim ersten Mal stolpern: read_excel braucht für .xlsx eine zusätzliche Bibliothek namens openpyxl. Seit pandas 1.2.0 (Ende 2020) ist openpyxl der Standard-Engine für dieses Format – die ältere xlrd-Bibliothek unterstützt moderne .xlsx-Dateien nicht mehr und ist nur noch für die alten .xls-Dateien zuständig. Wenn du also eine Fehlermeldung über eine fehlende Engine bekommst, ist die Lösung fast immer ein pip install openpyxl.

Gibst du sheet_name=None an, liefert pandas dir ein Dictionary aller Tabellenblätter als DataFrames zurück – praktisch, wenn du eine Mappe komplett einlesen willst. Und wenn deine Kopfzeile nicht in der ersten Zeile steht, hilft der header-Parameter, die richtige Zeile als Spaltennamen zu setzen.

Datenbanken: sqlite3 und der wichtigste Sicherheitshinweis

SQLite ist mein Standardbeispiel für Datenbankzugriff in Schulungen, weil es ohne Server auskommt und das Modul sqlite3 bereits in der Standardbibliothek steckt. Der Ablauf ist immer gleich: Verbindung öffnen, einen Cursor holen, eine Abfrage ausführen, das Ergebnis abholen, Verbindung schließen.

import sqlite3

conn = sqlite3.connect("shop.db")
cur = conn.cursor()

category = "books"
cur.execute(
    "SELECT name, price FROM products WHERE category = ?",
    (category,),
)
result = cur.fetchall()

for name, price in result:
    print(name, price)

conn.close()

Der entscheidende Teil steht in der execute-Zeile: das Fragezeichen als Platzhalter und der Wert als separates Tupel im zweiten Argument. Das ist keine Stilfrage, sondern eine Sicherheitsfrage. Wer stattdessen den Wert direkt in den SQL-String hineinbaut – per Konkatenation oder f-String – öffnet die Tür für SQL-Injection. Ein manipuliertes Eingabefeld kann dann die Abfrage umschreiben, Daten auslesen oder ganze Tabellen löschen.

Der Vergleich macht den Unterschied deutlich:

# unsafe: never do this
cur.execute(f"SELECT name, price FROM products WHERE category = '{category}'")

# safe: the driver handles quoting and escaping
cur.execute(
    "SELECT name, price FROM products WHERE category = ?",
    (category,),
)

Der parametrisierte Weg hat noch einen zweiten Vorteil neben der Sicherheit: Der Datenbanktreiber übernimmt das Quoting und Escaping für dich. Du musst dir keine Gedanken um Anführungszeichen im Wert machen. Ein wichtiges Detail dabei: Der Platzhalter ? steht ohne umschließende Anführungszeichen im SQL-Text – das Quoting macht der Treiber, nicht du.

Wenn du mehr Komfort willst oder zwischen verschiedenen Datenbanken wechseln möchtest, kommt SQLAlchemy ins Spiel. Mit create_engine("sqlite:///shop.db") baust du eine Verbindung auf, und pandas.read_sql(query, con) liest das Ergebnis direkt in einen DataFrame. Damit schließt sich der Kreis zwischen Datenbank und Analyse: Die Abfrage kommt aus SQL, das Ergebnis liegt sofort im vertrauten pandas-Modell.

Und XML? Nicht vergessen, aber selten gebraucht

XML begegnet dir seltener als früher, taucht aber in Konfigurationen, Exporten und älteren Schnittstellen weiterhin auf. Die Standardbibliothek deckt das mit xml.etree.ElementTree ab. Du parst die Quelle zu einem Baum, holst dir die Wurzel und navigierst über find, findall und iter zu den Elementen, die dich interessieren.

import xml.etree.ElementTree as ET

tree = ET.parse("catalog.xml")
root = tree.getroot()

for product in root.findall("product"):
    name = product.find("name").text
    price = product.get("price")        # read an attribute
    print(name, price)

Für einfache Strukturen reicht das vollkommen. Bei komplexeren XML-Dokumenten mit Namespaces oder tiefer Verschachtelung wird es schnell fummelig – dann greife ich gern zu spezialisierten Bibliotheken. Für den Alltag deckt ElementTree aber die meisten Fälle ab.

Welcher Reader für welches Format?

Damit du die Übersicht behältst, hier die Zuordnung als kompakte Liste:

  • Für CSV nimmst du csv.DictReader aus der Standardbibliothek zum zeilenweisen Lesen und pandas.read_csv, sobald es analytisch wird.
  • Für JSON reichen json.load und json.loads aus der Standardbibliothek; pandas.read_json hilft, wenn das JSON tabellarisch ist.
  • Für Excel im .xlsx-Format führt der Weg über pandas.read_excel mit installiertem openpyxl – die Standardbibliothek kann dieses Format nicht.
  • Für Datenbanken deckt sqlite3 den direkten, parametrisierten Zugriff ab; pandas.read_sql oder SQLAlchemy kommen dazu, wenn Analyse oder Portabilität gefragt sind.
  • Für XML genügt xml.etree.ElementTree aus der Standardbibliothek bei überschaubaren Dokumenten.

Drei Fallstricke, die immer wieder zuschlagen

In der Praxis sind es selten die Reader selbst, die Probleme machen, sondern drei wiederkehrende Themen rund ums Einlesen.

Encoding

Der Standard-Zeichensatz beim Öffnen einer Datei ist plattformabhängig – unter Windows oft cp1252, nicht UTF-8. Wenn deine Umlaute plötzlich als ä oder ö erscheinen oder du einen UnicodeDecodeError bekommst, ist fast immer das Encoding schuld. Die Lösung ist, es explizit anzugeben: encoding="utf-8". Für Dateien, die aus Excel exportiert wurden, brauchst du oft encoding="utf-8-sig", weil Excel eine sogenannte Byte Order Mark an den Anfang setzt, die das schlichte utf-8 als Sonderzeichen missdeutet.

Große Dateien komplett im Speicher

Es ist verlockend, eine CSV einfach mit pandas.read_csv("big.csv") komplett zu laden – bis die Datei mehrere Gigabyte hat und der Speicher überläuft. pandas bietet dafür chunksize: Statt eines DataFrames bekommst du dann einen Iterator, über den du die Datei in Blöcken abarbeitest.

import pandas as pd

total = 0
for chunk in pd.read_csv("big.csv", chunksize=100_000):
    total += chunk["amount"].sum()     # process one block at a time

print(total)

In der Standardbibliothek erreichst du dasselbe, indem du über DictReader Zeile für Zeile iterierst, statt alles in eine Liste zu materialisieren. Ein Hinweis noch: read_excel kennt kein natives Chunking – bei sehr großen Tabellen lohnt es sich, die Daten vorher nach CSV oder in eine Datenbank zu überführen.

SQL-Injection

Dieser Punkt kam schon vor, aber er ist wichtig genug für die Wiederholung: Baue niemals Benutzereingaben per String-Konkatenation oder f-String in eine SQL-Abfrage ein. Nutze immer Platzhalter und übergib die Werte separat. Das kostet dich keine zusätzliche Zeile Code und erspart dir eine ganze Klasse von Sicherheitslücken.

Der folgende Entscheidungsbaum fasst zusammen, wie ich in der Praxis den Weg wähle:

flowchart TD
  A{Tabellarische<br/>Analyse nötig?} -->|ja| B[pandas]
  A -->|nein| C{Zeilenweise,<br/>klein, ohne Abhängigkeit?}
  C -->|ja| D[Standardbibliothek]
  C -->|nein| B
  B --> E{Sehr große Datei?}
  E -->|ja| F[chunksize / Streaming]
  E -->|nein| G[direkt einlesen]

Fazit

Daten einzulesen ist kein Nebenschauplatz, sondern die Stelle, an der sich entscheidet, ob der Rest des Programms auf solidem Grund steht. Die gute Nachricht: Python liefert für jede Quelle das passende Werkzeug, und die Wahl ist meist einfach. Für zeilenweisen, direkten Zugriff ohne Zusatzabhängigkeit reicht die Standardbibliothek mit csv, json, xml.etree und sqlite3. Sobald es tabellarisch und analytisch wird oder Excel im Spiel ist, zahlt sich pandas mit seinem einheitlichen DataFrame-Modell aus.

Drei Dinge nimmst du am besten dauerhaft mit: Setze das Encoding immer explizit, damit Umlaute nicht zerfallen. Denke bei großen Dateien früh an chunksize oder zeilenweises Streamen, statt den Speicher vollzuladen. Und arbeite bei Datenbankabfragen ausnahmslos mit Platzhaltern, nie mit zusammengebautem SQL. Wer diese drei Reflexe verinnerlicht hat, kommt bei fast jeder Datenquelle sicher und schnell ans Ziel – und kann sich dem eigentlich Interessanten widmen: dem, was in den Daten steckt.

Weiterführende Quellen

Kommentare

Kommentar schreiben