else Study

yfinance 라이브러리의 Rate Limit 문제 해결하기: GitHub Actions에서 안정적인 데이터 수집을 위한 지침

Wootaeng 2025. 5. 8. 12:22
728x90
반응형

안녕하세요, 이번 글에서는 yfinance 라이브러리를 사용하여 미국 주식 시장 데이터를 자동으로 수집할 때 발생하는 Rate Limit 문제와 그 해결 과정을 공유하고자 합니다. 특히 GitHub Actions와 같은 CI/CD 환경에서 발생하는 특수한 문제들에 초점을 맞추었습니다.

배경: 프로젝트 요구사항과 발생한 문제

최근 미국 주식 시장 마감 시황을 자동으로 블로그에 게시하는 프로젝트를 개발하면서, NASDAQ 100에 포함된 약 100개 종목의 일별 OHLCV(시가, 고가, 저가, 종가, 거래량) 데이터를 수집해야 했습니다. 이를 위해 널리 사용되는 yfinance 라이브러리의 yf.download() 함수를 활용했습니다.

로컬 환경에서는 문제 없이 작동했으나, GitHub Actions에서 자동화 스크립트를 실행하자 다음과 같은 오류가 지속적으로 발생했습니다:

 
yfinance.exceptions.YFRateLimitError: Too Many Requests. Rate limited. Try after a while.

이는 Yahoo Finance 서버가 요청 빈도를 제한하는 Rate Limiting 메커니즘 때문이었습니다. 특히 GitHub Actions에서는 공유 IP 주소를 사용하기 때문에 이 문제가 더욱 심각했습니다.

문제 해결 과정

1. Chunking과 Delay 구현

첫 번째 접근법으로, 요청을 작은 단위로 나누고 각 요청 사이에 지연 시간을 두는 방식을 시도했습니다:

 
python
chunk_size = 25
wait_seconds = 20
all_processed_components = []

for i in range(0, len(ticker_list), chunk_size):
    chunk = ticker_list[i:i + chunk_size]
    try:
        chunk_data, failed_in_chunk = download_data_yfinance_batch(chunk)
        if chunk_data:
            all_processed_components.extend(chunk_data)
    except Exception as e:
        print(f"Chunk {i // chunk_size + 1} failed: {e}")
    
    # 다음 청크 처리 전 대기
    if i + chunk_size < len(ticker_list):
        time.sleep(wait_seconds)

이 방법은 로컬 환경에서는 어느 정도 효과가 있었지만, GitHub Actions 환경에서는 여전히 실패율이 높았습니다. GitHub Actions Runner들이 사용하는 공유 IP 풀에서 다른 프로세스들의 요청량이 합쳐져 전체 IP의 요청량이 Yahoo Finance의 임계값을 초과했을 가능성이 높았습니다.

2. curl_cffi를 통한 브라우저 모방

yfinance GitHub 이슈 트래커를 조사한 결과, curl_cffi 라이브러리를 사용하여 실제 브라우저 동작을 모방하는 방법이 제안되었습니다:

 
python
import requests
try:
    from curl_cffi import requests as curl_requests
    try:
        # Chrome 120 버전을 모방하는 세션 생성
        yf_session = curl_requests.Session(impersonate="chrome120")
        yf_session.headers.update({
            'User-Agent': 'Mozilla/5.0 ... Chrome/120.0.0.0 ...'
        })
    except Exception as e_session:
        yf_session = requests.Session()
        yf_session.headers.update({
            'User-Agent': 'Mozilla/5.0 ... Chrome/120.0.0.0 ...'
        })
except ImportError:
    yf_session = requests.Session()
    yf_session.headers.update({
        'User-Agent': 'Mozilla/5.0 ... Chrome/120.0.0.0 ...'
    })

이 세션 객체를 yfinance 함수 호출 시 전달했습니다:

 
python
def download_data_yfinance(tickers, ...):
    data = yf.download(tickers=..., session=yf_session)
    # ...

def get_index_data(ticker_symbol, ...):
    ticker = yf.Ticker(ticker_symbol, session=yf_session)
    # ...

