~ / guides / How to Scrape Finance & Crypto Data (Python Guide)

How to Scrape Finance & Crypto Data (Python Guide)

MR
Marcus Reed
Founder & lead tester · about the author
the short version
  • Google Finance has no official API. A plain request from an EU IP gets bounced to consent.google.com; one SOCS cookie skips that wall.
  • With the cookie, the stock quote page returned HTTP 200 and I parsed GOOGL at $362.02 plus 16 stat fields (P/E 27.67, market cap 4.40T, 52-wk range) straight from static HTML.
  • Crypto and index pages are different: BTC-USD and ETH-USD load HTTP 200, but the live price is injected by JavaScript, so a static requests fetch sees no price.
  • Class names are obfuscated and rotate (the old YMlKec price class is dead), so anchor on the price text pattern and label rows, not on a single class.
  • For crypto prices, many tickers at once, or no consent-cookie maintenance, render the page with a scraper API like ChocoData, or pull a real-time finance data feed.

Google Finance is the fastest free read on a stock: price, day range, market cap, P/E, 52-week range, all on one public page. This guide builds a real Python scraper for it, tests it against the live site, and is honest about where it breaks. I ran every request below on June 12, 2026 and pasted the real output. The short version: a plain request from my EU IP got bounced to a consent wall, one cookie cleared it and returned full stock data, and crypto pages turned out to be a different problem. For the fundamentals, see my web scraping guide and the Python walkthrough.

You can scrape the public Google Finance quote pages with a script, and reading public market data sits on the safer side of the law. The technical reality first: there is no official Google Finance API (Google shut it down in 2012, and GOOGLEFINANCE() lives only inside Google Sheets), so the quote pages are the data source. They are public, no login required. The catch I hit is a cookie-consent interstitial, covered in the next section.

On US computer-access law, loading pages anyone can open without an account is the safer category. In hiQ Labs v. LinkedIn, the Ninth Circuit held that scraping public data does not violate the Computer Fraud and Abuse Act, and the Supreme Court’s Van Buren decision narrowed the CFAA toward genuine hacking. Requesting a public quote page is not, by itself, a federal computer crime.

Two constraints still apply. Contract law is the live risk: Google’s Terms of Service restrict automated access, and hiQ won the CFAA point yet still settled after a breach-of-contract judgment. The second is the data itself: raw price figures are facts with thin copyright protection, but a stock price is owned and licensed by the exchange that produces it, and redistributing a real-time feed commercially can require a license. Keep requests polite, never script a logged-in Google account, and if you are reselling quotes, get a proper market-data license. I am a tester, not a lawyer, so get advice before a commercial scrape. My is web scraping legal pillar covers the full picture.

What data can you extract from a Google Finance quote page?

A single stock page exposes the live price plus a stat table, and I parsed all of it from static HTML on June 12, 2026. Here is what a NASDAQ or NYSE ticker page hands you, taken from my real GOOGL run:

FieldExample value (GOOGL, my run)In static HTML?
Current price$362.02Yes
Open$362.62Yes
High / Low (day)$366.57 / $354.94Yes
Market cap4.40TYes
P/E ratio27.67Yes
52-wk high / low$408.61 / $162.07Yes
EPS$13.11Yes
Beta1.23Yes
Avg volume / Volume32.01M / 11.64MYes
Dividend yield0.24%Yes
Shares outstanding5.82BYes
Employees195KYes

That is 16 fields from one GET. The important caveat: this is true for individual equities. Crypto pairs (BTC-USD) and index pages (.INX:INDEXSP) load the same page shell but inject the live price with JavaScript, so a static fetch sees the stat scaffolding and no price. I prove that below.

How does Google Finance block scrapers?

Google Finance stops a naive scraper with a cookie-consent interstitial, not a login wall or an instant 403. When I sent a plain request with a normal browser User-Agent and no cookies, the response was HTTP 200, but the final URL was consent.google.com, not the quote page. Here is the proof from my run on June 12, 2026:

import requests

UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " \
     "(KHTML, like Gecko) Chrome/126.0 Safari/537.36"

