用Python抓取Yahoo Finance:行情报价与历史数据
2026年9月30日 · 教程 · 15 分钟阅读
核心结论
如果目标是复现Yahoo Finance网页上显示的历史数据表,应使用Selenium等待JavaScript渲染完成;如果更关注研究用的结构化行情数据,可以使用yfinance。无论选择哪条路径,都要同时记录股票代码、交易日期、币种、采样频率、Close与Adj Close的含义以及获取时间。遇到HTTP 429时应停止继续重试,优先降低请求频率并遵守站点规则。
Yahoo Finance把实时报价、历史价格、公司行动和图表分布在不同页面区域。可靠的Yahoo Finance数据抓取不是从页面上随手抄一个数字,而是先确认字段定义,再把页面内容转换为可核验的结构化记录。本文以Apple(AAPL)为例,介绍页面准备、Python环境、Selenium抓表格、yfinance获取数据、CSV导出和常见故障处理。
本文只讨论公开可见数据和正常浏览器访问。自动化前请确认数据授权、Yahoo适用条款、robots规则和请求频率。Yahoo服务条款对未经授权的自动访问和数据提取设有限制,能在浏览器中打开页面并不代表可以无限量采集。可先查看Yahoo服务条款。
抓取前先读懂Yahoo Finance页面
打开https://finance.yahoo.com/quote/AAPL/后,页面顶部可能同时显示正常交易时段价格和盘后价格。采集时应保存对应标签、交易状态和时间戳,不要把屏幕上最大的数字直接当作“实时价格”。文章中的截图是特定时间的页面记录,不代表当前行情;使用时应以标签和截图时间为准。
报价页需要确认的字段
| 字段 | 页面含义 | 抓取时的注意事项 |
|---|---|---|
| Price | 当前或最近一次正常交易时段报价 | 与币种、交易所和页面时间一起保存 |
| Open、High、Low、Close | 当日开盘、最高、最低和收盘价 | 检查Low不高于Open与Close,High不低于两者 |
| Adj Close | 按拆股、股息或资本分配调整后的收盘价 | 长期收益和回测通常更适合使用它 |
| Volume | 当日成交量 | 保存为整数;缺失值不要自动改成0 |
| Dividends、Splits | 股息和拆股事件 | 事件行结构不同,应与价格表分开保存 |
进入Historical Data后,表格通常包含Date、Open、High、Low、Close、Adj Close和Volume。截图显示的是3个月、Daily频率的示例,页面数据会随交易时段持续更新,因此应同时记录抓取时间和市场状态。

Close和Adj Close有什么区别?
Close是该交易日未经后续股息影响调整的收盘价;Adj Close会把拆股、股息等资本分配因素纳入调整。短期监控可以保存Close,长期回测或收益计算则要明确选择Adj Close,并把选择写入输出元数据。两者不能在同一条时间序列中混用。
设置Yahoo Finance历史数据页面
1.选择日期范围
在Historical Data页面点击日期范围,可选择1M、3M、6M、YTD、1Y、5Y或Max,也可以输入开始和结束日期后点击Done。第一次运行建议先用1M或3M,加载更快,也便于人工核对。

2.选择采样频率
点击Daily可切换到Weekly或Monthly。Daily适合逐日分析,Weekly和Monthly能减少记录量,但它们不是从日线数据中随便抽一行;应把页面选择的频率作为数据源信息保存。

