소개
스터디 첫 OT 시간에, 스터디장인 여행가J님이 Google Apps Script를 활용한 자동화 예제를 시연해주셨습니다. 처음엔 “이게 정말 내가 할 수 있는 걸까?” 싶었지만, 생각보다 간단해 보였고 직접 따라 해보면 좋겠다는 생각이 들어 실행에 옮겼습니다.
진행 방법
이번 시도에서 사용한 도구는 다음과 같아요:
🛠️ 사용 도구
Google Apps Script - 자동화 툴
ChatGPT, - 코드수정요청
Gemini (제미나이) - 앱스 스 크립트 코드 작성
Google Drive, Google Sheets - 자동화가 일어나는 장소와 트리거(?)
💡 진행 흐름
구글 드라이브에 새폴더 만들기 (+신규를 클릭하고 폴더를 만들고 공유설정함)
여행가J님의 시연을 참고해 새 폴더 안에서 Google Apps Script 편집기로 진입
구글 앱 스크립트 클릭 - 만들고 공유 클릭 - 스크립트 만들기 클릭하면
위와 같은 이미지가 나오는데 파일 옆의 + 를 눌러서 해도 되고, 그냥 지금 Code.gs 옆의 내용을 지워서 넣어도 된다. 뭘? (지금 만들 앱스크립트 코드를)
구글시트와 연동되는 스크립트 코드를 제미나이를 통해 작성 (스터디장님이 준 프롬프트 입력)
[앞에서 만든 구글드라이브의 새로 만든 폴더 스타트업 18기 주소링크 첨부] [https://drive.google.com/drive/u/0/folders/1u2WXt48aG02XFG7u6J__rkGDEwkHY9q8] 이 폴더는 팀이 같이 쓰는 폴더이다. 1. 해당 폴더에 소비자 또는 대상자 구글시트 (설문조사 등) 파일을 업로드하면 동작되는 트리거 필요 2. 구글 시트의 설문 조사 내용을 바탕으로 구글 앱스크립트(open ai 4o api key 활용)의 코드를 활용해서 마케팅분석 3. 설문지의 질문들을 바탕으로 (1열 헤더 확인) 필요한 마케터의 역할 지정 4. 지정된 역할의 마케터가 되어서 분석해야하는 유의미한 포인트를 10개 제안 5. 제안된 유의미한 포인트들을 해당하는 마케터 역할의 19년차 전문가가 되어 보고서 작성 6. 보고서는 개조식으로 간결하게 작성 7.출력은 마크다운 문법, 코드 블럭형태로 정리하여 같은 폴 더안에 구글 Docs로 정리 8. 구글 DOC 이름은 [구글시트와 같은 이름+실행 날짜] 위의 내용을 수행하는 구글 앱스크립트(open ai 4o api key 활용) 코드가 필요해.프롬프트 결과로 나온 앱스크립트 코드 (시간관계상 최종 완성된 코드를 올림)
/** * 이 스크립트는 지정된 구글 드라이브 폴더에 새로운 구글 시트 파일이 생성되면, * 해당 시트의 데이터를 기반으로 OpenAI GPT-4o API를 사용하여 마케팅 분석 보고서를 생성하고 * 그 결과를 새로운 구글 문서로 저장하는 자동화 스크립트입니다. */ // --- 사용자 설정 영역 --- // 1. OpenAI에서 발급받은 API 키를 입력하세요. const OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY'; // 2. 대상 구글 드라이브 폴더의 ID를 입력하세요. // (폴더 URL의 'folders/' 뒤에 있는 문자열) const FOLDER_ID = '1u2WXt48aG02XFG7u6J__rkGDEwkHY9q8'; // --- 사용자 설정 영역 끝 --- /** * 스크립트 메뉴를 생성하여 수동으로 실행할 수 있도록 합니다. * UI 컨텍스트가 없는 환경(예: 스크립트 편집기에서 직접 실행)에서의 오류를 방지하기 위해 try-catch를 사용합니다. */ function onOpen() { try { SpreadsheetApp.getUi() .createMenu('마케팅 분석 자동화') .addItem('지금 바로 분석 실행', 'processNewSurveys') .addSeparator() .addItem('자동 실행 트리거 설정 (1시간마다)', 'createTimeDrivenTrigger') .addItem('처리 기록 초기화 (테스트용)', 'resetProcessedFiles') .addToUi(); } catch (e) { console.log("UI를 생성할 수 없는 환경에서 실행되었습니다. 이 스크립트는 Google Sheets에 연결되어야 메뉴가 표시됩니다."); } } /** * 테스트를 위해 처리 기록을 초기화하는 함수입니다. * 메뉴를 통해 실행할 수 있습니다. */ function resetProcessedFiles() { try { PropertiesService.getScriptProperties().deleteAllProperties(); SpreadsheetApp.getUi().alert('처리 기록이 초기화되었습니다. 이제 모든 파일을 다시 분석할 수 있습니다.'); } catch (e) { console.log("처리 기록이 초기화되었습니다. (UI 컨텍스트가 없어 알림은 표시되지 않았습니다.)"); } } /** * 1시간마다 'processNewSurveys' 함수를 실행하는 시간 기반 트리거를 생성합니다. * 스크립트를 처음 설정할 때 한 번만 실행하면 됩니다. */ function createTimeDrivenTrigger() { // 기존에 설정된 트리거가 있다면 삭제하여 중복 생성을 방지합니다. const allTriggers = ScriptApp.getProjectTriggers(); for (const trigger of allTriggers) { if (trigger.getHandlerFunction() === 'processNewSurveys') { ScriptApp.deleteTrigger(trigger); } } // 1시간마다 실행되는 새 트리거를 설정합니다. ScriptApp.newTrigger('processNewSurveys') .timeBased() .everyHours(1) .create(); try { SpreadsheetApp.getUi().alert('자동 분석 트리거가 설정되었습니다. 1시간마다 새로운 설문조사 파일을 확인합니다.'); } catch (e) { console.log("자동 분석 트리거가 설정되었습니다. (UI 컨텍스트가 없어 알림은 표시되지 않았습니다.)"); } } /** * 지정된 폴더를 확인하고, 아직 처리되지 않은 새로운 설문조사 시트를 분석합니다. */ function processNewSurveys() { try { const folder = DriveApp.getFolderById(FOLDER_ID); const files = folder.getFilesByType(MimeType.GOOGLE_SHEETS); const properties = PropertiesService.getScriptProperties(); let foundNewFile = false; while (files.hasNext()) { const file = files.next(); const fileId = file.getId(); const processed = properties.getProperty(fileId); // 'processed' 속성이 없는 파일만 처리합니다. if (!processed) { foundNewFile = true; console.log(`새로운 파일 발견: ${file.getName()}, 분석을 시작합니다.`); analyzeAndReport(file); // 처리된 파일 ID를 속성에 저장하여 중복 실행을 방지합니다. properties.setProperty(fileId, 'done'); } } if (!foundNewFile) { console.log("새로운 파일이 없습니다. 모든 파일이 이미 처리되었습니다. 다시 테스트하려면 '처리 기록 초기화'를 실행하세요."); } } catch (e) { console.error(`오류 발생: ${e.toString()}`); // 필요하다면 이메일 알림 등의 오류 처리 로직을 추가할 수 있습니다. } } /** * 개별 구글 시트 파일을 분석하고 보고서를 생성합니다. * @param {GoogleAppsScript.Drive.File} file 처리할 구글 시트 파일 객체 */ function analyzeAndReport(file) { try { const sheet = SpreadsheetApp.openById(file.getId()).getSheets()[0]; const data = sheet.getDataRange().getValues(); if (data.length < 2) { console.log(`'${file.getName()}' 파일에 데이터가 부족하여 분석을 건너뜁니다.`); return; } const headers = data[0].join(', '); // 헤더 (질문) const surveyData = data.slice(1).map(row => row.join(', ')).join('\n'); // 응답 데이터 // OpenAI API에 보낼 프롬프트를 생성합니다. const prompt = createPrompt(headers, surveyData); // OpenAI API를 호출하여 분석 결과를 받습니다. const analysisResult = callOpenAI(prompt); if (analysisResult) { // 결과물을 구글 문서로 생성합니다. createReportDocument(file.getName(), analysisResult); } else { console.error("OpenAI API로부터 유효한 응답을 받지 못했습니다.") } } catch (e) { console.error(`'${file.getName()}' 파일 처리 중 오류 발생: ${e.toString()}`); } } /** * OpenAI API에 전달할 프롬프트를 생성합니다. * @param {string} headers 설문조사 질문들 (쉼표로 구분된 문자열) * @param {string} surveyData 설문조사 응답 데이터 (줄바꿈으로 구분된 문자열) * @return {string} 완성된 프롬프트 문자열 */ function createPrompt(headers, surveyData) { return ` 당신은 19년차 데이터 기반 마케팅 전략가입니다. 주어진 설문조사 데이터를 바탕으로 심층적인 마케팅 분석 보고서를 작성해야 합니다. **분석 프로세스:** 1. **마케터 역할 지정:** 아래 설문조사 질문들을 보고, 이 데이터를 분석하기에 가장 적합한 마케터의 역할(예: 퍼포먼스 마케터, 브랜드 마케터, CRM 마케터 등)을 지정하십시오. 2. **유의미한 분석 포인트 제안:** 지정된 역할의 마케터가 되어, 해당 데이터를 통해 얻을 수 있는 유의미한 분석 포인트 10가지를 제안하십시오. 3. **전문가 보고서 작성:** 19년차 전문가의 시각으로, 위에서 제안한 10가지 포인트를 중심으로 상세한 마케팅 분석 보고서를 작성하십시오. 보고서는 반드시 개조식(bullet points)으로 간결하고 명확하게 작성해야 합니다. 최종 결과물은 마크다운 코드 블록 안에 정리해주십시오. --- **[설문조사 질문]** ${headers} **[설문조사 응답 데이터]** ${surveyData} --- **[출력 형식]** 분석의 모든 결과(역할 지정, 분석 포인트, 최종 보고서)를 다음 마크다운 형식에 맞춰 코드 블록 안에 작성해 주십시오. \`\`\`markdown ### 1. 마케터 역할 지정 - **지정된 역할:** [여기에 역할 입력] ### 2. 유의미한 분석 포인트 10가지 1. [포인트 1] 2. [포인트 2] 3. [포인트 3] 4. [포인트 4] 5. [포인트 5] 6. [포인트 6] 7. [포인트 7] 8. [포인트 8] 9. [포인트 9] 10. [포인트 10] ### 3. 19년차 전문가 마케팅 분석 보고서 - **핵심 요약:** [보고서의 전체 내용을 한두 문장으로 요약] - **세부 분석:** - **(분석 포인트 1 관련):** [분석 내용] - **(분석 포인트 2 관련):** [분석 내용] - ... (나머지 포인트에 대한 분석) ... - **전략적 제언:** - [분석 결과를 바탕으로 한 구체적인 실행 방안이나 전략 제안] \`\`\` `; } /** * OpenAI ChatCompletion API를 호출합니다. * @param {string} prompt API에 보낼 프롬프트 * @return {string | null} API 응답 텍스트 또는 실패 시 null */ function callOpenAI(prompt) { const apiUrl = 'https://api.openai.com/v1/chat/completions'; const payload = { model: 'gpt-4o', messages: [{ role: 'user', content: prompt }], temperature: 0.7, max_tokens: 2000 }; const options = { method: 'post', contentType: 'application/json', headers: { 'Authorization': `Bearer ${OPENAI_API_KEY}` }, payload: JSON.stringify(payload), muteHttpExceptions: true }; try { const response = UrlFetchApp.fetch(apiUrl, options); const responseCode = response.getResponseCode(); const responseBody = response.getContentText(); if (responseCode === 200) { const jsonResponse = JSON.parse(responseBody); // 마크다운 코드 블록 내용만 추출 const content = jsonResponse.choices[0].message.content; const markdownMatch = content.match(/```markdown\n([\s\S]*?)\n```/); return markdownMatch ? markdownMatch[1] : content; } else { console.error(`OpenAI API Error: ${responseCode} ${responseBody}`); return null; } } catch (e) { console.error(`API 호출 중 예외 발생: ${e.toString()}`); return null; } } /** * 분석 결과를 바탕으로 새로운 구글 문서를 생성합니다. * @param {string} originalFileName 원본 구글 시트 파일 이름 * @param {string} reportContent 보고서 내용 */ function createReportDocument(originalFileName, reportContent) { try { const dateStr = Utilities.formatDate(new Date(), 'GMT+9', 'yyyy-MM-dd'); const docName = `[${originalFileName}_분석 리포트] ${dateStr}`; const folder = DriveApp.getFolderById(FOLDER_ID); const doc = DocumentApp.create(docName); const body = doc.getBody(); // 보고서 내용을 파싱하여 구글 문서 스타일로 추가 const lines = reportContent.split('\n'); lines.forEach(line => { if (line.startsWith('### ')) { body.appendParagraph(line.replace('### ', '')).setHeading(DocumentApp.ParagraphHeading.HEADING3); } else if (line.startsWith('## ')) { body.appendParagraph(line.replace('## ', '')).setHeading(DocumentApp.ParagraphHeading.HEADING2); } else if (line.startsWith('# ')) { body.appendParagraph(line.replace('# ', '')).setHeading(DocumentApp.ParagraphHeading.HEADING1); } else if (line.trim().startsWith('- ')) { // 들여쓰기된 리스트 항목 처리 const indent = line.match(/^\s*/)[0].length / 4; // 4칸을 1단계 들여쓰기로 가정 const listItem = body.appendListItem(line.trim().substring(2)); if(indent > 0) { listItem.setIndentStart(36 * indent); // 1단계 = 36포인트 } } else if (line.trim().match(/^\d+\.\s/)) { body.appendListItem(line.trim().substring(line.indexOf(' ') + 1)).setGlyphType(DocumentApp.GlyphType.NUMBER); } else if (line.trim()) { body.appendParagraph(line); } }); doc.saveAndClose(); // 생성된 문서를 지정된 폴더로 이동 const docFile = DriveApp.getFileById(doc.getId()); folder.addFile(docFile); // 내 드라이브의 루트에 남아있는 파일은 삭제 DriveApp.getRootFolder().removeFile(docFile); console.log(`보고서 생성 완료: ${docName}`); } catch (e) { console.error(`구글 문서 생성 중 오류 발생: ${e.toString()}`); } }이때 코드를 저장하고 실행하면 다음과 같이 권한검토가 뜬다. 권한검토를 클릭하면
여기서 꼭 고급을 클릭해야 한다.
이후 실행을 하면 바로 되는 경우도 있고, 다양한 오류가 발생을 하는 경우도 있는데, 오류메시지를 복사하거나 캡쳐해서 제미나이나 챗GPT를 통해 보완하면 해결할 수 있다.
이 코드를 아까 새로 만들어 놓았던 앱스스크립트로 가서 붙이고 저장하고 실행.
최종 결과물 (와우!!! 생각보다 오류코드가 많이 나서 고생했음)
결과와 배운 점
❗ 시행착오: 에러가 예상보다 자주 발생했어요. 조금은 답답하기도 했지만, 제미나이와 ChatGPT에게 오류 메시지를 그대로 붙여넣으며 하나씩 해결해나갈 수 있었습니다.
🤔 깨달음:
Apps Script는 자동화를 처음 접하는 사람에게 정말 좋은 출발점이라는 생각이 들었어요.
꼭 완성도 높은 자동화를 처음부터 만들 필요는 없더라고요. 작게 시작해서, 조금씩 확장해가는 재미가 있었습니다.
✅ 다음 계획:
업무에 자주 사용하는 구글 폼 응답 자동 정리 작업을 이 구조에 맞춰서 구현해볼 예정입니다.
독자에게 전하고 싶은 말
처음 해보는 게 어렵게 느껴질 수 있지만, 진짜 중요한 건 "일단 해보는 것"이에요. 막히면 ChatGPT나 Gemini 같은 도구들이 도와줄 수 있고, 따라하면서 배우는 속도도 꽤 빠르답니다! 🏃♀️💨
"우선 해보고, 나중에 수정하면 된다"는 마음으로 도전해보세요 💪😄
도움 받은 글 (옵션)
스터디장님 두분(여행가J, 타이칸)의 상세한 안내와 설명, 상냥하고 친절한 버디님(허세임)의 도움으로 어렵지 않게 성공한 것 같습니다. 감사합니다.