흥미롭게도 yf.Ticker().history() 메서드를 사용한 인덱스 데이터 조회는 성공률이 향상되었지만, yf.download()는 여전히 실패했습니다. 두 함수가 내부적으로 다른 요청 방식이나 엔드포인트를 사용하는 것으로 추정됩니다.

3. 돌파구: 개별 Ticker 요청 전략 구현

최종적으로, 벌크 다운로드 대신 각 티커에 대해 개별적으로 요청하는 전략으로 전환했습니다:

 
python
def download_data_yfinance(tickers, period="1d", retry_count=3):
    processed_data = []
    failed_tickers_final = []
    
    for i, ticker_symbol in enumerate(tickers):
        success = False
        for attempt in range(retry_count):
            try:
                ticker_obj = yf.Ticker(ticker_symbol, session=yf_session)
                data = ticker_obj.history(period=period, auto_adjust=False, timeout=30)
                
                if not data.empty and not data.iloc[-1].isnull().any():
                    # 데이터 처리 로직
                    success = True
                    break
            except Exception as e:
                # 오류 처리 및 재시도 로직
                pass
        
        if not success:
            failed_tickers_final.append(ticker_symbol)
        
        # 각 요청 사이에 지연 시간 적용
        if i < len(tickers) - 1:
            wait_delay = 1.5  # 필요에 따라 조절 가능
            time.sleep(wait_delay)
    
    return processed_data, failed_tickers_final

이 접근법은 각 티커를 개별적으로 처리하고 요청 사이에 적절한 지연을 두어 Rate Limit을 회피하는 전략입니다. 요청 수는 늘어나지만, 각 요청이 단순하고 Rate Limit에 덜 민감한 history() 메서드를 활용합니다.

이 방법을 적용한 결과, GitHub Actions 환경에서도 100개 티커의 데이터를 안정적으로 수집할 수 있었습니다. 전체 수행 시간은 약 3-5분으로 늘어났지만, 실패 없이 데이터를 확보하는 안정성을 확보했습니다.

주요 교훈 및 최적화 전략

  1. Rate Limit의 현실을 인정하라: 특히 자동화 환경에서는 Rate Limit이 언제든 발생할 수 있으며, 단순 time.sleep만으로는 해결이 어려울 수 있습니다.
  2. 공유 IP 환경의 제약을 이해하라: GitHub Actions와 같은 CI/CD 환경에서는 내 요청량뿐만 아니라 동일 IP를 사용하는 다른 서비스의 트래픽도 Rate Limit에 영향을 미칩니다.
  3. 브라우저 모방 기술을 활용하라: curl_cffi와 같은 라이브러리를 통해 실제 브라우저 동작을 모방하면 일부 차단을 우회할 수 있습니다. 다만, 모든 함수에 동일한 효과를 기대하긴 어렵습니다.
  4. 요청 패턴을 변경하라: yf.download() 대신 yf.Ticker().history()와 같이 서버가 다르게 반응하는 엔드포인트나 요청 패턴을 활용할 수 있습니다.
  5. 개별 요청과 적절한 지연을 구현하라: 비록 처리 시간은 늘어나지만, 개별 요청과 충분한 대기 시간은 Rate Limit 우회에 가장 효과적인 방법입니다.
  6. fallback 메커니즘을 준비하라: 무료 API는 언제든 불안정해질 수 있으므로, FMP API와 같은 대체 데이터 소스를 항상 준비해 두는 것이 좋습니다.

결론

yfinance 라이브러리를 사용한 주식 데이터 자동화 수집은 예상보다 복잡한 과제였습니다. 특히 GitHub Actions와 같은 CI/CD 환경에서는 Rate Limit 문제가 더욱 두드러집니다. 그러나 적절한 전략을 통해 이러한 제약을 극복하고 안정적인 데이터 파이프라인을 구축할 수 있었습니다.

본 글이 비슷한 문제로 고민하는 개발자들에게 유용한 참고 자료가 되기를 바랍니다. 궁금한 점이나 더 나은 접근법이 있으시다면 언제든지 의견을 나눠주세요.

728x90
반응형