CoinMarketCap is the default reference for crypto market data: it tracks live prices, market capitalisation, trading volume, and rankings across thousands of coins, and it updates those numbers in near real time. That public table is exactly what powers price dashboards, portfolio trackers, and market research, which is why so many people want a clean structured copy of it.
This guide shows you how to scrape crypto prices from CoinMarketCap with Python. You build a small, runnable scraper that fetches the rendered listing page through the Crawling API, parses the fields you want with BeautifulSoup, and writes a clean record per coin. The whole walkthrough stays scoped to public market data: coin name, symbol, price, market cap, 24h change, and volume. The legality section near the end is not boilerplate, and CoinMarketCap ships an official API, so read both before you point this at real volume.
What you will build
A Python script that fetches the public CoinMarketCap front page, retrieves the rendered HTML through the Crawling API, and extracts a structured record for the top coins on the table. For each coin you will pull these fields:
- Name the coin's full name, like "Bitcoin".
- Symbol the ticker symbol, like "BTC".
- Price the current price in USD.
- Market cap the total market capitalisation shown on the row.
- 24h change the percentage move over the last 24 hours.
- Volume the 24-hour trading volume.
Why a plain request fails on CoinMarketCap
If you request CoinMarketCap with a bare HTTP client, you rarely get the clean price table you see in a browser. Two things work against you. First, CoinMarketCap renders much of its market table client-side: the initial HTML is a thin shell, and the rows only fill in after the page's JavaScript runs. Second, the site sits behind bot protection like Cloudflare, so datacenter IPs and request patterns that do not look like a real browser get challenged or rate-limited before they ever reach the rendered table.
So a naive requests.get() tends to return either an empty shell or a challenge page instead of coin data. A working CoinMarketCap scraper needs two things together: a browser that renders the page, and an IP the site reads as a normal visitor.
You can assemble that yourself with a headless browser plus a pool of rotating residential proxies, but stitching those together and keeping them healthy is most of the work. The Crawling API folds the hard parts into a single call: you send it the CoinMarketCap URL with a JavaScript token, it renders the page behind a trusted IP, and it hands back finished HTML for you to parse. If you would rather route your own requests through a rotating pool, the Smart Proxy gives you the same residential IP rotation as a drop-in proxy endpoint.
Crawlbase offers two token types. The normal token fetches static HTML; the JavaScript (JS) token renders the page in a real browser first. CoinMarketCap's market table is client-rendered, so you want the JS token here. The normal token tends to return the same thin shell a plain fetch would, with no rows to parse out of it.
Prerequisites
You need a few things in place before writing any code. None of them take long.
Basic Python. You should be comfortable writing and running a Python script and installing packages with pip. If you are new to parsing HTML, our guide on how to use BeautifulSoup in Python covers the selector basics this tutorial assumes.
Python 3.8 or later. Confirm your version with python --version. If you do not have it, install it from python.org or through a distribution like Anaconda.
A Crawlbase account and JS token. Sign up, open your dashboard, and copy your JavaScript (JS) token from the account docs page. Treat the token like a password: it authenticates your requests, so keep it out of version control.
Set up the project
Create a virtual environment so project dependencies stay isolated, then install the two libraries the scraper needs.
python --version python -m venv cmc_env source cmc_env/bin/activate pip install crawlbase beautifulsoup4
On Windows, activate the environment with cmc_env\Scripts\activate instead of the source line. Two dependencies do the work: crawlbase is the official client for the Crawling API, and beautifulsoup4 parses the returned HTML so you can pull out individual fields by CSS selector.
Step 1: Fetch the rendered page
Start by getting the finished page. Import the CrawlingAPI class, initialize it with your JS token, and request the CoinMarketCap URL. Checking the status code before you parse keeps failures loud instead of silent.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("latin1") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": page_url = "https://coinmarketcap.com/" html = crawl(page_url) print(html[:500] if html else "No HTML returned")
The two wait options matter for a client-rendered target like this: ajax_wait tells the API to wait for asynchronous content to finish loading, and page_wait holds for a fixed number of milliseconds after load so the late-rendering table rows appear before the page is captured. Five seconds is a reasonable start; raise it if the table comes back empty. CoinMarketCap sends pages in a range of encodings, so decoding with latin1 avoids the byte errors a strict UTF-8 decode can throw. Run the script and you should see real market markup, not the thin shell a plain fetch returns.
CoinMarketCap's table needs a rendered page behind a trusted IP, in one call. The Crawling API takes a JS token, runs the page in a real browser, rotates through residential IPs server-side, and hands you finished HTML, so you skip running a headless browser fleet and a proxy pool yourself. Point it at the public front page on the free tier first.
Step 2: Parse the coin fields with BeautifulSoup
With rendered HTML in hand, load it into BeautifulSoup and walk the table rows. CoinMarketCap lays each coin out as a row in the main market table, with the name and symbol in the coin cell and the numeric fields in the columns to the right. Wrap each row's extraction in a try/except so one missing field does not crash the whole run.
from bs4 import BeautifulSoup def text_of(row, selector): el = row.select_one(selector) return el.get_text(strip=True) if el else None def scrape_coins(html, limit=10): soup = BeautifulSoup(html, "html.parser") rows = soup.select("table tbody tr")[:limit] coins = [] for row in rows: try: cells = row.select("td") coins.append({ "name": text_of(row, "p.coin-item-name"), "symbol": text_of(row, "p.coin-item-symbol"), "price": cells[3].get_text(strip=True), "change_24h": cells[4].get_text(strip=True), "market_cap": cells[7].get_text(strip=True), "volume_24h": cells[8].get_text(strip=True), }) except (IndexError, AttributeError) as e: print("Skipping a row due to error:", e) return coins
The name and symbol have stable, human-readable class hooks, p.coin-item-name and p.coin-item-symbol, so you target them directly. The numeric columns (price, 24h change, market cap, and volume) are pulled positionally from the row's td cells, because CoinMarketCap's price cell carries a hashed, frequently changing class name that is not safe to hard-code. The text_of helper returns None instead of throwing when an element is missing, and the try/except guards against a row whose column layout differs, so one odd row never takes down the run.
CoinMarketCap's markup, especially its hashed class names, changes without notice. Treat the selectors and column indexes above as a starting template, not a contract. When a field comes back wrong or empty, right-click the cell in your browser's dev tools, check the live structure, and adjust the selector or index. Periodic selector maintenance is normal for any production scraper, not a sign something is broken.
Step 3: Put it together
Now wire the fetch and the parse into one runnable script. Fetch the rendered HTML, hand it to the parser, and print the structured records as JSON.
import json from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("latin1") print(f"Request failed: {response['status_code']}") return None def text_of(row, selector): el = row.select_one(selector) return el.get_text(strip=True) if el else None def scrape_coins(html, limit=10): soup = BeautifulSoup(html, "html.parser") rows = soup.select("table tbody tr")[:limit] coins = [] for row in rows: try: cells = row.select("td") coins.append({ "name": text_of(row, "p.coin-item-name"), "symbol": text_of(row, "p.coin-item-symbol"), "price": cells[3].get_text(strip=True), "change_24h": cells[4].get_text(strip=True), "market_cap": cells[7].get_text(strip=True), "volume_24h": cells[8].get_text(strip=True), }) except (IndexError, AttributeError) as e: print("Skipping a row due to error:", e) return coins def main(): html = crawl("https://coinmarketcap.com/") if not html: return data = scrape_coins(html) print(json.dumps(data, indent=2)) if __name__ == "__main__": main()
What the output looks like
Run the full script with python scraper.py and you get a clean list of structured records, ready to write to JSON, CSV, or a database. The exact prices will differ from the sample below, since the market moves constantly.
[ { "name": "Bitcoin", "symbol": "BTC", "price": "$92,477.64", "change_24h": "2.14%", "market_cap": "$1.83T", "volume_24h": "$38.20B" }, { "name": "Ethereum", "symbol": "ETH", "price": "$1,744.77", "change_24h": "1.06%", "market_cap": "$210.6B", "volume_24h": "$14.95B" } ]
Exporting to CSV and JSON
One JSON dump to the console is fine for a demo, but you will usually want the data on disk to store or analyse later. The records are already a list of flat dictionaries, so writing both formats is a few lines. Reuse the crawl and scrape_coins functions from the full script above.
import csv import json html = crawl("https://coinmarketcap.com/") coins = scrape_coins(html) fields = ["name", "symbol", "price", "change_24h", "market_cap", "volume_24h"] with open("crypto_prices.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(coins) with open("crypto_prices.json", "w") as f: json.dump(coins, f, indent=2) print(f"Saved {len(coins)} coins to crypto_prices.csv and crypto_prices.json")
From here, a CSV drops straight into a spreadsheet or a pandas DataFrame for analysis, and the JSON file is easy to load into a downstream service. If you want a live feed rather than a one-off snapshot, schedule the script to run on an interval and append each pull to a dated file.
Scaling to more coins and pages
The front page lists the top coins, but CoinMarketCap paginates the full ranking. The shape of the work does not change: the listing structure is the same on every page, so the parser you already wrote keeps working. Walk the numbered pages, fetch each through the Crawling API, and collect the rows.
all_coins = [] for page in range(1, 4): # first three pages of the ranking url = f"https://coinmarketcap.com/?page={page}" html = crawl(url) if html: all_coins.extend(scrape_coins(html, limit=100)) print(f"Collected {len(all_coins)} coins across pages")
If you need this to run unattended at scale, queue the page URLs through the asynchronous Crawler instead of looping synchronously, so you submit the whole set of pages and collect results as they finish. For more on rendering-heavy targets, our walkthrough on how to scrape JavaScript pages with Python goes deeper on the wait-and-render pattern this scraper relies on.
Staying unblocked
Even with rendering handled, CoinMarketCap watches for scraper-shaped traffic. A few habits keep a run healthy, and they apply to any protected, JavaScript-heavy target.
- Pace your requests. Pulling every page in a tight loop is the fastest way to get throttled. Spread requests out, and for a live feed every few minutes is plenty rather than hammering on a tight cadence.
- Lean on rotation. A pool of residential IPs spreads requests across many real-user addresses so no single one trips a rate limit. The Crawling API handles this for you; if you roll your own stack, this is the part to get right.
- Read the status codes. A run that starts returning challenges or errors is telling you the current rate or IP tier is no longer enough. Treat that as signal to back off, not noise to ignore.
For the broader playbook, see how to scrape websites without getting blocked and the deeper dive on how to bypass captchas while web scraping.
Is it legal to scrape CoinMarketCap?
Whether scraping CoinMarketCap is allowed depends on the site's terms of service, your jurisdiction, and what you do with the data. CoinMarketCap publishes a Terms of Service and a robots.txt, and both set the boundary for automated access. None of the code here changes that; it just makes the technical part work. Read those documents and treat them as the limit on what you collect and how often you request it, especially before any commercial project.
Keep your scope tight. The market table covered here is genuinely public data: coin name, symbol, price, market cap, 24h change, and volume, all visible without an account. That is the line that keeps the work defensible. What this guide does not cover is just as important: anything behind a login, account or watchlist data tied to a user, personal information, and copyrighted media you would redistribute are all out of scope. Do not bypass authentication to reach data the public table does not already show.
One more thing specific to this target: CoinMarketCap ships an official API. For production systems, commercial use, or high-volume pulls, that official API is the right path, because it gives you a supported contract, predictable rate limits, and data you are clearly licensed to use. Scraping the public site fits learning, prototyping, and small public-data jobs, not a business that needs guaranteed uptime and clear usage rights. When in doubt, prefer the official API.
Key takeaways
- The market table is client-rendered and protected. A plain fetch returns a thin shell or a challenge, so you must render the page behind a trusted IP before you parse it.
-
One call does both. The Crawling API with a JS token renders the page and rotates residential IPs server-side;
ajax_waitandpage_waitcontrol how long it waits for the rows to load. -
Mix selector strategies. Anchor name and symbol on
p.coin-item-nameandp.coin-item-symbol, pull the numeric columns positionally, and expect hashed class names to drift. - Export and scale cleanly. Flat dictionaries write straight to CSV or JSON, and the same parser works across paginated ranking pages.
- Prefer the official API for production. Stay on public market data, respect the ToS and robots.txt, and use CoinMarketCap's official API for commercial or high-volume use.
Frequently Asked Questions (FAQs)
Why does a plain request return no crypto prices from CoinMarketCap?
Two reasons. First, CoinMarketCap renders much of its market table client-side with JavaScript, so the raw HTML is a thin shell that only fills in after the page's scripts run. Second, the site sits behind bot protection like Cloudflare, so automated traffic from datacenter IPs is often challenged or rate-limited before it reaches the data. Rendering the page with the Crawling API's JS token, behind a trusted IP, is what makes the rows appear.
Do I need the normal token or the JS token for CoinMarketCap?
The JS token. The normal token fetches static HTML, which on CoinMarketCap tends to be the same thin shell a plain fetch returns. The JS token renders the page in a real browser before handing back the HTML, so the table rows, including price, market cap, and volume, are present when BeautifulSoup parses them.
How often can I scrape crypto prices?
For a live feed, pulling every few minutes is usually safe and keeps you well under any rate ceiling. Hammering the site in a tight loop is the quickest way to get throttled or challenged. Route through rotating residential IPs, watch your status codes, and back off when you start seeing challenges. For guaranteed throughput, the official CoinMarketCap API is the better fit.
My selectors return wrong values. What changed?
Almost certainly CoinMarketCap's markup. Its hashed CSS class names change frequently and column order can shift, so selectors and positional indexes that worked last month can break. Anchor on the stable p.coin-item-name and p.coin-item-symbol hooks where you can, re-inspect a live page in your browser's dev tools when a field looks off, and update the selector or the cell index. Periodic selector maintenance is normal for any production scraper.
Should I scrape CoinMarketCap or use its official API?
For learning, prototyping, and small public-data jobs, scraping the public table is a fine way to get name, symbol, price, market cap, 24h change, and volume. For production systems, commercial use, or high-volume pulls, use CoinMarketCap's official API: it gives you a supported contract, predictable rate limits, and clear usage rights. Choose the tool that matches the stakes of your project.
What crypto data can I legally collect?
Stick to public market data shown without an account: coin name, symbol, price, market cap, 24h change, and volume. Do not collect account, watchlist, or personal data, do not touch anything behind a login, and never bypass authentication. Respect CoinMarketCap's terms of service and robots.txt, and for commercial or bulk use rely on the official API rather than scaling a scraper against the public site.
Crawl any site at scale, without fighting infrastructure.
Crawlbase handles proxies, fingerprints, and CAPTCHAs so your team ships data pipelines instead of maintaining crawl plumbing. 1,000 requests free, no card required.
