본문 바로가기
TIL

구글 애널리틱스 데이터들을 매일 아침 디스코드 메시지로 받기 (구글 앱스크립트 활용)

by 고니누나 2025. 3. 7.
728x90


배경

구글 어낼리틱스 (Google Analytics 4)를 활용하면 웹사이트 트래픽, 사용자 행동 데이터를 분석할 수 있다. 하지만 나는 여러 가지 프로젝트를 동시에 관리하고 있어서 봐야 할 계정이 많은 편이다. 블로그 트래픽까지 따로 살펴봐야 하니, 매일 수동으로 구글 애널리틱스에 접속하는 게 번거로웠다.
 
이 문제를 해결하기 위해 구글 앱 스크립트(Google Apps Script)를 활용해 여러 계정의 구글 애널리틱스 데이터를 가져오고, 구글 스프레드 시트에 저장한 후, 디스코드로 자동 알림을 보내는 시스템을 만들어보기로했다. 


원하는 결과

1. Google Analytics 데이터를 Google Sheets에 저장
2. 매일 아침 9시에 자동 실행
3. 디스코드 (Discord Webhook)을 활용해 지정한 채널에 메시지 전송

1단계. 구글 어낼리틱스 데이터 가져오기

먼저, Google Analytics API를 활성화한다. 
1. Google Cloud Console에 접속
2. 새로운 프로젝트 생성 (또는 기존 프로젝트 선택)
3. [API 및 서비스] > [라이브러리] 에서 Google Analytics Data API를 검색하여 활성화

2단계. 구글 스프레드시트 만들기

1. 구글 드라이브에서 스프레드시트 새 파일을 생성하고 Google Analytics 데이터를 저장할 시트를 만든다.
2. 시트의 ID(주소창에서 docs.google.com/spreadsheets/d/이부분/edit)를 복사하여 기록한다.

3단계. 디스코드 웹훅(Webhook) 만들기

디스코드에서 프로젝트들의 자동 알림을 받을 수 있는 별도 서버를 개설한 후, 웹훅(Webhook URL)을 만들었다. 
 
1. 디스코드에서 서버 설정 > 통합 > 웹훅으로 이동
2. 웹훅 만들기 클릭
3. 이름 설정 (예: GA4 Report Bot)
4. 메시지를 보낼 채널 선택 (예: analytics-reports)
5. 웹훅 URL 복사: 이제 이 Webhook URL을 Google Apps Script에서 사용한다.

4단계. 구글 앱 스크립트 코드 설정

아래 코드를 Google Apps Script에 붙여 넣고 실행하면, 구글 어낼리틱스 데이터를 스프레드시트에 자동으로 저장한 후 디스코드로 전송할 수 있다.
 
아래 코드에서 구글 애널리틱스 속성 ID 값과, 디스코드 웹훅 URL, 구글 스프레드 시트 ID는 본인 것으로 교체하면 된다. 구글 어낼리틱스 계정에서 속성 ID 값은 아래 이미지 참고. 빨간 박스 부분의 값을 복사해야 한다. (G-로 시작하는 값이 아니라 숫자로만 이루어진 값임)
 
fetchGA4Data()를 수동 실행하여 Google Analytics API 접근 권한을 허용한다. 

// @ts-nocheck
// 1. 설정 값 입력
const GA_ACCOUNTS = {
  "구글어낼리틱스 속성ID": "구글어낼리틱스계정명(원하는 이름으로 입력",
  "구글어낼리틱스 속성ID": "구글어낼리틱스계정명(원하는 이름으로 입력"
};

const GA_VIEW_IDS = Object.keys(GA_ACCOUNTS);
const SPREADSHEET_ID = "구글스프레드시트ID";
const DISCORD_WEBHOOK_URL = "디스코드 웹훅 URL";

