Dateien aus Verzeichnisstrukturen sammeln

Wenn ich Daten anfrage, kommt es häufig vor, dass die Daten in einem großen Paket geliefert werden.
Selten allerdings als eine große Datei.
Aus den verschiedensten Gründen werden die Dateien aufgeteilt. Es entstehen mehrere Dateien, die in mehrere Unterverzeichnisse aufgeteilt abgelegt werden.

Wie bekommst du jetzt einen Überblick über die Daten?
Klar, du musst sie erst einmal wieder zusammenfühen.

In den letzten Artikeln zum Einlesen von CSV Dateien und Zusammenführen von mehreren CSV Dateien habe ich dir schon gezeigt, wie du mehrere Dateien zusammenführen kannst.

In dem Artikel soll es darum gehen, wie du dasselbe Ziel erreichst, aber eben mit Dateien auf mehrere Unterverzeichnisse verteilt.

Dazu habe ich dir ein paar Beispieldaten schon in einer Struktur abgelegt. Du kannst sie also einfach herunterladen und direkt mitarbeiten.

Du weißt nicht, was du mit den Dateien machen sollst? Hier findest du Hilfe.

Hier gibt es auch ein Video dazu:

Die Struktur

Hier ein Überblick, wie die Struktur aussieht.

Wie du siehst, gibt es 3 Ebenen.
Direkt im Verzeichnis liegt eine Datei und zwei Ordner.
In dem einen Ordner (Ebene 1-1) gibt es einen weiteren Ordner (Ebene 2-1).
Darin liegt dann die Ebene 3-1, sowie eine weitere Datei.
In Ebene 3-1 wiederum liegt dann die letzte Datei.
Du hast hier also 3 Ebenen mit und ohne Daten.

Zurück nach oben in mitarbeiter-dateien gibt es neben der Ebene 1-1 noch die Ebene 1-2. Hier findest du eine weitere Datei.

Insgesamt gibt es also zwei Pfade und verschiedene Tiefen mit und ohne Dateien.

Einzelne Verzeichnisse durchlaufen

Wie schon im letzten Artikel gezeigt, kannst du mit dem os.listdir() den Inhalt von einem Verzeichnis auflisten.

Du kannst hier also einfach sagen:

import os

inhalt = os.listdir('/content/drive/MyDrive/dateien/mitarbeiter-dateien')
print(inhalt)

Wie du siehst, bekommst du den Inhalt von mitarbeiter-dateien ausgegeben.
In der Liste findest du sowohl die beiden Ordner ebene 1-1 und ebene 1-2, als auch die Datei mitarbeiter_1.csv.

Im Programm weißt du natürlich noch nicht, was du einlesen kannst und was nicht.
Du musst also zunächst herausfinden, was eine Datei und was ein Ordner ist.

Dafür gibt es im os Modul auch wieder eine Funktion: os.path.isfile().
Die Funktion prüft einfach, ob an einem Pfad eine Datei oder ein Ordner liegt.
Ist es eine Datei, bekommst du True. False entsprechend bei einem Ordner.

Damit kannst du jetzt deine Liste durchlaufen und dir am besten erstmal die Pfade zu den Dateien merken.

import os

basis_pfad = '/content/drive/MyDrive/dateien/mitarbeiter-dateien'
datei_pfade = []
inhalt = os.listdir(basis_pfad)

for eintrag in inhalt:
  voller_pfad = f'{basis_pfad}/{eintrag}'
  if os.path.isfile(voller_pfad):
    datei_pfade.append(voller_pfad)

print(datei_pfade)

Dafür legst du erstmal wieder eine Variable für den Basis-Pfad zu deinen Dateien an.
Und dann noch eine Variable mit einer leeren Liste.
Hier sammelst du deine gefundenen Dateipfade.

Als Nächstes kannst du mit os.listdir(basis_pfad) deinen Pfad einlesen und mit einer for-Schleife die gefundenen Einträge in dem Ordner durchlaufen.

Für jeden Eintrag prüfst du dann mit os.path.isfile(eintrag), ob es sich um eine Datei handelt oder nicht.

Ist es eine Datei, musst du nur noch den vollständigen Pfad zusammenbauen und zu deinen Dateipfaden hinzufügen.

Den Zusammenbau übernimmt ein f-String wieder ganz elegant und schon ist die Datei gefunden.

Die Prozedur kannst du jetzt für jeden Ordner einzeln wiederholen.
Schön dämlich, oder?
Dann kannst du auch gleich manuell alle Pfade zu den einzelnen Dateien aufschreiben. Das geht wahrscheinlich sogar schneller.

