CSV 업로드 정리 및 표준화하여 Google Sheets와 Drive에가져오기
중급
이것은Document Extraction, Multimodal AI분야의자동화 워크플로우로, 10개의 노드를 포함합니다.주로 Code, Webhook, GoogleDrive, GoogleSheets 등의 노드를 사용하며. CSV 업로드을 정리하고 표준화하여 Google 스프레드시트와 Drive에가져오기
사전 요구사항
- •HTTP Webhook 엔드포인트(n8n이 자동으로 생성)
- •Google Drive API 인증 정보
- •Google Sheets API 인증 정보
사용된 노드 (10)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"meta": {
"instanceId": "2000c64071c20843606b95c63795bb0797c41036047055a6586498e855b96efc"
},
"nodes": [
{
"id": "24e5fd68-0441-4541-b543-fcaef4f8ec6c",
"name": "설정 안내",
"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": "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": "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": "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": "데이터 정리 및 표준화",
"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": "정리된 CSV 생성",
"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": "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": "기존 시트 지우기",
"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": "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": "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
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
중급 - 문서 추출, 멀티모달 AI
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
새로운 Calendly 예약을 Google 시트에 자동 추가
새 Calendly 예약을 Google 시트에 자동 추가
Code
Webhook
Google Sheets
+
Code
Webhook
Google Sheets
5 노드David Olusola
고객관계관리
Instagram 잠재 고객을 Google 스프레드시트에 자동 저장
Instagram 잠재 고객을 Google 스프레드시트에 자동 저장
Code
Webhook
Google Sheets
+
Code
Webhook
Google Sheets
4 노드David Olusola
리드 생성
Zoom 녹화를 Google 드라이브에 자동 저장 및 Airtable에 회의 기록
Zoom 녹화를 Google 드라이브에 자동 저장 및 Airtable에 회의 기록
Code
Webhook
Airtable
+
Code
Webhook
Airtable
7 노드David Olusola
파일 관리
Google Suite, PDF, 이메일을 사용한 비즈니스 보험 제출 자동화
Google Suite, PDF, 이메일을 통한 비즈니스 보험 제출 프로세스 자동화
If
Set
Code
+
If
Set
Code
37 노드David Olusola
문서 추출
Postiz를 사용하여 AI 뉴스 비디오를 자동으로 생성하고 소셜 미디어에 게시
GPT-4o와 HeyGen을 사용하여 AI 뉴스 비디오를 생성하고 소셜 미디어에 게시
Set
Code
Wait
+
Set
Code
Wait
37 노드David Olusola
콘텐츠 제작
태양광 잠재 고객 자격 심사
Google Sheets 및 Gmail을 사용한 태양광 잠재 고객 자격 심사 및 후속 조치 자동화
If
Code
Gmail
+
If
Code
Gmail
17 노드David Olusola
영업
워크플로우 정보
난이도
중급
노드 수10
카테고리2
노드 유형5
저자
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
외부 링크
n8n.io에서 보기 →
이 워크플로우 공유