Purpose
Return the N highest-performing individual stocks on Yahoo Finance — the "Top Gainers" list ranked by intraday percent change. For each stock it returns symbol, company name, last price, absolute change, percent change, volume, market cap, and exchange. Read-only; never trades, logs in, or mutates anything.
Assumption made (no human to clarify): "highest performing single stocks" is interpreted as Yahoo Finance's headline Day Gainers list — the equities with the largest positive percent change in the current/most-recent US session — which is exactly what the site surfaces at /markets/stocks/gainers/. This list applies Yahoo's built-in liquidity/size floors (see Gotchas), so it is the "top performers among real, tradeable large/mid-cap names," not the absolute highest-percent movers including micro-caps. If you want the unfiltered highest movers, see the alternate scrIds in Gotchas.
When to Use
- "Give me the top 5 (or N) performing / gaining stocks today."
- Daily market-open or market-close monitoring of the biggest movers.
- Seeding a watchlist or downstream analysis with the day's leaders.
- Any flow that would otherwise scrape the Yahoo Finance "Top Gainers" table — the JSON API is faster, cheaper, and structurally stable.
Workflow
Yahoo Finance's "Top Gainers" page is a thin client over a public predefined-screener JSON API that needs no crumb, no auth, no cookies, and no residential proxy. A plain GET returns ranked results. Lead with the API; the browser path is a ~100× more expensive fallback that returns the identical data.
1. Call the predefined screener (recommended)
GET https://query1.finance.yahoo.com/v1/finance/screener/predefined/saved
?scrIds=day_gainers
&count=5
&formatted=falsescrIds=day_gainersis the "Top Gainers" list (ranked byregularMarketChangePercentdescending).count=Ncaps how many rows you get back (use the N the user asked for).formatted=falsereturns raw numeric values (32.7582). Omit it (orformatted=true) to get{raw, fmt}objects ({"raw":32.7582,"fmt":"+32.76%"}) if you want display strings.query2.finance.yahoo.comis an interchangeable mirror.
A plain curl (or any HTTP client) works — no proxy needed. Under restricted egress, run it through browserless_function. That runs in a browser page context, so a bare fetch has no egress until the page is navigated on-origin: page.goto the API host first, then page.evaluate a same-origin fetch:
export default async function ({ page }) {
await page.goto("https://query1.finance.yahoo.com/", {
waitUntil: "domcontentloaded",
});
const data = await page.evaluate(async () => {
const r = await fetch(
"/v1/finance/screener/predefined/saved?scrIds=day_gainers&count=5&formatted=false",
);
return r.json();
});
// project inside the function — don't ship the raw payload back
const d = data.finance.result[0];
return {
data: { title: d.title, total: d.total, quotes: d.quotes },
type: "application/json",
};
}2. Parse the result
The payload is finance.result[0] with:
title→"Day Gainers"total→ full count of matching stocks (e.g. 173);count→ how many were returnedquotes[]→ already sorted byregularMarketChangePercentdescending
For each quotes[i] read:
symbol,shortName(fallbacklongName)regularMarketPrice,regularMarketChange,regularMarketChangePercentregularMarketVolume,marketCap,fullExchangeNamequoteType(will be"EQUITY"— predefined gainers are single stocks, not ETFs/funds),marketState
curl -s "https://query1.finance.yahoo.com/v1/finance/screener/predefined/saved?scrIds=day_gainers&count=5&formatted=false" > /tmp/g.json
node -e '
const fs=require("fs");
const d=JSON.parse(fs.readFileSync("/tmp/g.json","utf8")).finance.result[0];
console.log(d.title, "of", d.total);
d.quotes.slice(0,5).forEach((q,i)=>console.log(`${i+1}. ${q.symbol} ${q.shortName} ${q.regularMarketChangePercent.toFixed(2)}% @ ${q.regularMarketPrice}`));
'(If you fetched via browserless_function, data.finance.result[0] is already the parsed object — drop the outer JSON.parse.)
3. Emit the ranked list
Take the first N quotes (already in descending-percent order) and shape them into the Expected Output below. Done — no pagination needed for a top-N request.
Browser fallback (only if the API is unreachable)
The API has no auth and no anti-bot, so the browser path should rarely be needed. If you must, one browserless_agent call (the session persists across calls, keyed by proxy/profile — nothing to release):
// browserless_agent commands (no proxy arg)
{ "method": "goto", "params": { "url": "https://finance.yahoo.com/markets/stocks/gainers/", "waitUntil": "load", "timeout": 45000 } },
{ "method": "waitForTimeout", "params": { "time": 3500 } }, // table hydrates after load
{ "method": "screenshot", "params": { "fullPage": false } }The page renders the same "Top Stock Gainers Today" table (Symbol / Name / Price / Change / Change % / Volume). The bare yahoofinance.com domain 301-redirects to finance.yahoo.com. Prefer the screenshot (read with vision) or an evaluate that parses the table rows over a raw text/html grab — the page body is mostly Yahoo nav chrome and sometimes leads with an "Oops, something went wrong" fragment even when the table is present. Validated 2026-05-31: the rendered table (DELL 420.91 / +103.86 / +32.76% / 38.022M) matched the API row exactly.
Site-Specific Gotchas
day_gainersis filtered, not the raw top %. The predefined screener applies Yahoo's built-in floors (US region, minimum price, minimum intraday market cap, minimum volume). So the #1 row may be a large-cap up 30%, while a micro-cap up 85% won't appear. For broader/unfiltered movers use a differentscrIds:small_cap_gainers(verified: surfaced REPL +85.68% vs. day_gainers' top of +32.76%), or build a custom screener (see crumb gotcha). Pick the list that matches the user's intent and state which list you used in your answer.- Predefined screener (
/v1/finance/screener/predefined/saved) needs NO crumb. A plain GET returns 200 (verified with and without a residential proxy). It setsA1/A3cookies in the response but does not require any on the request. - Custom screeners DO need a crumb + cookie. The unfiltered query endpoint
POST /v1/finance/screener?crumb=...requires first fetching a session cookie from any Yahoo page, thenGET /v1/test/getcrumbwith that cookie. Only go down this path if a predefinedscrIdsdoesn't fit; for "top N gainers" the predefined list is sufficient and crumb-free. formatted=falsevs default. Default responses wrap every numeric in{raw, fmt}. Passformatted=falsefor plain numbers, which is simpler to parse.countcaps the rows;totalis the full match count.count=5returns 5 quotes buttotalmay read 173 — don't mistaketotalfor "stocks up today."- Results are pre-sorted descending by percent change. No client-side sort needed;
quotes[0]is the top performer. - Market-closed = static, last-session values. On weekends/holidays
marketStateisCLOSEDand the percentages reflect the last trading session's close (the list is stable, not live). Re-fetching returns identical numbers until the next session. - All rows are
quoteType: "EQUITY"— the predefined gainers list is single stocks only, so it already satisfies "single stocks" (no ETFs/funds to filter out). - API host ≠ web host. The data lives on
query1/query2.finance.yahoo.com; the human page isfinance.yahoo.com. The bareyahoofinance.com301-redirects tofinance.yahoo.com. - No anti-bot wall observed. Pre-run probe reported no antibots; API fetched 200 both with and without a proxy; the gainers page loaded directly with no consent/captcha interstitial. No stealth or residential
proxyarg was required for any successful step.
Expected Output
{
"source": "yahoo_finance",
"list": "day_gainers",
"list_title": "Day Gainers",
"ranked_by": "regularMarketChangePercent_desc",
"market_state": "CLOSED",
"total_matching": 173,
"as_of": "2026-05-31",
"stocks": [
{
"rank": 1,
"symbol": "DELL",
"name": "Dell Technologies Inc.",
"price": 420.91,
"change": 103.86,
"change_percent": 32.76,
"volume": 38175314,
"market_cap": 273409769472,
"exchange": "NYSE",
"quote_type": "EQUITY"
},
{
"rank": 2,
"symbol": "OKTA",
"name": "Okta, Inc.",
"price": 123.27,
"change": 28.55,
"change_percent": 30.14,
"volume": 17181770,
"market_cap": 21614956544,
"exchange": "NasdaqGS",
"quote_type": "EQUITY"
},
{
"rank": 3,
"symbol": "NTAP",
"name": "NetApp, Inc.",
"price": 174.29,
"change": 31.89,
"change_percent": 22.39,
"volume": 15902415,
"market_cap": 34519937024,
"exchange": "NasdaqGS",
"quote_type": "EQUITY"
},
{
"rank": 4,
"symbol": "TBBB",
"name": "BBB Foods Inc.",
"price": 37.82,
"change": 5.09,
"change_percent": 15.55,
"volume": 6205247,
"market_cap": 4457357312,
"exchange": "NYSE",
"quote_type": "EQUITY"
},
{
"rank": 5,
"symbol": "TEAM",
"name": "Atlassian Corporation",
"price": 107.61,
"change": 14.32,
"change_percent": 15.35,
"volume": 13829691,
"market_cap": 27307958272,
"exchange": "NasdaqGS",
"quote_type": "EQUITY"
}
]
}Note: the example values are a captured snapshot (US market CLOSED, 2026-05-31). Live values change each session, but the shape and the descending-percent ordering are stable.