본문 바로가기
카테고리 없음

구글 앱스 스크립트 기초 가이드

by richjin7285 2025. 10. 9.

구글 앱스 스크립트 기초 가이드 사진

구글 앱스 스크립트(Google Apps Script, 이하 GAS)는 자바스크립트 기반으로 구글 워크스페이스(스프레드시트, 문서, 드라이브, 지메일, 캘린더 등)를 자동화·연동·확장하는 플랫폼입니다. 브라우저만 있으면 바로 코드를 작성하고 실행할 수 있으며, 시간/이벤트 트리거로 스케줄러를 만들고, 웹 앱으로 폼이나 미니 백엔드까지 배포 가능합니다. “매일 아침 스프레드시트 요약을 메일로 보내기”, “구글 폼 응답을 읽어 PDF 영수증을 생성해 드라이브에 저장하기”, “여러 시트의 데이터를 병합해서 대시보드를 갱신하기” 같은 일을 몇 줄의 코드로 해결할 수 있죠.

이 가이드는 처음 시작하는 분을 위해 ① 환경·권한·트리거를 이해하고, ② 핵심 문법과 서비스 객체(SpreadsheetApp/DriveApp/GmailApp 등)를 익히며, ③ 실무 레시피로 바로 효과를 체감하는 흐름으로 구성했습니다. 모든 예시는 복붙→실행이 가능하도록 최소 설정만 요구합니다. 목표는 간단합니다. 오늘 1개 자동화를 만들어 사람의 반복을 도구로 치환하는 것. 그 첫걸음을 지금 시작해 보죠.

 

1) 시작하기: 편집기, 권한(OAuth), 트리거, 로그·디버그까지 한 번에

앱스 스크립트는 설치가 필요 없습니다. 스프레드시트에서 확장 프로그램 > 앱스 스크립트를 열거나, script.google.com으로 들어가 프로젝트를 생성하면 즉시 코드를 작성할 수 있습니다. 아래 순서를 그대로 따라 하면 “만들기→권한 부여→실행→자동화”의 전체 흐름을 경험할 수 있어요.

① 프로젝트 열기(두 가지 방법)

  • 스프레드시트 기반 — 스프레드시트를 연 뒤 확장 프로그램 > 앱스 스크립트를 클릭하면 그 파일에 종속된 컨테이너 바운드 프로젝트가 열립니다(파일과 수명 공유·간편 참조 장점).
  • 독립형script.google.com > 새 프로젝트: 여러 파일·서비스를 아우르는 범용 스크립트를 만들 때 적합.

② 첫 함수: “시트에서 합계 구해 메일로 보내기”

function sendDailySpend() {
  const ss = SpreadsheetApp.getActive();                 // 현재 스프레드시트
  const sh = ss.getSheetByName('대시보드') || ss.getSheets()[0];
  const total = sh.getRange('B2').getDisplayValue();     // 예: B2 = 오늘 지출 합계
  const subject = `📊 오늘 지출 요약 (${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd')})`;
  const body = `오늘 지출 합계: ${total}\n- 자동 발송: Apps Script`;
  GmailApp.sendEmail(Session.getActiveUser().getEmail(), subject, body);
}

에디터 상단의 실행 ▶ 버튼을 누르면 최초 1회 권한(OAuth) 요청이 뜹니다. “권한 검토” → 사용하는 서비스(지메일/스프레드시트) 접근 권한 허용을 진행하세요. 이 과정은 “스크립트가 어떤 리소스를 사용해 무엇을 하려는지”를 승인받는 절차입니다.

③ 트리거로 자동화(스케줄러)

  • 에디터 좌측 시계 아이콘(트리거)트리거 추가 → 함수 선택(sendDailySpend)
  • 이벤트 소스: 시간 기반 → 매일/매시 등 원하는 주기로 설정
  • 이제 버튼 누르지 않아도 정해진 시간에 자동으로 메일이 전송됩니다.

④ 로그·디버그: Logger·Stackdriver(Execution log)

function demoLog() {
  const tz = Session.getScriptTimeZone();
  Logger.log('현재 시간: %s', Utilities.formatDate(new Date(), tz, 'yyyy/MM/dd HH:mm:ss'));
}
  • 실행 ▶ 후 상단 메뉴 실행 기록 또는 로그 보기에서 출력 확인.
  • 디버그 버튼으로 중단점(breakpoint)을 걸어 변수 상태를 단계별 확인 가능.

