Scrape Live Stock Data from Yahoo Finance

Yahoo Finance is one of the fastest ways to turn a personal watchlist into a dataset you control.

In this guide we will build a Python scraper that:

  • fetches real Yahoo Finance quote pages
  • extracts the visible quote snapshot for each ticker
  • pulls a few summary rows from the same page
  • exports clean CSV for downstream alerts or dashboards

Mandatory screenshot of the target site:

Yahoo Finance quote page for Apple

Keep quote-page fetches stable with ProxiesAPI

Quote pages are fine at small volume, but watchlists quickly become repetitive, bursty traffic. ProxiesAPI gives you a cleaner fetch layer when you scale beyond manual checks.


What we are scraping

We will use standard Yahoo Finance quote URLs such as:

  • https://finance.yahoo.com/quote/AAPL
  • https://finance.yahoo.com/quote/MSFT
  • https://finance.yahoo.com/quote/NVDA

The page gives us two useful surfaces:

  1. visible quote widgets rendered as fin-streamer elements
  2. a large embedded JSON payload that includes the same quote data plus summary fields

That combination is practical. We can keep the parser simple, validate against what the user sees, and avoid reverse-engineering a private API flow for a basic watchlist job.

This is still scraping, not a licensed market-data feed. Treat it as a learning or internal-monitoring workflow, not a compliance-grade trading input.


Setup

python3 -m venv .venv
source .venv/bin/activate
pip install requests beautifulsoup4 lxml pandas

We will use:

  • requests for HTTP
  • BeautifulSoup for light HTML extraction
  • pandas for CSV export

Step 1: Build a ProxiesAPI-ready fetch layer

Even a small quote monitor benefits from explicit timeouts, retries, and a clean user agent.

from __future__ import annotations

import os
import random
import time
from urllib.parse import quote_plus

import requests

TIMEOUT = (10, 30)
PROXIESAPI_KEY = os.getenv("PROXIESAPI_KEY", "")

