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-Eingangfür neue Dateien,CSV-Archivfür erfolgreich verarbeitete Dateien,CSV-Fehlerfü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.
- Lege eine kleine Testdatei mit der beschriebenen Kopfzeile in
CSV-Eingangab. - Warte bei einer Windows-Synchronisierung, bis die Datei auch im Drive-Ordner sichtbar ist.
- Starte
processCsvFilesim Editor. - Kontrolliere das Blatt
Auswertung. Jede CSV-Datenzeile muss dort als eigene Tabellenzeile erscheinen. - Prüfe, ob die Datei nach
CSV-Archivverschoben wurde und im BlattProtokollden StatusERFOLGerhalten 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
.csvlautet. - Datei landet im Fehlerordner: Lies die letzte Meldung im Blatt
Protokollund vergleiche Kopfzeile, Trennzeichen, Spaltenzahl und Zahlenformat mit dem festgelegten Datenmodell. - Datei liegt im Archiv, aber Werte fehlen: Suche im Blatt
Auswertungnach 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.