3.选择合适的采集方式
Yahoo Finance页面依赖JavaScript动态渲染,简单的requests.get()可能拿不到历史表格,也可能直接收到429。遇到429不要立即循环重试:若响应包含Retry-After,至少等待指定时间;没有该字段时,停止任务并执行由运维人员设定的冷却时间。常见选择如下:
1.需要复现网页当前显示内容时,用Selenium打开真实浏览器并等待表格出现。
2.需要研究型结构化数据时,用yfinance,但要明确它是独立开源项目,不是Yahoo官方SDK。
3.已经手动保存渲染后的页面时,可离线解析快照,避免继续请求站点。
| 需求 | 推荐方式 | 选择理由 |
|---|---|---|
| 复现网页当前显示的历史表格 | Selenium | 能读取浏览器渲染后的表格和页面字段 |
| 获取研究用结构化行情 | yfinance | 调用更简单,便于后续分析和批量整理 |
| 解析已经保存的页面 | 离线解析 | 减少对Yahoo Finance的再次请求 |
准备Python环境
建议为项目创建独立虚拟环境,并安装示例直接使用的依赖:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install selenium pandas yfinance requests
Windows PowerShell命令如下:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install selenium pandas yfinance requests
用下面的代码确认环境版本:
import sys
import pandas as pd
import requests
import selenium
import yfinance as yf
print("Python:", sys.version.split()[0])
print("pandas:", pd.__version__)
print("requests:", requests.__version__)
print("selenium:", selenium.__version__)
print("yfinance:", yf.__version__)
为了便于复现,还应记录操作系统、浏览器版本、Python和依赖版本、股票代码、日期范围、频率、代理类型(如使用)、命令、行数以及脱敏后的输出。Selenium示例可保存为scrape_yahoo.py,在已激活的环境中运行python scrape_yahoo.py。成功标准是价格表非空、日期不重复,并且事件表包含Date、Symbol和Event列。现代Selenium通常可通过Selenium Manager寻找兼容的Chrome驱动;企业环境若禁止自动下载,应由管理员预装驱动并指定路径。
用Selenium抓取Yahoo Finance历史价格
下面的程序打开AAPL历史数据页,等待同时包含Date和Adj Close表头的表格出现,再逐行解析价格和公司行动。它根据表头语义定位表格,而不是依赖容易变化的CSS类名;遇到空表、列数异常或OHLC关系不合理时会明确报错。
from datetime import datetime, timezone
import math
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
URL = "https://finance.yahoo.com/quote/AAPL/history/"
SYMBOL = "AAPL"
DATE_RANGE = "REPLACE_WITH_CAPTURED_DATE_RANGE"
FREQUENCY = "REPLACE_WITH_CAPTURED_FREQUENCY"
CURRENCY = "REPLACE_WITH_CAPTURED_CURRENCY"
COLUMNS = ["Date", "Open", "High", "Low", "Close", "Adj Close", "Volume"]
def parse_number(text):
value = text.strip().replace(",", "")
if value in {"", "-", "--", "N/A"}:
return None
number = float(value)
if not math.isfinite(number):
raise ValueError(f"Invalid number: {text}")
return number
options = webdriver.ChromeOptions()
options.add_argument("--lang=en-US")
driver = None
try:
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(30)
driver.get(URL)
wait = WebDriverWait(driver, 30)
table = wait.until(EC.presence_of_element_located((
By.XPATH,
"//table[.//th[contains(normalize-space(.),'Date')] "
"and .//th[contains(normalize-space(.),'Adj Close')]]"
)))
prices, events = [], []
for row in table.find_elements(By.CSS_SELECTOR, "tbody tr"):
cells = [c.text.strip() for c in row.find_elements(By.CSS_SELECTOR, "td")]
if not cells:
continue
date = datetime.strptime(cells[0], "%b %d, %Y").date().isoformat()
if len(cells) == 2:
events.append({"Date": date, "Symbol": SYMBOL, "Event": cells[1]})
continue
if len(cells) != 7:
raise ValueError(f"Unexpected row with {len(cells)} cells")
values = [parse_number(value) for value in cells[1:]]
op, high, low, close, adj_close, volume = values
if all(v is not None for v in (op, high, low, close)):
if not low <= min(op, close) <= max(op, close) <= high:
raise ValueError(f"OHLC check failed on {date}")
prices.append(dict(zip(COLUMNS, [date] + values)))
data = pd.DataFrame(prices, columns=COLUMNS)
if data.empty or data["Date"].duplicated().any():
raise ValueError("Empty data or duplicate dates")
data["Volume"] = data["Volume"].astype("Int64")
data = data.sort_values("Date").reset_index(drop=True)
event_data = pd.DataFrame(events, columns=["Date", "Symbol", "Event"])
metadata = {
"symbol": SYMBOL, "source": URL, "currency": CURRENCY,
"date_range": DATE_RANGE, "frequency": FREQUENCY,
"retrieved_at": datetime.now(timezone.utc).isoformat(),
}
except (TimeoutException, WebDriverException) as exc:
raise RuntimeError("Yahoo Finance未加载出可用的历史数据表,请检查同意弹窗、访问策略和页面结构") from exc
finally:
if driver is not None:
driver.quit()
print(data.tail())
print(event_data)
print(metadata)
脚本只读取当前已渲染的表格,不会自动改变页面的日期范围或频率。运行前应手动选择这些控件,并把页面上实际看到的值填入DATE_RANGE、FREQUENCY和CURRENCY。
如何区分价格行和事件行?
普通价格行应有7个单元格,依次对应Date、Open、High、Low、Close、Adj Close和Volume。股息或拆股行通常只有日期和事件描述,应放入独立的event_data,不要硬塞进价格表。parse_number()会去除千位分隔符,把缺失标记转成None,不会把未知值伪装成0。解析完成后检查日期重复、空结果和基本OHLC关系,可及时发现页面改版、半加载或解析错误。
用yfinance获取结构化数据
yfinance适合个人研究和分析,调用方式比浏览器自动化简单。其官方文档说明该项目与Yahoo没有隶属关系,也未获Yahoo背书,并提示Yahoo Finance接口面向个人使用。使用前仍需确认自己的授权和用途。
from datetime import datetime, timezone
import yfinance as yf
ticker = yf.Ticker("AAPL")
try:
history = ticker.history(
period="3mo", interval="1d", auto_adjust=False,
actions=True, timeout=20,
)
except Exception as exc:
if "429" in str(exc) or "rate limit" in str(exc).lower():
raise RuntimeError("Yahoo Finance触发限流,请停止请求并执行规定的冷却时间") from exc
raise
if history.empty:
raise RuntimeError("Yahoo Finance没有返回历史记录")
required = {"Open", "High", "Low", "Close", "Adj Close", "Volume"}
missing = required.difference(history.columns)
if missing:
raise RuntimeError(f"缺少字段: {sorted(missing)}")
print(history.tail())
metadata = {
"symbol": "AAPL", "source": "yfinance", "date_range": "3mo",
"frequency": "1d", "adjustment": "auto_adjust=False",
"retrieved_at": datetime.now(timezone.utc).isoformat(),
}
print(metadata)
这里显式设置auto_adjust=False,因此同时保留Close和Adj Close;开启自动调整后,字段结构和价格含义都会变化。当前yfinance源码仍兼容raise_errors参数,但已将其标记为deprecated,因此示例不再使用该参数;发布前应按锁定的yfinance版本验证异常处理行为。按日期请求时,结束日期通常是排他的,应检查实际返回的最后一个交易日。币种也不能从股票代码猜测,应从返回的元数据或来源页面读取后再用于比较和导出。
yfinance遇到429怎么办?
429表示请求频率或当前网络出口受到限制,并不代表Python安装失败。应停止重试,记录状态码和时间;如果原始响应提供Retry-After,至少等待该时间,否则执行运维人员设定的冷却时间。随后检查是否有其他程序共用同一出口,减少股票数量和日期范围,增加缓存与合理间隔,并重新确认使用条款。不要使用无限循环、验证码绕过或高频切换身份来掩盖错误。
导出可用Excel打开的文件
CSV便于审计,Excel可以直接打开。utf-8-sig能减少非英文环境下的编码问题:
import json
from pathlib import Path
output = Path("output")
output.mkdir(exist_ok=True)
data.to_csv(output / "AAPL-history.csv", index=False, encoding="utf-8-sig")
event_data.to_csv(output / "AAPL-events.csv", index=False, encoding="utf-8-sig")
if "metadata" not in globals():
raise RuntimeError("请先运行一条采集路径以生成metadata")
with (output / "AAPL-metadata.json").open("w", encoding="utf-8") as handle:
json.dump(metadata, handle, ensure_ascii=False, indent=2)
check = pd.read_csv(output / "AAPL-history.csv")
if len(check) != len(data):
raise RuntimeError("导出校验失败")
print(check.tail())
在Excel中选择“数据→从文本/CSV”,打开AAPL-history.csv,确认编码为UTF-8、分隔符为逗号后加载。导入后抽查首行、末行、Volume是否为整数、Date是否被错误转换,以及Close和Adj Close是否仍是两个独立字段。
受控采集中的代理网络配置
代理只能改变网络出口、地区和连接稳定性,不能替代数据授权,也不能保证目标站点始终返回成功。需要固定出口、可重复验证和较长会话时,可以了解静态住宅代理;需要按国家或城市进行合规的公开数据采集时,可以了解住宅代理。如果要了解公开数据采集场景中的网络出口选择,可参考网络爬虫代理。配置Python请求时,可参考Python代理接入和代理参数。

Requests示例应从环境变量读取凭据,并对用户名和密码进行URL编码,绝不能把真实凭据写进文章、截图或代码仓库:
import os
from urllib.parse import quote
import requests
host = os.environ["ROLA_PROXY_HOST"]
port = os.environ["ROLA_PROXY_PORT"]
user = quote(os.environ["ROLA_PROXY_USER"], safe="")
password = quote(os.environ["ROLA_PROXY_PASSWORD"], safe="")
proxy = f"http://{user}:{password}@{host}:{port}"
session = requests.Session()
session.trust_env = False
session.proxies.update({"http": proxy, "https": proxy})
response = session.get("https://ipinfo.io/json", timeout=20)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After", "未提供")
raise RuntimeError(f"代理检查触发429,请停止并遵守Retry-After: {retry_after}")
if response.status_code in {401, 403, 407}:
raise RuntimeError(f"代理检查失败,HTTP {response.status_code},请核对授权和配置")
response.raise_for_status()
print(response.json().get("country"))
先用IP检查接口确认协议、认证方式和地区,再运行数据采集程序。SOCKS5需要客户端具备相应支持并使用已授权的端点。如果Yahoo返回401、403或429,应保留响应、遵守Retry-After并调查原因,不要把换IP当作无限重试机制。金融数据强调可复现性,固定出口通常比每次请求随机切换更便于审计。
代理检查接口返回HTTP 200,只能说明代理出口检查成功,不代表Yahoo Finance一定返回了完整业务数据。排查时应区分:401通常与授权或会话有关;403通常与访问策略或权限有关;407表示代理认证失败;429表示目标站限流;DNS错误和连接超时则优先检查网络、端口与代理连通性。
常见问题与排查
页面能打开,但代码找不到历史表格
常见原因是JavaScript尚未完成渲染,或定位器已失效。先在可见浏览器中确认Historical Data表格确实出现,再检查XPath是否仍能找到包含Date和Adj Close的表头。不要一开始就启用无头模式。
只抓到几十条记录
页面可能只渲染了当前日期范围,也可能需要滚动加载。先缩小范围并检查行数,再决定是否滚动;每次滚动后等待行数稳定,并按日期去重。不了解页面上限时,不能把当前行数当成完整历史数据集。
Download按钮不能直接下载CSV
下载入口和权限可能因地区、账号或产品方案而变化。截图中Download旁有锁定标记,因此不能把它描述成所有用户都可免费使用的固定方式。可改用页面表格、已授权接口或符合用途的yfinance流程。
页面顶部数字与历史表格不一致
两处数据可能属于不同更新时间或交易时段。先比较日期、正常交易/盘后标签、币种、调整设置和刷新时间,再判断是否真的存在错误。
如何区分Yahoo Finance抓取中的HTTP错误?
先按状态码定位问题:401多与授权或会话有关,403多与访问策略或权限有关,407表示代理认证失败,429表示请求频率受到限制。DNS错误和连接超时不属于HTTP状态码,应检查解析、端口、代理协议和网络连通性。针对429,应停止循环重试,读取Retry-After(如有),并执行冷却、降频和缓存策略。
总结
高质量的Yahoo Finance数据抓取,关键不在于写出一个选择器,而在于保留字段含义、交易时段、调整规则和数据来源。先确认Price、Close、Adj Close、Volume及事件行,再根据目标选择Selenium或yfinance;对空结果、页面结构变化和429做显式处理,最后分别导出价格、公司行动和采集元数据。需要固定出口或地区验证时,可以把Rola IP作为合规网络基础设施,但仍必须遵守数据授权和Yahoo Finance的适用规则。