CoinGecko 在一个平台上追踪数千种加密货币,其行情页面包含驱动价格追踪、投资组合工具和研究所需的结构化数据:代币名称和符号、当前价格、市值、24小时交易量、24小时和7天价格变化以及市值排名。对于关注一篮子代币的用户,这些公开行情数据是原始素材,而在数百行之间手动复制数据既耗时又会在您刚完成时就已过期。

本指南向您展示如何用 Python 从 CoinGecko 提取加密货币数据。CoinGecko 发布了官方公开 API,对于任何生产工作负载,这应该是您首先考虑的路径。这里的 HTML 爬取方式是一种教学性备选方案,适用于免费 API 层不覆盖的字段或页面,且完全限定在公开行情数据范围内, 这些是事实性数据而非个人数据。文末有一个真正的合法性说明;在大规模爬取前请务必阅读。

您将构建的内容

一个 Python 脚本,通过 Crawling API 获取渲染后的 CoinGecko 行情页面,用 BeautifulSoup 解析每一行代币,并为每种代币生成一条结构化记录。运行示例以 CoinGecko 主代币列表的顶部代币为准,我们提取以下字段:

  • 名称:完整的币名,如 Bitcoin 或 Ethereum。
  • 符号:简短的代币符号,如 BTC 或 ETH。
  • 价格:您所选法币的当前价格。
  • 市值:该代币的总市值。
  • 交易量:过去 24 小时的交易量。
  • 24小时涨跌幅:过去 24 小时的价格变化百分比。
  • 7天涨跌幅:过去 7 天的价格变化百分比。
  • 排名:该行显示的市值排名。

为什么普通请求在 CoinGecko 上会失败

用裸 HTTP 客户端请求 CoinGecko 行情页面,通常会得到状态码 200 但正文中只有部分表格数据。两个因素对您不利。首先,CoinGecko 通过 JavaScript 在浏览器中为其行情表格的大部分内容补充数据:价格和百分比变化实时更新,部分行只有在页面脚本运行后才会填充。解析第一个响应,您可能只得到一个不完整的表格,而非完整的行集合。其次,与任何高流量网站一样,CoinGecko 会监测自动流量,而以紧密循环请求的数据中心 IP 会在到达渲染内容之前被限速或挑战。

因此,一个可用的爬虫需要在单次请求中实现两件事:真正渲染页面的浏览器,以及网站视为真实访客的 IP。您可以自己组合无头浏览器和轮换住宅代理池,但将它们组合并保持运行正常才是大部分工作。Crawling API 将两者整合进一次调用:您发送带 JavaScript token 的 URL,它在可信 IP 后渲染页面,并为您返回可解析的完整 HTML。

API 优先

CoinGecko 提供免费的公开 API,已经以干净的 JSON 格式返回名称、符号、价格、市值、交易量、排名和百分比变化。对于生产用途,请使用它:这是经过授权的路径,完全避免了解析 HTML。仅在 API 层不公开的页面专属字段或视图时才使用 HTML 爬取,且无论哪种方式都请遵守 CoinGecko 的速率限制。

前提条件

在编写代码之前,您需要准备几样东西,都不会花太长时间。

基础 Python 知识。您应当能够编写和运行 Python 脚本,并使用 pip 安装包。如果您不熟悉解析部分,BeautifulSoup 指南是本教程的好伴侣。

Python 3.8 或更高版本。使用 python --version 确认版本。如果尚未安装,请从 python.org 或通过 Anaconda 等发行版安装,并确保 Python 在您的 PATH 中。

Crawlbase 账户和 JS token。注册后,打开控制台,从账户文档页面复制 JavaScript(JS)token。Crawlbase 提供 1,000 次免费请求,足够完成本指南。请像对待密码一样保管 token,勿将其纳入版本控制。

搭建项目

创建虚拟环境以隔离项目依赖,然后安装爬虫所需的库。

bash
python --version

python -m venv coingecko_env
source coingecko_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,请使用 coingecko_env\Scripts\activate 代替 source 命令。两个依赖项各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML 以便按 CSS 选择器提取各字段。jsoncsv 均为标准库,导出步骤无需额外安装。

步骤一:获取渲染后的 CoinGecko 页面

首先获取完整页面。导入 CrawlingAPI 类,用您的 JS token 初始化,并请求 CoinGecko 代币 URL。CoinGecko 在客户端部分更新其表格,因此传入 ajax_waitpage_wait 以等待动态内容加载完毕再捕获页面。在解析之前检查 Crawlbase 的 pc_status,可以让失败清晰呈现而非被静默忽略。

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0",
    "ajax_wait": "true",
    "page_wait": 5000,
}

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["pc_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['pc_status']}")
    return None