session = requests.Session()
session.headers.update({
    "User-Agent": (
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/124.0.0.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
})


def proxiesapi_url(target_url: str) -> str:
    if not PROXIESAPI_KEY:
        raise RuntimeError("Set PROXIESAPI_KEY before enabling ProxiesAPI")
    return (
        "https://api.proxiesapi.com/?auth_key="
        + quote_plus(PROXIESAPI_KEY)
        + "&url="
        + quote_plus(target_url)
    )


def fetch_html(url: str, *, use_proxiesapi: bool = False, attempts: int = 4) -> str:
    last_error = None
    for attempt in range(1, attempts + 1):
        try:
            target = proxiesapi_url(url) if use_proxiesapi else url
            response = session.get(target, timeout=TIMEOUT)
            response.raise_for_status()
            html = response.text
            if "Will be right back" in html or len(html) < 100_000:
                raise RuntimeError(f"unexpected HTML size: {len(html)}")
            return html
        except Exception as exc:
            last_error = exc
            time.sleep(min(12, 2 ** (attempt - 1)) + random.random())
    raise RuntimeError(f"fetch failed for {url}: {last_error}")

If you only scrape a few symbols once a day, direct requests may be enough. Once you start cycling through larger watchlists or intraday refreshes, keeping the proxy layer outside the parser makes life easier.


Step 2: Extract the quote header

Yahoo renders the visible price with fin-streamer nodes. The page contains other market widgets too, so we keep the selector narrow by first finding the quote title and then reading the first visible quote block.

from bs4 import BeautifulSoup


def parse_quote_header(html: str) -> dict:
    soup = BeautifulSoup(html, "lxml")

    title = soup.select_one("h1")
    name_line = title.get_text(" ", strip=True) if title else None

    prices = [el.get_text(" ", strip=True) for el in soup.select('fin-streamer[data-field="regularMarketPrice"]')]
    changes = [el.get_text(" ", strip=True) for el in soup.select('fin-streamer[data-field="regularMarketChange"]')]
    percents = [el.get_text(" ", strip=True) for el in soup.select('fin-streamer[data-field="regularMarketChangePercent"]')]

    return {
        "name_line": name_line,
        "regular_market_price": prices[0] if prices else None,
        "regular_market_change": changes[0] if changes else None,
        "regular_market_change_percent": percents[0] if percents else None,
    }

On a live page for AAPL, the important visible selectors are:

  • h1
  • fin-streamer[data-field="regularMarketPrice"]
  • fin-streamer[data-field="regularMarketChange"]
  • fin-streamer[data-field="regularMarketChangePercent"]

These are real page selectors, not guessed class names.


Step 3: Parse summary fields from the embedded page JSON

For fields like volume, market cap, and day range, the embedded page data is more stable than trying to locate a particular table layout.

import json
import re


ROOT_APP_RE = re.compile(r"root\\.App\\.main\\s*=\\s*(\\{.*?\\});\\n", re.DOTALL)


def extract_root_app(html: str) -> dict:
    match = ROOT_APP_RE.search(html)
    if not match:
        raise RuntimeError("Could not find Yahoo root.App.main JSON")
    return json.loads(match.group(1))


def parse_summary_payload(payload: dict, symbol: str) -> dict:
    stores = payload["context"]["dispatcher"]["stores"]
    quote_store = stores["QuoteSummaryStore"]
    price = quote_store["price"]
    summary = quote_store.get("summaryDetail", {})

    return {
        "symbol": symbol.upper(),
        "currency": price.get("currency"),
        "exchange": price.get("exchangeName"),
        "market_state": price.get("marketState"),
        "market_cap": (summary.get("marketCap") or {}).get("fmt"),
        "previous_close": (summary.get("previousClose") or {}).get("fmt"),
        "open": (summary.get("open") or {}).get("fmt"),
        "day_low": (summary.get("dayLow") or {}).get("fmt"),
        "day_high": (summary.get("dayHigh") or {}).get("fmt"),
        "volume": (summary.get("volume") or {}).get("fmt"),
        "avg_volume": (summary.get("averageVolume") or {}).get("fmt"),
    }

Why mix DOM selectors and page JSON?

  • the DOM is excellent for the visible headline quote
  • the JSON is excellent for secondary stats

That hybrid pattern is common on modern sites.


Step 4: Scrape a whole watchlist

import pandas as pd


def scrape_symbol(symbol: str, *, use_proxiesapi: bool = False) -> dict:
    url = f"https://finance.yahoo.com/quote/{symbol.upper()}"
    html = fetch_html(url, use_proxiesapi=use_proxiesapi)

    header = parse_quote_header(html)
    payload = extract_root_app(html)
    summary = parse_summary_payload(payload, symbol)

    return {
        "symbol": symbol.upper(),
        "name_line": header["name_line"],
        "regular_market_price": header["regular_market_price"],
        "regular_market_change": header["regular_market_change"],
        "regular_market_change_percent": header["regular_market_change_percent"],
        **summary,
        "quote_url": url,
    }


def scrape_watchlist(symbols: list[str], *, use_proxiesapi: bool = False) -> pd.DataFrame:
    rows = []
    for symbol in symbols:
        rows.append(scrape_symbol(symbol, use_proxiesapi=use_proxiesapi))
        time.sleep(random.uniform(0.8, 1.6))
    return pd.DataFrame(rows)

Step 5: Export CSV

def main() -> None:
    symbols = ["AAPL", "MSFT", "NVDA", "AMZN", "GOOG"]
    df = scrape_watchlist(symbols, use_proxiesapi=bool(PROXIESAPI_KEY))
    df.to_csv("yahoo-finance-watchlist.csv", index=False)
    print(df[["symbol", "regular_market_price", "regular_market_change_percent", "volume"]])


if __name__ == "__main__":
    main()

Typical output looks like this:

  symbol regular_market_price regular_market_change_percent   volume
0   AAPL               213.45                         +0.81%   42.1M
1   MSFT               517.08                         -0.22%   18.4M
2   NVDA               171.32                         +1.94%   66.7M

The exact values change continuously, which is the point of the scraper.


What usually breaks first

Yahoo Finance is manageable, but finance sites are never static forever. The common failure modes are:

  • layout changes that move or rename visible widgets
  • rate limiting when you hammer many symbols in bursts
  • suspiciously small HTML responses that are not real quote pages

The fix is usually operational, not architectural:

  • keep timeouts and retries
  • cache recent snapshots
  • add response-size sanity checks
  • use ProxiesAPI when you start rotating through larger symbol sets

When to switch away from page scraping

Use this approach when you need:

  • a lightweight watchlist export
  • internal monitoring
  • a tutorial-friendly dataset

Switch to a dedicated market-data source when you need:

  • hard SLA expectations
  • deep historical coverage
  • legal clarity for redistribution
  • sub-second decision making

Scraping quote pages is a solid engineering pattern. It is not a substitute for a licensed trading feed.


Final thoughts

Yahoo Finance is a strong teaching target because it shows two useful scraping habits at once:

  • validate against visible page elements
  • prefer structured data when the page already exposes it

That gives you a scraper that is easier to debug than pure API reverse-engineering and less fragile than relying on random CSS classes alone.

If you want the smallest reliable starter, begin with five symbols, once-daily snapshots, CSV export, and one sanity alert for short responses. That is enough to build something useful before you scale it up.

Keep quote-page fetches stable with ProxiesAPI

Quote pages are fine at small volume, but watchlists quickly become repetitive, bursty traffic. ProxiesAPI gives you a cleaner fetch layer when you scale beyond manual checks.

Related guides

Scrape Stock Prices and Financial Data with Python
Use Python + ProxiesAPI to pull Yahoo Finance quote pages, key stats tables, and historical price rows into CSV without building a heavyweight browser scraper.
tutorial#python#stocks#finance
Scrape Live Stock Data from Yahoo Finance
Show how to pull live quote fields, daily change, volume, and market-cap data from Yahoo Finance quote pages into a clean CSV.
tutorial#python#yahoo-finance#stocks
Scrape Live Stock Data from Yahoo Finance with Python (Quotes + Key Stats)
A resilient Yahoo Finance scraper in Python: fetch quote pages via ProxiesAPI, extract live-ish quote fields + key stats from embedded JSON, handle retries, and export to CSV.
tutorial#python#yahoo-finance#stocks
Scrape Yahoo Finance Top Gainers/Losers Screener with ProxiesAPI (CSV Export)
Scrape Yahoo Finance movers tables (gainers + losers), extract tickers, prices, % change, and volume using stable data-testid anchors, then export to CSV. Includes selector rationale and a screenshot.
tutorial#python#yahoo-finance#stocks