발행일: 2026-10-06(화) 11:44
← 목차로 돌아가기|하루 30분 n8n 시리즈 ② 실무 자동화 레시피 100
RECIPE 022

구글 애널리틱스 트래픽 급락 → 자동 경고

  • ▸Webhook 또는 Google Drive Trigger로 CSV 파일 업로드를 감지할 수 있다
  • ▸CSV 데이터를 파싱해 빈 값, 중복, 형식 오류를 자동 정제할 수 있다
  • ▸정제된 데이터를 Google Sheets에 저장하고 오류 행만 별도 시트에 기록할 수 있다

✅사전 준비

  • □Google Drive 자격증명 연결 완료
  • □Google Sheets 자격증명 연결 완료
  • □Sheets에 정제 결과 저장용 시트 2개 생성: '정제완료' / '오류행'
  • □처리할 CSV 컬럼 구조 확정 (예: 이름/이메일/전화번호/가입일)

CSV 파싱 — Spreadsheet File 노드 설정

Operation: Read
File Format: CSV
Options:
  Header Row:    true
  Delimiter:     , (콤마)
  Include Empty: false

데이터 정제 — Code 노드

const items = $input.all();
const clean = [];
const errors = [];

items.forEach(({ json }) => {
  const issues = [];

  // 이메일 형식 검증
  if (!json.이메일?.match(/^[^@]+@[^@]+\.[^@]+$/)) {
    issues.push('이메일 형식 오류');
  }

  // 필수값 체크
  if (!json.이름?.trim()) issues.push('이름 누락');

  // 전화번호 정제 (숫자+하이픈만)
  if (json.전화번호) {
    json.전화번호 = json.전화번호.replace(/[^0-9-]/g, '');
  }

  if (issues.length > 0) {
    errors.push({ ...json, 오류내용: issues.join(', ') });
  } else {
    clean.push(json);
  }
});

return [
  ...clean.map(j => ({ json: { ...j, _type: 'clean' } })),
  ...errors.map(j => ({ json: { ...j, _type: 'error' } })),
];

정제완료/오류 분기 — IF 노드

조건: {{ $json._type === 'clean' }}
True  → Sheets '정제완료' 시트에 Append
False → Sheets '오류행' 시트에 Append
💡 TIP중복 제거가 필요하다면 Code 노드에서 이메일 기준으로 Map을 만들어 마지막 값만 남기는 방식을 쓰세요. const unique = Object.values(Object.fromEntries(items.map(i => [i.이메일, i]))) 한 줄로 처리됩니다.
⚡

워크플로우 JSON

아래 JSON을 복사해 n8n에 바로 임포트하세요

📥 n8n 임포트 방법
n8n 화면 우측 상단 메뉴 (⋮) → Import from JSON → 아래 JSON 전체 복사 후 붙여넣기 → Import 클릭
{
  "name": "recipe022_ga_traffic_drop_alert",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "id": "schedule-022-01",
      "name": "매일 오전 9시",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        200,
        400
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://analyticsdata.googleapis.com/v1beta/properties/{{ $env.GA4_PROPERTY_ID }}:runReport",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "dateRanges",
              "value": "=[{\"startDate\": \"yesterday\", \"endDate\": \"yesterday\"}, {\"startDate\": \"7daysAgo\", \"endDate\": \"7daysAgo\"}]"
            },
            {
              "name": "metrics",
              "value": "[{\"name\": \"sessions\"}]"
            }
          ]
        },
        "options": {}
      },
      "id": "http-022-02",
      "name": "GA4 세션 데이터 가져오기",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        420,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const rows = $json.rows || [];\nconst yesterday = parseInt(rows[0]?.metricValues?.[0]?.value || 0);\nconst lastWeek = parseInt(rows[1]?.metricValues?.[0]?.value || 0);\nconst dropRate = lastWeek > 0 ? ((lastWeek - yesterday) / lastWeek * 100).toFixed(1) : 0;\nconst isAlert = parseFloat(dropRate) >= 30;\nreturn [{ json: { yesterday, lastWeek, dropRate, isAlert } }];"
      },
      "id": "code-022-03",
      "name": "트래픽 감소율 계산",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "leftValue": "={{ $json.isAlert }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        }
      },
      "id": "if-022-04",
      "name": "30% 이상 급락 여부 확인",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        860,
        400
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": "#marketing-alerts",
        "text": "=🚨 트래픽 이상 감지!\n어제 세션: {{ $json.yesterday.toLocaleString() }}\n전주 동일: {{ $json.lastWeek.toLocaleString() }}\n감소율: -{{ $json.dropRate }}%\n→ SEO 또는 서버 문제 가능성을 확인해주세요."
      },
      "id": "slack-022-05",
      "name": "Slack 경고 발송",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1080,
        300
      ]
    }
  ],
  "connections": {
    "매일 오전 9시": {
      "main": [
        [
          {
            "node": "GA4 세션 데이터 가져오기",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GA4 세션 데이터 가져오기": {
      "main": [
        [
          {
            "node": "트래픽 감소율 계산",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "트래픽 감소율 계산": {
      "main": [
        [
          {
            "node": "30% 이상 급락 여부 확인",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "30% 이상 급락 여부 확인": {
      "main": [
        [
          {
            "node": "Slack 경고 발송",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "timezone": "Asia/Seoul"
  },
  "meta": {
    "chapter": 2,
    "recipe": "022",
    "title": "구글 애널리틱스 트래픽 급락 → 자동 경고",
    "difficulty": "⭐⭐⭐",
    "estimatedTime": "45분",
    "apps": [
      "Google Analytics API",
      "Google Sheets",
      "Slack"
    ]
  }
}
← 이전 레시피☰ 목차다음 레시피 →