if __name__ == "__main__":
    coins_url = "https://www.coingecko.com/"
    html = crawl(coins_url)
    print(html[:500] if html else "No HTML returned")

两个等待选项对于部分客户端渲染的目标至关重要。ajax_wait 告知 API 等待异步内容加载完成,page_wait 则在加载后额外等待固定毫秒数,以便延迟渲染的单元格在页面被捕获前出现。五秒是合理的起点;如果行数据返回稀少,可适当延长。运行 python coingecko_scraper.py,您应该看到真实的 CoinGecko 行情标记,而非占位内容。这表示在编写任何选择器之前渲染已正常工作。

Crawlbase Crawling API

CoinGecko 需要在可信 IP 后获取渲染页面,通过一次调用完成,这正是上面的 ajax_waitpage_wait 选项所设置的。Crawling API 接受 JS token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,并为您返回完整 HTML,让您无需自行运行无头集群和代理池。先在免费套餐上指向公开行情页面进行测试。

步骤二:解析代币行

CoinGecko 代币页面是一个表格,每行对应一种代币。将渲染后的 HTML 加载到 BeautifulSoup 中,选择表格正文各行,然后通过数据属性读取每个单元格。CoinGecko 使用 data-coin-table-target 值标记其行情单元格,使得按字段而非脆弱的列位置进行定位成为可能。

python
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 parse_coins(html):
    soup = BeautifulSoup(html, "html.parser")
    rows = soup.select("table tbody tr")
    coins = []
    for row in rows:
        name = text_of(row, '[data-coin-table-target="coinName"]')
        if not name:
            continue
        coins.append({
            "rank": text_of(row, "td:nth-child(2)"),
            "name": name,
            "symbol": text_of(row, '[data-coin-table-target="coinSymbol"]'),
            "price": text_of(row, '[data-coin-table-target="price"]'),
            "change_24h": text_of(row, '[data-coin-table-target="priceChange24h"]'),
            "change_7d": text_of(row, '[data-coin-table-target="priceChange7d"]'),
            "volume_24h": text_of(row, '[data-coin-table-target="volume"]'),
            "market_cap": text_of(row, '[data-coin-table-target="marketCap"]'),
        })
    return coins

text_of 辅助函数在行内查询一个元素并返回其去除首尾空白的文本,或在元素缺失时返回 None,确保某种代币缺少字段时不会中断循环。排名从第二列单元格读取,名称和符号来自各自的标记元素,价格、两列涨跌幅、交易量和市值各自映射到一个 data-coin-table-target 值。if not name: continue 守护跳过没有代币名称的分隔行或标题行。

选择器会发生漂移

CoinGecko 的类名和 data-coin-table-target 属性可能在不通知的情况下更改,且百分比涨跌幅列在窄视口上有时布局不同。请将这里的选择器视为起始模板,而非固定合约。当某个字段返回 None 时,请在浏览器开发者工具中重新检查实时页面并更新选择器。定期维护选择器是任何生产爬虫的正常工作,不是出了什么问题的标志。

步骤三:处理代币页面的分页

单个行情页面只是完整列表的一个切片。CoinGecko 使用 ?page=N 查询参数进行分页,因此您逐页收集代币到您设置的上限。在获取操作周围加一个小型重试包装,可以避免单次慢请求终止整个运行。

python
import time

def fetch_html(page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        html = crawl(page_url)
        if html:
            return html
        if attempt < max_retries:
            print(f"Retrying ({attempt + 1}/{max_retries})...")
            time.sleep(1)
    print(f"Unable to fetch {page_url}")
    return None

def collect_all_coins(base_url, max_pages):
    all_coins = []
    for page in range(1, max_pages + 1):
        page_url = f"{base_url}?page={page}"
        html = fetch_html(page_url)
        if html:
            all_coins.extend(parse_coins(html))
        time.sleep(2)
    return all_coins

fetch_html 在失败时最多重试两次(中间有短暂停顿),成功时返回 HTML,放弃时返回 Nonecollect_all_coins 从第一页遍历到您的 max_pages 上限(防止长列表失控运行),将每页解析为代币记录并累积。页面之间的 time.sleep(2) 限制了运行速率,使您保持友好于网站的速率限制。

步骤四:组合完整脚本

现在将各部分整合到一个可运行的脚本中:遍历页面,解析每种代币,并将记录导出到 JSON 和 CSV。

python
import csv
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

OPTIONS = {
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0",
    "ajax_wait": "true",
    "page_wait": 5000,
}

def crawl(page_url):
    response = api.get(page_url, OPTIONS)
    if response["headers"]["pc_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['pc_status']}")
    return None

def fetch_html(page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        html = crawl(page_url)
        if html:
            return html
        if attempt < max_retries:
            time.sleep(1)
    return None

def text_of(row, selector):
    el = row.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_coins(html):
    soup = BeautifulSoup(html, "html.parser")
    rows = soup.select("table tbody tr")
    coins = []
    for row in rows:
        name = text_of(row, '[data-coin-table-target="coinName"]')
        if not name:
            continue
        coins.append({
            "rank": text_of(row, "td:nth-child(2)"),
            "name": name,
            "symbol": text_of(row, '[data-coin-table-target="coinSymbol"]'),
            "price": text_of(row, '[data-coin-table-target="price"]'),
            "change_24h": text_of(row, '[data-coin-table-target="priceChange24h"]'),
            "change_7d": text_of(row, '[data-coin-table-target="priceChange7d"]'),
            "volume_24h": text_of(row, '[data-coin-table-target="volume"]'),
            "market_cap": text_of(row, '[data-coin-table-target="marketCap"]'),
        })
    return coins

def collect_all_coins(base_url, max_pages):
    all_coins = []
    for page in range(1, max_pages + 1):
        html = fetch_html(f"{base_url}?page={page}")
        if html:
            all_coins.extend(parse_coins(html))
        time.sleep(2)
    return all_coins

def save_outputs(records):
    with open("coingecko_coins.json", "w") as f:
        json.dump(records, f, indent=2)
    if not records:
        return
    with open("coingecko_coins.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=records[0].keys())
        writer.writeheader()
        writer.writerows(records)

def main():
    coins_url = "https://www.coingecko.com/"
    coins = collect_all_coins(coins_url, max_pages=2)
    save_outputs(coins)
    print(f"Saved {len(coins)} coins")

if __name__ == "__main__":
    main()

该脚本最多遍历两个行情页面,通过重试包装获取每页,将其解析为代币记录,并用两秒停顿限制循环速率。save_outputs 同时写入 JSON 文件和 CSV(以第一条记录的键作为标题),使您拥有下游工具所需格式的数据。调整 max_pages 和基础 URL,以适应您关心的市场切片。

输出示例

运行 python coingecko_scraper.py,您将得到每种代币的干净结构化记录,可直接用于分析、存入数据库或导入电子表格。以下数值是用于展示结构的示意性占位符,而非实时行情。

json
[
  {
    "rank": "1",
    "name": "Bitcoin",
    "symbol": "BTC",
    "price": "$X,XXX.XX",
    "change_24h": "+1.2%",
    "change_7d": "-3.4%",
    "volume_24h": "$XX,XXX,XXX,XXX",
    "market_cap": "$X,XXX,XXX,XXX,XXX"
  },
  {
    "rank": "2",
    "name": "Ethereum",
    "symbol": "ETH",
    "price": "$X,XXX.XX",
    "change_24h": "+0.6%",
    "change_7d": "+2.1%",
    "volume_24h": "$XX,XXX,XXX,XXX",
    "market_cap": "$XXX,XXX,XXX,XXX"
  }
]

对应的 CSV 包含相同的列,每种代币一行,可直接导入 pandas 或任意电子表格,按市值排序、按7天涨跌幅筛选或绘制交易量图表。从这里,相同的模式也适用于其他行情追踪工具;这种方法与 从 CoinMarketCap 爬取加密货币价格的方式非常接近。

大规模运行时保持不被屏蔽

即使渲染问题已解决,CoinGecko 仍会监测爬虫特征的流量。以下几个习惯可以保持较长时间运行的健康,适用于任何高流量数据网站。

  • 限制请求速率。在紧密循环中猛烈请求页面是最快被限速或挑战的方式。上面的两秒停顿是下限,而非上限;对于较大的任务请适当延长,并遵守网站发布的速率限制。
  • 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,使任何单个地址都不会触发速率限制。Crawling API 为您处理这些;如果您自己搭建技术栈,这是最关键的部分。
  • 读取状态码。当运行开始返回非 200 的 pc_status 值时,说明当前速率或 IP 等级已不够用。将其视为退让的信号,而非可以忽略的噪声。

对于较大规模的爬取,异步 Crawler 可将请求排队并通过 webhook 交付结果,适合在不保持开放连接的情况下运行多个页面。有关更广泛的策略,请参阅 如何在不被屏蔽的情况下爬取网站

爬取 CoinGecko 是否合法?

先说明更好的路径:CoinGecko 发布了官方公开 API,以干净的 JSON 格式返回代币名称、符号、价格、市值、24小时交易量、百分比变化和排名,并有文档化的速率限制和付费层供更高吞吐量使用。对于任何生产或商业用途,该 API 是经过授权的路径,既更稳定,也比解析渲染 HTML 更尊重平台。本指南中的 HTML 爬取方式是一种教学性备选方案,适用于页面专属字段或一次性探索,而非在官方 API 存在时的替代品。

爬取网站本身是否被允许,取决于 CoinGecko 的服务条款、您所在的司法管辖区以及您对数据的用途。请阅读 CoinGecko 的条款和其 robots.txt,并将两者视为您采集内容和频率的边界。将请求量控制在不给服务器造成负担的水平,对于轻量偶尔使用以外的任何情况请优先使用 API。这里的行情数据是事实性、非个人性的,比用户生成内容的风险更低,但条款仍然约束自动访问。

本指南刻意将范围限定在公开行情数据:任何人无需账号即可看到的代币名称、符号、价格、市值、交易量、百分比变化和排名。它不涵盖登录墙或付费墙后面的任何内容、任何个人或账户数据,或 CoinGecko 的品牌、Logo 或编辑内容的再发行(这些仍是其财产)。如果您的项目需要可靠的大规模访问,正确路径是 CoinGecko 的官方 API 或授权数据源,而非更复杂的爬虫。如需更广泛了解数据提供商,请参阅 全球最佳金融数据提供商

回顾

核心要点

  • 优先使用 API。CoinGecko 的官方公开 API 以 JSON 格式返回名称、符号、价格、市值、交易量、涨跌幅和排名,是任何生产工作负载的正确路径。
  • HTML 爬取是备选方案。需要爬取页面时,由于页面部分是客户端渲染的,请在解析前使用 Crawling API 的 JS token 进行渲染;ajax_waitpage_wait 控制等待时长。
  • 通过数据属性定位。CoinGecko 使用 data-coin-table-target 值标记其行情单元格,因此按属性而非脆弱的列位置读取每个字段。
  • 分页并导出。沿 CoinGecko 的 ?page=N 页面遍历到上限,用短暂停顿限制运行速率,并将记录写入 JSON 和 CSV。
  • 坚守公开数据。遵守 CoinGecko 的服务条款、robots.txt 和速率限制,仅采集公开行情数据,绝不触碰登录账户、付费内容或受版权保护的内容。

常见问题

我应该使用 CoinGecko API 还是爬取 HTML?

使用 API。CoinGecko 提供免费的公开 API,以干净的 JSON 格式返回代币名称、符号、价格、市值、24小时交易量、百分比变化和排名,并有文档化的速率限制。它比解析 HTML 更稳定,是生产用途的授权路径。仅在您所使用的 API 层不公开的字段或视图时才爬取渲染页面,且在此情况下也请遵守相同的速率限制。

为什么普通请求只返回部分 CoinGecko 表格?

因为 CoinGecko 通过 JavaScript 在客户端补充其行情表格的大部分内容:价格和百分比变化实时更新,部分单元格只有在浏览器中运行页面脚本后才会填充。原始 HTTP 请求可能返回状态码 200 但表格数据不完整。要获取完整的行集合,您需要先渲染页面,这正是 Crawling API 的 JS token 为您处理的事情。

CoinGecko 需要普通 token 还是 JS token?

JS token。普通 token 获取静态 HTML,在部分客户端渲染的页面上可能错过实时单元格。JS token 在返回 HTML 之前先在真实浏览器中渲染页面,确保当 BeautifulSoup 解析时,代币行及其价格、涨跌幅、交易量和市值单元格都已存在。

我可以从 CoinGecko 提取哪些数据?

公开行情数据:代币名称和符号、当前价格、市值、24小时交易量、24小时和7天百分比变化以及市值排名。这是任何访客都可以看到的事实性、非个人性行情数据。请远离登录墙或付费墙后面的任何内容,不要转载 CoinGecko 的 Logo、品牌或编辑内容。

我的选择器返回 None,是什么发生了变化?

几乎可以肯定是 CoinGecko 的标记发生了变化。其类名和 data-coin-table-target 属性可能在不通知的情况下更改,且百分比涨跌幅列在窄视口上布局不同。请在浏览器开发者工具中重新检查实时页面并更新选择器。定期维护选择器是任何生产爬虫的正常工作,这也是应尽可能优先使用官方 API 的另一个原因。

我可以将爬取的 CoinGecko 数据用于商业用途吗?

请将其视为法律问题而非技术问题。CoinGecko 的服务条款约束自动访问和再利用,商业或大规模用途通常应使用其官方 API 或付费层,而非爬虫。请审阅条款,对于任何大规模用途使用 API 或授权数据源,并在基于这些数据构建产品之前寻求法律建议。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量