Yahoo Finance 是追踪股票、指数和加密货币最常用的平台之一,其行情页面包含推动价格追踪、筛选和研究所需的结构化市场数据:最新价格、当日涨跌幅、前收盘价、市值、成交量和日内价格区间。对于监控自选股列表的人来说,这些公开行情数据就是原材料,而手动逐一查看数十个股票代码既缓慢又容易出错。

本指南将向你展示如何用可靠的方式使用 Python 抓取 Yahoo Finance。你将构建一个小型的可运行爬虫,通过 Crawling API 获取渲染后的行情页面,使用 BeautifulSoup 解析你想要的字段,循环遍历股票代码列表,并导出干净的 JSON 和 CSV。整个教程都限定在公开市场数据的范围内,结尾的合法性部分不是套话,请在对任何真实数据量运行此工具之前仔细阅读。

你将构建什么

一个 Python 脚本,接受股票代码列表,通过 Crawling API 获取每个渲染后的 Yahoo Finance 行情页面,并为每个代码提取结构化记录。运行示例使用 AAPLTSLABTC-USD。我们提取以下字段:

  • 价格:该代码的最新成交价格。
  • 涨跌幅:相较前收盘价的价格变动,包括绝对值和百分比。
  • 前收盘价:上一交易日的收盘价格。
  • 市值:行情统计数据中显示的总市值。
  • 成交量:当前交易日内的成交股数或单位数。
  • 日内价格区间:当前交易日的最低和最高价格区间。

为什么普通请求在 Yahoo Finance 上失效

如果你用普通 HTTP 客户端请求 Yahoo Finance 的行情 URL,你会得到一个状态码为 200 的响应,但响应体中只有极少数字。原因有两点:第一,行情页面通过 JavaScript 在浏览器中渲染实时价格、涨跌幅和统计数据表,因此初始 HTML 只是一个薄薄的外壳,只有页面脚本运行后才会填充内容。解析这个初始响应,你得到的是占位符或空节点,而不是真实数据。第二,Yahoo 会迅速识别自动化流量:来自数据中心的 IP 和不像真实浏览器的请求模式,在到达渲染后的内容之前就会被限速、重定向到同意页面或受到挑战。

因此,一个能正常工作的 Yahoo Finance 爬虫需要在单次请求中同时具备两点:一个能真正渲染页面的浏览器,以及一个让平台认为是真实访客的 IP 地址。你可以自己搭建无头浏览器加轮换住宅代理池来实现,但将这两者整合并保持正常运行占去了大部分工作量。Crawling API 将两者合并为一次调用:你发送带有 JavaScript token 的 URL,它在受信任的 IP 后端渲染页面,并将完整的 HTML 返回供你解析。如果你想了解这类渲染目标的背景知识,使用 Python 抓取 JavaScript 页面的指南是很好的补充阅读。

为什么需要 JS token

Crawlbase 提供两种 token 类型。普通 token 获取静态 HTML;JavaScript(JS)token 则先在真实浏览器中渲染页面。Yahoo Finance 在客户端填充其价格和统计数据字段,因此这里需要使用 JS token。普通 token 会返回与普通请求相同的薄外壳,其中几乎没有可解析的有用内容。

前置条件

在编写任何代码之前,你需要准备好以下几项,都不需要太长时间。

基本 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 yahoo_env
source yahoo_env/bin/activate

pip install crawlbase beautifulsoup4

在 Windows 上,请用 yahoo_env\Scripts\activate 替代 source 那行来激活环境。两个依赖各司其职:crawlbase 是 Crawling API 的官方客户端,beautifulsoup4 解析返回的 HTML,让你可以通过 CSS 选择器提取各个字段。jsoncsv 都随标准库一起提供,因此导出步骤无需额外安装。

第一步:获取渲染后的行情页面

从获取完整页面开始。导入 CrawlingAPI 类,用你的 JS token 初始化它,并请求 Yahoo Finance 行情 URL。Yahoo 异步加载其价格和统计数据,因此要传入 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__":
    quote_url = "https://finance.yahoo.com/quote/AAPL"
    html = crawl(quote_url)
    print(html[:500] if html else "No HTML returned")

两个等待选项对于这类客户端渲染目标非常重要。ajax_wait 告诉 API 等待异步内容加载完成,page_wait 在页面加载后再等待固定的毫秒数,确保实时价格稳定后才捕获页面。五秒是一个合理的起点;如果数据仍然为空,可以适当增大该值。用 python yahoo_scraper.py 运行脚本,你应该能看到真实的行情标记,而不是普通请求返回的外壳。这证明渲染正常工作,然后你才可以编写选择器。

Crawlbase Yahoo Finance Scraper

Yahoo Finance 行情需要在受信任的 IP 后端渲染页面,并在一次调用中完成,这正是上面的 ajax_waitpage_wait 选项所设置的。Crawling API 接受 JS token,在真实浏览器中运行页面,在服务器端轮换住宅 IP,并将完整的 HTML 返回给你,让你无需自己运行无头浏览器集群和代理池。先在免费层针对公开行情页面测试。

第二步:解析头部价格和涨跌幅

Yahoo Finance 通过行情头部的稳定 data-testid 属性暴露其实时数据,使其成为可靠的解析目标。将渲染后的 HTML 加载到 BeautifulSoup,从这些属性读取标题、价格和涨跌幅。每次查询都有防护,确保缺失字段返回 None 而不是导致运行崩溃。

python
from bs4 import BeautifulSoup

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

def scrape_header(html):
    soup = BeautifulSoup(html, "html.parser")
    return {
        "title": text_of(soup, "div.hdr h1"),
        "price": text_of(soup, '.livePrice[data-testid="qsp-price"]'),
        "change": text_of(soup, '.priceChange[data-testid="qsp-price-change"]'),
        "change_percent": text_of(soup, '[data-testid="qsp-price-change-percent"]'),
    }

text_of 辅助函数查询一个元素并返回其去除空白的文本,或在元素不存在时返回 None,因此省略某个字段的代码不会中断循环。这些选择器直接来自 Yahoo 的行情头部:titlediv.hdr h1 标题读取公司名称和代码,price 从标记为 qsp-price 的实时价格节点读取,两个涨跌幅选择器分别读取相对于它的绝对变动(qsp-price-change)和百分比变动(qsp-price-change-percent)。

第三步:解析统计数据表

在头部下方,Yahoo Finance 渲染了一个包含前收盘价、市值、成交量、日内价格区间等的小型统计数据块。每个指标都位于带有 data-field 属性的列表项中,因此你通过字段名而不是脆弱的位置来读取值节点。即使 Yahoo 对网格进行重新排序,这种解析方式也能保持稳定。

python
STAT_FIELDS = {
    "previous_close": "regularMarketPreviousClose",
    "market_cap": "marketCap",
    "volume": "regularMarketVolume",
    "day_range": "regularMarketDayRange",
}

def scrape_stats(soup):
    stats = {}
    for key, field in STAT_FIELDS.items():
        stats[key] = text_of(
            soup, f'li[data-field="{field}"] span.value, li[data-field="{field}"] fin-streamer'
        )
    return stats

STAT_FIELDS 映射将每个输出名称关联到 Yahoo 的内部字段名。Yahoo 将实时值包裹在 fin-streamer 元素中,将静态值包裹在 span.value 中,因此选择器会同时尝试两者,并获取存在的那个。要查找某个指标的确切字段名,在浏览器中打开行情页面,右键单击值,并读取其列表项上的 data-field 属性。日内价格区间以类似 168.49 - 171.05 的单个字符串返回,如果需要独立的最低价和最高价,可以在后面用连字符进行分割。

选择器会漂移

Yahoo Finance 会定期修改其行情标记,生成的类名也会随之改变。这里使用的 data-testiddata-field 属性比类名更稳定,但将每个选择器视为起始模板而非合同仍然是正确的态度。当某个字段返回 None 时,在浏览器的开发者工具中重新检查实时页面并更新选择器。定期维护是任何生产级爬虫的正常工作,不是出了问题的信号。

第四步:组装完整脚本

现在将各部分连接成一个可运行的脚本:循环遍历股票代码列表,用小型重试包装器获取每个渲染后的行情页面,将头部字段和统计数据解析到单个记录中,并将记录导出为 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,
}

STAT_FIELDS = {
    "previous_close": "regularMarketPreviousClose",
    "market_cap": "marketCap",
    "volume": "regularMarketVolume",
    "day_range": "regularMarketDayRange",
}

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(soup, selector):
    el = soup.select_one(selector)
    return el.get_text(strip=True) if el else None

