바이브코딩으로 홈페이지 만들기

4. Python 데이터 수집기 만들기

JUN_Kestrel 2026. 6. 30. 20:41

왜 Python 수집기를 따로 만들었을까?

프로젝트를 처음 시작했을 때는 React + Spring Boot만으로 서비스를 구성할 계획이었다. 외부 API를 호출하는 작업도 Spring Boot에서 모두 처리하면 될 것이라고 생각했다.

하지만 프로젝트를 구체화하면서 DART 공시, 한국은행 ECOS, 주가 데이터 등 여러 외부 데이터를 수집해야 했고, 이 과정에서 Claude Code는 데이터 수집기를 별도의 Python 프로젝트로 분리하는 구조를 제안했다.

처음에는 Spring Boot에서도 충분히 가능하지 않을까 생각했지만, 실제로는 Python을 선택하는 것이 훨씬 자연스러웠다.

가장 큰 이유는 FinanceDataReader였다. 국내 주식 데이터를 다루는 데 가장 많이 사용하는 라이브러리 중 하나인데 Python 생태계에서 제공된다. 여기에 pandas를 이용한 데이터 가공, requests를 이용한 API 호출 등 데이터 처리에 필요한 도구들도 대부분 Python에서 잘 갖춰져 있었다.

결과적으로 데이터 수집은 Python이 담당하고, Spring Boot는 저장된 데이터를 사용자에게 제공하는 역할로 분리했다.

지금 돌아보면 이 선택은 프로젝트 전체에서 가장 만족스러운 결정 중 하나였다.

 

어떤 데이터를 수집하는가?

Jipyo에서 제공하는 대부분의 데이터는 외부 API와 공개 데이터를 이용해 자동으로 수집된다.

데이터출처수집 주기

공시 DART OpenAPI 매시간
경제지표 한국은행 ECOS 매시간
100대 통계지표 한국은행 ECOS 매시간
경제 뉴스 정책브리핑 RSS 매시간
일별 주가 FinanceDataReader 매일 16:00
재무제표 DART OpenAPI 매주 일요일 02:00

사용자가 직접 데이터를 입력하는 구조가 아니라, Python 수집기가 정해진 시간마다 데이터를 가져와 PostgreSQL에 저장하는 방식으로 동작한다.

 

Collector 구조

Python 수집기는 기능별로 역할을 분리해 구성했다.

collector/
│
├── main.py
├── config.py
├── db/
├── dart/
├── ecos/
├── market/
├── rss/
└── scripts/

각 디렉토리는 하나의 역할만 담당한다.

  • main.py : 수집기의 진입점이자 전체 스케줄을 관리하는 오케스트레이터
  • config.py : API 키와 환경 설정 관리
  • db : SQLAlchemy와 데이터베이스 공통 기능
  • dart : DART OpenAPI를 이용한 공시 및 재무제표 수집
  • ecos : 한국은행 ECOS 경제지표 수집
  • market : FinanceDataReader를 이용한 주가 수집
  • rss : 정책브리핑 RSS 수집

이처럼 데이터 출처별로 모듈을 분리해 두니 새로운 데이터 소스를 추가하거나 유지보수할 때도 영향을 최소화할 수 있었다.

 

ETL 구조로 데이터를 처리했다

수집기는 단순히 API를 호출해서 저장하는 구조가 아니다.

외부 데이터를 가져온 뒤 필요한 형태로 가공하고 데이터베이스에 저장하는 ETL(Extract → Transform → Load) 구조를 사용했다.

전체 흐름은 다음과 같다.

ETL 아키텍처 다이어그램
  1. Extract
    • DART, ECOS, FinanceDataReader, RSS에서 데이터를 가져온다.
  2. Transform
    • 응답 데이터를 정리하고 컬럼명을 통일한다.
    • pandas를 이용해 필요한 데이터만 추출하고 계산을 수행한다.
    • 중복 데이터나 불필요한 데이터를 제거한다.
  3. Load
    • PostgreSQL에 UPSERT 방식으로 저장한다.
    • 수집 완료 후 sync_status 테이블에 마지막 동기화 시간을 기록한다.

덕분에 관리자 페이지에서는 각 데이터가 언제 마지막으로 갱신되었는지도 확인할 수 있도록 구현했다.

 

Cron 대신 Python 스케줄러를 사용했다

처음에는 Linux의 cron을 사용할까도 고민했지만, 이번 프로젝트에서는 Python의 schedule 라이브러리를 사용했다.

핵심 로직은 매우 단순하다.

while True:
    schedule.run_pending()
    time.sleep(60)

60초마다 실행 가능한 작업이 있는지 확인하고, 실행 시간이 되면 해당 작업을 수행하는 구조다.

