관리 메뉴

즐겁게, 코드

Google Apps Script로 주식 주가 정보 스크래핑하기 본문

🎨 프론트엔드/뚝딱뚝딱!

Google Apps Script로 주식 주가 정보 스크래핑하기

Chamming2 2025. 1. 19. 21:45

요즘 주식 데이터 분석에 흥미가 생겨 오렌지사과 님의 블로그를 잘 읽고 있는데, 구글 시트에 주가를 적재하는 부분에 호기심이 생겼다.

아래에 소개할 코드는 위 작업을 자동화한 것이다.

위 설명을 대강 요약하면 "특정 페이지에 접속해 페이지 전체를 복사한 뒤, 주가 데이터를 제외한 부분을 손으로 발라내야 한다" 는 것인데, 이런 작업을 쉽게 만드는 것이 개발자의 의무 아니겠던가!

 

일단 위 작업을 손으로 반복하는 것은 최선이 아닐 것 같아 스크래핑용 람다 함수를 만들어야 할지 고민하고 있었는데, 오늘 다룰 Google Apps Script 를 사용하면 너무나도 손쉽게 작업을 자동화할 수 있다.

1. 주가 데이터를 수집하는 방법

주가 데이터를 분석하기 위해서는 기본적으로 특정 기간에서의 OHLCV (시가, 고가, 저가, 종가, 거래량) 정보가 필요한데, 데이터를 수집하기 위해 yfinance, FinanceDataReader 등의 파이썬 라이브러리를 사용하거나 유료 API 서비스를 사용하는 방법, 웹 포탈을 직접 스크래핑하는 방법 등이 있다.

 

상기한 두 파이썬 라이브러리는 모두 훌륭하지만 데이터를 원하는 모습으로 가공하기 어렵다는 점과 데이터를 구글 시트에 적재하는 로직을 추가로 개발해야 한다는 점, 일일 호출 횟수 등에 대한 정보가 불확실하다는 점 등에서 스크래핑 로직을 직접 구현하고 싶었다.

2. Apps Script 찾기

Apps Script는 구글 워크스페이스 (캘린더, 독스, 스프레드시트, 슬라이드 등) 제품군을 프로그래밍을 통해 제어하는 기능인데, 스크래핑 로직 구현을 위해 구글 시트에서 [확장 프로그램] - [Apps Script] 메뉴를 찾아보자.

Apps Script 메뉴에 진입하면 AWS Lambda의 코드 에디터같은 화면이 나타난다.

Apps Script는 일반적인 자바스크립트 함수의 문법을 대부분 활용할 수 있으면서 구글 시트와 상호작용할 수 있는 *SpreadsheetApp 라는 강력한 인터페이스를 제공한다. (하나 특이한 점을 꼽자면 .js 가 아닌 .gs 확장자를 사용한다는 특징이 있는데, VSCode에서도 잘 인식되는 등 개발에 큰 지장은 없다.)

💡 SpreadSheetApp 인스턴스에 대해서는 이 문서 에서 더 확인할 수 있다.

3. 주가 데이터를 구글 시트에 옮겨오기

이제 본격적으로 야후 파이낸셜의 주가 데이터를 가공한 뒤 구글 시트로 옮겨 볼텐데, 약간의 준비 작업이 필요하다.

1. B1, B2, B3 셀에 데이터 입력하기

B1 : 티커 / B2 : 조회 시작일 / B3 : 조회 종료일

B1 셀에는 티커의 단축번호 또는 영문 티커명 (Ex. QQQ, SPY)를 입력한다.

(만약 국내 티커의 단축번호를 모르겠다면 구글, investing 등에서 간단히 알아낼 수 있다.)

예를 들어 삼성전자의 단축번호는 005930 이다.

B2 셀에는 조회를 시작할 날짜를 YYYY-MM-DD 형식으로 입력한다.
(만약 해당 셀을 빈 칸으로 두면 조회 시작일을 1970년 1월 1일을 기준으로 조회하게 된다.)

 

