Python Real-Time Stock Price Scraper

Written by

in

If you’ve ever wanted to build a Python real-time stock price scraper that can pull the latest market data on the fly, you’re in the right place. In this guide we’ll walk through the entire process—from choosing the right data source to writing clean, maintainable code that respects website terms of service. By the end, you’ll have a fully functional scraper that can feed live stock quotes into your analysis pipelines, dashboards, or trading bots.

Why Build Your Own Real‑Time Stock Scraper?

While there are plenty of paid APIs offering low‑latency market data, building a custom scraper gives you:

  • Cost control – No recurring subscription fees.
  • Flexibility – Scrape exactly the fields you need (price, volume, bid/ask spread, etc.).
  • Learning opportunity – Deepen your Python, web‑automation, and data‑handling skills.

However, it’s crucial to respect legal and ethical guidelines. Always check the target site’s robots.txt and terms of service before you start scraping.

Choosing the Right Data Source

Not all websites are created equal when it comes to real‑time data. Here are three common options:

  1. Public financial portals (e.g., Yahoo Finance, MarketWatch) – Often provide delayed quotes for free, but they may use JavaScript to render data.
  2. Websocket‑enabled platforms (e.g., Binance, Alpaca) – Offer true real‑time streams but usually require API keys.
  3. Brokerage dashboards – Provide the most accurate data but typically need authentication and are subject to stricter usage policies.

For a beginner‑friendly tutorial we’ll focus on Yahoo Finance because it’s widely accessible, has a clean HTML structure, and offers a public JSON endpoint that updates every few seconds.

Setting Up the Python Environment

Before writing any code, make sure you have a clean virtual environment and the required packages installed.

python -m venv stock-scraper-env
source stock-scraper-env/bin/activate  # On Windows use `stock-scraper-env\Scripts\activate`
pip install requests beautifulsoup4 pandas lxml

We’ll use requests for HTTP calls, BeautifulSoup for optional HTML parsing, and pandas to store the data in a tidy DataFrame.

Understanding Yahoo Finance’s JSON Endpoint

When you visit a stock’s page on Yahoo Finance, the browser makes a background request to a URL that looks like this:

https://query1.finance.yahoo.com/v8/finance/chart/AAPL?interval=1m&range=1d

Replace AAPL with any ticker symbol. The interval parameter defines the granularity (1m = one minute), and range sets the time window. The response is a compact JSON object containing timestamps, open, high, low, close, and volume arrays.

Fetching the JSON Data

import requests
import pandas as pd
from datetime import datetime

def fetch_stock_json(ticker: str, interval: str = "1m", range_: str = "1d"):
    url = f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
    params = {"interval": interval, "range": range_}
    response = requests.get(url, params=params, timeout=10)
    response.raise_for_status()
    return response.json()

Parsing the JSON into a DataFrame

def json_to_dataframe(json_data):
    result = json_data["chart"]["result"][0]
    timestamps = result["timestamp"]
    indicators = result["indicators"]["quote"][0]

    df = pd.DataFrame({
        "datetime": [datetime.fromtimestamp(ts) for ts in timestamps],
        "open": indicators["open"],
        "high": indicators["high"],
        "low": indicators["low"],
        "close": indicators["close"],
        "volume": indicators["volume"]
    })
    df.set_index("datetime", inplace=True)
    return df

Building a Real‑Time Loop

To keep the scraper “real‑time,” we’ll poll the endpoint every few seconds and append new rows to a CSV file. Below is a minimal yet robust implementation:

import time
import csv
from pathlib import Path

def append_to_csv(df: pd.DataFrame, file_path: Path):
    if not file_path.exists():
        df.to_csv(file_path, mode='w', header=True)
    else:
        df.to_csv(file_path, mode='a', header=False)