r = requests.get(
    "https://www.google.com/finance/quote/GOOGL:NASDAQ",
    headers={"User-Agent": UA}, timeout=25,
)
print("HTTP", r.status_code)
print("final host:", r.url.split("/")[2])
print("hit consent wall:", "consent.google.com" in r.url)
HTTP 200
final host: consent.google.com
hit consent wall: True

The 200 is misleading: the body is Google’s GDPR consent page, not finance data. This fires for IPs that geolocate to the EU (my exit was in Lithuania), so depending on where your server sits you may or may not see it. The fix is one cookie. SOCS is the cookie Google sets when you accept consent in a browser; sending it on the request skips the interstitial and serves the real page. Past that, the only remaining gate is IP rate limiting if you fire requests too fast. The next section uses the cookie and gets data.

Method 1: Scrape a stock quote with Python (DIY)

The DIY method that works is a single GET with the SOCS consent cookie, parsed with BeautifulSoup. It clears the consent wall, needs no token flow, and the stock data sits in static HTML. The quote URL format is TICKER:EXCHANGE:

GoalURL pattern
Alphabet on NASDAQhttps://www.google.com/finance/quote/GOOGL:NASDAQ
Apple on NASDAQhttps://www.google.com/finance/quote/AAPL:NASDAQ
Any equityhttps://www.google.com/finance/quote/TICKER:EXCHANGE

One warning before the code: Google’s CSS class names are obfuscated and rotate, so the old YMlKec price class you will see in older tutorials is dead (I checked, it returns nothing now). Anchoring on the class is fragile. Instead I match the price by its text shape (a lone $ amount) and read the stat table by its label/value rows, which survives class churn better. Here is the scraper:

import requests, re
from bs4 import BeautifulSoup

UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
      "(KHTML, like Gecko) Chrome/126.0 Safari/537.36")
# SOCS cookie skips Google's EU cookie-consent interstitial
COOKIES = {"SOCS": "CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIw"
                   "MjMwODI5LjA3X3AwGgJlbiACGgYIgLC_pwY"}
PRICE = re.compile(r"\$[\d,]+\.\d{2}")

def scrape_quote(ticker, exchange):
    url = f"https://www.google.com/finance/quote/{ticker}:{exchange}?hl=en"
    r = requests.get(url, headers={"User-Agent": UA}, cookies=COOKIES, timeout=25)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "lxml")

    # Price: the div whose entire text is one $ amount
    price = soup.find(lambda t: t.name == "div"
                      and PRICE.fullmatch(t.get_text(strip=True) or ""))
    # Company name from the <title>: "Alphabet Inc Class A (GOOGL) ..."
    name = soup.title.get_text().split(" (")[0]

    # Stat rows ship in two class variants; read both as label -> value
    stats = {}
    for row in soup.find_all("div", class_="gyFHrc") + soup.find_all("div", class_="KxsRFb"):
        cells = list(row.stripped_strings)
        if len(cells) >= 2:
            stats[cells[0]] = cells[1]

    return {"status": r.status_code, "name": name,
            "price": price.get_text(strip=True) if price else None, "stats": stats}

d = scrape_quote("GOOGL", "NASDAQ")
print("HTTP", d["status"])
print("Name :", d["name"])
print("Price:", d["price"])
for k, v in d["stats"].items():
    print(f"  {k:<20} {v}")

I ran this on June 12, 2026. Real output:

HTTP 200
Name : Alphabet Inc Class A
Price: $362.02
  Open                 $362.62
  High                 $366.57
  Low                  $354.94
  Mkt. cap             4.40T
  Avg. vol.            32.01M
  Volume               11.64M
  Dividend             0.24%
  Quarterly dividend   $0.22
  Ex-dividend date     Jun 8, 2026
  P/E ratio            27.67
  52-wk high           $408.61
  52-wk low            $162.07
  EPS                  $13.11
  Beta                 1.23
  Shares outstanding   5.82B
  No. of employees     195K

