들어가며
3편까지 데이터를 수집하고 DB에 넣었다. 이제 이 데이터를 웹 브라우저에서 볼 수 있게 만들어야 한다.
DB에 직접 접속해서 SQL을 치는 건 나만 할 수 있다. 누구든 브라우저만 열면 볼 수 있으려면, DB와 브라우저 사이에 "중간 다리"가 필요하다. 이 역할을 하는 게 백엔드 서버다.
이번 편에서는 FastAPI로 55개 API 엔드포인트를 만들고, 실시간 시세까지 연동한 과정을 다룬다.
왜 FastAPI인가
Python으로 API 서버를 만들 수 있는 프레임워크는 여러 가지다. Django, Flask, FastAPI 등. 이 중에서 FastAPI를 선택한 이유:
- 빠르다 — 비동기(async) 기반이라 여러 요청을 동시에 처리할 수 있다
- 코드가 적다 — 함수 하나 만들면 API 하나가 된다. 설정 파일 따로 필요 없다
- 자동 문서화 — API를 만들면 테스트 페이지가 자동으로 생긴다
- 데이터 수집기도 Python — 같은 언어라서 DB 접근 코드를 공유할 수 있다
Django는 기능이 너무 많고(관리자 페이지, ORM 등 안 쓸 것들이 많다), Flask는 비동기 지원이 약하다. FastAPI는 딱 필요한 것만 가볍게 쓸 수 있어서 개인 프로젝트에 적합했다.
설명은 이렇지만 사실은 Claude Code가 추천한 것이다.
API가 뭔데?
비개발자를 위해 잠깐 설명하면 — API는 "이런 주소로 요청하면, 이런 데이터를 돌려줄게"라는 약속이다.
예를 들어:
- /api/dashboard → 대시보드에 보여줄 전체 시장 요약 데이터
- /api/stock/005930/info → 삼성전자의 상세 정보
- /api/stock/005930/prices → 삼성전자의 주가 차트 데이터
- /api/screener?per_max=10&roe_min=15 → PER 10 이하, ROE 15% 이상 종목 목록
브라우저(프론트엔드)가 이 주소로 요청을 보내면, 서버(백엔드)가 DB에서 데이터를 꺼내서 JSON 형태로 돌려준다. 프론트엔드는 받은 데이터를 예쁘게 그려주기만 하면 된다.
55개 API, 9개 라우터
처음에는 API가 5~6개였다. 대시보드, 종목 상세, 주가 차트 정도. 그런데 기능이 하나씩 추가되면서 55개까지 늘어났다.
API가 50개가 넘으면 파일 하나에 다 넣기 어렵다. 그래서 라우터라는 단위로 분리했다:
| 라우터 | 담당 | 주요 API 예시 |
| 종목 (stock) | 종목 검색, 상세 정보, 주가 | 종목 검색, 종목 상세, 차트 데이터 |
| 시장 (market) | 대시보드, 지수, 환율 | 대시보드, 지수 데이터, 환율/원자재 |
| 시그널 (signal) | 52주 신고가, 수급, 퀀트 | 시그널 목록, 트리맵, 퀀트 추천 |
| 스크리너 (screener) | 조건 검색 | 필터링 + 정렬 + 페이지네이션 |
| ETF (etf) | ETF 분석 | ETF 대시보드, 카테고리, 랭킹 |
| 증권사 (broker) | 증권사 추천 | 컨센서스, 개별 리포트 |
| 비교 (compare) | 종목 비교 | 최대 5종목 병렬 비교 |
| 이벤트 (event) | 이벤트 캘린더 | 실적 발표, 배당락일 등 |
| 방문자 (visitor) | 방문 통계 | 일별 방문자 수 |
라우터를 나누면 좋은 점은, ETF 관련 수정을 할 때 ETF 파일만 보면 된다는 것이다. 다른 기능에 영향을 줄 걱정 없이 독립적으로 개발할 수 있다.
실시간 시세는 어떻게?
DB에 저장된 건 장 마감 후의 종가다. 그런데 장중에 사이트를 열면 실시간 시세를 보고 싶다.
직접 증권사 실시간 API를 연동하면 좋겠지만, 웹소켓 연결을 유지해야 하고 3,970종목을 동시에 구독하면 트래픽이 상당하다. 개인 프로젝트에서는 과한 접근이다.
대신 1분 주기로 전 종목 시세를 갱신하는 방식을 택했다. 서버가 백그라운드에서 1분마다 외부 API를 호출해서 캐시에 저장하고, 프론트엔드는 이 캐시를 읽는다.
완벽한 실시간은 아니지만, 1분 지연이면 일반 투자자 입장에서 충분하다. HTS처럼 초 단위 틱 데이터가 필요한 게 아니니까.
캐시 - 같은 걸 반복해서 계산하지 않기
대시보드를 한 번 열면 내부적으로 API 호출이 여러 번 일어난다. 시장 요약, 상승/하락 TOP, 수급 순위, 시그널... 각각이 DB에서 수천 ~ 수만 행을 조회하고 계산한다.
매번 같은 계산을 반복하면 느리다. 장 마감 후 데이터는 다음 날까지 바뀌지 않으니, 한 번 계산한 결과를 메모리에 저장해두고 다음 요청에는 저장된 결과를 바로 돌려준다. 이게 캐시(cache)다.
캐시 덕분에 첫 번째 요청은 2~3초 걸려도, 두 번째 요청부터는 수십ms 만에 응답한다. 서버를 시작할 때 미리 캐시를 채워놓으면 첫 방문자도 기다릴 필요가 없다.
무거운 계산은 서버 시작할 때 미리
시그널 페이지는 3,970종목의 1년치 주가를 전부 분석해서 52주 신고가, 골든크로스, 거래량 급증 등을 찾아낸다. 이 계산이 꽤 무거운데, 첫 방문자가 10초를 기다리게 할 수는 없다.
그래서 서버가 시작될 때 백그라운드에서 미리 계산을 돌린다. 서버가 준비되는 동안 캐시가 채워지고, 사용자가 접속했을 때는 이미 결과가 준비되어 있다.
퀀트 추천도 마찬가지다. 3개 전략의 추천 종목을 산출하는 데 각각 수 초씩 걸리는데, 서버 시작 시 미리 계산해놓는다.
보안과 안정성
요청 횟수 제한 (Rate Limit)
누군가 악의적으로 API를 초당 수백 번 호출하면 서버가 다운될 수 있다. 그래서 IP별로 분당 요청 횟수를 제한한다. 정상적인 사용에는 걸리지 않는 수준으로 설정했다.
특히 외부 API를 호출하는 경로(공시, 뉴스 등)는 더 엄격하게 제한한다. 외부 서비스에 과도한 요청을 보내면 우리 쪽이 차단당할 수 있기 때문이다.
요청 로깅
모든 API 요청을 로그로 남긴다. 어떤 경로가 몇 ms 걸렸는지, 에러가 났는지 기록한다. 느린 요청(2초 이상)이나 서버 에러(500)는 별도로 경고를 남겨서, 문제가 생기면 바로 알 수 있게 했다.
CORS 설정
브라우저 보안 정책상, 다른 도메인에서 API를 호출하면 차단된다. 우리 프론트엔드 도메인만 허용하도록 CORS(Cross-Origin Resource Sharing)를 설정했다.
구조 요약
백엔드 전체를 그림으로 그리면 이렇다:
브라우저 요청
↓
[Rate Limit] → 초과 시 차단
↓
[요청 로깅] → 시간/상태 기록
↓
[라우터 9개] → 담당 API로 분배
↓
[캐시 확인] → 있으면 즉시 반환
↓ (없으면)
[DB 조회] → SQLite에서 데이터 가져오기
↓
[실시간 시세 병합] → 장중이면 캐시된 실시간 데이터 합침
↓
JSON 응답
요청이 들어오면 보안 체크 → 라우터 분배 → 캐시/DB 조회 → 응답 순서로 흐른다. 단순한 구조지만 55개 API가 이 흐름을 공유한다.
삽질 기록
처음에는 파일 하나였다
초기에는 main.py 하나에 모든 API를 넣었다. 500줄이 넘어가니까 함수 찾기도 힘들고, 한 곳을 고치면 다른 데가 깨지기 시작했다. 라우터 분리를 하고 나서야 관리가 수월해졌다.
주기적으로 리팩토링이 필요한지 체크했는데 어느 규모 이상이 되니 AI가 리팩토링이 필요하다고 한다.
캐시 안 비웠더니
데이터를 교정한 적이 있는데, DB는 수정했는데 대시보드에는 여전히 옛날 데이터가 보였다. 서버 메모리에 캐시된 옛날 값이 남아있었기 때문이다. 캐시를 비우고 서버를 재시작해야 했다. 이후로는 데이터 수정 후 반드시 캐시를 초기화하는 습관이 생겼다.
첫 방문자 10초 대기
시그널 캐시를 미리 안 채워놨더니, 첫 방문자가 시그널 탭을 열었을 때 10초 넘게 로딩이 걸렸다. "페이지 로딩이 느리다"는 피드백을 줬더니 서버 시작 시 미리 캐시를 채우는 방식으로 바꿨다.
외부 API 장애 전파
실시간 시세를 가져오는 외부 API가 느려지면, 그 영향이 대시보드 전체로 퍼졌다. 외부 호출에 타임아웃을 짧게 걸고, 실패해도 DB 데이터(장 마감 종가)를 보여주도록 폴백 처리를 넣었다.
정리
- FastAPI: 가볍고 빠른 Python 웹 프레임워크, 비동기 지원
- 55개 API, 9개 라우터: 기능별로 분리해서 독립적으로 관리
- 실시간 시세: 1분 주기 백그라운드 갱신, 완벽하진 않지만 실용적
- 캐시: 한 번 계산한 결과를 재활용, 서버 시작 시 미리 채움
- 보안: Rate Limit + 요청 로깅 + CORS
이제 데이터 수집(2편), DB(3편), 백엔드(4편)까지 완성했다. 남은 건 사용자가 실제로 보는 화면 - 프론트엔드다.
다음 편 예고
- 5편: 모든 정보를 화면에서 보여주는 프론트엔드 만들기
'바이브 코딩 > 주식 정보 분석 사이트 만들기' 카테고리의 다른 글
| 6편: 종목 상세 페이지 - 종목의 모든 정보를 쉽게 찾아서 보자 (0) | 2026.03.16 |
|---|---|
| 5편: 모든 정보를 화면에서 보여주는 프론트엔드 만들기 (0) | 2026.03.15 |
| 3편: SQLite DB에 3,970 종목 정보 담기 (0) | 2026.03.14 |
| 2편: 증권사 API로 3,970종목 데이터 자동 수집하기 (0) | 2026.03.14 |
| 1편: AI 한테 주식 정보 사이트를 만들어 달라고 했다. (0) | 2026.03.13 |