// 2. Google Analytics 데이터 가져오기
function fetchGA4Data() {
  const today = new Date();
  today.setDate(today.getDate() - 1);
  today.setHours(today.getHours() + 9); // 한국 시간 반영
  const formattedDate = today.toISOString().split("T")[0];

  let sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName("GA Data");
  if (!sheet) {
    sheet = SpreadsheetApp.openById(SPREADSHEET_ID).insertSheet("GA Data");
  }

  // ✅ 헤더 생성 (없으면 추가)
  setupHeaders(sheet);

  // ✅ 기존 날짜 데이터 삭제 (중복 방지)
  removeExistingDateData(sheet, formattedDate);

  let rowData = [formattedDate]; // 날짜 먼저 추가
  let message = `📊 **Google Analytics 데이터 보고서 (${formattedDate})**\n\n`;

  GA_VIEW_IDS.forEach(viewId => {
    const serviceName = GA_ACCOUNTS[viewId] || "Unknown Service";
    const url = `https://analyticsdata.googleapis.com/v1beta/properties/${viewId}:runReport`;

    const payload = {
      "dateRanges": [{ "startDate": formattedDate, "endDate": formattedDate }],
      "metrics": [{ "name": "activeUsers" }, { "name": "sessions" }]
    };

    const options = {
      "method": "post",
      "contentType": "application/json",
      "headers": { "Authorization": `Bearer ${ScriptApp.getOAuthToken()}` },
      "payload": JSON.stringify(payload)
    };

    let activeUsers = 0;
    let sessions = 0;

    try {
      const response = UrlFetchApp.fetch(url, options);
      const data = JSON.parse(response.getContentText());

      if (data.rows && data.rows.length > 0) {
        activeUsers = parseInt(data.rows[0].metricValues[0].value) || 0;
        sessions = parseInt(data.rows[0].metricValues[1].value) || 0;
      }
    } catch (e) {
      console.error(`${serviceName} 데이터 가져오기 실패: ${e.message}`);
    }

    rowData.push(activeUsers, sessions);
    message += `📍 **${serviceName}** (${viewId})\n👥 **방문자:** ${activeUsers}\n📈 **세션:** ${sessions}\n\n`;
  });

  // ✅ 데이터 추가 (가로 형태)
  sheet.appendRow(rowData);

  // ✅ 디스코드 알림 전송
  sendDiscordMessage(message);
}

// 3. 헤더 설정 (1행에 서비스명 & 방문자수, 세션수)
function setupHeaders(sheet) {
  const headers = ["날짜"];
  GA_VIEW_IDS.forEach(viewId => {
    const serviceName = GA_ACCOUNTS[viewId];
    headers.push(`${serviceName} 방문자수`, `${serviceName} 세션수`);
  });

  const firstRow = sheet.getRange(1, 1, 1, headers.length).getValues()[0];
  if (JSON.stringify(firstRow) !== JSON.stringify(headers)) {
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
  }
}

// 4. 기존 날짜 데이터 삭제 (중복 방지)
function removeExistingDateData(sheet, date) {
  const data = sheet.getDataRange().getValues();
  for (let i = data.length - 1; i >= 1; i--) {
    if (data[i][0] === date) {
      sheet.deleteRow(i + 1);
    }
  }
}

// 5. 디스코드 메시지 전송
function sendDiscordMessage(message) {
  const payload = { "content": message };
  const options = { "method": "post", "contentType": "application/json", "payload": JSON.stringify(payload) };
  UrlFetchApp.fetch(DISCORD_WEBHOOK_URL, options);
}

// 6. 트리거 설정 (중복 실행 방지)
function createTrigger() {
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === "fetchGA4Data") {
      ScriptApp.deleteTrigger(trigger);
    }
  });

  ScriptApp.newTrigger("fetchGA4Data")
    .timeBased()
    .atHour(9)
    .everyDays(1)
    .create();
}
 

아래 이미지는 실제 코드를 넣은 것. 

코드를 넣은후 위쪽에 Run 왼쪽에 있는 것을 눌러서 저장한 후, Run을 누르면 스프레드시트에 데이터가 저장되고, 디스코드에 메시지가 오는 것을 확인할 수 있다.

 

728x90

5단계. 자동 알림을 받기위한 트리거 설정

이제, 아래와 같이 트리거 메뉴로 이동해서 새로운 트리거를 만든다. 

트리거를 만들지 않으면, 매일 아침 '자동'으로 코드가 실행되지 않으니 주의.

구글 앱스크립트 트리거 설정 화면

 


추가: 텔레그램으로 알림을 받는게 더 나을까? (Telegram vs Discord)

혼자 받아보는 데이터지만, 텔레그램으로 알림을 받는 게 나을지 디스코드로 받는 게 나을지 잠시 고민했었다. 
나는 아래와 같은 두가지 이유 때문에 디스코드로 정했는데,
 
1. 지금은 혼자 보고 있지만 다른 사람들과 협업, 공유 해야하는 순간이 올수도 있고,
2. 운영중인 서비스들로부터 오는 다른 자동 알림들도 한 군데에 모아서 관리해야 했기 때문에 채널별 설정과 관리가 쉬운 디스코드가 나의 상황에 적합했다. 

 

기능TelegramDiscord
설정 난이도쉬움쉬움
포맷 지원MarkdownRich Text (Embed 등 가능)
채널 공유개인 메시지 가능서버 채널 공유 용이
확장성봇 API로 확장 가능Webhook 활용 간편

 
 

이 과정을 통해 구글 드라이브 내 스프레드 시트에 전날 방문자 및 세션 데이터가 자동 저장되고, 디스코드 채널에 1개의 메시지로 여러 개의 계정 데이터가 한 번에 전송되었다. 트리거를 설정해 두었기 때문에 매일 자동 실행되서 이제 구글 어낼리틱스를 수동으로 확인할 필요가 없게 되었다. 😀

 
 

728x90