def real_time_scraper(ticker: str, duration_minutes: int = 30, poll_interval: int = 5):
    end_time = time.time() + duration_minutes * 60
    csv_path = Path(f"{ticker}_realtime.csv")
    print(f"Starting real‑time scrape for {ticker}. Output → {csv_path}")

    while time.time() < end_time:
        try:
            json_data = fetch_stock_json(ticker)
            df = json_to_dataframe(json_data)
            latest_row = df.iloc[[-1]]  # Get the most recent entry
            append_to_csv(latest_row, csv_path)
            print(f"[{datetime.now().strftime('%H:%M:%S')}] {ticker} price: {latest_row['close'].values[0]}")
        except Exception as e:
            print(f"Error: {e}")
        time.sleep(poll_interval)

# Example usage:
# real_time_scraper("AAPL", duration_minutes=10, poll_interval=5)

Key points to note:

  • Polling interval – Adjust based on the site’s rate limits; 5‑10 seconds is usually safe for free endpoints.
  • Error handling – Network hiccups are inevitable; a simple try/except ensures the loop continues.
  • CSV storage – Storing data locally enables quick back‑testing or feeding into a database later.

Scaling Up: From One Ticker to Many

If you need to monitor a watchlist of 20‑30 symbols, threading or asynchronous I/O can keep the scraper responsive.

Using concurrent.futures for Simple Parallelism

from concurrent.futures import ThreadPoolExecutor, as_completed

def scrape_multiple(tickers, duration_minutes=15, poll_interval=5):
    with ThreadPoolExecutor(max_workers=len(tickers)) as executor:
        futures = [
            executor.submit(real_time_scraper, ticker, duration_minutes, poll_interval)
            for ticker in tickers
        ]
        for future in as_completed(futures):
            try:
                future.result()
            except Exception as exc:
                print(f"Thread raised an exception: {exc}")

# Example:
# watchlist = ["AAPL", "MSFT", "GOOGL", "TSLA"]
# scrape_multiple(watchlist, duration_minutes=5, poll_interval=7)

Threading works well because the bottleneck is network I/O, not CPU.

Legal and Ethical Considerations

Even though the JSON endpoint is publicly accessible, you should:

  • Read and honor the robots.txt file of the domain.
  • Limit request frequency to avoid overloading the server.
  • Include a descriptive User‑Agent header that identifies your script.
# Example of a polite request header
headers = {
    "User-Agent": "MyRealTimeScraper/1.0 (+https://github.com/username/repo)"
}
response = requests.get(url, params=params, headers=headers)

If you need guaranteed low‑latency data for production trading, consider a licensed API instead of scraping.

Optimizing for Production

When you move from a hobby project to a production‑grade system, think about:

  • Persisting data in a time‑series database (e.g., InfluxDB or TimescaleDB) for fast queries.
  • Monitoring – Use health checks and alerting (Prometheus + Grafana) to detect scraper failures.
  • Containerization – Dockerize the script for easy deployment and scaling.
  • Security – Store any API keys or credentials in environment variables or secret managers.

Full Example: A Ready‑to‑Run Script

Below is a compact script that incorporates polite headers, error logging, and CSV output. Save it as realtime_stock_scraper.py and run it with your desired ticker list.

#!/usr/bin/env python3
import requests, pandas as pd, time, csv
from datetime import datetime
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed

HEADERS = {"User-Agent": "PythonRealTimeScraper/1.0 (+https://example.com)"}
BASE_URL = "https://query1.finance.yahoo.com/v8/finance/chart"

def fetch(ticker, interval="1m", range_="1d"):
url = f"{BASE_URL}/{ticker}"
params = {"interval": interval, "range": range_}
r = requests.get(url, params=params, headers=HEADERS, timeout=10)
r.raise_for_status()
return r.json()

def parse(json_data):
result = json_data["chart"]["result"][0]
ts = result["timestamp"]
q = result["indicators"]["quote"][0]
df = pd.DataFrame({
"datetime": [datetime.fromtimestamp(t) for t in ts],
"open

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *