Skip to content

Still "Too Many Requests" Error also in Version 0.2.62 #2526

@Hektiker24

Description

@Hektiker24

Describe bug

Issue Description
yfinance shows different behavior between interactive Python interpreter mode and script execution mode. In script mode, fetching stock information fails with a "Too Many Requests" error, while identical code works correctly in interactive mode.

###Root Cause
The problem originates in the _get_ticker_tz() function (in base.py) and its interaction with the caching system:

  1. In interactive mode, _fetch_ticker_tz() is called when a symbol is not in the cache (tkr-tz.db) or when the cache is invalid.
  2. In script mode, however, _fetch_ticker_tz() is not called if the symbol already exists in the cache, even if the script runs in a completely new process.
  3. The critical issue is that _fetch_ticker_tz() doesn't just retrieve the timezone but also has important side effects: it initializes cookies and crumb values in the YfData class that are required for all subsequent API requests.
  4. When this initialization is bypassed, API requests fail with "Too Many Requests" errors due to missing authentication information.

###How to Reproduce

  1. Run in interactive Python interpreter:
import yfinance as yf
yf.Ticker("AAPL").history()  # Works fine
  1. Then run a script with the same symbol:
import yfinance as yf
yf.Ticker("AAPL").history()  # Fails with "Too Many Requests"
  1. If you clear the cache or change the cache directory, the script suddenly works again:
# Clear cache before using yfinance
import os, platformdirs, sqlite3
cache_dir = os.path.join(platformdirs.user_cache_dir(), "py-yfinance")
tz_db = os.path.join(cache_dir, "tkr-tz.db")
if os.path.exists(tz_db):
    conn = sqlite3.connect(tz_db)
    conn.execute("DELETE FROM _kv")
    conn.execute("DELETE FROM _tz_kv")
    conn.commit()
    conn.close()

import yfinance as yf
yf.Ticker("AAPL").history()  # Works again

Technical Analysis

(Generated by Claude Sonnet 3,7)
The difference in behavior is due to how the cache is handled between interactive mode and script mode:

  1. In _get_ticker_tz(), the cache is retrieved with c = cache.get_tz_cache() and checked for the symbol with c.lookup(self.ticker).
  2. If the symbol is found, no further API calls are made, which are necessary for cookie initialization.
  3. In interactive mode, these cookies are likely built correctly through shared session use, while in script mode this initialization is skipped.
  4. The actual authentication happens in YfData._get_crumb_csrf() and YfData._get_cookie_basic(), which can be bypassed depending on the cache status.

Suggested Solution for the yfinance Project

(Generated by Claude Sonnet 3.7)

The _get_ticker_tz() function should be modified to ensure necessary cookies are initialized even when symbols are loaded from cache:

def _get_ticker_tz(self, proxy, timeout):
    proxy = proxy or self.proxy
    if self._tz is not None:
        return self._tz
    c = cache.get_tz_cache()
    tz = c.lookup(self.ticker)
    
    # Even if we have the timezone from cache, ensure
    # cookies are initialized by making a light API call
    # or by checking a session variable
    
    if tz and not utils.is_valid_timezone(tz):
        c.store(self.ticker, None)
        tz = None
    
    if tz is None:
        tz = self._fetch_ticker_tz(proxy, timeout)
        
        if utils.is_valid_timezone(tz):
            c.store(self.ticker, tz)
        else:
            tz = None
    else:
        # Even when we have the timezone, make sure cookies are set
        self._ensure_cookies_set(proxy, timeout)
    
    self._tz = tz
    return tz

def _ensure_cookies_set(self, proxy, timeout):
    # Minimal call to initialize cookies without fetching unnecessary data
    try:
        self._data._get_crumb_csrf(proxy, timeout)
    except Exception:
        # Ignore errors here as this is just an attempt to set cookies
        pass

Simple code that reproduces your problem

see above

Debug log from yf.enable_debug_mode()

Run in interactive Mode
DEBUG Entering history()
DEBUG Entering _fetch_ticker_tz()
DEBUG Entering get()
DEBUG Entering _make_request()
DEBUG url=https://query2.finance.yahoo.com/v8/finance/chart/GOOG
DEBUG params=frozendict.frozendict({'range': '1d', 'interval': '1d'})
DEBUG Entering _get_cookie_and_crumb()

Run in skript:
DEBUG Entering history()
DEBUG GOOG: Yahoo GET parameters: {'period1': '2025-05-28 00:00:00-04:00', 'period2': '2025-06-10 04:00:39-04:00', 'interval': '5m', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG Entering get()
DEBUG Entering _make_request()
DEBUG url=https://query2.finance.yahoo.com/v8/finance/chart/GOOG
DEBUG params={'period1': 1748404800, 'period2': 1749542439, 'interval': '5m', 'includePrePost': False, 'events': 'div,splits,capitalGains'}
DEBUG Entering _get_cookie_and_crumb()

Bad data proof

No response

yfinance version

0.2.62

Python version

3.12

Operating system

Windows 11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions