Quick answer: for the big centralized exchanges and coin aggregators, reach for the official REST API first, because crypto is one of the few domains where free, generous, real-time price feeds actually exist. Fall back to a fetch-and-extract API only for the pages that have no endpoint, hide their numbers behind JavaScript, or block datacenter traffic. Most trading bots end up using both, and the skill is knowing which tool each source deserves.
Crypto is a different animal from the rest of finance. A Yahoo Finance quote or an earnings page is something you usually have to scrape, because the underlying feed is licensed and expensive. Crypto grew up open. Binance, Coinbase, Kraken, and CoinGecko all publish free JSON endpoints that return live prices, order books, and 24-hour stats without an API key or with a very cheap one. If you skip past those and go straight to scraping a webpage, you are doing extra work for worse data. So this post starts with the API-first path and only escalates to fetching when the API is not there.
Start With the Official Exchange API
If you want the spot price of a pair on a specific exchange, that exchange almost certainly hands it to you directly. Binance is the canonical example:
import requests
r = requests.get(
"https://api.binance.com/api/v3/ticker/24hr",
params={"symbol": "BTCUSDT"},
)
data = r.json()
print(data["lastPrice"], data["priceChangePercent"], data["volume"])
No key, no rendering, no proxy. You get the last trade price, the 24-hour change, high, low, and volume as clean numbers. Coinbase, Kraken, and Bybit all offer equivalents. For a market-wide view rather than a single venue, CoinGecko aggregates thousands of coins across hundreds of exchanges:
r = requests.get(
"https://api.coingecko.com/api/v3/simple/price",
params={"ids": "bitcoin,ethereum,solana", "vs_currencies": "usd"},
)
print(r.json()) # {'bitcoin': {'usd': 64210}, ...}
The lesson from the broader web scraping vs API question applies with full force here: when a clean, sanctioned endpoint exists, use it. Scraping the HTML version of a price you could have requested as JSON is fragile for no reason. It breaks when the site restyles, it is slower, and it puts load on a page instead of a feed built to serve you.
So when do you actually need to fetch and scrape? Three situations.
When You Do Need to Fetch a Page
The source has no public API. Plenty of valuable crypto data lives on pages that never shipped an endpoint. A DeFi analytics dashboard, a newer DEX, a token-unlock schedule, a launchpad calendar, a governance forum, an NFT floor-price page: these render numbers into HTML and call it a day. If you want that data in a pipeline, the page is your API whether it likes it or not.
The numbers are rendered client-side. Many crypto dashboards are single-page apps that fetch their data with JavaScript after the initial HTML loads. A plain requests.get hands you an empty shell with no prices in it. This is the same wall you hit anywhere on the modern web, and it is why scraping JavaScript-rendered sites needs a browser engine, not a raw HTTP client.
You are hitting rate limits or IP blocks. Free tiers throttle hard. CoinGecko's free plan caps you at a low request-per-minute ceiling, and hammering it from an AWS box earns a fast 429. Aggregator webpages often block datacenter IP ranges outright. When your polling volume outgrows the free tier or your requests come from a flagged network, a fetch API that carries residential IPs and handles the retries becomes the pragmatic path.
For all three, the move is the same: hand the page to a service that renders it, gets past the anti-bot layer, and returns clean data. With the link.sc fetch API you can ask for structured JSON directly instead of parsing HTML.
import requests
schema = {
"type": "object",
"properties": {
"token": {"type": "string"},
"price_usd": {"type": "number"},
"change_24h_pct": {"type": "number"},
"tvl_usd": {"type": "number"},
"volume_24h_usd": {"type": "number"},
},
}
resp = requests.post(
"https://api.link.sc/v1/fetch",
headers={"x-api-key": "lsc_YOUR_KEY"},
json={
"url": "https://some-defi-dashboard.example/pools/eth-usdc",
"format": "json",
"schema": schema,
},
)
pool = resp.json()["data"]
Now pool is a small object like {"token": "ETH-USDC", "price_usd": 3180.42, "change_24h_pct": -1.8, "tvl_usd": 412000000, "volume_24h_usd": 88000000}. You never touched a CSS selector, so a redesign of the dashboard does not break your code, and the JavaScript-populated fields show up because the service rendered the page before extracting.
Building a Trading Bot Feed
A trading bot cares about two things from its data layer: freshness and reliability. The pattern that satisfies both is a small polling loop over a mixed set of sources, each read with the right tool, normalized into one shape.
def get_price(source):
if source["type"] == "exchange":
r = requests.get(source["url"], params=source.get("params"))
return normalize_exchange(source["name"], r.json())
else: # a page with no API
r = requests.post(
"https://api.link.sc/v1/fetch",
headers={"x-api-key": "lsc_YOUR_KEY"},
json={"url": source["url"], "format": "json", "schema": schema},
)
return r.json()["data"]
def poll(sources):
return {s["name"]: get_price(s) for s in sources}
Direct exchange calls stay direct because they are fast and free. The scraped sources route through the fetch API so a challenge or a flagged IP does not stall the loop. Everything lands in the same normalized dict, and your strategy code reads one uniform structure regardless of where each number came from.
A few things matter more for a bot than for a one-off scrape:
Poll at a sane cadence. Crypto moves fast, but you rarely need sub-second data unless you are doing high-frequency work, in which case you want a websocket stream, not REST polling. For most bots, a few seconds between polls is plenty and keeps you comfortably under free-tier limits.
Cross-check your sources. A single exchange can print a bad tick or halt a pair. If a price feeds a live order, confirm it against a second venue before acting. Reading a spot price from one place and a market-wide reference from CoinGecko gives you a cheap sanity check.
Keep the raw payload. Alongside the normalized number, store what the source actually returned. When a trade looks wrong in hindsight, you want the exact data your bot saw, not a reconstruction.
Where the Line Falls
The whole decision comes down to one question per source: does it offer a clean feed. Centralized exchanges and the big aggregators do, so call them directly and enjoy the free real-time data that makes crypto easier than the rest of finance. DeFi dashboards, obscure DEXes, and any page that renders numbers in JavaScript or blocks your IP do not, so route those through a fetch API that returns structured JSON. A production bot is almost always a blend, and the fetch layer exists to cover the sources that were never built to be read by a machine.
If you want to wire this up, the developer quickstart walks through your first call. You get 500 free credits a month, which covers a lot of aggregator polling while you tune the strategy.
Ready to feed your bot clean crypto data from every source? Get your free link.sc API key and make your first fetch call in under two minutes.