-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
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:
- 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.
- 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.
- 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.
- When this initialization is bypassed, API requests fail with "Too Many Requests" errors due to missing authentication information.
###How to Reproduce
- Run in interactive Python interpreter:
import yfinance as yf
yf.Ticker("AAPL").history() # Works fine
- Then run a script with the same symbol:
import yfinance as yf
yf.Ticker("AAPL").history() # Fails with "Too Many Requests"
- 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:
- In _get_ticker_tz(), the cache is retrieved with c = cache.get_tz_cache() and checked for the symbol with c.lookup(self.ticker).
- If the symbol is found, no further API calls are made, which are necessary for cookie initialization.
- In interactive mode, these cookies are likely built correctly through shared session use, while in script mode this initialization is skipped.
- 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