Nettoyer et normaliser des fichiers CSV pour les importer dans Google Sheets et Drive
Ceci est unDocument Extraction, Multimodal AIworkflow d'automatisation du domainecontenant 10 nœuds.Utilise principalement des nœuds comme Code, Webhook, GoogleDrive, GoogleSheets. Nettoyer et normaliser les fichiers CSV à importer dans Google Sheets et Drive
- •Point de terminaison HTTP Webhook (généré automatiquement par n8n)
- •Informations d'identification Google Drive API
- •Informations d'identification Google Sheets API
Nœuds utilisés (10)
{
"meta": {
"instanceId": "2000c64071c20843606b95c63795bb0797c41036047055a6586498e855b96efc"
},
"nodes": [
{
"id": "24e5fd68-0441-4541-b543-fcaef4f8ec6c",
"name": "Instructions de configuration",
"type": "n8n-nodes-base.stickyNote",
"position": [
-496,
-112
],
"parameters": {
"width": 760,
"height": 696,
"content": "🧹 **SETUP REQUIRED:**\n\n1. **Upload Method:**\n - Send CSV files via webhook\n - Use form-data or base64 encoding\n - Max file size: 10MB recommended\n\n2. **Google Sheets (Optional):**\n - Connect Google Sheets OAuth\n - Replace YOUR_GOOGLE_SHEET_ID\n - Or disable Sheets node if not needed\n\n3. **Google Drive (Optional):**\n - Connect Google Drive OAuth\n - Set destination folder ID\n - Cleaned CSVs saved automatically\n\n4. **Cleaning Rules:**\n - Removes duplicates, empty rows\n - Standardizes formats (phone, email)\n - Fixes common data issues\n - Validates required columns\n\n🎯 Upload dirty CSV → Get clean data!"
},
"typeVersion": 1
},
{
"id": "b452e138-2c91-4934-8be6-e3c190407db3",
"name": "Téléchargement CSV Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-160,
160
],
"webhookId": "csv-upload-webhook",
"parameters": {
"path": "csv-upload",
"options": {
"noResponseBody": false
},
"httpMethod": "POST"
},
"typeVersion": 1
},
{
"id": "e6a91fe0-7d79-4e45-900b-5200b6af7cbf",
"name": "Extraire le contenu CSV",
"type": "n8n-nodes-base.code",
"position": [
48,
160
],
"parameters": {
"jsCode": "// Extract and validate CSV upload\nconst requestData = $input.first();\nlet csvContent = '';\nlet filename = 'uploaded_file.csv';\n\nif (requestData.binary && requestData.binary.data) {\n csvContent = requestData.binary.data.toString();\n filename = requestData.binary.data.filename || filename;\n} else if (requestData.json.csv_content) {\n csvContent = requestData.json.csv_content;\n filename = requestData.json.filename || filename;\n} else if (requestData.json.file_base64) {\n csvContent = Buffer.from(requestData.json.file_base64, 'base64').toString();\n filename = requestData.json.filename || filename;\n} else {\n throw new Error('No CSV content found in request');\n}\n\nif (!csvContent || csvContent.length < 10) {\n throw new Error('Invalid or empty CSV file');\n}\n\nconst initialRows = csvContent.split('\\n').length - 1;\n\nreturn {\n json: {\n filename,\n original_content: csvContent,\n file_size_bytes: csvContent.length,\n initial_row_count: initialRows,\n upload_timestamp: new Date().toISOString(),\n processing_started: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "57875349-1fa5-454b-aa02-1255a1c87fb4",
"name": "Analyser les données CSV",
"type": "n8n-nodes-base.code",
"position": [
240,
160
],
"parameters": {
"jsCode": "// Parse and clean CSV data\nconst uploadInfo = $input.first().json;\nconst csvContent = uploadInfo.original_content;\n\nconst lines = csvContent.split('\\n').map(line => line.trim()).filter(line => line.length > 0);\nif (lines.length === 0) throw new Error('No valid rows found in CSV');\n\nconst headers = lines[0].split(',').map(header => header.replace(/\"/g, '').trim().toLowerCase().replace(/\\s+/g, '_'));\n\nconst rawRows = [];\nfor (let i = 1; i < lines.length; i++) {\n const values = lines[i].split(',').map(val => val.replace(/\"/g, '').trim());\n if (values.length === headers.length && values.some(val => val.length > 0)) {\n const row = {};\n headers.forEach((header, index) => row[header] = values[index] || '');\n rawRows.push(row);\n }\n}\n\nreturn {\n json: {\n ...uploadInfo,\n headers,\n raw_rows: rawRows,\n parsed_row_count: rawRows.length\n }\n};"
},
"typeVersion": 2
},
{
"id": "275eb9f3-40bc-4c2e-bd8e-7dd707f9dc21",
"name": "Nettoyer et standardiser les données",
"type": "n8n-nodes-base.code",
"position": [
448,
160
],
"parameters": {
"jsCode": "// Clean CSV rows\nconst data = $input.first().json;\nlet cleanedRows = [...data.raw_rows];\n\nconst cleanEmail = e => e ? (e.toLowerCase().trim().match(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/) ? e : '') : '';\nconst cleanPhone = p => p ? p.replace(/\\D/g, '') : '';\nconst cleanName = n => n ? n.trim().split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ') : '';\nconst cleanText = t => t ? t.trim().replace(/\\s+/g, ' ') : '';\n\ncleanedRows = cleanedRows.map(row => {\n const cleaned = {};\n Object.keys(row).forEach(key => {\n let value = row[key];\n if (key.includes('email')) cleaned[key] = cleanEmail(value);\n else if (key.includes('phone')) cleaned[key] = cleanPhone(value);\n else if (key.includes('name')) cleaned[key] = cleanName(value);\n else cleaned[key] = cleanText(value);\n });\n cleaned._data_quality_score = Math.round((Object.values(cleaned).filter(v => v).length / Object.keys(cleaned).length) * 100);\n return cleaned;\n});\n\nconst highQualityRows = cleanedRows.filter(r => r._data_quality_score >= 30);\n\nreturn {\n json: {\n ...data,\n cleaned_rows: highQualityRows,\n cleaning_summary: {\n original_count: data.parsed_row_count,\n after_cleaning: highQualityRows.length,\n average_quality_score: Math.round(highQualityRows.reduce((s, r) => s + r._data_quality_score, 0) / highQualityRows.length)\n },\n processing_completed: new Date().toISOString()\n }\n};"
},
"typeVersion": 2
},
{
"id": "c5d3faa0-0c48-4782-b436-2c51c39ec39a",
"name": "Générer un CSV nettoyé",
"type": "n8n-nodes-base.code",
"position": [
640,
160
],
"parameters": {
"jsCode": "// Generate cleaned CSV\nconst data = $input.first().json;\nconst headers = data.headers;\nconst cleanedRows = data.cleaned_rows;\n\nlet csvContent = headers.join(',') + '\\n';\ncsvContent += cleanedRows.map(r => headers.map(h => r[h] || '').join(',')).join('\\n');\n\nconst cleanedFilename = `cleaned_${Date.now()}_${data.filename}`;\n\nreturn {\n json: { ...data, cleaned_csv_content: csvContent, cleaned_filename: cleanedFilename },\n binary: {\n data: Buffer.from(csvContent, 'utf8'),\n fileName: cleanedFilename,\n mimeType: 'text/csv'\n }\n};"
},
"typeVersion": 2
},
{
"id": "56a06147-0e57-44aa-8c24-20e284486bbe",
"name": "Enregistrer dans Google Drive",
"type": "n8n-nodes-base.googleDrive",
"position": [
848,
80
],
"parameters": {
"name": "={{ $json.cleaned_filename }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"options": {},
"folderId": {
"__rl": true,
"mode": "list",
"value": "root",
"cachedResultName": "/ (Root folder)"
}
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "IPz4dCJVFC8uaoHw",
"name": "Google Drive account 2"
}
},
"typeVersion": 3
},
{
"id": "3b1b4bd8-3c65-4ed9-b87c-f6a5885d1712",
"name": "Effuer la feuille existante",
"type": "n8n-nodes-base.googleSheets",
"position": [
848,
240
],
"parameters": {
"operation": "clear",
"sheetName": "Sheet1",
"documentId": "YOUR_GOOGLE_SHEET_ID"
},
"typeVersion": 4
},
{
"id": "a856710d-317a-4d97-92f0-2946c91511ec",
"name": "Préparer pour Sheets",
"type": "n8n-nodes-base.code",
"position": [
1040,
240
],
"parameters": {
"jsCode": "// Prepare data for Google Sheets\nconst data = $input.first().json;\nreturn data.cleaned_rows.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "d1e73fbe-6d7c-47df-8e74-02d4b3ce7fa8",
"name": "Importer dans Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"position": [
1248,
240
],
"parameters": {
"options": {},
"operation": "append",
"sheetName": "Sheet1",
"documentId": "YOUR_GOOGLE_SHEET_ID"
},
"typeVersion": 4
}
],
"pinData": {},
"connections": {
"57875349-1fa5-454b-aa02-1255a1c87fb4": {
"main": [
[
{
"node": "275eb9f3-40bc-4c2e-bd8e-7dd707f9dc21",
"type": "main",
"index": 0
}
]
]
},
"b452e138-2c91-4934-8be6-e3c190407db3": {
"main": [
[
{
"node": "e6a91fe0-7d79-4e45-900b-5200b6af7cbf",
"type": "main",
"index": 0
}
]
]
},
"c5d3faa0-0c48-4782-b436-2c51c39ec39a": {
"main": [
[
{
"node": "56a06147-0e57-44aa-8c24-20e284486bbe",
"type": "main",
"index": 0
},
{
"node": "3b1b4bd8-3c65-4ed9-b87c-f6a5885d1712",
"type": "main",
"index": 0
}
]
]
},
"a856710d-317a-4d97-92f0-2946c91511ec": {
"main": [
[
{
"node": "d1e73fbe-6d7c-47df-8e74-02d4b3ce7fa8",
"type": "main",
"index": 0
}
]
]
},
"e6a91fe0-7d79-4e45-900b-5200b6af7cbf": {
"main": [
[
{
"node": "57875349-1fa5-454b-aa02-1255a1c87fb4",
"type": "main",
"index": 0
}
]
]
},
"3b1b4bd8-3c65-4ed9-b87c-f6a5885d1712": {
"main": [
[
{
"node": "a856710d-317a-4d97-92f0-2946c91511ec",
"type": "main",
"index": 0
}
]
]
},
"275eb9f3-40bc-4c2e-bd8e-7dd707f9dc21": {
"main": [
[
{
"node": "c5d3faa0-0c48-4782-b436-2c51c39ec39a",
"type": "main",
"index": 0
}
]
]
}
}
}Comment utiliser ce workflow ?
Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.
Dans quelles scénarios ce workflow est-il adapté ?
Intermédiaire - Extraction de documents, IA Multimodale
Est-ce payant ?
Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.
Workflows recommandés
David Olusola
@dae221I help ambitious businesses eliminate operational bottlenecks and scale faster with AI automation. My clients typically see 40-60% efficiency gains within 90 days. Currently accepting 3 new projects this quarter - david@daexai.com
Partager ce workflow