운영 환경이 Docker 컨테이너이기 때문에 별도의 cron을 관리하는 것보다 하나의 프로세스 안에서 스케줄을 관리하는 편이 훨씬 단순하고 유지보수하기 쉬웠다.

 

왜 작업 시간을 모두 다르게 설정했을까?

모든 작업을 정각에 실행하는 것이 가장 단순한 방법이다.

하지만 실제 운영에서는 그렇게 하지 않았다.

실행 시간작업

매시 :00 공시 수집
매시 :20 경제지표 수집
매시 :30 재무지표 계산
매시 :40 100대 통계지표 수집
매시 :50 정책브리핑 RSS 수집
매일 16:00 일별 주가 수집
일요일 02:00 재무제표 수집 및 재계산

매시간 수행되는 작업을 10분 간격으로 나눈 이유는 크게 세 가지다.

첫 번째는 외부 API 호출 제한이다.

DART, ECOS, 정책브리핑 API를 동시에 호출하면 순간적으로 요청이 몰릴 수 있다. 호출을 분산하면 스로틀링이나 호출 제한에 걸릴 가능성을 줄일 수 있다.

두 번째는 데이터베이스 부하다.

여러 파이프라인이 동시에 대량의 데이터를 저장하면 PostgreSQL 연결과 쓰기 작업이 한꺼번에 몰리게 된다. 작업 시간을 나누면 데이터베이스 부하도 자연스럽게 분산된다.

세 번째는 메모리 사용량이다.

각 작업은 pandas DataFrame을 이용해 데이터를 처리한다. 여러 작업이 동시에 실행되면 메모리 사용량이 급격히 증가할 수 있기 때문에, 홈서버 환경에서는 특히 작업을 분산하는 것이 중요했다.

 

주가는 왜 하루에 한 번만 수집할까?

주가는 매시간 수집하지 않는다.

FinanceDataReader에서 제공하는 일별 데이터는 장 마감 이후 확정된 종가를 기준으로 제공된다.

장중에 계속 데이터를 가져오면 아직 확정되지 않은 값을 저장하게 되므로 의미가 없다.

그래서 장 마감 이후 데이터가 안정적으로 반영되는 시간을 고려해 매일 16:00에 한 번만 수집하도록 구성했다.

정확한 데이터를 저장하는 것이 빈번하게 저장하는 것보다 더 중요하다고 판단했다.

 

재무제표는 왜 주 1회만 수집할까?

재무제표는 더 자주 가져올 필요가 없었다.

분기 단위로 갱신되는 데이터이기 때문에 매시간 또는 매일 호출하는 것은 의미가 없다.

또한 DART OpenAPI는 호출 횟수 제한이 있으며, 전체 상장기업의 재무제표를 수집하면 상당한 시간이 소요된다.

그래서 서버 사용량이 적은 일요일 새벽 2시에 한 번만 실행하도록 구성했다.

이렇게 하면 API 사용량도 절약할 수 있고 다른 작업에도 영향을 주지 않는다.

 

Claude Code는 얼마나 잘 만들어줬을까?

이번 프로젝트에서 가장 놀랐던 부분은 외부 API를 연동하는 과정이었다.

DART나 ECOS API는 공식 문서를 읽어야 구현할 수 있는데, Claude Code는 관련 문서를 참고해 API 요청 코드와 응답 처리, 페이지네이션, 예외 처리까지 대부분 스스로 작성했다.

나는 어떤 데이터를 수집해야 하는지와 전체 구조만 설명했고, 세부 구현은 대부분 AI가 진행했다.

물론 중간에 수정 요청을 하거나 구조를 조금 바꾸는 경우는 있었지만, 예상했던 것보다 훨씬 높은 수준의 결과물을 만들어 주었다.

특히 여러 외부 API를 동일한 ETL 구조로 정리해 주는 모습은 상당히 인상적이었다.

 

마무리

이번 프로젝트를 진행하면서 다시 한 번 느낀 것은 Python은 데이터 수집에 정말 강한 언어라는 점이었다.

그리고 그보다 더 놀라웠던 것은 Claude Code의 능력이었다.

단순히 코드를 생성하는 수준이 아니라 공식 문서를 이해하고, ETL 구조를 제안하고, 데이터 수집 파이프라인까지 구현하는 모습을 보면서 단순한 코드 생성기를 넘어 함께 개발하는 파트너처럼 느껴졌다.

이번 글에서도 직접 코드를 작성한 부분은 거의 없었다. 구조를 결정하고 요구사항을 전달하는 것은 사람이 했지만, 실제 구현은 AI가 담당했다.

다음 글에서는 이렇게 수집한 데이터를 사용자에게 제공하기 위해 Spring Boot REST API를 어떻게 구성했는지 소개해 보려고 한다.