⑤ 권한·제한 이해(중요)

  • 스코프: 코드가 사용하는 서비스에 따라 필요한 권한 범위가 자동 지정됩니다(예: Gmail 읽기/쓰기, Drive 파일 편집 등).
  • 할당량(Quota): 일·분 단위 호출 제한이 있습니다(예: 메일 전송 건수, Drive API 호출 수). 설계를 일괄 처리/캐시 중심으로 구성하세요.
  • 시간제한: 단일 실행 시간 한도가 있습니다(유료 워크스페이스에서 상향). 긴 작업은 청크 분할 또는 재귀 트리거 전략을 사용.

⑥ 보안 팁

  • 민감 정보(토큰/키)프로퍼티 서비스(PropertiesService)에 저장하고 코드에 하드코딩하지 않습니다.
  • 외부 웹훅/웹앱은 도메인 제한·토큰 검증을 반드시 구현하세요.
첫 성공 루틴 — (1) 스프레드시트에서 앱스 스크립트 열기 → (2) 위 메일 함수를 붙여 실행 → (3) 권한 승인 → (4) 시간 트리거 설정 → (5) 로그로 결과 확인. 여기까지가 GAS 데뷔전의 전 과정입니다.

 

2) 핵심 문법·서비스 빠르게 익히기: SpreadsheetApp·DriveApp·GmailApp·CalendarApp

핵심 문법·서비스 사진

GAS는 표준 자바스크립트 문법을 기반으로 하되, 구글 서비스에 접근하는 서비스 객체를 제공합니다. 가장 자주 쓰는 네 가지를 예시로 익혀보겠습니다. 모든 코드는 독립 실행이 가능하며, 변수만 여러분 상황에 맞게 바꾸면 됩니다.

① SpreadsheetApp — 시트 읽기/쓰기/찾기

function sheetBasics() {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('원천') || ss.insertSheet('원천');
  // 읽기
  const values = sh.getRange(2,1, sh.getLastRow()-1, 5).getValues(); // A2:E
  // 쓰기(한 번에 쓰면 빠름)
  sh.getRange(1,6).setValue('합계');                                  // F1 = "합계"
  const sums = values.map(r => [ (Number(r[4])||0) + (Number(r[3])||0) ]);
  sh.getRange(2,6, sums.length, 1).setValues(sums);                    // F열 채우기
  // 찾기
  const found = sh.createTextFinder('오류').findNext();
  if (found) found.setBackground('#fde68a');                           // '오류'가 있으면 노랗게
}

② DriveApp — 폴더/파일 생성·검색·이동

function driveBasics() {
  const root = DriveApp.getRootFolder();
  const folderName = '자동리포트';
  // 폴더 찾거나 만들기
  const it = DriveApp.getFoldersByName(folderName);
  const folder = it.hasNext() ? it.next() : DriveApp.createFolder(folderName);
  // 파일 만들기
  const file = DriveApp.createFile('readme.txt', '자동 생성된 파일', 'text/plain');
  file.moveTo(folder);                                                 // 폴더로 이동
  // 목록 출력
  const files = folder.getFiles();
  while (files.hasNext()) {
    const f = files.next();
    Logger.log('%s / %s bytes', f.getName(), f.getSize());
  }
}

③ GmailApp — 자동 메일 발송(HTML 템플릿 포함)

function mailWithHtml() {
  const to = Session.getActiveUser().getEmail();
  const subject = '주간 리포트';
  const html = `
    <h3 style="margin:0 0 8px">주간 요약</h3>
    <p>이번 주 지출 합계: <strong>₩1,245,000</strong></p>
    <p style="color:#64748b">* Apps Script 자동 발송</p>`;
  GmailApp.sendEmail(to, subject, 'HTML 보기 지원 필요', {htmlBody: html});
}

④ CalendarApp — 일정 생성·조회(가계부 리마인드 등)

function calendarReminder() {
  const cal = CalendarApp.getDefaultCalendar();
  const tomorrow = new Date(Date.now() + 24*60*60*1000);
  cal.createAllDayEvent('가계부 정리', tomorrow, {description: '카테고리/예산 점검'});
}

⑤ Utilities·Session·PropertiesService — 시간·환경·비밀키

function helpers() {
  // 시간 포맷
  const now = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm');
  // 스크립트 속성(비밀키 저장)
  const props = PropertiesService.getScriptProperties();
  props.setProperty('WEBHOOK_URL', 'https://example.com/hook'); // 저장
  const url = props.getProperty('WEBHOOK_URL');                 // 읽기
  Logger.log('%s / %s', now, url);
}

⑥ onEdit/onFormSubmit — 이벤트 기반 실행(시트 편집/폼 응답)

