구글 스프레드시트 API 연동해서 데이터 자동 수집하기

매일 아침 사이트 몇 곳을 돌면서 숫자를 확인하고, 그걸 구글 시트에 옮겨 적는 일. 한 번에 10분이면 끝나니까 별것 아닌 것 같지만 주 5일이면 한 달에 세 시간이 넘습니다. 게다가 손으로 옮기다 보면 자릿수를 틀리거나 하루를 통째로 빠뜨리는 일이 반드시 생깁니다. 구글 스프레드시트 API를 파이썬에 연결해두면 이 과정을 스크립트 한 개로 대체할 수 있습니다. 이 글에서는 인증 키를 발급받는 것부터 시작해서, 시트를 읽고 쓰는 기본 코드, 매일 자동으로 데이터를 쌓는 실전 스크립트, 그리고 실제로 자주 막히는 지점까지 순서대로 정리합니다.

1단계: 서비스 계정 만들기 (약 5분)

개인 구글 계정으로 로그인하는 OAuth 방식도 있지만, 자동화 목적이라면 서비스 계정이 정답입니다. 브라우저 로그인 창이 뜨지 않고, 토큰이 만료돼서 새벽에 스크립트가 멈추는 일도 없기 때문입니다. 서비스 계정은 쉽게 말해 사람이 아닌 프로그램용 구글 계정이고, 이메일 주소를 하나 받습니다.

  1. 구글 클라우드 콘솔에 접속해 프로젝트를 새로 만듭니다.
  2. 왼쪽 메뉴에서 API 및 서비스 > 라이브러리로 이동해 Google Sheets API를 검색한 뒤 사용 설정합니다.
  3. 같은 화면에서 Google Drive API도 사용 설정합니다. 시트를 이름으로 열거나 새로 만들 때 필요합니다.
  4. API 및 서비스 > 사용자 인증 정보에서 사용자 인증 정보 만들기 > 서비스 계정을 선택하고 이름을 지정합니다.
  5. 만들어진 서비스 계정을 클릭 → 키 탭 → 키 추가 > 새 키 만들기 > JSON을 누르면 키 파일이 내려받아집니다.
  6. 내려받은 JSON 파일을 service-account.json이라는 이름으로 스크립트와 같은 폴더에 둡니다.

여기서 가장 많이 놓치는 마지막 한 단계가 있습니다. JSON 파일을 열어보면 client_email 항목에 ...@...iam.gserviceaccount.com 형태의 주소가 들어 있습니다. 연동할 구글 시트를 열어 이 주소를 편집자로 공유해야 합니다. 사람에게 공유하듯이 우측 상단 공유 버튼을 누르고 붙여넣으면 됩니다. 이걸 빼먹으면 아래에서 설명할 403 오류가 납니다.

2단계: 라이브러리 설치와 첫 연결

구글이 제공하는 공식 클라이언트를 직접 써도 되지만 코드가 길어집니다. gspread는 스프레드시트 작업만 놓고 보면 훨씬 짧게 끝나서, 자동화 용도로는 이쪽이 편합니다.

# 파이썬 3.8 이상이면 그대로 됩니다
python -m pip install gspread google-auth

# 설치 확인
python -c "import gspread; print(gspread.__version__)"

설치가 끝났으면 연결을 확인합니다. 스프레드시트 ID는 주소창에서 가져옵니다. 예를 들어 주소가 docs.google.com/spreadsheets/d/1AbCdEf.../edit이라면 1AbCdEf... 부분이 ID입니다.

import gspread

# 다운로드한 서비스 계정 JSON 키 경로
gc = gspread.service_account(filename="service-account.json")

# 주소창의 /d/ 와 /edit 사이 문자열이 스프레드시트 ID입니다
sh = gc.open_by_key("1AbCdEfGhIjKlMnOpQrStUvWxYz1234567890")
ws = sh.sheet1

# A1 셀 읽기
print(ws.acell("A1").value)

# 맨 아래에 한 줄 추가
ws.append_row(["2026-09-05", "테스트", 123])

시트 맨 아래에 한 줄이 추가됐다면 연동은 끝난 겁니다. 나머지는 전부 이 위에 얹는 응용입니다.

3단계: 데이터 읽기 — 상황별 네 가지 방법

시트를 읽는 방법은 여러 가지인데, 어떤 걸 쓰느냐에 따라 코드 길이와 속도가 꽤 달라집니다.

import gspread

gc = gspread.service_account(filename="service-account.json")
ws = gc.open_by_key("스프레드시트ID").worksheet("주문내역")

# 1) 첫 행을 키로 쓰는 딕셔너리 리스트 -> 가장 많이 쓰는 형태
records = ws.get_all_records()
for row in records:
    print(row["주문번호"], row["금액"])

# 2) 값만 2차원 리스트로
values = ws.get_all_values()

