Robustes CSV-Parsing in Python: Umgang mit BOM, Dialekten und Zeilenumbrüchen
Ein Leitfaden zur Verwendung des Python csv-Moduls, um Trennzeichen automatisch zu erkennen, Unicode Byte Order Marks (BOM) mit utf-8-sig zu handhaben und plattformspezifische Zeilenumbruchprobleme mithilfe der Sniffer-Klasse und korrekter Dateieröffnungsparameter zu lösen.
Auf dieser Seite
Die kurze Antwort
Verwenden Sie für eine UTF-8-CSV mit optionaler BOM encoding='utf-8-sig' und newline=''. Ist das Trennzeichen vorgegeben, geben Sie es ausdrücklich an. Bei unbekanntem Dialekt schätzt Sniffer.sniff das Format aus dekodiertem Text; es erkennt nicht, ob eine Kopfzeile vorhanden ist. Prüfen Sie die Spalten vor DictReader und behandeln Sie csv.Error.
Verständnis von CSV-Dialekten und Formaten
Das CSV-Format (Comma Separated Values) verfügt über keinen einzelnen, universellen Standard. Während RFC 4180 eine Richtlinie vorgibt, implementieren viele Anwendungen, insbesondere Microsoft Excel, subtile Variationen bei Trennzeichen, Anführungszeichen und Zeilenenden. Diese Diskrepanzen machen manuelles String-Splitting unzuverlässig, da eine einzelne Datei statt Kommas Semikolons verwenden oder komplexe Anführungszeichen-Regeln enthalten kann.
Das Python csv-Modul abstrahiert diese Unterschiede durch das Konzept eines 'Dialekts'. Ein Dialekt ist eine Sammlung von Parametern - wie dem Trennzeichen, dem Anführungszeichen und dem Zeilenendungs-Zeichen -, die definiert, wie eine bestimmte Anwendung ihre Daten formatiert. Anstatt für jede neue Datenquelle eine eigene Parsing-Logik zu schreiben, können Sie das Modul verwenden, um diese Variationen automatisch zu handhaben.
Automatisierte Formaterkennung mit Sniffer
Sniffer.sniff erhält eine Zeichenkette, keine rohen Bytes. 1024 Zeichen sind ein mögliches Beispiel, keine zuverlässige Mindestgröße oder Garantie. Setzen Sie die Dateiposition nach dem Lesen zurück. Sniffer.has_header ist eine separate, fehleranfällige Heuristik; bekannte Exportspezifikationen sind vorzuziehen.
Umgang mit Byte Order Marks (BOM)
Viele Windows-basierte Anwendungen setzen ein Byte Order Mark (BOM) vor UTF-8-Dateien, um die Kodierung zu identifizieren. Wenn Sie eine solche Datei mit dem Standard-Codec 'utf-8' öffnen, wird das BOM (die Byte-Sequenz 0xef, 0xbb, 0xbf) als tatsächliche Daten behandelt, was oft dazu führt, dass der erste Spaltenname durch unsichtbare Zeichen korrumpiert wird.
Um dies zu lösen, verwenden Sie das 'utf-8-sig'-Encoding in der open()-Funktion. Dieser Codec ist speziell darauf ausgelegt, das UTF-8 BOM zu erkennen und es während des Dekodierungsprozesses zu überspringen, sodass Ihr erster Header-Name sauber und verwendbar bleibt.
Konfiguration der Dateieröffnung für CSV
Ein häufiger Fehler bei der Verwendung des csv-Moduls ist das Versäumnis, den Parameter newline anzugeben. Laut der Python-Dokumentation sollten Sie beim Öffnen einer Datei für das csv-Modul immer newline='' verwenden.
Wenn Sie dies weglassen, führt die Python I/O-Ebene möglicherweise ihre eigene Zeilenumbruch-Übersetzung durch, was zu unerwartetem Verhalten führen kann, wie z. B. zusätzlichen Leerzeilen oder einer fehlerhaften Handhabung von Anführungszeichen-Feldern, die interne Zeilenumbrüche enthalten. Das Setzen von newline='' überträgt die volle Kontrolle über die Zeilenendungen an den internen Parser des csv-Moduls.
Lesen von Daten als Listen mit csv.reader
Die csv.reader-Funktion gibt einen Iterator zurück, der jede Zeile als Liste von Strings liefert. Dies ist ideal, wenn Sie nur an der Position der Daten interessiert sind (z. B. der dritten Spalte) und die Werte nicht auf spezifische Namen mappen müssen. In Kombination mit einem erkannten Dialekt übernimmt der Reader die gesamte Arbeit des Splittens und Entfernens von Anführungszeichen.
import csv
from pathlib import Path
# Illustrative UTF-8 input with BOM, semicolons and a quoted newline.
Path('example.csv').write_text(
'\ufeffID;Name\n1;"Ada; Lovelace"\n2;"Grace\nHopper"\n',
encoding='utf-8', newline=''
)
with open('example.csv', newline='', encoding='utf-8-sig') as f:
sample = f.read(1024) # Characters, not bytes.
f.seek(0)
try:
dialect = csv.Sniffer().sniff(sample, delimiters=',;\t')
except csv.Error as error:
raise ValueError('Cannot determine CSV dialect; specify it explicitly') from error
for row in csv.reader(f, dialect=dialect):
print(row)
# Expected illustrative output:
# ['ID', 'Name']
# ['1', 'Ada; Lovelace']
# ['2', 'Grace\nHopper']Mapping von Zeilen auf Dictionaries mit DictReader
Ohne fieldnames verwendet DictReader die erste Zeile als Schlüssel, unabhängig von Sniffer. Das folgende Beispiel setzt die Kopfzeile ID und Name aus dem vorherigen Beispiel voraus. Geben Sie bei Dateien ohne Kopfzeile fieldnames an oder verwenden Sie csv.reader. Lehnen Sie unerwartete Spalten vor der Verarbeitung ab.
Eine korrekte Kopfzeile garantiert nicht dieselbe Feldzahl in jeder Zeile. DictReader ergänzt fehlende Werte standardmäßig mit None und speichert zusätzliche Werte unter einem None-Schlüssel. Doppelte Spaltennamen können einen Wörterbuchwert überschreiben. Prüfen Sie eindeutige Spaltennamen und die Zeilenstruktur, bevor Sie die Daten als validiert behandeln.
import csv
# Uses example.csv created above; its delimiter and header are known.
with open('example.csv', newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f, delimiter=';')
if reader.fieldnames != ['ID', 'Name']:
raise ValueError('Unexpected CSV header')
for row in reader:
print(row['ID'], repr(row['Name']))
# Expected illustrative output:
# 1 'Ada; Lovelace'
# 2 'Grace\nHopper' Verwaltung der Kodierung und Fehlerbehandlung
Beim Umgang mit vielfältigen Datenquellen können Sie auf Zeichen stoßen, die nicht der erwarteten Kodierung entsprechen. Die open()-Funktion erlaubt ein 'errors'-Argument. Die Verwendung von 'strict' (Standard) löst einen UnicodeDecodeError aus, wenn ein ungültiges Byte gefunden wird, was für die Datenvalidierung nützlich ist. Wenn Sie problematische Zeichen überspringen möchten, können Sie 'ignore' oder 'replace' verwenden.
Stellen Sie immer sicher, dass die Kodierung mit der Quelle übereinstimmt. Während 'utf-8-sig' das BOM handhabt, müssen Sie die Kodierung explizit als 'latin-1' angeben, falls die Datei tatsächlich so kodiert ist, um Dekodierungsfehler zu vermeiden.
Erweiterte Formatierung mit benutzerdefinierten Dialekten
Wenn Sie auf ein hochgradig nicht-standardmäßiges Dateiformat stoßen, das der Sniffer nicht identifizieren kann, können Sie einen benutzerdefinierten Dialekt mit csv.register_dialect() definieren. Dies ermöglicht es Ihnen, das Trennzeichen, das Anführungszeichen und andere Parameter festzulegen, die dann in Ihren Reader- oder Writer-Objekten per Name referenziert werden können. Dies ist besonders nützlich für wiederkehrende proprietäre Formate innerhalb einer Organisation.
Was Sie prüfen sollten
- Überprüfen Sie, ob newline='' im Aufruf der open()-Funktion vorhanden ist.
- Bestätigen Sie, dass encoding='utf-8-sig' verwendet wird, wenn die Datei ein BOM enthält.
- Stellen Sie sicher, dass f.seek(0) nach dem Lesen einer Stichprobe für das Sniffing aufgerufen wird, bevor die Datei an den Reader übergeben wird.
- Prüfen Sie, ob die Stichprobengröße für das Sniffing groß genug ist, um das Trennzeichen zu erfassen.
Geltungsbereich
Die Methode csv.Sniffer.sniff() verwendet Heuristiken und kann falsch-positive oder falsch-negative Ergebnisse liefern, wenn die Stichprobe zu klein oder die Daten hochgradig unregelmäßig sind. DictReader erfordert eine gültige Header-Zeile, um Schlüssel korrekt zu mappen; wenn kein Header vorhanden ist, wird die erste Zeile als Schlüssel verwendet, was zu Datenverlust oder Fehlern führen kann.