function onEdit(e) {
  const range = e.range;                          // 편집된 범위
  if (range.getColumn() === 7) {                  // 예: G열(상태) 바뀌면
    range.setBackground('#e0f2fe');               // 색상 표시
  }
}
function onFormSubmit(e) {
  const row = e.values;                            // 폼 응답 한 행
  const [time, category, price] = row;             // 폼 질문 순서에 맞춰 구조 분해
  if (Number(price) > 1000000) {
    GmailApp.sendEmail(Session.getActiveUser().getEmail(), '고액 지출 감지', `${category}: ${price}`);
  }
}

⑦ HTML Service로 간단한 웹 앱(폼·대시보드) 만들기

// Code.gs
function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('미니 대시보드');
}
function addItem(name, price) {
  const sh = SpreadsheetApp.getActive().getSheetByName('원천');
  sh.appendRow([new Date(), name, Number(price)||0]);
  return 'OK';
}
<!-- index.html -->
<div style="font-family:system-ui;padding:16px">
  <h3>지출 입력</h3>
  <input id="name" placeholder="항목" />
  <input id="price" type="number" placeholder="금액" />
  <button onclick="submit()">저장</button>
  <script>
    function submit(){
      const n = document.getElementById('name').value;
      const p = document.getElementById('price').value;
      google.script.run.withSuccessHandler(()=>alert('OK')).addItem(n,p);
    }
  </script>
</div>
  • 배포: 배포 > 새 배포 > 유형: 웹 앱 → “나로 실행” / “접근 권한: 본인 또는 도메인” 선택.
핵심 요약SpreadsheetApp으로 데이터, DriveApp으로 파일, GmailApp/CalendarApp으로 커뮤니케이션, 트리거로 자동화. 도구의 조합이 곧 생산성입니다.

 

3) 실전 자동화 레시피: 바로 효과 나는 10가지 스니펫

기초를 익혔다면, 바로 써먹는 짧고 강력한 레시피들로 체감을 높여 보세요. 각 스니펫은 독립 실행 가능하며, 여러분의 시트/폴더 이름만 바꾸면 됩니다.

① 전일 요약을 메일로(스프레드시트 → 지메일)

function mailYesterdaySummary() {
  const tz = Session.getScriptTimeZone();
  const y = new Date(Date.now()-24*60*60*1000);
  const ymd = Utilities.formatDate(y, tz, 'yyyy-MM-dd');
  const sh = SpreadsheetApp.getActive().getSheetByName('거래');
  const rows = sh.getRange(2,1, sh.getLastRow()-1, 5).getValues();
  let spend=0, income=0;
  rows.forEach(r => {
    const d = Utilities.formatDate(new Date(r[0]), tz, 'yyyy-MM-dd');
    if (d===ymd) { if (r[1]==='지출') spend+=r[4]; if (r[1]==='수입') income+=r[4]; }
  });
  const body = `📅 ${ymd}\n- 지출: ${spend.toLocaleString()}원\n- 수입: ${income.toLocaleString()}원\n- 순현금흐름: ${(income+spend).toLocaleString()}원`;
  GmailApp.sendEmail(Session.getActiveUser().getEmail(), `[요약] ${ymd}`, body);
}

② 완료 행을 아카이브로 이동

function archiveDone() {
  const ss = SpreadsheetApp.getActive(), src = ss.getSheetByName('작업'), dst = ss.getSheetByName('아카이브')||ss.insertSheet('아카이브');
  const data = src.getDataRange().getValues(); const keep=[data[0]], move=[];
  for (let i=1;i<data.length;i++){ (data[i][3]==='완료' ? move : keep).push(data[i]); }
  if (move.length) dst.getRange(dst.getLastRow()+1,1,move.length,move[0].length).setValues(move);
  src.clearContents(); src.getRange(1,1,keep.length,keep[0].length).setValues(keep);
}

③ 여러 스프레드시트에서 범위 끌어와 병합(간편 ETL)

function mergeSheets() {
  const urls = ['스프레드시트URL1','스프레드시트URL2'];
  const dst = SpreadsheetApp.getActive().getSheetByName('원천')||SpreadsheetApp.getActive().insertSheet('원천');
  dst.clearContents();
  let r = 1;
  urls.forEach(u => {
    const sh = SpreadsheetApp.openByUrl(u).getSheets()[0];
    const rg = sh.getDataRange().getValues();
    dst.getRange(r,1,rg.length,rg[0].length).setValues(rg);
    r += rg.length;
  });
}

④ 피벗 테이블 자동 생성(시트 내부)