def scrape_quote(html, symbol):
    soup = BeautifulSoup(html, "html.parser")
    record = {
        "symbol": symbol,
        "title": text_of(soup, "div.hdr h1"),
        "price": text_of(soup, '.livePrice[data-testid="qsp-price"]'),
        "change": text_of(soup, '.priceChange[data-testid="qsp-price-change"]'),
        "change_percent": text_of(soup, '[data-testid="qsp-price-change-percent"]'),
    }
    for key, field in STAT_FIELDS.items():
        record[key] = text_of(
            soup, f'li[data-field="{field}"] span.value, li[data-field="{field}"] fin-streamer'
        )
    return record

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

def main():
    symbols = ["AAPL", "TSLA", "BTC-USD"]
    records = []
    for symbol in symbols:
        url = f"https://finance.yahoo.com/quote/{symbol}"
        html = fetch_html(url)
        if html:
            records.append(scrape_quote(html, symbol))
        time.sleep(2)

    save_outputs(records)
    print(f"Saved {len(records)} quotes")

if __name__ == "__main__":
    main()

脚本循环遍历股票代码列表,用重试包装器获取每个行情页面,将头部字段和统计数据合并到一条记录中,并用两秒钟的睡眠对循环进行节流。save_outputs 使用第一条记录的键作为表头,同时写入一个 JSON 文件和一个 CSV 文件,让你的下游工具获得它需要的格式。在 symbols 列表中添加或删除代码,以适应你自己的自选股列表。

输出结果示例

python yahoo_scraper.py 运行完整脚本,你将得到每个代码对应的干净结构化记录,可直接用于分析、入库或导入电子表格。下面的数值仅供参考,实时数据每个交易日都会变化。

json
[
  {
    "symbol": "AAPL",
    "title": "Apple Inc. (AAPL)",
    "price": "168.99",
    "change": "-3.70",
    "change_percent": "(-2.14%)",
    "previous_close": "172.69",
    "market_cap": "2.61T",
    "volume": "54,318,920",
    "day_range": "168.49 - 171.05"
  },
  {
    "symbol": "TSLA",
    "title": "Tesla, Inc. (TSLA)",
    "price": "156.90",
    "change": "-4.58",
    "change_percent": "(-2.84%)",
    "previous_close": "161.48",
    "market_cap": "499.81B",
    "volume": "112,045,300",
    "day_range": "155.41 - 160.39"
  }
]

对应的 CSV 包含相同的列,每个代码一行,可以直接放入 pandas 或任何电子表格,用于绘制自选股列表图表或比较各代码的当日涨跌情况。

扩展到更多股票代码并保持不被封锁

即使渲染问题已解决,Yahoo 仍会监控具有爬虫特征的流量。以下几个习惯有助于保持较长时间的运行顺畅,适用于任何难度较高的商业目标。

  • 控制请求频率。在紧密循环中高频请求行情页面是被限速或受到挑战最快的方式。上面的两秒钟睡眠是下限,而非上限;对于更大的自选股列表,适当扩大间隔,并变换代码而不是以全速爬取同一路径。
  • 依赖 IP 轮换。住宅 IP 池将请求分散到众多真实用户地址,确保没有单一地址触发速率限制。Crawling API 为你处理这一切;如果你自建技术栈,这是最需要做好的部分。
  • 关注状态码。如果运行中开始出现非 200 的 pc_status 值,说明当前的请求频率或 IP 层级已经不够用了。将其视为回退的信号,而不是可以忽略的噪声。

对于大型自选股列表,异步 Crawler 可以排队请求并将结果传递到 webhook,适用于获取数百个行情页面而无需保持连接打开。更完整的方法论请参见如何在不被封锁的情况下抓取网站大规模金融数据抓取的注意事项。相同的解析模式可以迁移到其他市场数据来源,例如从 CoinMarketCap 抓取加密货币价格

抓取 Yahoo Finance 是否合法?

抓取 Yahoo Finance 是否被允许,取决于 Yahoo 的服务条款、你所在的司法管辖区以及你对数据的用途。Yahoo 的条款限制自动化访问和批量数据收集,因此无论你的工具多么谨慎,抓取行为都可能违反这些条款。行情页面上显示的数据是事实性市场数据而非个人数据,隐私风险较低,但这并不意味着你不受网站条款的约束。本文中的代码不会改变这些事实;它只是让技术部分得以实现。请阅读 Yahoo Finance 的服务条款及其 robots.txt,并将两者视为数据收集的边界。