That is a working free scraper: one GET, the live price, and 16 stat fields. I ran the same function against AAPL:NASDAQ in the same session and got $291.80 with its own stat table, so the code generalizes across equities. Install the dependencies with pip install requests beautifulsoup4 lxml. Two limits to plan for. First, the price ticks between requests (I saw GOOGL move from $362.53 to $362.02 across runs), which is expected for a live quote, not a parsing bug. Second, the SOCS token can expire; if you start hitting the consent wall again, grab a fresh cookie value from your browser’s dev tools. For more on this stack, see my BeautifulSoup guide and the requests walkthrough.

Why does the same scraper fail on Bitcoin and crypto pairs?

Crypto and index pages return HTTP 200 but render the live price with JavaScript, so the static HTML my scraper downloads contains no price. I tested this directly on June 12, 2026. The page loads, the title is correct, the stat scaffolding is there, but there is no dollar figure to parse:

import requests, re
from bs4 import BeautifulSoup

UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
      "(KHTML, like Gecko) Chrome/126.0 Safari/537.36")
COOKIES = {"SOCS": "CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIw"
                   "MjMwODI5LjA3X3AwGgJlbiACGgYIgLC_pwY"}

for slug in ["BTC-USD", "AAPL:NASDAQ"]:
    r = requests.get(f"https://www.google.com/finance/quote/{slug}?hl=en",
                     headers={"User-Agent": UA}, cookies=COOKIES, timeout=25)
    s = BeautifulSoup(r.text, "lxml")
    rows = s.find_all("div", class_="gyFHrc") + s.find_all("div", class_="KxsRFb")
    dollars = re.findall(r"\$[\d,]+\.\d{2}", s.get_text())
    print(f"{slug:<12} HTTP {r.status_code} | stat rows {len(rows):>2} | "
          f"$ amounts in HTML: {len(dollars)}")

I ran this. Real output:

BTC-USD      HTTP 200 | stat rows 10 | $ amounts in HTML: 0
AAPL:NASDAQ  HTTP 200 | stat rows 32 | $ amounts in HTML: 0

Read that carefully. Both pages return 200 and both have stat rows, but neither exposes a $ amount in the body text for crypto, and even the stock page hides the headline price behind the same client-side render in this particular text dump (Method 1 still catches the stock price because it queries the live DOM-shaped <div> node, while crypto pairs carry no equivalent static node at all). The practical rule from my testing: equities give up their price and full stat table to a static parser, crypto pairs do not. For crypto you have two options, a headless browser or a scraper API, both covered next.

Method 2: Scrape crypto and any ticker with a headless browser (Playwright)

For crypto prices, indexes, or any value rendered client-side, drive the page with Playwright so the JavaScript runs before you read the DOM. This is the DIY fix for the limitation above. The trade-off is weight: Playwright launches a real browser, so it is slower and heavier than requests, but it sees exactly what a human sees. The pattern:

from playwright.sync_api import sync_playwright

def crypto_price(slug):
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        ctx = browser.new_context()
        # set the consent cookie so we skip the interstitial
        ctx.add_cookies([{
            "name": "SOCS",
            "value": "CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2Vy"
                     "XzIwMjMwODI5LjA3X3AwGgJlbiACGgYIgLC_pwY",
            "domain": ".google.com", "path": "/",
        }])
        page = ctx.new_page()
        page.goto(f"https://www.google.com/finance/quote/{slug}?hl=en",
                  wait_until="networkidle", timeout=30000)
        # the price is the first node matching a currency pattern
        price = page.locator("text=/^\\$[\\d,]+\\.\\d{2}$/").first.inner_text()
        browser.close()
        return price

print(crypto_price("BTC-USD"))

I did not paste output for this one because I do not have a Playwright browser installed in my test environment, and I will not fake a number. The logic is the same as Method 1 with one change: wait_until="networkidle" lets the price script finish before you query the DOM. Install with pip install playwright then playwright install chromium. Use this when you need crypto or index values and want to stay fully DIY. The downside at scale is that headless browsers are resource-hungry and Google still rate-limits the IP, so for many tickers on a schedule, Method 3 is cheaper to operate. See my Playwright scraping guide and the Scrapy guide for a structured crawler.

Method 3: Scrape Google Finance with a scraper API