# 3) 특정 범위만 (필요한 만큼만 읽는 게 속도에 유리합니다)
part = ws.get("A2:C100")

# 4) 열 하나만
amounts = ws.col_values(3)

실무에서는 get_all_records()를 가장 많이 씁니다. 첫 행을 열 이름으로 인식해서 딕셔너리로 돌려주기 때문에, 열 순서가 바뀌어도 코드를 고칠 필요가 없습니다. 다만 첫 행에 빈 셀이나 중복된 이름이 있으면 오류가 납니다. 헤더 행은 반드시 채워두세요.

4단계: 데이터 쓰기 — 호출 횟수를 줄이는 게 핵심

쓰기에서 초보자가 가장 흔히 만드는 실수는 반복문 안에서 한 줄씩 쓰는 것입니다. 100줄을 넣으면 API를 100번 호출하게 되고, 느릴 뿐 아니라 할당량 초과로 중간에 끊깁니다.

rows = [
    ["2026-09-05", "노트북", 1_290_000],
    ["2026-09-05", "모니터", 320_000],
    ["2026-09-05", "키보드", 89_000],
]

# 나쁜 예: 한 줄씩 append_row -> 3회 API 호출
for r in rows:
    ws.append_row(r)

# 좋은 예: 한 번에 -> 1회 API 호출
ws.append_rows(rows, value_input_option="USER_ENTERED")

# 특정 범위를 통째로 덮어쓰기
ws.update("A2:C4", rows, value_input_option="USER_ENTERED")

# 시트 비우기
ws.clear()

value_input_option은 값을 어떻게 해석할지 정하는 옵션입니다. 기본값인 RAW는 입력한 그대로 문자열처럼 저장하고, USER_ENTERED는 사람이 직접 타이핑한 것처럼 처리합니다. 즉 USER_ENTERED를 쓰면 날짜는 날짜로, 숫자는 숫자로 인식되고 =SUM(A1:A10) 같은 수식도 수식으로 들어갑니다. 수집한 값을 나중에 시트에서 계산에 쓸 거라면 이쪽을 쓰세요.

5단계: 매일 데이터를 쌓는 실전 스크립트

이제 앞의 조각들을 합쳐 실제로 돌아가는 수집기를 만들어보겠습니다. 공개 환율 API에서 값을 받아와 시트에 날짜별로 누적하는 스크립트입니다. 시트가 없으면 만들고, 오늘 데이터가 이미 있으면 건너뛰도록 해서 여러 번 실행해도 안전합니다.

"""환율을 매일 수집해서 구글 시트에 한 줄씩 쌓는 스크립트."""
import datetime
import requests
import gspread

SHEET_ID = "여기에_스프레드시트_ID"
KEY_FILE = "service-account.json"
HEADER = ["수집일", "통화", "기준가"]


def fetch_rates():
    """공개 환율 API에서 원화 기준 환율을 받아옵니다."""
    res = requests.get(
        "https://api.frankfurter.app/latest",
        params={"base": "USD", "symbols": "KRW,JPY,EUR"},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()["rates"]


def get_worksheet():
    gc = gspread.service_account(filename=KEY_FILE)
    sh = gc.open_by_key(SHEET_ID)
    try:
        ws = sh.worksheet("환율")
    except gspread.WorksheetNotFound:
        ws = sh.add_worksheet(title="환율", rows=1000, cols=10)
        ws.append_row(HEADER)
    return ws


def main():
    today = datetime.date.today().isoformat()
    ws = get_worksheet()

    # 같은 날짜가 이미 있으면 중복 적재하지 않습니다
    if today in ws.col_values(1):
        print(f"{today} 데이터가 이미 있습니다. 건너뜁니다.")
        return

    rates = fetch_rates()
    rows = [[today, cur, price] for cur, price in sorted(rates.items())]
    ws.append_rows(rows, value_input_option="USER_ENTERED")
    print(f"{len(rows)}건 기록 완료")


if __name__ == "__main__":
    main()

여기서 눈여겨볼 부분은 중복 방지입니다. 자동 실행 스크립트는 재시도나 수동 실행으로 하루에 두 번 이상 돌아가는 일이 흔합니다. 그때마다 같은 데이터가 쌓이면 나중에 집계가 전부 어긋나므로, 적재 전에 날짜를 한 번 확인하는 세 줄이 사실상 가장 중요한 코드입니다.

인증 방식 비교

구글 시트에 접근하는 방법은 크게 세 가지이고, 용도가 명확히 갈립니다.

방식로그인 창적합한 상황주의점
서비스 계정없음서버·스케줄러에서 무인 실행시트를 서비스 계정 이메일에 공유해야 함
OAuth 사용자 인증최초 1회 필요내 개인 시트를 내 PC에서만 다룰 때토큰 만료 시 재인증 필요
API 키없음공개(웹에 게시)된 시트 읽기 전용쓰기 불가, 비공개 시트 접근 불가

자동화가 목적이라면 사실상 서비스 계정 한 가지만 알면 됩니다. 나머지 둘은 “왜 안 되지”를 검색하다 마주쳤을 때 구분만 할 수 있으면 충분합니다.

자주 만나는 오류와 해결

  • 403 The caller does not have permission — 열에 아홉은 시트를 서비스 계정 이메일에 공유하지 않은 경우입니다. JSON 키의 client_email 값을 복사해 해당 시트에 편집자로 추가하세요.
  • 403 Google Sheets API has not been used in project … — 클라우드 콘솔에서 API 사용 설정을 안 한 상태입니다. 오류 메시지에 포함된 링크를 그대로 열어 사용 설정하면 됩니다. 반영까지 1~2분 걸릴 수 있습니다.
  • SpreadsheetNotFound — open()으로 이름을 찾을 때 나는 오류입니다. 이름 대신 open_by_key()로 ID를 직접 넘기는 편이 훨씬 안정적입니다.
  • 429 Quota exceeded — 짧은 시간에 너무 많이 호출했습니다. 구글 시트 API는 분 단위 요청 한도가 있어서, 반복문 안에서 셀 단위로 읽고 쓰면 금방 걸립니다. 아래 재시도 코드와 일괄 처리로 해결합니다.
  • APIError: Unable to parse range — 시트 이름에 공백이나 특수문자가 있을 때입니다. 시트 이름을 영문·숫자로 단순하게 바꾸거나 worksheet()로 워크시트 객체를 먼저 가져와 쓰세요.
import time
import gspread


def with_retry(func, *args, tries=5, **kwargs):
    """429(할당량 초과)면 대기 시간을 두 배씩 늘리며 재시도합니다."""
    delay = 2
    for attempt in range(tries):
        try:
            return func(*args, **kwargs)
        except gspread.exceptions.APIError as e:
            if e.response.status_code != 429 or attempt == tries - 1:
                raise
            print(f"할당량 초과. {delay}초 후 재시도")
            time.sleep(delay)
            delay *= 2


# 사용
with_retry(ws.append_rows, rows, value_input_option="USER_ENTERED")

매일 자동으로 돌리기

스크립트가 완성됐으면 마지막은 스케줄 등록입니다. 윈도우라면 작업 스케줄러, 맥이나 리눅스라면 cron을 씁니다.

# 리눅스 / 맥 : 매일 오전 8시 실행
# crontab -e 로 열어서 아래 한 줄 추가
0 8 * * * /home/user/project/.venv/bin/python /home/user/project/collect.py >> /home/user/project/collect.log 2>&1

여기서 파이썬 경로를 반드시 절대 경로로 적어야 합니다. 스케줄러는 우리가 터미널에서 쓰던 환경 변수를 물려받지 않기 때문에, python이라고만 쓰면 가상환경이 아닌 시스템 파이썬이 실행되면서 ModuleNotFoundError가 납니다. 자동 실행이 조용히 실패하는 원인 대부분이 이것입니다. 로그를 파일로 남겨두는 것도 꼭 해두세요. 화면에 뜨지 않는 오류는 몇 주가 지나서야 발견됩니다.

보안에서 꼭 지킬 것

  • JSON 키 파일을 깃허브에 올리지 마세요. .gitignore에 service-account.json을 먼저 추가한 뒤 작업을 시작하는 습관을 들이면 안전합니다. 공개 저장소에 올라간 키는 자동 스캐너에 거의 즉시 수집됩니다.
  • 실수로 올렸다면 파일만 지우는 걸로는 부족합니다. 클라우드 콘솔에서 해당 키를 삭제하고 새로 발급해야 합니다. 커밋 기록에 값이 그대로 남아 있기 때문입니다.
  • 서비스 계정에는 필요한 시트만 공유합니다. 폴더 통째로 공유하면 그 폴더의 모든 파일에 접근 권한이 생깁니다.
  • 읽기만 하면 되는 스크립트라면 시트를 뷰어 권한으로만 공유하세요. 잘못된 코드가 원본을 덮어쓰는 사고를 구조적으로 막을 수 있습니다.

마무리

처음부터 완성된 수집기를 만들려고 하면 인증 단계에서 지치기 쉽습니다. 오늘은 서비스 계정 키를 발급받고, 빈 시트에 append_row로 한 줄 넣어보는 것까지만 해보세요. 여기까지가 전체 난이도의 절반 이상이고, 한 줄이 들어가는 걸 확인하고 나면 나머지는 원하는 데이터를 가져오는 평범한 파이썬 코드일 뿐입니다.

다음 글에서는 깃허브 이슈·PR 템플릿을 만들어 협업 속도를 높이는 방법을 다루겠습니다.

댓글 남기기