金融数据更难突破的限制是授权问题,而不是隐私问题。Yahoo 显示的价格、市值和成交量并非 Yahoo 自己的读数:它们来自交易所和第三方市场数据提供商,这些数据流携带各自的重新分发限制。从公开页面获取一个数字,并不赋予你发布、转售或在其上构建商业产品的权利。坚守公开行情和统计数据页面,将请求量控制在不会给 Yahoo 服务器造成压力的范围内,不要抓取任何登录后、付费墙后或 Yahoo Finance Plus 等高级层级后的内容。

本指南特意限定在公开市场数据范围内,因为这是使工作具有可辩护性的边界。对于超出轻量研究、临时分析或个人自选股追踪的任何需求,正确的途径是获取授权数据流:Yahoo 及其数据合作伙伴,以及专业的市场数据供应商和交易所 API,都为生产用途提供符合条款的官方访问。如果你在权衡各种选择,我们的全球最佳金融数据提供商综述是一个好的起点。对于商业或大规模使用,获取授权数据流才是正确途径,而不是更复杂的爬虫。

回顾

核心要点

  • Yahoo Finance 是客户端渲染的。普通请求返回带有占位符值的薄外壳,因此必须先渲染页面才能解析价格和统计数据。
  • 渲染和受信任的 IP 缺一不可。带 JS token 的 Crawling API 在一次调用中完成两者;ajax_waitpage_wait 控制等待实时数据的时长。
  • 通过稳定属性进行解析。data-testid 值(如 qsp-price)读取头部数据,从 data-field 名称(如 marketCap)读取统计数据,这比类名能更好地应对标记变化。
  • 循环并导出。遍历股票代码列表,用短暂睡眠控制运行节奏,将记录写入 JSON 和 CSV,让数据可以直接导入 pandas 或电子表格。
  • 注意授权问题。这些数据来自有各自重新分发条款的交易所和数据提供商;坚守公开页面,任何商业或大规模使用都应使用授权数据流。

常见问题

为什么普通请求从 Yahoo Finance 返回不了价格?

因为 Yahoo 用 JavaScript 在客户端加载其实时价格、涨跌幅和统计数据表。初始 HTML 只是一个外壳,只有页面的脚本在浏览器中运行后才会填充内容,因此原始 HTTP 请求返回状态码 200,但价格节点是占位符或空值。要获取真实数据,必须先渲染页面,这正是 Crawling API 的 JS token 为你处理的事情。

抓取 Yahoo Finance 需要普通 token 还是 JS token?

需要 JS token。普通 token 获取静态 HTML,在 Yahoo 行情页面上与普通请求返回的薄外壳相同。JS token 在将 HTML 返回之前先在真实浏览器中渲染页面,因此 BeautifulSoup 解析时价格、涨跌幅和统计数据是存在的。

从 Yahoo Finance 行情页面可以提取哪些字段?

页面上公开的市场数据:最新价格、相对前收盘价的绝对和百分比涨跌幅、前收盘价本身、市值、成交量和日内价格区间。这些是事实性的行情和统计字段,不是个人数据。坚守公开行情页面,避免访问登录后或高级层级的内容。

我的选择器返回 None。是什么变了?

很可能是 Yahoo 的标记发生了变化。该网站会定期修改其行情布局,生成的类名也会随之改变。这里使用的 data-testid 属性(qsp-priceqsp-price-change)和 data-field 名称(marketCapregularMarketVolume)比类名更稳定,但它们也可能发生变化。在浏览器的开发者工具中重新检查实时页面并更新选择器;定期维护是任何生产级爬虫的正常工作。

如何在不被封锁的情况下抓取多个股票代码?

循环遍历代码列表,保持请求之间的短暂睡眠,并让 Crawling API 轮换住宅 IP,确保没有单一地址触发速率限制。对于大型自选股列表,转移到异步 Crawler,它排队请求并将结果发布到 webhook,而不是保持连接打开。关注 pc_status 响应头,如果开始返回非 200 值则及时退让。

我可以将抓取的 Yahoo Finance 数据用于商业目的吗?

将其视为法律问题,而不是技术问题。Yahoo 上的价格和统计数据来自有各自重新分发条款的交易所和授权市场数据提供商,Yahoo 自身的服务条款也限制自动化收集和重用。对于商业或大规模使用,正确的途径是授权市场数据流或官方 API,而不是爬虫。在基于这些数据构建产品之前,请仔细审查条款并寻求法律建议。

开始构建

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

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

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