B3 셀에는 조회를 종료할 날짜를 YYYY-MM-DD 형식으로 입력한다.
(만약 해당 셀을 빈 칸으로 두면 조회 종료일을 오늘 날짜를 기준으로 조회하게 된다.)

 

2. 내 시트 아이디, 시트 이름 기억하기

어떤 시트를 코드로 제어할 것인지 알려주기 위해 시트 아이디를 알려줄 필요가 있다.

여러분의 구글 시트 URL 중 /d/ 뒤에 오는 것이 구글 시트 아이디이다.

/d/ 뒤에 오는 영어 + 숫자 조합이 구글 시트 아이디이다.
나의 경우에는 "1iRDrqGSjUOvSrqP2ZTbC3JDzWLTxYCnfEw3X21udxMk" 이 시트 아이디가 된다.

그리고 Apps Script가 어떤 시트에 작업을 실행할지 알려줄 수 있도록 시트 이름을 미리 정해두자.

구글 시트 최하단을 보면 시트명 목록들이 나타날 것이다.

시트 아이디와 시트명은 기억해두고 있다가 바로 다음에 이어질 코드에 입력하면 된다.

3. 코드 실행하기

이제 Apps Script에 새로운 함수를 하나 만들고, 다음 코드를 붙여넣는다.

이 코드는 맨 위의 사진의 순서에 따라 야후 파이낸스를 스크래핑하고, 분석에 유리하도록 일부 행과 열의 데이터를 가공한다.

(Ex. Aug 17, 1993 -> 1993-08-17, 배당금 지급 정보 행을 삭제)

/**
 * Yahoo Finance에서 주식 데이터를 가져와 Google Sheets에 추가
 */
function importYahooFinanceData() {
  const spreadsheetId = "구글 시트 아이디"; // 구글 시트 ID
  const sheetName = "테이블을 복사할 시트 이름"; // 데이터가 들어갈 시트 이름
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);

  if (!sheet) {
    throw new Error(`${sheetName} 시트를 찾을 수 없습니다.`);
  }

  // 조회 조건 읽기
  const ticker = sheet.getRange("B1").getValue().toString().trim();
  if (!ticker) {
    throw new Error("B1 셀에 유효한 티커를 입력해주세요.");
  }

  const startDateInput = sheet.getRange("B2").getValue().toString().trim();
  const endDateInput = sheet.getRange("B3").getValue().toString().trim();

  const todayTimestamp = Math.floor(new Date().getTime() / 1000);
  const startDate = startDateInput
    ? Math.floor(new Date(startDateInput).getTime() / 1000)
    : 0;
  const endDate = endDateInput
    ? Math.floor(new Date(endDateInput).getTime() / 1000)
    : todayTimestamp;
  


  if (isNaN(startDate) || isNaN(endDate)) {
    throw new Error("B2 또는 B3에 유효한 날짜 형식을 입력해주세요.");
  }

  const url = `https://finance.yahoo.com/quote/${ticker}/history?period1=${startDate}&period2=${endDate}&interval=1d`;
  const html = UrlFetchApp.fetch(url).getContentText();

  // 데이터를 파싱하기 위해 HTML 내용을 분석
  const rawData = parseYahooFinanceHtml(html);


  // "dividend" 문자열이 포함된 행 필터링 및 날짜 형식 변환
  const filteredData = rawData
    .filter(row => row.length === 7 && !row.some(cell => cell.toLowerCase().includes("dividend")))
    .map(row => {
      row[0] = parseDate(row[0]); // 첫 번째 열의 날짜를 변환
      return row;
    });

  if (filteredData.length === 0) {
    throw new Error("유효한 데이터가 없습니다.");
  }

  // 첫 번째 행에 라벨 추가
  const labels = ["Date", "Open", "High", "Low", "Close*", "Adj Close**", "Volume"];
  const dataWithLabels = [labels, ...filteredData];

  // 구글 시트의 A6부터 데이터 추가
  const startRow = 6; // A6부터 시작
  sheet.getRange(startRow, 1, dataWithLabels.length, dataWithLabels[0].length).setValues(dataWithLabels);

  Logger.log("데이터 가져오기 완료");
}

