前两篇讲了为什么自建和技术选型。 本文直接上代码,实测对比四个数据源。
1. 快速上手
python
# === AKShare ===
pip install akshare
import akshare as ak
df = ak.stock_zh_a_hist(symbol='000001', period='daily', adjust='qfq')
# 返回:日期、开盘、收盘、最高、最低、成交量、成交额、振幅、涨跌幅、涨跌额、换手率
# === Tushare ===
pip install tushare
import tushare as ts
ts.set_token('your_token_here')
pro = ts.pro_api()
df = pro.daily(ts_code='000001.SZ', start_date='20240101', end_date='20241231')
# 返回:ts_code, trade_date, open, high, low, close, pre_close, change, pct_chg, vol, amount
# === yfinance ===
pip install yfinance
import yfinance as yf
df = yf.Ticker('000001.SZ').history(period='1y')
# 返回:Open, High, Low, Close, Volume, Dividends, Stock Splits
# === Baostock ===
pip install baostock
import baostock as bs
bs.login()
rs = bs.query_history_k_data_plus('sz.000001',
'date,open,high,low,close,volume', start_date='2024-01-01', end_date='2024-12-31')
df = rs.get_data()
bs.logout()
2. 数据质量实测
以平安银行 000001 为样本,对比 2025-01-02 日线数据:
python
import akshare as ak
import tushare as ts
import yfinance as yf
import baostock as bs
# 基准数据(东方财富网页端手工验证)
benchmark = {'open': 11.25, 'high': 11.42, 'low': 11.02, 'close': 11.38}
def test_accuracy(name, data):
errors = {}
for field in ['open', 'high', 'low', 'close']:
diff = abs(data[field] - benchmark[field])
errors[field] = f'{diff:.2f}'
return errors
# AKShare
ak_df = ak.stock_zh_a_hist(symbol='000001', period='daily', start_date='20250102', end_date='20250102')
ak_data = {'open': ak_df['开盘'].iloc[0], 'high': ak_df['最高'].iloc[0],
'low': ak_df['最低'].iloc[0], 'close': ak_df['收盘'].iloc[0]}
print('AKShare:', test_accuracy('AKShare', ak_data))
# 输出:误差全为 0.00
# yfinance
yf_df = yf.Ticker('000001.SZ').history(start='2025-01-02', end='2025-01-03')
yf_data = {'open': yf_df['Open'].iloc[0], 'high': yf_df['High'].iloc[0],
'low': yf_df['Low'].iloc[0], 'close': yf_df['Close'].iloc[0]}
print('yfinance:', test_accuracy('yfinance', yf_data))
# 输出:±0.01-0.02 偏差(Yahoo 使用自有数据源)
结论:AKShare、Tushare、Baostock 与东方财富一致。yfinance 有 ±0.01 的系统偏差。
3. 更新时效
python
from datetime import datetime
import akshare as ak
# 检测当日数据是否已更新
today = datetime.now().strftime('%Y%m%d')
df = ak.stock_zh_a_hist(symbol='000001', period='daily', start_date=today, end_date=today)
if len(df) > 0:
print(f'✅ {today} 日线已更新')
else:
print(f'⏳ {today} 日线尚未更新(通常在15:30后)')
| 数据源 | 当日可用时间 | 盘中数据 |
|---|---|---|
| AKShare | 15:30 | ✅ 有实时接口 |
| Tushare | 16:00+ | ❌ 免费版 |
| yfinance | 18:00+ | ❌ |
| Baostock | 次日上午 | ❌ |
4. AKShare 防封 IP 方案
python
import time
import random
import akshare as ak
import psycopg2
from datetime import datetime, timedelta
def safe_fetch(symbol, max_retries=3):
"""带退避重试的安全拉取"""
for attempt in range(max_retries):
try:
df = ak.stock_zh_a_hist(
symbol=symbol, period='daily',
start_date='20200101', end_date=datetime.now().strftime('%Y%m%d'),
adjust='qfq' # 前复权
)
return df
except Exception as e:
wait = random.uniform(3, 8) * (attempt + 1)
print(f'⚠️ {symbol} 失败 (attempt {attempt+1}): {e}')
print(f' 等待 {wait:.0f} 秒重试...')
time.sleep(wait)
print(f'❌ {symbol} 3次重试全部失败,跳过')
return None
def fetch_all_with_throttle(stock_list):
"""全量拉取 + 限速 + 本地缓存"""
conn = psycopg2.connect('postgresql://trader:xxx@localhost:5432/quant')
cur = conn.cursor()
for i, symbol in enumerate(stock_list):
# 1. 检查本地是否已有今日数据
cur.execute("""
SELECT 1 FROM kline_daily
WHERE symbol = %s AND trade_date = CURRENT_DATE
""", (symbol,))
if cur.fetchone():
print(f'[{i+1}/{len(stock_list)}] {symbol} ✅ 已有今日数据,跳过')
continue
# 2. 拉取 + 入库
df = safe_fetch(symbol)
if df is not None:
for _, row in df.iterrows():
cur.execute("""
INSERT INTO kline_daily (symbol, trade_date, open, high, low, close, volume)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (symbol, trade_date) DO UPDATE SET
open=EXCLUDED.open, high=EXCLUDED.high, low=EXCLUDED.low,
close=EXCLUDED.close, volume=EXCLUDED.volume
""", (symbol, row['日期'], row['开盘'], row['最高'], row['最低'], row['收盘'], row['成交量']))
conn.commit()
print(f'[{i+1}/{len(stock_list)}] {symbol} ✅ 拉取成功')
# 3. 限速:随机间隔 0.5-1.5 秒
time.sleep(random.uniform(0.5, 1.5))
cur.close()
conn.close()
print('🎉 全量拉取完成')
# 使用
stock_list = ['000001', '000002', '000333', '600519', '601318'] # 示例
fetch_all_with_throttle(stock_list)
5. yfinance 美股 + A 股混用
python
import yfinance as yf
# 美股
aapl = yf.Ticker('AAPL')
print(f"AAPL 市值: ${aapl.info['marketCap']:,}")
print(f"AAPL PE: {aapl.info['trailingPE']:.1f}")
# A 股(深交所 .SZ,上交所 .SS)
midea = yf.Ticker('000333.SZ')
print(f"\n美的集团最新价: ¥{midea.history(period='1d')['Close'].iloc[-1]:.2f}")
# 港股
tencent = yf.Ticker('0700.HK')
print(f"腾讯最新价: HK${tencent.history(period='1d')['Close'].iloc[-1]:.2f}")
# 加密货币
btc = yf.Ticker('BTC-USD')
print(f"比特币: ${btc.history(period='1d')['Close'].iloc[-1]:,.0f}")
6. 综合评分
| AKShare | Tushare | yfinance | Baostock | |
|---|---|---|---|---|
| 数据覆盖 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| A股质量 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 稳定性 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 入门门槛 | 不用注册 | 要实名 | 不用注册 | 不用注册 |
| 代码量 | 中 | 中 | 少 | 少 |
7. 我的推荐
python
# 生产环境(A股):Tushare 主力 + AKShare 补充
import tushare as ts # 日线、复权、财务
import akshare as ak # 资金流向、龙虎榜
# 美股:yfinance 一把梭
import yfinance as yf # 全搞定
# 新手入门:AKShare + yfinance
import akshare as ak # A股
import yfinance as yf # 美股 + 全球
# 两个都不用注册,5分钟跑起来