CSV-Dateien aus Windows automatisch mit Google Apps Script auswerten

Lesedauer: 10 Min – Beitrag erstellt: 13. September 2026, zuletzt aktualisiert: 13. September 2026
Transparenzhinweis: Dieser Beitrag wurde ganz oder teilweise mithilfe generativer KI erstellt. Das Titelbild stammt entweder aus eigener KI-gestützter Erstellung oder aus einer lizenzierten Bildquelle.

Google Apps Script kann CSV-Dateien nicht unmittelbar aus einem lokalen Windows-Ordner lesen. Für eine automatische Verarbeitung müssen die Dateien zuerst in einen festgelegten Google-Drive-Ordner gelangen, etwa über Google Drive für Desktop oder einen manuellen Upload. Ein zeitgesteuerter Apps-Script-Trigger liest neue Dateien dort ein, prüft ihre Struktur, schreibt die berechneten Werte in Google Tabellen und verschiebt die Quelldateien anschließend in einen Archiv- oder Fehlerordner.

Die folgende Lösung verwendet ein festes Datenmodell mit den Spalten Datum, Artikel, Menge und Einzelpreis. Sie ist auf semikolongetrennte CSV-Dateien mit UTF-8-Codierung und deutschem Dezimalkomma ausgelegt. Trennzeichen, Spalten und Zahlenformat lassen sich im Skript anpassen.

Warum ein lokaler Windows-Ordner nicht direkt erreichbar ist

Google Apps Script wird in der Google-Cloud ausgeführt und besitzt keinen direkten Zugriff auf Laufwerke wie C:DatenCSV. Auch ein zeitgesteuerter Trigger läuft unabhängig davon, ob der Windows-PC eingeschaltet ist. Der lokale Ordner muss deshalb mit einem Ordner in Google Drive verbunden werden.

Dafür bieten sich zwei Arbeitsweisen an:

  • Synchronisierter Ordner: Google Drive für Desktop überträgt Dateien aus einem eingebundenen Windows-Ordner nach Google Drive. Erst nach erfolgreicher Synchronisierung kann das Skript sie verarbeiten.
  • Manueller oder anderweitig automatisierter Upload: Eine Anwendung exportiert die CSV-Datei, danach wird sie in den vorgesehenen Drive-Eingangsordner hochgeladen.

Das Skript überwacht also nicht Windows selbst, sondern einen Drive-Ordner. Diese Trennung ist für die Fehlerdiagnose wichtig: Erscheint eine Datei nicht in Google Drive, liegt das Problem vor der Apps-Script-Verarbeitung. Ist sie in Drive vorhanden, aber nicht ausgewertet, sind Trigger, Dateiformat und Skriptausführung zu prüfen.

Ordner und Zieltabelle vorbereiten

Lege in Google Drive drei getrennte Ordner an:

  • CSV-Eingang für neue Dateien,
  • CSV-Archiv für erfolgreich verarbeitete Dateien,
  • CSV-Fehler für Dateien, deren Aufbau nicht verarbeitet werden konnte.

Die benötigten IDs findest du in den jeweiligen Adressen von Drive-Ordnern und Google Tabellen. Bei einem Drive-Ordner ist die ID der Zeichenabschnitt hinter folders/. Bei einer Tabelle steht sie zwischen /d/ und /edit. Übernimm nur diese IDs in die Konfiguration und nicht die vollständigen Adressen.

Die erwartete CSV-Struktur sieht beispielsweise so aus:

Datum;Artikel;Menge;Einzelpreis
2026-03-01;USB-Adapter;2;12,50
2026-03-01;HDMI-Kabel;3;8,90

Für die erste Datenzeile berechnet das Skript 2 × 12,50 = 25,00. Die zweite Zeile ergibt 3 × 8,90 = 26,70. In der Zieltabelle werden Menge, Einzelpreis und Gesamtwert als Zahlen gespeichert, sodass Summen und Diagramme darauf aufbauen können.

Vollständiges Apps Script für Einlesen, Prüfung und Auswertung

Öffne über die Google-Tabelle den Apps-Script-Editor und ersetze den vorhandenen Inhalt durch den folgenden vollständigen Code. Trage anschließend die vier IDs im Objekt CONFIG ein. Die Funktion processCsvFiles ist später das Ziel des zeitgesteuerten Triggers.

const CONFIG = {
  inputFolderId: 'ID_DES_EINGANGSORDNERS',
  archiveFolderId: 'ID_DES_ARCHIVORDNERS',
  errorFolderId: 'ID_DES_FEHLERORDNERS',
  spreadsheetId: 'ID_DER_GOOGLE_TABELLE',
  delimiter: ';',
  encoding: 'UTF-8',
  maxFilesPerRun: 20
};

function processCsvFiles() {
  const lock = LockService.getScriptLock();
  if (!lock.tryLock(1000)) {
    console.log('Eine andere Verarbeitung läuft bereits.');
    return;
  }

  try {
    const inputFolder = DriveApp.getFolderById(CONFIG.inputFolderId);
    const archiveFolder = DriveApp.getFolderById(CONFIG.archiveFolderId);
    const errorFolder = DriveApp.getFolderById(CONFIG.errorFolderId);
    const spreadsheet = SpreadsheetApp.openById(CONFIG.spreadsheetId);
    const outputSheet = getOrCreateOutputSheet_(spreadsheet);
    const logSheet = getOrCreateLogSheet_(spreadsheet);
    const files = inputFolder.getFiles();
    let processedCount = 0;

    while (files.hasNext() && processedCount < CONFIG.maxFilesPerRun) {
      const file = files.next();
      const fileName = file.getName();

      if (!fileName.toLowerCase().endsWith('.csv')) {
        continue;
      }

      processedCount++;

      try {
        const csvText = file.getBlob().getDataAsString(CONFIG.encoding);
        const parsedRows = Utilities.parseCsv(csvText, CONFIG.delimiter);
        const resultRows = validateAndTransform_(parsedRows, file);

        removeExistingRowsForFile_(outputSheet, file.getId());

        if (resultRows.length > 0) {
          outputSheet
            .getRange(outputSheet.getLastRow() + 1, 1, resultRows.length, resultRows[0].length)
            .setValues(resultRows);
        }

        appendLog_(logSheet, file, 'ERFOLG', resultRows.length, '');
        file.moveTo(archiveFolder);
      } catch (error) {
        appendLog_(logSheet, file, 'FEHLER', 0, error.message);
        file.moveTo(errorFolder);
      }
    }
  } finally {
    lock.releaseLock();
  }
}

function validateAndTransform_(rows, file) {
  if (rows.length === 0) {
    throw new Error('Die CSV-Datei ist leer.');
  }

  const expectedHeader = ['Datum', 'Artikel', 'Menge', 'Einzelpreis'];
  const header = rows[0].map(value => String(value).trim());

  if (header.length !== expectedHeader.length) {
    throw new Error('Die Kopfzeile enthält nicht genau vier Spalten.');
  }

  for (let column = 0; column < expectedHeader.length; column++) {
    if (header[column] !== expectedHeader[column]) {
      throw new Error(
        'Spalte ' + (column + 1) + ' muss ' + expectedHeader[column] + ' heißen.'
      );
    }
  }

  const output = [];

  for (let rowIndex = 1; rowIndex < rows.length; rowIndex++) {
    const row = rows[rowIndex];

    if (row.every(value => String(value).trim() === '')) {
      continue;
    }

    if (row.length !== expectedHeader.length) {
      throw new Error(
        'Zeile ' + (rowIndex + 1) + ' enthält nicht genau vier Spalten.'
      );
    }

    const dateText = String(row[0]).trim();
    const article = String(row[1]).trim();
    const quantity = parseGermanNumber_(row[2]);
    const unitPrice = parseGermanNumber_(row[3]);

    if (dateText === '') {
      throw new Error('In Zeile ' + (rowIndex + 1) + ' fehlt das Datum.');
    }

    if (article === '') {
      throw new Error('In Zeile ' + (rowIndex + 1) + ' fehlt der Artikel.');
    }

    if (!Number.isFinite(quantity) || !Number.isFinite(unitPrice)) {
      throw new Error(
        'Zeile ' + (rowIndex + 1) + ' enthält eine ungültige Zahl.'
      );
    }

    output.push([
      file.getName(),
      file.getId(),
      dateText,
      article,
      quantity,
      unitPrice,
      quantity * unitPrice
    ]);
  }

  return output;
}

function parseGermanNumber_(value) {
  const normalized = String(value)
    .trim()
    .replace(/s/g, '')
    .replace(/./g, '')
    .replace(',', '.');

  if (normalized === '') {
    return NaN;
  }

  return Number(normalized);
}

function getOrCreateOutputSheet_(spreadsheet) {
  const sheetName = 'Auswertung';
  let sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  }

  if (sheet.getLastRow() === 0) {
    sheet.appendRow([
      'Quelldatei',
      'Datei-ID',
      'Datum',
      'Artikel',
      'Menge',
      'Einzelpreis',
      'Gesamtwert'
    ]);
    sheet.setFrozenRows(1);
  }

  return sheet;
}