/**
 * Yahoo Finance HTML에서 표 데이터를 파싱
 */
function parseYahooFinanceHtml(html) {
  const tableRegex = /<table.*?>([\s\S]*?)<\/table>/; // 첫 번째 테이블 매칭
  const match = html.match(tableRegex);
  if (!match) {
    throw new Error("테이블을 찾을 수 없습니다.");
  }

  const tableHtml = match[1];
  const rowRegex = /<tr.*?>([\s\S]*?)<\/tr>/g;
  const cellRegex = /<t[dh].*?>([\s\S]*?)<\/t[dh]>/g;

  const rows = [];
  let rowMatch;

  while ((rowMatch = rowRegex.exec(tableHtml)) !== null) {
    const rowHtml = rowMatch[1];
    const row = [];
    let cellMatch;
    while ((cellMatch = cellRegex.exec(rowHtml)) !== null) {
      const cellText = cellMatch[1]
        .replace(/<.*?>/g, "") // 태그 제거
        .trim();
      row.push(cellText);
    }
    rows.push(row);
  }

  return rows;
}

/**
 * 날짜 문자열을 "YYYY-MM-DD" 형식으로 변환
 */
function parseDate(dateString) {
  try {
    const date = new Date(dateString);
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, "0");
    const day = String(date.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
  } catch (e) {
    Logger.log(`날짜 변환 실패: ${dateString}`);
    return dateString; // 변환 실패 시 원래 값 반환
  }
}

위의 코드를 통으로 복사한 뒤 바로 실행하지 말고, 시트 아이디와 이름을 입력하는 과정을 꼭 기억하자.

결과

위 과정을 잘 따라했다면 야후 파이낸스에서 데이터를 깔끔하게 발라와 사용할 수 있게 된다.

나는 테스트를 위해 TIGER 미국배당다우존스 의 데이터를 불러와 봤는데, 국내 주식도 문제 없이 불러와지는 모습이다.

이제 잘 가공된 주식 데이터를 획득할 수 있게 되었으니, 분석 또는 개인 매매일지 기록용 등으로도 무궁무진하게 활용할 수 있게 되었다.

기타

1. Apps Script는 스케줄링을 지원한다.

조금 놀란 부분인데, Apps Script는 이처럼 시트, 캘린더의 작업을 자동화할 수 있을 뿐만 아니라 특정 시점, 주기마다 배치로 실행하는 것도 정말 쉽게 가능하다. (Ex. 장 종료 이후 주가 정보를 갱신한다던가 등의 동작을 구현할 수 있다.)

2. Apps Script의 사용량은 넉넉한 편이 아니다.

Apps Script 서비스 할당량 문서

비용 정책은 살짝 빡빡하게 느껴졌는데, 일일 수정 가능 용량이 50,000셀 / 일 이라면 역대 주가 데이터를 한번에 갱신하는 방법으로는 제대로 된 활용이 어려울 것 같았다. (*예를 들어 SPY의 역대 주가 데이터는 8,000행이 조금 넘는다.)

💡 그런데 실제로 테스트했을 때는 8,000(행) * 7(열) 데이터를 몇 번씩 업데이트했음에도 함수가 잘 실행된다.
쿼터 사용량은 조금 더 테스트를 돌려 봐야 파악할 수 있을 것 같다.

주가 정보 업데이트 배치를 매일 돌린다고 하면 제일 최근 데이터를 최하단 행에 추가하는 등, 쓰기 횟수를 최소화해야 여러 종목들에 대한 데이터 분석을 원활하게 진행할 수 있을 것으로 보인다.

 

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