jashdoshi77 commited on
Commit
702ea51
Β·
1 Parent(s): 3a97160

fix: add Yahoo crumb auth for v10/quoteSummary company info

Browse files
backend/app/services/data_ingestion/yahoo.py CHANGED
@@ -50,12 +50,29 @@ _PERIOD_MAP = {
50
 
51
  _USE_CURL = False
52
  _curl_session = None
 
53
 
54
  try:
55
  from curl_cffi.requests import Session as CurlSession
56
  _curl_session = CurlSession(impersonate="chrome")
57
  _USE_CURL = True
58
- logger.info("curl_cffi available β€” using direct Yahoo API with Chrome TLS")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  except ImportError:
60
  logger.info("curl_cffi not available β€” using yfinance (local dev mode)")
61
 
@@ -134,59 +151,62 @@ def _direct_fetch_info(ticker: str) -> dict:
134
  if not _USE_CURL or not _curl_session:
135
  return _yfinance_fetch_info(ticker)
136
 
137
- # ── Attempt 1: v10/quoteSummary (full fundamentals) ──────────────
138
- try:
139
- url = _SUMMARY_URL.format(ticker=ticker)
140
- modules = "summaryProfile,summaryDetail,defaultKeyStatistics,financialData,price"
141
- resp = _curl_session.get(url, params={"modules": modules}, timeout=10)
142
- if resp.status_code == 200:
143
- data = resp.json()
144
- qr = data.get("quoteSummary", {}).get("result")
145
- if qr and len(qr) > 0:
146
- r = qr[0]
147
- profile = r.get("summaryProfile", {})
148
- detail = r.get("summaryDetail", {})
149
- stats = r.get("defaultKeyStatistics", {})
150
- fin = r.get("financialData", {})
151
- price = r.get("price", {})
152
-
153
- def _raw(obj, key):
154
- """Extract raw value from Yahoo's {raw, fmt} format."""
155
- v = obj.get(key, {})
156
- if isinstance(v, dict):
157
- return v.get("raw")
158
- return v
159
-
160
- return {
161
- "longName": price.get("longName"),
162
- "shortName": price.get("shortName") or ticker,
163
- "currency": price.get("currency", "USD"),
164
- "exchange": price.get("exchangeName") or price.get("exchange"),
165
- "sector": profile.get("sector"),
166
- "industry": profile.get("industry"),
167
- "country": profile.get("country"),
168
- "website": profile.get("website"),
169
- "longBusinessSummary": (profile.get("longBusinessSummary") or "")[:500],
170
- "fullTimeEmployees": profile.get("fullTimeEmployees"),
171
- "marketCap": _raw(price, "marketCap"),
172
- "trailingPE": _raw(detail, "trailingPE") or _raw(stats, "trailingPE"),
173
- "forwardPE": _raw(detail, "forwardPE") or _raw(stats, "forwardPE"),
174
- "priceToBook": _raw(detail, "priceToBook") or _raw(stats, "priceToBook"),
175
- "dividendYield": _raw(detail, "dividendYield"),
176
- "trailingEps": _raw(stats, "trailingEps"),
177
- "fiftyTwoWeekHigh": _raw(detail, "fiftyTwoWeekHigh"),
178
- "fiftyTwoWeekLow": _raw(detail, "fiftyTwoWeekLow"),
179
- "averageVolume": _raw(detail, "averageVolume"),
180
- "beta": _raw(detail, "beta") or _raw(stats, "beta3Year"),
181
- "totalRevenue": _raw(fin, "totalRevenue"),
182
- "profitMargins": _raw(fin, "profitMargins"),
183
- "returnOnEquity": _raw(fin, "returnOnEquity"),
184
- "debtToEquity": _raw(fin, "debtToEquity"),
185
- "regularMarketPrice": _raw(price, "regularMarketPrice"),
186
- "previousClose": _raw(detail, "previousClose"),
187
- }
188
- except Exception as e:
189
- logger.debug("v10 quoteSummary failed for %s: %s", ticker, e)
 
 
 
190
 
191
  # ── Attempt 2: chart API meta (basic fallback) ──────────────────
192
  try:
 
50
 
51
  _USE_CURL = False
52
  _curl_session = None
53
+ _yahoo_crumb = None
54
 
55
  try:
56
  from curl_cffi.requests import Session as CurlSession
57
  _curl_session = CurlSession(impersonate="chrome")
58
  _USE_CURL = True
59
+
60
+ # Acquire Yahoo consent cookies + crumb token for API authentication
61
+ try:
62
+ # Step 1: Visit fc.yahoo.com to get cookies
63
+ _curl_session.get("https://fc.yahoo.com", timeout=5)
64
+ # Step 2: Fetch crumb token
65
+ crumb_resp = _curl_session.get(
66
+ "https://query2.finance.yahoo.com/v1/test/getcrumb", timeout=5
67
+ )
68
+ if crumb_resp.status_code == 200 and crumb_resp.text:
69
+ _yahoo_crumb = crumb_resp.text.strip()
70
+ logger.info("curl_cffi available β€” using direct Yahoo API with Chrome TLS (crumb acquired)")
71
+ else:
72
+ logger.info("curl_cffi available β€” using direct Yahoo API with Chrome TLS (no crumb)")
73
+ except Exception as e:
74
+ logger.warning("Crumb acquisition failed: %s β€” chart API still works", e)
75
+
76
  except ImportError:
77
  logger.info("curl_cffi not available β€” using yfinance (local dev mode)")
78
 
 
151
  if not _USE_CURL or not _curl_session:
152
  return _yfinance_fetch_info(ticker)
153
 
154
+ # ── Attempt 1: v10/quoteSummary (full fundamentals, needs crumb) ──
155
+ if _yahoo_crumb:
156
+ try:
157
+ url = _SUMMARY_URL.format(ticker=ticker)
158
+ modules = "summaryProfile,summaryDetail,defaultKeyStatistics,financialData,price"
159
+ resp = _curl_session.get(
160
+ url, params={"modules": modules, "crumb": _yahoo_crumb}, timeout=10
161
+ )
162
+ if resp.status_code == 200:
163
+ data = resp.json()
164
+ qr = data.get("quoteSummary", {}).get("result")
165
+ if qr and len(qr) > 0:
166
+ r = qr[0]
167
+ profile = r.get("summaryProfile", {})
168
+ detail = r.get("summaryDetail", {})
169
+ stats = r.get("defaultKeyStatistics", {})
170
+ fin = r.get("financialData", {})
171
+ price = r.get("price", {})
172
+
173
+ def _raw(obj, key):
174
+ """Extract raw value from Yahoo's {raw, fmt} format."""
175
+ v = obj.get(key, {})
176
+ if isinstance(v, dict):
177
+ return v.get("raw")
178
+ return v
179
+
180
+ return {
181
+ "longName": price.get("longName"),
182
+ "shortName": price.get("shortName") or ticker,
183
+ "currency": price.get("currency", "USD"),
184
+ "exchange": price.get("exchangeName") or price.get("exchange"),
185
+ "sector": profile.get("sector"),
186
+ "industry": profile.get("industry"),
187
+ "country": profile.get("country"),
188
+ "website": profile.get("website"),
189
+ "longBusinessSummary": (profile.get("longBusinessSummary") or "")[:500],
190
+ "fullTimeEmployees": profile.get("fullTimeEmployees"),
191
+ "marketCap": _raw(price, "marketCap"),
192
+ "trailingPE": _raw(detail, "trailingPE") or _raw(stats, "trailingPE"),
193
+ "forwardPE": _raw(detail, "forwardPE") or _raw(stats, "forwardPE"),
194
+ "priceToBook": _raw(detail, "priceToBook") or _raw(stats, "priceToBook"),
195
+ "dividendYield": _raw(detail, "dividendYield"),
196
+ "trailingEps": _raw(stats, "trailingEps"),
197
+ "fiftyTwoWeekHigh": _raw(detail, "fiftyTwoWeekHigh"),
198
+ "fiftyTwoWeekLow": _raw(detail, "fiftyTwoWeekLow"),
199
+ "averageVolume": _raw(detail, "averageVolume"),
200
+ "beta": _raw(detail, "beta") or _raw(stats, "beta3Year"),
201
+ "totalRevenue": _raw(fin, "totalRevenue"),
202
+ "profitMargins": _raw(fin, "profitMargins"),
203
+ "returnOnEquity": _raw(fin, "returnOnEquity"),
204
+ "debtToEquity": _raw(fin, "debtToEquity"),
205
+ "regularMarketPrice": _raw(price, "regularMarketPrice"),
206
+ "previousClose": _raw(detail, "previousClose"),
207
+ }
208
+ except Exception as e:
209
+ logger.debug("v10 quoteSummary failed for %s: %s", ticker, e)
210
 
211
  # ── Attempt 2: chart API meta (basic fallback) ──────────────────
212
  try: