18.Python으로 주식 데이터 수집기 만들기 — yfinance부터 한국 주식까지 완벽 가이드
이 글에서는 미국·한국 주식 데이터를 Python으로 자동 수집하는 방법, 수집한 데이터를 가공하고 CSV와 엑셀로 저장하는 방법, 그리고 기술적 지표 계산까지 — 실제 분석에 바로 활용할 수 있는 수준으로 안내해 드리겠습니다.
주식 데이터 수집을 자동화해야 하는 이유
주식 분석의 품질은 데이터의 품질에서 시작됩니다. 그런데 데이터를 수집하는 과정 자체에 시간을 소모하고 있다면, 정작 분석과 판단에 쓸 시간이 줄어듭니다.
HTS에서 차트를 보며 수치를 메모하거나, 금융 사이트에서 표를 복사해 엑셀에 붙여넣는 방식은 수집하는 사람이 됩니다. Python으로 데이터 수집기를 만들어두면 분석하는 사람이 될 수 있습니다. 데이터 수집은 코드가 맡고, 사람은 해석과 판단에만 집중할 수 있는 구조입니다.
택배 비유를 들자면, 직접 물건을 가지러 가는 것과 정기 배송을 신청해두는 것의 차이입니다. 한 번 설정해두면 이후에는 자동으로 원하는 데이터가 쌓입니다.
자동 수집이 주는 실질적 이점
- 일관성 확보 — 동일한 기준과 시각에 수집되어 데이터 품질이 균일합니다
- 과거 데이터 일괄 수집 — 수년치 일봉 데이터를 몇 초 만에 확보할 수 있습니다
- 멀티 종목 동시 처리 — 관심 종목 수백 개를 코드 한 번으로 처리합니다
- 분석 파이프라인 연결 — 수집 즉시 지표 계산, 시각화, 알림 발송까지 자동화됩니다
환경 설정 — 라이브러리 설치
pip install yfinance pandas requests beautifulsoup4 openpyxl
| 라이브러리 | 역할 |
|---|---|
yfinance |
야후 파이낸스 기반 글로벌 주식 데이터 수집 |
pandas |
데이터 가공 및 분석 |
requests + beautifulsoup4 |
한국 주식 데이터 크롤링 |
openpyxl |
엑셀 저장 |
yfinance로 미국·글로벌 주식 데이터 수집하기
단일 종목 데이터 수집
yfinance는 야후 파이낸스를 통해 전 세계 주식, ETF, 지수 데이터를 무료로 제공합니다. 티커(Ticker) 심볼만 알면 됩니다.
import yfinance as yf
import pandas as pd
# 단일 종목 수집 — Apple
ticker = yf.Ticker("AAPL")
# 일봉 데이터 (최근 1년)
df = ticker.history(period="1y")
print(df.head())
print(f"\n수집된 데이터: {len(df)}일치")
기간 지정 및 주기 변경
# 특정 기간 지정
df = ticker.history(start="2023-01-01", end="2024-12-31")
# 주봉 데이터
df_weekly = ticker.history(period="2y", interval="1wk")
# 월봉 데이터
df_monthly = ticker.history(period="5y", interval="1mo")
# 1시간봉 (최근 60일까지만 가능)
df_hourly = ticker.history(period="60d", interval="1h")
기업 기본 정보 함께 수집하기
ticker = yf.Ticker("AAPL")
info = ticker.info
print(f"회사명: {info.get('longName')}")
print(f"섹터: {info.get('sector')}")
print(f"시가총액: ${info.get('marketCap', 0):,.0f}")
print(f"PER: {info.get('trailingPE', 'N/A')}")
print(f"배당수익률: {info.get('dividendYield', 0) * 100:.2f}%")
여러 종목 한 번에 수집하기
# 관심 종목 리스트
tickers = ["AAPL", "MSFT", "GOOGL", "NVDA", "TSLA"]
# 한 번에 다운로드
df_multi = yf.download(tickers, period="1y", auto_adjust=True)
# 종가만 추출
close_prices = df_multi["Close"]
print(close_prices.tail())
한국 주식 데이터 수집하기
한국 주식도 yfinance로 수집할 수 있습니다. 코스피 종목은 티커 뒤에 .KS, 코스닥은 .KQ를 붙입니다.
yfinance로 한국 주식 수집
# 삼성전자 (코스피)
samsung = yf.Ticker("005930.KS")
df_samsung = samsung.history(period="1y")
print(df_samsung[["Open", "High", "Low", "Close", "Volume"]].tail(10))
# 카카오 (코스피)
kakao = yf.Ticker("035720.KS")
# 셀트리온 (코스피)
celltrion = yf.Ticker("068270.KS")
# 에코프로 (코스닥)
ecopro = yf.Ticker("086520.KQ")
⚠️ 주의: yfinance의 한국 주식 데이터는 야후 파이낸스 서버 상태에 따라 간헐적으로 누락이 발생할 수 있습니다. 실시간성이 중요하거나 데이터 정확도가 필수인 경우 증권사 API 또는 한국거래소(KRX) 데이터를 병행 사용하시길 권장합니다.
KRX 정보데이터시스템에서 종목 코드 일괄 수집
한국거래소는 전체 상장 종목 목록을 공개 API로 제공합니다. pip 설치 없이 requests만으로 접근 가능합니다.
import requests
import pandas as pd
def get_krx_stock_list() -> pd.DataFrame:
url = "https://kind.krx.co.kr/corpgeneral/corpList.do"
params = {
"method": "download",
"searchType": "13",
}
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, params=params, headers=headers)
response.encoding = "euc-kr"
df = pd.read_html(response.text, header=0)[0]
df = df[["회사명", "종목코드"]]
df["종목코드"] = df["종목코드"].astype(str).str.zfill(6)
df["yf_ticker"] = df["종목코드"] + ".KS"
return df
stock_list = get_krx_stock_list()
print(f"코스피 상장 종목 수: {len(stock_list)}")
print(stock_list.head(10))
기술적 지표 직접 계산하기
수집한 가격 데이터로 분석에 필요한 기술적 지표를 직접 계산합니다.
이동평균선 (MA)
import yfinance as yf
df = yf.Ticker("005930.KS").history(period="1y")
# 이동평균선
df["MA5"] = df["Close"].rolling(window=5).mean()
df["MA20"] = df["Close"].rolling(window=20).mean()
df["MA60"] = df["Close"].rolling(window=60).mean()
print(df[["Close", "MA5", "MA20", "MA60"]].tail(10))
RSI (상대강도지수)
def calculate_rsi(series: pd.Series, period: int = 14) -> pd.Series:
delta = series.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_gain = gain.rolling(window=period).mean()
avg_loss = loss.rolling(window=period).mean()
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
return rsi
df["RSI14"] = calculate_rsi(df["Close"])
print(df[["Close", "RSI14"]].tail(10))
MACD
def calculate_macd(series: pd.Series,
fast: int = 12,
slow: int = 26,
signal: int = 9) -> pd.DataFrame:
ema_fast = series.ewm(span=fast, adjust=False).mean()
ema_slow = series.ewm(span=slow, adjust=False).mean()
macd_line = ema_fast - ema_slow
signal_line = macd_line.ewm(span=signal, adjust=False).mean()
histogram = macd_line - signal_line
return pd.DataFrame({
"MACD": macd_line,
"Signal": signal_line,
"Histogram": histogram
})
macd_df = calculate_macd(df["Close"])
df = pd.concat([df, macd_df], axis=1)
print(df[["Close", "MACD", "Signal", "Histogram"]].tail(10))
수집 데이터 저장하기 — CSV와 엑셀 모두 활용
CSV로 저장
from datetime import datetime
def save_stock_data(df: pd.DataFrame, ticker: str):
today = datetime.now().strftime("%Y%m%d")
filename = f"{ticker.replace('.', '_')}_{today}.csv"
df.to_csv(filename, encoding="utf-8-sig")
print(f"저장 완료: {filename}")
return filename
save_stock_data(df, "005930.KS")
여러 종목을 시트별로 엑셀 저장
from openpyxl import Workbook
import yfinance as yf
import pandas as pd
tickers = {
"삼성전자": "005930.KS",
"SK하이닉스": "000660.KS",
"NAVER": "035420.KS",
}
with pd.ExcelWriter("korean_stocks.xlsx", engine="openpyxl") as writer:
for name, ticker in tickers.items():
df = yf.Ticker(ticker).history(period="6mo")
df["MA20"] = df["Close"].rolling(20).mean()
df["RSI14"] = calculate_rsi(df["Close"])
# [수정] 인덱스가 날짜 형식(DatetimeIndex)인 경우 시간대(Timezone) 제거
if isinstance(df.index, pd.DatetimeIndex):
df.index = df.index.tz_localize(None)
# 혹시 컬럼 중에도 날짜가 존재할 경우를 대비한 안전장치
for col in df.select_dtypes(include=['datetime64[ns, UTC]', 'datetimetz']).columns:
df[col] = df[col].dt.tz_localize(None)
df.to_excel(writer, sheet_name=name)
print(f"{name} 저장 완료")
print("\n전체 저장 완료: korean_stocks.xlsx")
실전 예제 — 관심 종목 일괄 스크리닝
RSI가 30 이하인 과매도 종목을 자동으로 필터링하는 스크리닝 예제입니다.
import yfinance as yf
import pandas as pd
from datetime import datetime
watch_list = {
"삼성전자": "005930.KS",
"SK하이닉스": "000660.KS",
"카카오": "035720.KS",
"NAVER": "035420.KS",
"현대차": "005380.KS",
"LG에너지솔루션": "373220.KS",
}
results = []
for name, ticker in watch_list.items():
try:
df = yf.Ticker(ticker).history(period="3mo")
if len(df) < 20:
continue
close = df["Close"].iloc[-1]
ma20 = df["Close"].rolling(20).mean().iloc[-1]
rsi = calculate_rsi(df["Close"]).iloc[-1]
vol = df["Volume"].iloc[-1]
results.append({
"종목명": name,
"현재가": f"{close:,.0f}",
"MA20": f"{ma20:,.0f}",
"MA20대비": f"{(close/ma20 - 1)*100:+.1f}%",
"RSI14": f"{rsi:.1f}",
"과매도": "⚠️ 과매도" if rsi < 30 else "",
})
except Exception as e:
print(f"{name} 수집 실패: {e}")
result_df = pd.DataFrame(results)
print(result_df.to_string(index=False))
result_df.to_csv(
f"screening_{datetime.now().strftime('%Y%m%d')}.csv",
encoding="utf-8-sig",
index=False
)
자주 발생하는 오류와 해결법
오류 1 — 한국 주식 데이터가 빈 DataFrame으로 반환되는 경우
원인: 야후 파이낸스 서버 일시 장애 또는 티커 오류
해결: .KS / .KQ 접미사 재확인, 잠시 후 재시도. 지속되면 pykrx 라이브러리 병행 사용
pip install pykrx
from pykrx import stock
# pykrx로 삼성전자 일봉 데이터 수집
df = stock.get_market_ohlcv("20240101", "20241231", "005930")
print(df.head())
오류 2 — yfinance 데이터에 결측값(NaN)이 포함된 경우
원인: 거래정지일, 공휴일 등으로 데이터 공백 발생
해결: fillna() 또는 dropna()로 처리
df = df.dropna(subset=["Close"]) # Close가 NaN인 행 제거
df["MA20"] = df["MA20"].fillna(method="ffill") # 이전 값으로 채우기
오류 3 — 대량 요청 시 속도 저하 또는 차단
원인: 야후 파이낸스 요청 속도 제한
해결: 종목 간 time.sleep(0.5~1) 추가, 한 번에 너무 많은 종목을 동시 요청하지 않도록 배치 처리
마무리 — 핵심 요약
✅ Python 주식 데이터 수집기 체크리스트
- 미국 주식:
yf.Ticker("AAPL").history(period="1y") - 한국 주식: 티커 뒤에
.KS(코스피) 또는.KQ(코스닥) 추가 - KRX 종목 코드 목록: kind.krx.co.kr 공개 API 활용
- 기술적 지표: MA는
rolling().mean(), RSI·MACD는 직접 계산 함수 작성 - 저장: 단일 종목은 CSV, 다중 종목은 시트 분리 엑셀
- 데이터 불안정 시:
pykrx라이브러리를 보조 수단으로 활용 - 대량 수집 시: 반드시
time.sleep()추가해 서버 부하 방지
다음으로 알아두면 좋은 것
데이터 수집기가 완성됐다면, 다음 단계는 수집된 데이터를 Streamlit 대시보드로 시각화하거나, 매일 자동 수집 후 이상 신호 발생 시 알림을 발송하는 파이프라인을 구축하는 것입니다. schedule 라이브러리로 장 마감 후 자동 수집을 예약하고, RSI 과매도·골든크로스 등 조건이 충족될 때 텔레그램이나 이메일로 알림을 받는 시스템까지 연결하면 완전한 자동화 분석 환경이 갖춰집니다.
댓글
댓글 쓰기