[핵심 요약] — 주가 데이터 소스를 yfinance → pykrx → KIS API로 세 번 갈아탔고, 뉴스 소스도 Brave 1개에서 5개(Google RSS, 네이버 API, KIS News, 네이버 금융, Brave)로 확장했다. 소스마다 잡는 기사 성격이 달라 병렬 운영이 효과적이다.
본 포스팅은 AI 데이터 파이프라인 구축 과정에서 주가 데이터 소스 3회 마이그레이션(yfinance → pykrx → KIS API)과 뉴스 소스 5개 병렬 운영 전략을 기록한 엔지니어링 문서입니다.
2편에서 다섯 번 넘어진 이야기를 했는데, 그중 두 가지가 데이터 소스 문제였습니다. 주가 데이터가 들쭉날쭉했고, 뉴스 소스를 하나만 쓰다 API 한도를 초과했습니다.
겉으로 보면 단순한 실수처럼 보이는데, 막상 겪어보면 예상하기 어려운 문제들이었습니다. 이 글은 주가 데이터 소스를 세 번, 뉴스 소스를 한 번에서 다섯 개로 늘리기까지의 과정을 정리한 기록입니다.
처음부터 두 가지 데이터가 필요했습니다
이 시스템에서 쓰는 데이터는 크게 두 종류입니다.
하나는 주가와 재무 데이터입니다. 현재가, 거래량, 이동평균, PBR, PER, 목표주가, 외국인·기관 수급 같은 것들입니다. 종목이 지금 어떤 상태인지를 숫자로 파악하는 용도입니다.
다른 하나는 뉴스와 공시 데이터입니다. 종목과 관련된 최근 기사, DART에 올라온 실적·수주·배당 공시가 여기에 해당합니다. 숫자만으로는 잡히지 않는 맥락을 채워주는 역할입니다.
처음 설계할 때는 두 종류 모두 “일단 아무 소스나 쓰고 나중에 바꾸면 된다”는 생각이었습니다. 실제로는 나중이 생각보다 빨리 왔습니다.
주가 데이터 — 세 번 갈아탔습니다
첫 번째: yfinance
가장 먼저 선택한 건 yfinance였습니다. Yahoo Finance를 파이썬으로 크롤링하는 비공식 라이브러리인데, 설치 한 줄에 종목 코드만 넣으면 바로 데이터가 나오는 게 매력이었습니다.
그런데 한국 주식에서 바로 문제가 생겼습니다. yfinance는 한국 종목을 조회할 때 005930.KS처럼 뒤에 .KS(코스피) 또는 .KQ(코스닥)를 붙여야 합니다. 문제는 어떤 종목이 어느 시장에 속하는지를 항상 알 수 없었다는 겁니다. 종목 코드만 있고 시장 구분이 없는 경우가 많았습니다.
그것만이 아니었습니다. 장 마감 직후 데이터 업데이트가 미국 시간대 기준으로 이루어지다 보니, 한국 기준으로는 마감 후 몇 시간이 지나야 반영되는 경우가 있었습니다. Docker 환경에서는 SSL 인증서 문제로 가끔 아예 연결이 안 되기도 했습니다. 서버 상황에 따라 빈 데이터가 반환되는 건 덤이었습니다.
두 번째: pykrx
yfinance를 버리고 pykrx로 넘어갔습니다. 한국거래소 공식 사이트를 파싱하는 라이브러리라서 한국 주식에 더 잘 맞을 거라 생각했습니다.
처음에는 그랬습니다. 그런데 쓰다 보니 근본적인 한계가 보였습니다. 공식 사이트를 파싱하는 방식이라 KRX 사이트 구조가 조금이라도 바뀌면 파싱이 깨질 수 있었고, Docker에서 의존성 설치가 복잡했습니다. 무엇보다 실시간 데이터가 없었고, PBR이나 PER 같은 밸류에이션 데이터가 불안정했습니다.
비공식 라이브러리의 한계는 결국 비슷했습니다. 외부 사이트 구조에 의존하는 이상 언제 깨질지 모른다는 불안감을 안고 운영해야 했습니다.
세 번째: KIS API
결국 한국투자증권 오픈트레이딩 API로 갔습니다. 증권사가 공식으로 제공하는 API라 앞선 두 가지의 불안 요소가 없었습니다. 현재가, 기술지표(PBR, PER, RSI 포함), 일봉 OHLCV, 증권사 컨센서스 목표주가, 외국인·기관 수급 데이터까지 무료로 제공합니다.
완벽하냐면 그건 아닙니다. 초당 15건이라는 Rate limit이 있어서 3,000개 종목을 스캔하면 3~4분이 걸립니다. ETN 상품(코드 500000번대 이상)은 조회 자체가 안 돼서 유니버스에서 제외했습니다.
그리고 주말 버그가 있었습니다.
주말에만 이상한 결과가 나왔습니다
2편에서 언급했던 ‘주말에만 이상한 결과가 나왔던 버그’의 원인이 KIS API였습니다.
평일에는 멀쩡하게 작동하다가 주말에만 분석 결과가 이상하게 나왔습니다. 한참 들여다봤는데 재현이 안 됐습니다. 당연히 평일에 테스트하고 있었으니까요.
결국 직접 주말에 시스템을 돌려보고 나서야 원인을 찾았습니다. KIS API가 주말에는 일부 필드를 None으로 반환했습니다. 장이 열리지 않으니 특정 데이터가 없는 건 당연한데, 이걸 None으로 받았을 때 처리하는 코드가 없었습니다. 아무 값도 없는 채로 판단 엔진에 들어가니 결과가 이상하게 나온 것입니다.
평일 개발 환경에서는 절대 발견할 수 없는 유형의 버그였습니다.
뉴스 데이터 — Brave 하나에서 다섯 개로
주가 데이터를 세 번 갈아타는 동안 뉴스 데이터에서도 한 번 크게 실수했습니다.
처음에는 Brave Search API만 썼습니다. 유료긴 하지만 검색 품질이 좋아서 메인 소스로 선택했습니다. 종목당 목표주가, 수주, 뉴스 세 가지 키워드로 검색하는 구조였습니다.
계산을 안 했습니다. 보유 종목 21개에 하루 3건씩이면 63건, 평일 21일이면 월 1,323건입니다. Brave 유료 플랜 한도가 월 1,000건이었는데, 이걸 첫 달에 바로 초과했습니다.
대안을 찾아야 했습니다. 그러면서 자연스럽게 소스를 하나씩 추가했습니다.
Google RSS는 무료에 건수가 많았습니다. 네이버 뉴스 API도 무료였습니다. KIS에서도 뉴스를 제공하고 있었고, 네이버 금융 종목뉴스 페이지를 스크래핑하는 것도 가능했습니다. Brave는 한도 관리를 하면서 보조 소스로 유지했습니다.
이렇게 다섯 개가 됐습니다.
다섯 개를 실제로 14일간 측정해봤습니다
그냥 감으로 쓰기가 찜찜해서 최근 14일치 실제 수집 데이터를 비교해봤습니다.
Google RSS가 32,100건으로 압도적이었습니다. 본문도 평균 341자로 가장 풍부했습니다. 다만 뉴스가 많은 종목일수록 관련 없는 기사도 많이 섞여 들어와서 나중에 중복 제거 단계에서 많이 걸러집니다.
기대가 컸던 건 KIS News였는데 실망스러웠습니다. 공식 투자 뉴스라 품질이 높을 거라 생각했는데 2,833건 중 빈 본문이 46.8%였습니다. KIS는 뉴스 제목만 제공하고 본문이 없어서, 제목으로 네이버에서 재검색해 본문을 보강하는 방식인데 이 매칭 성공률이 53%에 그쳤습니다.
의외로 좋았던 건 네이버 금융 종목뉴스였습니다. HTML 스크래핑 방식이라 언제 구조가 바뀔지 모른다는 불안함은 있지만, 네이버 편집팀이 해당 종목과 직접 관련된 기사만 태깅해놓은 곳이라 노이즈가 적었습니다. 건수는 2,304건으로 적은 편이지만 유의미한 기사 비율은 높았습니다.
다섯 개를 합쳐도 실제로 쓰이는 건 절반
소스를 늘리면 같은 기사가 여러 소스에서 중복으로 들어옵니다. 삼성전자 실적 기사 하나가 구글, 네이버, Brave에서 각각 잡히는 식입니다.
중복 제거를 두 단계로 처리합니다. 먼저 URL이 완전히 같은 경우는 DB 저장 자체를 막습니다. 그다음은 URL이 달라도 같은 기사인 경우인데, 이건 제목을 코사인 유사도로 비교해서 0.45 이상이면 같은 기사로 봅니다.
결과적으로 수집 건수의 절반 정도만 실제 분석에 쓰입니다. 5월 12일은 217건 수집에서 106건(49%), 5월 13일은 183건에서 105건(57%)이 사용됐습니다.
DART 공시는 다른 차원의 데이터입니다
뉴스와 별개로 DART 공시도 수집합니다. 사업보고서, 반기보고서, 주요사항보고서(수주, 배당, 유상증자)가 대상입니다.
뉴스와 결정적으로 다른 점이 있습니다. 공시는 허위로 작성하면 법적 책임이 따릅니다. 수주 계약이나 실적 발표처럼 중요한 이벤트는 뉴스보다 공시가 훨씬 신뢰할 수 있습니다.
판단 엔진에 넘기는 프롬프트에 뉴스 요약과 함께 최근 공시를 포함시키는데, 공시가 있는 날과 없는 날의 결과 품질 차이가 체감될 정도입니다.
소스마다 잘 잡히는 기사가 다릅니다
다섯 개를 병렬로 쓰는 이유는 단순히 건수를 늘리려는 게 아닙니다. 소스마다 잡아내는 기사의 성격이 다릅니다.
대형주 화제성 뉴스는 Google RSS가 제일 빠릅니다. 소형주처럼 뉴스가 적은 종목은 Google보다 Brave나 네이버 금융 종목뉴스에서 유의미한 기사가 나오는 경우가 많습니다. 투자 관련 키워드 검색은 Brave가 특화돼 있습니다.
하나만 쓰면 놓치는 기사가 생기고, 다섯 개를 쓰면 중복이 많아지는 대신 커버리지가 넓어집니다. 그 절충점에서 지금의 구조가 만들어졌습니다.
마무리하며
데이터 소스를 바꿀 때마다 코드 수정보다 테스트가 더 오래 걸렸습니다. yfinance를 버릴 때도, KIS API의 주말 버그를 찾을 때도 그랬습니다. 특히 주말 버그는 평일에는 재현이 안 되니까 의심조차 하기 어려웠습니다.
지금은 5개 뉴스 소스에 KIS API, DART 공시까지 더해 수집 구조가 꽤 무거워졌습니다. 그래도 월 운영 비용은 여전히 만 원 이하입니다. 2편에서 잡은 비용 구조가 이 단계까지 버텨주고 있습니다.
다음 글에서는 3,000개 종목을 매일 스캔하는 시장 레이더 구조를 다룹니다. 어떤 신호를 보고 종목을 솎아내는지를 정리하겠습니다.
시리즈 목차