function makePivotGAS() {
  const ss = SpreadsheetApp.getActive();
  const src = ss.getSheetByName('원천').getDataRange();
  const psh = ss.getSheetByName('피벗') || ss.insertSheet('피벗'); psh.clear();
  const pivot = psh.getRange(1,1).createPivotTable(src);
  pivot.addRowGroup(1).showTotals(true);      // 1열(예: 날짜)
  pivot.addColumnGroup(2);                    // 2열(예: 채널)
  pivot.addPivotValue(5, SpreadsheetApp.PivotTableSummarizeFunction.SUM); // 5열(금액) 합계
}

⑤ 차트를 이미지로 내보내 드라이브 저장

function exportChartAsImage() {
  const sh = SpreadsheetApp.getActive().getSheetByName('대시보드');
  const chart = sh.getCharts()[0];
  const blob = chart.getAs('image/png').setName('dashboard.png');
  DriveApp.createFile(blob);
}

⑥ 드라이브 폴더의 최신 파일 찾기

function getLatestFileInFolder() {
  const folder = DriveApp.getFoldersByName('input').next();
  let latest=null, t=0;
  const files = folder.getFiles();
  while (files.hasNext()){
    const f = files.next();
    if (f.getLastUpdated().getTime() > t) { t = f.getLastUpdated().getTime(); latest = f; }
  }
  Logger.log('최신 파일: %s', latest && latest.getName());
}

⑦ 슬랙/웹훅으로 알림 보내기(간단 POST)

function postWebhook() {
  const url = PropertiesService.getScriptProperties().getProperty('WEBHOOK_URL');
  const payload = {text: 'Apps Script 테스트 알림 ✅'};
  UrlFetchApp.fetch(url, {method:'post', contentType:'application/json', payload: JSON.stringify(payload)});
}

⑧ onEdit으로 데이터 검증(잘못된 값 즉시 되돌리기)

function onEdit(e) {
  const r = e.range, sh = r.getSheet();
  if (sh.getName()==='원천' && r.getColumn()===5) { // 금액열
    const v = Number(r.getValue());
    if (isNaN(v)) { r.setValue(''); SpreadsheetApp.getActive().toast('숫자만 입력하세요'); }
  }
}

⑨ 시간 초과 회피(청크 분할)

function processChunk() {
  const props = PropertiesService.getScriptProperties();
  const idx = Number(props.getProperty('IDX')||'2');  // 2행부터 처리
  const sh = SpreadsheetApp.getActive().getSheetByName('원천');
  const last = sh.getLastRow();
  for (let r=idx; r<=last; r++){
    // ...행 처리...
    if ((r-idx) % 500 === 0 && (Date.now()%60000)>50000) { // 대략 시간 체크
      props.setProperty('IDX', String(r+1));
      ScriptApp.newTrigger('processChunk').timeBased().after(1000).create();
      return;
    }
  }
  props.deleteProperty('IDX'); // 완료
}

⑩ 에러 핸들링·알림(예외 발생 시 메일)

function safeWrapper() {
  try {
    // 핵심 작업 함수 호출
    mailYesterdaySummary();
  } catch (err) {
    const msg = `오류: ${err.message}\nStack: ${err.stack}`;
    GmailApp.sendEmail(Session.getActiveUser().getEmail(), 'GAS 오류 알림', msg);
    throw err;
  }
}
적용 순서 — (1) 스니펫 1개 선택(메일/아카이브/피벗 등) → (2) 시트·폴더명만 맞추고 실행 → (3) 트리거 연결 → (4) 로그/토스트로 결과 확인 → (5) 팀 공유 시 권한 안내.

 

결론: 작은 자동화 1개가 하루 10분을 되돌려 준다

자동화 사진

앱스 스크립트의 가치는 “복잡한 개발”이 아니라 업무의 마찰을 줄이는 것에 있습니다. 브라우저에서 바로 코드를 쓰고, 스프레드시트/드라이브/지메일을 원클릭으로 묶어, 사람이 반복하던 클릭을 도구에 맡기는 순간, 하루 10분·한 달 수시간의 여유가 생깁니다. 이 가이드를 통해 환경·권한·트리거를 설정하고, 핵심 서비스 객체의 사용감을 익혔으며, 실전 스니펫으로 바로 효과를 냈을 것입니다.

다음 단계는 간단합니다. 오늘은 “전일 요약 메일”을 자동화하고, 내일은 “완료 행 아카이브”를 붙이고, 이번 주에는 “피벗·차트 갱신 + 이미지 내보내기”까지 확장해 보세요. 중요한 건 완벽함이 아니라 작게 시작해 꾸준히 확장하는 리듬입니다. 자동화가 쌓일수록 여러분은 입력·정리에 쓰던 시간을 해석과 의사결정에 투입하게 됩니다. 그게 도구가 우리를 돕는 가장 정확한 방식이니까요.