Also: Automatisieren!

Eine Verzeichnishierarchie durchlaufen

Ein Verzeichnis mit Unterverzeichnissen nennt man eine Verzeichnishierarchie.
Durch die Prüfung mit os.path.isfile() hast du jetzt schon herausgefunden was eine Datei und was ein Verzeichnis ist.

Das hilft natürlich nicht nur, um Dateien einzusammeln. Das Wissen, was ein Verzeichnis ist, kannst du auch direkt weiter benutzen.

Das Einsammeln von Dateien in dem gefundenen Unterverzeichnis funktioniert genauso, wie bei dem Verzeichnis gerade eben.
Das heißt, dein Code hier ist wiederverwendbar!

Und was machst du mit wiederverwendbarem Code?
Richtig! Du lagerst ihn in eine eigene Funktion aus, damit du ihn auch wiederverwenden kannst.

import os

basis_pfad = '/content/drive/MyDrive/dateien/mitarbeiter-dateien'
datei_pfade = []

def dateien_einsammeln(pfad):
  inhalt = os.listdir(pfad)

  for eintrag in inhalt:
    voller_pfad = f'{pfad}/{eintrag}'
    if os.path.isfile(voller_pfad):
      datei_pfade.append(voller_pfad)

dateien_einsammeln(basis_pfad)
print(datei_pfade)

Was ist eine Funktion?

Eine Funktion ist einfach nur ein Stück Code, dass du unter einem bestimmten Namen wiederverwenden möchtest.

Dafür benutzt du erstmal das Keyword def, um Python mitzuteilen, dass du eine Funktion definieren willst.
Als Nächstes folgt der Name, unter dem du das Stück Code später aufrufen möchtest.

Namen für Funktionen werden kleingeschrieben und im „Snake Case“. Das heißt, besteht dein Name aus mehreren Worten, werden alle kleingeschrieben und mit einem Unterstrich ( _ ) voneinander getrennt.

Achtung: auf keinen Fall Leerzeichen oder Umlaute verwenden!
Auch Sonderzeichen sollten nicht verwendet werden. Und Funktionen dürfen nicht mit Zahlen beginnen.
Ansonsten bist du frei in der Wahl.
Meine Empfehlung ist nur, dass du die Namen so sprechend wie möglich machst, damit du später beim Lesen des Namens auch weißt, was darin passiert.

Nach dem Namen kommen die Funktionsklammern.
Die Klammern brauchst du immer.
Wenn du möchtest, kannst du hier Variablennamen definieren.
Also Variablen, die dann in der Funktion verfügbar sind und die beim Aufruf der Funktion übergeben werden müssen.

Als nächstes ist eine Funktion einfach nur ein Block.
Sämtlicher Code muss also einmal eingerückt werden.

Das war’s auch schon zu Funktionen! Also weiter im Text.

Änderungen am Code

Der Code zum Einlesen des Ordners ist jetzt in eine Funktion mit dem Namen dateien_einsammeln gewandert.

Dazu benutzt du den basis_pfad jetzt nicht mehr direkt, sondern übergibst ihn als Parameter an die Funktion.
Der Code, der vorher den basis_pfad benutzt hat, wird noch umgestellt, um jetzt den Funktionsparameter pfad zu benutzen.
Das ist einmal in Zeile 6 der Parameter, in Zeile 7 das Einlesen des Ordnerinhalts und in Zeile 11 der Zusammenbau des vollständigen Dateipfads.

Da der Code jetzt in einer Funktion gekapselt ist, musst du die Funktion jetzt natürlich noch aufrufen und damit die Ausführung anstoßen. Das machst du in Zeile 13, indem du den Namen der Funktion nennst und mit den Funktionsklammern den Funktionsparameter pfad belegst. Bei dem Aufruf soll als Pfad der basis_pfad genutzt werden. Also gibst du den hier an.

Schon funktioniert dein Code wieder wie vorher, nur eben jetzt mit einer Funktion.

Aber wieso der Aufwand?
Nun ja ganz einfach: Der Code lässt sich jetzt wiederverwenden.
Beim Durchlaufen hast du ja nicht nur festgestellt, was eine Datei ist, sondern eben auch was keine Datei ist.
Und alles, was keine Datei ist, ist ein Ordner und kann auch wieder durchlaufen werden.

Theoretisch kannst du jetzt also zusätzlich zu deinen gefundenen Dateien auch eine Liste für gefundene Ordner anlegen und die dann wiederum durchlaufen. Das müsstest du allerdings für jede Ebene wieder neu und manuell machen.
Auch nicht spaßig.

Also: Wieder Automatisieren 😉
Und genau deshalb hast du den Code wiederverwendbar gemacht.
Welcher Ordner durchsucht wird, ist dem Code ja egal. Es wird der genommen, den du reingibst.
Also gibst du jetzt jeden Ordner rein, den du finden kannst.
Und das automatisch direkt beim Durchlaufen.

import os

basis_pfad = '/content/drive/MyDrive/dateien/mitarbeiter-dateien'
datei_pfade = []

def dateien_einsammeln(pfad):
  inhalt = os.listdir(pfad)

  for eintrag in inhalt:
    voller_pfad = f'{pfad}/{eintrag}'
    if os.path.isfile(voller_pfad):
      datei_pfade.append(voller_pfad)
    else:
      dateien_einsammeln(voller_pfad)

dateien_einsammeln(basis_pfad)
print(datei_pfade)

Was hat sich geändert?
In Zeile 12 kam der else Zweig dazu.

Es wird jetzt also erst geprüft, ob der Eintrag eine Datei ist.
Ist es eine Datei, kommt sie in die Liste der Datei Pfade.
Ist es keine Datei und somit ein Ordner, wird einfach nochmal derselbe Code aufgerufen. Jetzt nur mit dem gefundenen Unterordner, um darin zu suchen.

Hier passiert dann automatisch wieder dasselbe.
Der Code geht die Inhalte von dem Unterordner durch und prüft für jeden Eintrag, ob es eine Datei oder ein Ordner ist.
Ist es eine Datei > ab in die Datei-Liste.
Ist es ein Ordner, wieder sich selbst aufrufen mit dem neuen, gefundenen Pfad.

Lass mich das einmal mit unseren Daten als Beispiel zeigen.
Du rufst die Funktion „dateien_einsammeln“ mit dem Pfad „…mitarbeiter-dateien“ auf.
Also mit deinem Basispfad.

Im ersten Durchlauf mit diesem Basispfad erhältst du eine Liste:

['ebene 1-1', 'ebene 1-2', 'mitarbeiter_1.csv']

Dann kommt die Prüfung:
Ist ‚ebene 1-1‚ eine Datei?
Nein.
Also (Zeile 13) rufe ‚dateien_einsammeln‚ wieder auf.
Diesmal mit dem Pfad ‚mitarbeiter-dateien/ebene 1-1‚.
Damit erhält die Funktion diese Liste:

['ebene 2-1']

Erneute Prüfung:
Ist ‚ebene 2-1‚ eine Datei?
Nein.
Also neuer Aufruf mit ‚mitarbeiter_dateien/ebene 1-1/ebene 2-1
Hier gibt es die Liste:

['ebene 3-1', 'mitarbeiter_4.csv']

Erneute Prüfung: Ist ‚ebene 3-1‚ eine Datei?
Nein.
Also neuer Aufruf mit ‚mitarbeiter-dateien/ebene 1-1/ebene 2-1/ebene 3-1‚.
Hier gibt es diese Liste:

['mitarbeiter_2.csv']

Erneute Prüfung: Ist ‚mitarbeiter_2.csv‚ eine Datei?
Ja.
Also neuer Eintrag in der Liste ‚datei_pfade‚ mit dem Eintrag ‚mitarbeiter-dateien/ebene 1-1/ebene 2-1/ebene 3-1/mitarbeiter_2.csv

Es gibt keine weiteren Einträge in der Liste, also wird die Funktion hier beendet.
Das Beenden führt dazu, dass der vorherige Aufruf fortgesetzt wird.
Das ist der, der mit der Liste hier gearbeitet hat:

['ebene 3-1', 'mitarbeiter_4.csv']

Die ‚ebene 3-1‚ ist gerade geprüft worden.
Der nächste Durchlauf prüft also ‚mitarbeiter_4.csv‚.
Das ist wieder eine Datei, also gibt es einen neuen Eintrag in ‚datei_pfade‚:
mitarbeiter-dateien/ebene 1-1/ebene 2-1/mitarbeiter_4.csv

Damit ist auch diese Funktion fertig, weil es keine weiteren Einträge in der Liste mehr gibt.
Es geht also zurück an den vorherigen Aufruf, der diese Liste hier gefunden hatte:

['ebene 2-1']

Die ‚ebene 2-1‚ ist geprüft und weitere Einträge gibt es nicht.
Also auch hier fertig und wieder einen Schritt zurück:

['ebene 1-1', 'ebene 1-2', 'mitarbeiter_1.csv']

Die ‚ebene 1-1‚ ist damit vollständig geprüft. Also weiter mit ‚ebene 1-2‚.
Hier findet sich die Liste:

['mitarbeiter_3.csv']

Prüfung > ‚mitarbeiter_3.csv‚ ist eine Datei > neuer Eintrag für Dateien ‚mitarbeiter-dateien/ebene 1-2/mitarbeiter_3.csv‚.

Damit ist auch diese Liste abgearbeitet, es geht wieder einen Schritt zurück und der nächste und auch letzte Eintrag ist ‚mitarbeiter_1.csv‚.
Wieder eine Datei, wieder ein Eintrag in der Liste.

Fertig.
Damit sind alle Listen abgearbeitet und du hast alle Datei Pfade in deiner Liste zusammen gesammelt.

Und das alles, was hier passiert ist, nennt sich Rekursion.

Was ist Rekursion?

Rekursion ist, wenn eine Funktion sich selbst wieder aufruft. Im Ablauf einer Funktion wird zum Beispiel ein Verzeichnis durchlaufen. Hat es Unterverzeichnisse, ruft die Funktion sich selbst mit diesem Unterverzeichnis erneut auf.

Viele Anfänger haben Angst vor Rekursion, weil es ja ein so kompliziertes Thema ist.
Aber wie du gerade gesehen hast, muss es das gar nicht sein.
Es ist einfach nur eine Funktion, die sich selbst mit neuen Werten aufruft und wartet, bis der neue Aufruf fertig ist.
Dann macht sie genau da weiter, wo sie vorher aufgehört hat.

Warum ist Rekursion gefährlich?

Bei Rekursion ruft eine Funktion sich selbst wieder auf. Dieser neue Aufruf kann auch wieder sich selbst aufrufen. Wenn keine Bedingung eingebaut wird, dass es nicht zu einem weiteren Aufruf kommt, rufen die Funkionen sich selbst endlos auf.

Das führt dann so weit, bis der Computer nicht mehr in der Lage ist die Aufrufe nachzuverfolgen und einfach aufgibt.
Oder der Arbeitsspeicher aufgebraucht ist, weil jeder Aufruf besonders viele Daten vorhält, während er wartet.

Wenn du bei rekursiven Aufrufen darauf achtest, dass nicht zu viele Daten vorgehalten werden und, dass es auch saubere Abbruch Bedingungen gibt und es damit irgendwann nicht mehr zu weiteren Aufrufen kommt, dann hast du kein Problem und Rekursion ist eine tolle Sache, die dir viel umständliche Arbeit abnehmen kann.

Rekursion ist toll – geht das auch einfacher?

Rekursion wird dir immer wieder begegnen und du solltest auf jeden Fall wissen, was da passiert, warum es passiert und was die Vor- und Nachteile sind.
Deshalb wollte ich dir mit diesem einfachen Beispiel das Thema erklären und näher bringen.

Genau diese Implementierung hat Python aber schon für dich übernommen.
Das rekursive durchlaufen kannst du dir sparen, wenn du aus dem os Modul die Funktion walk() benutzt.

Verzeichnishierarchien durchlaufen

Wie kann ich Verzeichnishierarchien durchlaufen?

Mit os.walk() in einer Schleife durchläuft Python automatisch vollständige Pfadstrukturen. Dabei wird mit jedem Durchlauf das aktuelle Verzeichnis, sowie alle enthaltenen Dateien und Ordner geliefert.

Mit os.walk() liefert Python einen Generator.
Ein Generator muss dich erstmal nicht weiter stören, das ist wieder ein eigenes Thema.
Das einzige, was du dazu wissen musst, ist, dass du einen Generator wie eine Liste auch in einer Schleife durchlaufen kannst.

Wenn du das machst, bekommst du drei Werte mit jedem Durchlauf.
Den Pfad, in dem du dich gerade befindest, alle Unterverzeichnisse in dem aktuellen Pfad und alle Dateien in dem aktuellen Pfad.

import os

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  print('### Neuer Durchlauf ###')
  print(aktuelles_verzeichnis)
  print(unterverzeichnisse)
  print(dateien)

Wie du siehst, werden mit os.walk() automatisch alle möglichen Pfade durchlaufen und jeweils die Verzeichnisse und Dateien schön voneinander getrennt geliefert. Wenn du also nur die Dateien haben willst, sammelst du einfach nur die Dateien ein:

import os

gefundene_dateien = []

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  gefundene_dateien.extend(dateien)

print(gefundene_dateien)

Vollständige Pfade

Erst legst du wieder eine Liste an, die am Ende alle Dateien beinhalten soll. Dann durchläufst du mit os.walk() alle Pfade und erweiterst deine Sammelliste einfach mit allen Dateien, die du findest.

Jetzt hast du aber natürlich nur die Dateinamen eingesammelt. Es fehlt noch der Pfad dazu, damit du die Datei später auch wiederfindest.

Der Pfad wird dir in der Variablen ‚aktuelles_verzeichnis‚ geliefert.
Also musst du lediglich den Pfad mit dem Dateinamen zusammen bauen:

import os

gefundene_dateien = []

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  for datei in dateien:
    gefundene_dateien.append(f'{aktuelles_verzeichnis}/{datei}')

print(gefundene_dateien)

Da os.walk() dir alle Dateien in dem aktuellen Verzeichnis als Liste liefert, durchläufst du die Liste einfach und baust dir für jede Datei den vollständigen Pfad zusammen. Dafür kann ganz einfach wieder ein f-String dienen.

Der Nachteil bei dem f-String ist allerdings, dass einfach ein ‚/‘ zur Trennung im Pfad genutzt wird. Nicht jedes Betriebssystem arbeitet mit einem einfachen ‚/‘ zur Trennung – Windows ist hier ein tolles Beispiel.

Um deine Pfade Betriebssystemunabhängig und damit wiederverwendbar und sicherer zu machen bietet Python dir natürlich auch wieder eine Möglichkeit.

Wie schon erwähnt ist das os Modul für die Kommunikation mit dem Betriebssystem und so auch für Pfade in einem Betriebssystem zuständig.
Deshalb bringt das os Modul ein eigenes Modul ‚path‚ nur für die Pfade mit.

In dem path Modul wiederum findest du eine Funktion join() mit der du Pfade zusammenstecken kannst und sie entsprechend der Vorgabe des Betriebssystems verbunden werden.

Der Umbau dafür ist ganz einfach:

import os

gefundene_dateien = []

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  for datei in dateien:
    gefundene_dateien.append(os.path.join(aktuelles_verzeichnis, datei))

print(gefundene_dateien)

Ersetze deinen f-String einfach mit os.path.join() – os Modul > path Modul > join Funktion – und übergebe erst deinen Pfad und dann das Stück, dass du an den Pfad anfügen willst. Also deinen Dateinamen. Und schon kümmert sich Python um den Rest.

Du kannst deinen Code jetzt natürlich noch ein bisschen abkürzen. Dafür nutzt du an Stelle der for-Schleife eine List Comprehension:

import os

gefundene_dateien = []

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  gefundene_dateien.extend([os.path.join(aktuelles_verzeichnis, datei) for datei in dateien])

print(gefundene_dateien)

Du findest Listen unübersichtlich und hast lieber direkt den Dateinamen und dann erst den Pfad? Dann probier es doch mit einem Dictionary!

import os

gefundene_dateien = {}

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  gefundene_dateien.update({datei: os.path.join(aktuelles_verzeichnis, datei) for datei in dateien})

print(gefundene_dateien)

Durch das update() werden gleich mehrere Einträge zu einem Dictionary zugefügt. Die „Dictionary Comprehension“ erzeugt aus allen Dateien jeweils einen Eintrag mit dem Dateinamen als Schlüssel und dem vollständigen Pfad als Wert.

Dictionaries lassen sich aber nicht sonderlich schön lesen. Also wandelst du die Ausgabe jetzt einfach noch in ein schön formatiertes Json um und schon sind alle Glücklich!

import os, json

gefundene_dateien = {}

for aktuelles_verzeichnis, unterverzeichnisse, dateien in os.walk('/content/drive/MyDrive/dateien/mitarbeiter-dateien'):
  gefundene_dateien.update({datei: os.path.join(aktuelles_verzeichnis, datei) for datei in dateien})

print(json.dumps(gefundene_dateien, indent=4))

Zusammenfassung

Damit können wir für heute abschließen.

Du hast gelernt, wie du mit Python problemlos mit nur wenigen Zeilen Code ganze Verzeichnisstrukturen durchlaufen und dir gezielt Daten herausziehen kannst.

Nicht nur das! Du weißt jetzt auch wie Python das macht, was im Hintergrund passiert und wie du es auch ohne diese Hilfe lösen könntest.

Dafür hast du das os Modul verwendet, um mit dem Betriebssystem zu kommunizieren.
Mit f-Strings hast du Variablen genutzt, um Texte zusammenzubauen.
Und am Ende hat das json Modul dir zu einer schönen Darstellung verholfen.

Ich hoffe du hattest Spaß, hast etwas Neues gelernt und wir sehen uns im nächsten Artikel wieder! 😉

Bis bald.


Scroll to Top