function getOrCreateLogSheet_(spreadsheet) {
  const sheetName = 'Protokoll';
  let sheet = spreadsheet.getSheetByName(sheetName);

  if (!sheet) {
    sheet = spreadsheet.insertSheet(sheetName);
  }

  if (sheet.getLastRow() === 0) {
    sheet.appendRow([
      'Zeitpunkt',
      'Dateiname',
      'Datei-ID',
      'Status',
      'Datenzeilen',
      'Meldung'
    ]);
    sheet.setFrozenRows(1);
  }

  return sheet;
}

function removeExistingRowsForFile_(sheet, fileId) {
  const lastRow = sheet.getLastRow();

  if (lastRow < 2) {
    return;
  }

  const fileIds = sheet.getRange(2, 2, lastRow - 1, 1).getValues();

  for (let index = fileIds.length - 1; index >= 0; index--) {
    if (String(fileIds[index][0]) === fileId) {
      sheet.deleteRow(index + 2);
    }
  }
}

function appendLog_(sheet, file, status, dataRows, message) {
  sheet.appendRow([
    new Date(),
    file.getName(),
    file.getId(),
    status,
    dataRows,
    message
  ]);
}

Der Code verarbeitet pro Lauf höchstens 20 Dateien. Diese Begrenzung verhindert, dass ein großer Eingang einen einzelnen Skriptlauf unnötig verlängert. Verbleibende Dateien werden beim nächsten Triggerlauf berücksichtigt.

Erster Test vor der Automatisierung

Führe processCsvFiles zunächst einmal manuell im Apps-Script-Editor aus. Beim ersten Start fordert Google Berechtigungen für Drive und Google Tabellen an. Prüfe dabei, ob das angezeigte Google-Konto Zugriff auf alle drei Ordner sowie die Zieltabelle besitzt.

Anleitung
1Lege eine kleine Testdatei mit der beschriebenen Kopfzeile in CSV-Eingang ab.
2Warte bei einer Windows-Synchronisierung, bis die Datei auch im Drive-Ordner sichtbar ist.
3Starte processCsvFiles im Editor.
4Kontrolliere das Blatt Auswertung. Jede CSV-Datenzeile muss dort als eigene Tabellenzeile erscheinen.
5Prüfe, ob die Datei nach CSV-Archiv verschoben wurde und im Blatt Protokoll den Status ERFOLG erhalten hat.

  1. Lege eine kleine Testdatei mit der beschriebenen Kopfzeile in CSV-Eingang ab.
  2. Warte bei einer Windows-Synchronisierung, bis die Datei auch im Drive-Ordner sichtbar ist.
  3. Starte processCsvFiles im Editor.
  4. Kontrolliere das Blatt Auswertung. Jede CSV-Datenzeile muss dort als eigene Tabellenzeile erscheinen.
  5. Prüfe, ob die Datei nach CSV-Archiv verschoben wurde und im Blatt Protokoll den Status ERFOLG erhalten hat.

Mit den beiden Beispielzeilen müssen in der Spalte Gesamtwert die Zahlen 25 und 26,7 stehen. Werden sie linksbündig als Text dargestellt oder entstehen falsche Größenordnungen, stimmt das Zahlenformat der Quelldatei wahrscheinlich nicht mit parseGermanNumber_ überein.

Den Zeittrigger für den automatischen Lauf einrichten

Erst nach einem erfolgreichen manuellen Test sollte der Trigger eingerichtet werden. Öffne im Apps-Script-Editor den Bereich Trigger, füge einen neuen Trigger hinzu und wähle folgende Zuordnung:

  • auszuführende Funktion: processCsvFiles,
  • Bereitstellung: Head,
  • Ereignisquelle: zeitgesteuert,
  • Intervall: passend zum tatsächlichen Importbedarf.

Zeitgesteuerte Apps-Script-Ausführungen starten nicht zwingend sekundengenau. Für typische Dateiimporte ist ein Intervall von einigen Minuten oder ein stündlicher Lauf meist geeigneter als die Erwartung einer sofortigen Reaktion. Benötigt ein Prozess eine Verarbeitung direkt nach dem Windows-Export, reicht ein Zeittrigger allein nicht für eine harte Echtzeitzusage.

Der verwendete Script Lock verhindert, dass zwei Ausführungen gleichzeitig denselben Eingangsordner bearbeiten. Kann ein Lauf die Sperre nicht übernehmen, beendet er sich ohne Änderung. Die Dateien bleiben im Eingang und werden bei einer späteren Ausführung erneut gefunden.

Wie die Prüfung fehlerhafte CSV-Dateien behandelt

Die gesamte Datei gilt als fehlerhaft, sobald eine Datenzeile vom festgelegten Schema abweicht. Dadurch gelangen keine teilweise plausiblen Ergebnisse unbemerkt in die Auswertung. Das Skript prüft insbesondere:

  • ob die Datei Inhalt besitzt,
  • ob die Kopfzeile exakt vier erwartete Spalten enthält,
  • ob jede Datenzeile ebenfalls vier Felder besitzt,
  • ob Datum und Artikel nicht leer sind,
  • ob Menge und Einzelpreis in Zahlen umgewandelt werden können.

Bei einem Fehler schreibt das Skript die Meldung in das Blatt Protokoll und verschiebt die Datei nach CSV-Fehler. Eine Meldung wie Zeile 4 enthält nicht genau vier Spalten. deutet häufig auf ein falsches Trennzeichen oder ein nicht korrekt eingeschlossenes Trennzeichen innerhalb eines Feldes hin. Bearbeite die Quelldatei und lege eine korrigierte Fassung anschließend wieder im Eingangsordner ab.

Trennzeichen, Codierung und Zahlenformat richtig anpassen

CSV ist kein vollständig einheitliches Format. Ein Windows-Programm kann Semikolon, Komma oder Tabulator als Trennzeichen verwenden. Öffne eine Beispieldatei bei Bedarf in einem Texteditor und prüfe die tatsächlichen Zeichen, statt dich allein auf die Darstellung in Excel zu verlassen.

Für eine kommagetrennte Datei änderst du in CONFIG den Wert von delimiter auf ein Komma. Die Funktion zur deutschen Zahlenumwandlung geht dagegen davon aus, dass Punkte Tausendertrennzeichen und Kommas Dezimaltrennzeichen sind. Ein Wert wie 1.234,56 wird damit zu 1234,56.

Enthält die Quelle englisch formatierte Zahlen wie 1234.56, darf die Funktion nicht unverändert verwendet werden: Sie würde den Punkt entfernen und daraus 123456 machen. Für solche Dateien muss die Zahlenumwandlung passend zur Quelle geändert werden, beispielsweise durch direktes Einlesen mit Number(String(value).trim()). Mische beide Zahlenformate nicht innerhalb derselben Spalte.

Umlaute oder Sonderzeichen, die als Ersatzzeichen erscheinen, weisen meistens auf eine abweichende Zeichencodierung hin. Das Skript liest UTF-8. Stelle den Export unter Windows möglichst auf UTF-8 um. Eine Anpassung der Skriptcodierung ist nur sinnvoll, wenn das exportierende Programm dauerhaft eine andere, eindeutig bekannte Codierung verwendet.

Entscheidungsroute bei ausbleibender Verarbeitung

Eine kurze Prüfreihenfolge trennt Synchronisierungs-, Format- und Triggerprobleme voneinander:

  • Datei fehlt in Google Drive: Prüfe Google Drive für Desktop, die Synchronisierung des Windows-Ordners und mögliche Uploadkonflikte. Das Skript kann die Datei in diesem Zustand nicht sehen.
  • Datei liegt im Drive-Eingang, aber bleibt dort: Starte die Funktion manuell und öffne im Apps-Script-Editor die Ausführungen. Prüfe außerdem, ob die Dateiendung wirklich .csv lautet.
  • Datei landet im Fehlerordner: Lies die letzte Meldung im Blatt Protokoll und vergleiche Kopfzeile, Trennzeichen, Spaltenzahl und Zahlenformat mit dem festgelegten Datenmodell.
  • Datei liegt im Archiv, aber Werte fehlen: Suche im Blatt Auswertung nach dem Dateinamen oder der Datei-ID. Kontrolliere bei einer leeren Auswertung, ob die Quelldatei außer der Kopfzeile überhaupt Datenzeilen enthielt.
  • Der manuelle Lauf funktioniert, der automatische nicht: Prüfe, ob der Trigger vorhanden ist, die richtige Funktion aufruft und unter einem Konto mit Zugriff auf Ordner und Tabelle ausgeführt wird.

Doppelte Daten, große Importe und veränderte Quelldateien

Vor dem Schreiben entfernt das Skript vorhandene Ergebniszeilen mit derselben Drive-Datei-ID. Wird dieselbe Datei während eines unterbrochenen Ablaufs erneut verarbeitet, entstehen dadurch normalerweise keine doppelten Zeilen. Eine neu hochgeladene Kopie erhält jedoch eine andere Drive-ID und gilt deshalb als neue Quelle, selbst wenn Dateiname und Inhalt identisch sind.

Für eine strengere Dublettenprüfung wäre zusätzlich ein Hash des Dateiinhalts nötig. Das ist sinnvoll, wenn Benutzer regelmäßig Kopien derselben Exportdatei hochladen. Eine Prüfung nur anhand des Dateinamens ist riskant, weil wiederkehrende Exporte häufig identisch benannt werden, obwohl sie neue Daten enthalten.

Bei großen Dateien wird das zeilenweise Löschen vorhandener Ergebnisse langsamer. Dann ist ein anderes Datenmodell geeigneter: Jede Quelldatei erhält ein eigenes Tabellenblatt, alte Importblöcke werden gesammelt ersetzt oder die Daten werden in einem dafür ausgelegten Speicherdienst verarbeitet. Google Tabellen und Apps Script besitzen Ausführungs- und Größenbeschränkungen, deren verfügbare Kontingente vom verwendeten Konto und Dienststand abhängen können. Den tatsächlichen Umfang solltest du deshalb mit repräsentativen Dateien testen, nicht nur mit einer kleinen Beispieldatei.

Zugriffsrechte und sichere Ordnertrennung

Das Apps Script arbeitet mit den Berechtigungen des Kontos, das den Trigger angelegt hat. Verliert dieses Konto den Zugriff auf einen Ordner oder die Tabelle, kann die Verarbeitung ausfallen. Bei gemeinsam genutzten Abläufen sollte deshalb klar geregelt sein, wem Ordner, Tabelle, Skript und Trigger gehören.

Lege ausschließlich erwartete Importdateien in den Eingangsordner. Das Skript ignoriert zwar Dateien ohne CSV-Endung, doch sensible Exporte bleiben auch nach der Verarbeitung in Google Drive gespeichert. Zugriffsrechte, Aufbewahrungsdauer und Löschregeln müssen zum Inhalt der CSV-Dateien passen. Das Archiv ist kein Backup des ursprünglichen Windows-Systems, sondern lediglich der Nachweis, welche hochgeladenen Dateien verarbeitet wurden.

Mit der Trennung aus Windows-Export, Drive-Eingang, validierter Auswertung und Protokoll bleibt jeder Verarbeitungsschritt überprüfbar. Sobald der manuelle Test einschließlich Archivierung erfolgreich ist, kann der Zeittrigger den regelmäßigen Import übernehmen; Fehlerdateien bleiben getrennt und lassen sich anhand ihrer protokollierten Meldung gezielt korrigieren.


Unsere Redaktion

Über 15 Jahre Erfahrung mit Windows- und PC-Problemen aller Art. Wir sind Euer Technikratgeber seit 2009.

Mitarbeiter Porträt Martin Keller

Martin Keller

34, Hamburg, gelernter IT-Systemadministrator und Schachfreund. Mag außerdem gerne gutes Bier.

Mitarbeiter Porträt Daniel Cho

Daniel Cho

29, Frankfurt am Main, Data Analyst. Fotografie-begeistert und Stratege durch und durch. Kann alles.

Mitarbeiterin Porträt Sofia Mendes

Sofia Mendes

27, Köln, Projektmanagerin. Workshop-Junkie und Handy-süchtig. Sprachen-Genie mit italienischen Wurzeln.

Mitarbeiter Porträt Tobias Wagner

Tobias Wagner

36, Stuttgart, Softwareentwickler. Digital Native und PC-Freak durch und durch. Spielt perfekt Gitarre.

Mitarbeiter Porträt Enzokuhle Dlamini

Enzokuhle Dlamini

55, Düsseldorf, Personalmanagerin. Liebt ihren Garten genauso wie WordPress. Geboren in Südafrika.

Mitarbeiter Porträt Joachim Freising

Joachim Freising

52, Bergisch-Gladbach, Teamleiter IT. Technik-affin. Hat für jedes Problem eine Lösung parat. Sehr geduldig.

Unsere Redaktion:

Über 15 Jahre Erfahrung mit Windows- und PC-Problemen aller Art. Wir sind Euer Technikratgeber seit 2009.

Mitarbeiter Porträt Martin Keller

Martin Keller

Mitarbeiter Porträt Daniel Cho

Daniel Cho

Mitarbeiterin Porträt Sofia Mendes

Sofia Mendes

Mitarbeiter Porträt Tobias Wagner

Tobias Wagner

Mitarbeiter Porträt Enzokuhle Dlamini

Enzokuhle Dlamini

Mitarbeiter Porträt Joachim Freising

Joachim Freising

Schreibe einen Kommentar