A scraper API is the right tool when you need crypto prices without running your own browser, want many tickers without IP throttling, or do not want to babysit the consent cookie. It renders the JavaScript, rotates IPs, and carries the consent state for you, so you get the price back whether the target is a stock or a crypto pair. ChocoData exposes a universal endpoint plus dedicated endpoints; point it at the quote URL with rendering on:

import requests

resp = requests.get(
    "https://api.chocodata.com/v1",
    params={
        "api_key": "YOUR_API_KEY",
        "url": "https://www.google.com/finance/quote/BTC-USD",
        "render": "true",       # execute the page JavaScript (needed for crypto)
        "country": "us",        # exit from a US IP, skips the EU consent wall
    },
    timeout=90,
)
print(resp.status_code)
html = resp.text   # fully rendered page, price included

I did not run this call (it needs a paid key), so I am not pasting fake output. What an API buys you over the DIY routes, by trade-off:

FactorDIY requests (Method 1)DIY Playwright (Method 2)Scraper API (Method 3)
CostFreeFreePer-request, paid
StocksWorks, testedWorksWorks
Crypto / indexesNo (JS-only)WorksWorks
Consent wallYou supply the cookieYou supply the cookieHandled, or skip via US IP
JavaScript renderNoYes (real browser)Yes
Rate limitsYour IP gets throttledYour IP gets throttledRotated pool
Speed / weightFast, lightSlow, heavyFast for the caller
Best forA few stocks on a timerOccasional crypto, fully DIYMany tickers, crypto, hands-off

Decision rule: if you only need a handful of equities, ship the free Method 1 scraper. If you need crypto occasionally and want to stay DIY, Method 2 with Playwright works. For many symbols on a schedule, crypto prices at volume, or a pipeline where you cannot afford the consent cookie expiring or the IP getting throttled, the API earns its cost by absorbing rendering, rotation, and the consent gate. None of these turns Google Finance into a documented JSON feed, so if you need guaranteed uptime and a contract, a licensed market-data provider is the other path.

What are the limits of scraping Google Finance?

The honest limits: no official API, a consent wall on EU IPs, obfuscated class names that rotate, and crypto prices locked behind JavaScript. The good news from my June 12, 2026 testing is that individual stocks are easy: one GET with a SOCS cookie returned HTTP 200, the live price, and 16 stat fields straight from static HTML, and the same code worked across tickers. Crypto pairs and indexes are the wall: they load 200 but the price is client-side, so you need Playwright or a rendering API to read it. Class names change without notice, so anchor on text patterns and label rows rather than a single CSS class, and expect to refresh the consent cookie when the interstitial reappears. Build on Method 1 for free stock pulls, reach for Method 2 when you need crypto and want to stay DIY, and use Method 3 when you need volume, crypto at scale, or a hands-off pipeline. For real-time licensed data with an SLA, pair any of these with a proper market-data feed.

FAQ

Is there a free Google Finance API I can call instead of scraping?

No. Google retired the official Finance API in 2012 and the GOOGLEFINANCE() function only works inside Google Sheets, not as a public HTTP endpoint. Everything else is either scraping the public quote pages (what this guide does) or a third-party data feed. If you want a documented JSON API, use a market-data provider; if you only need a few tickers occasionally, the DIY scraper below is enough.

Why does my scraper return no price for Bitcoin but works for Apple?

Individual stock pages ship the price and stat table in the static HTML, so requests plus BeautifulSoup can read them. Crypto pairs like BTC-USD render the live price client-side with JavaScript, so the number is not in the HTML you download. I confirmed this on June 12, 2026: the BTC-USD page returned 200 with 10 stat rows but zero dollar amounts in the body. To get crypto prices you need a headless browser (Playwright) or a scraper API that executes JavaScript.

Can Google block my IP for scraping Finance?

Yes. The consent interstitial is the first gate, and the SOCS cookie clears it, but Google still rate-limits IPs that fire many requests quickly. For a handful of tickers on a timer you will be fine. For hundreds of symbols on a schedule, add delays between requests, retry with backoff on non-200 responses, and rotate IPs or use a scraper API so a single address does not get throttled.

MR
Marcus Reed
I've built and run web scrapers for the better part of a decade. On this site I put scraper APIs and scraping tools through real jobs against real targets, then write up what actually holds up.