Files
etf_ws/get_etf_data.py
2026-10-21 19:16:29 +08:00

174 lines
5.7 KiB
Python

# -*- coding: utf-8 -*-
import akshare as ak
from datetime import datetime, timedelta
import json
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time
# 缓存文件路径
CACHE_FILE = "./fund_cache.json"
# 缓存保留天数
CACHE_DAYS = 10
# 基金代码
FUND_CODE = "510300"
# 网络请求重试配置
RETRY_TIMES = 3 # 重试次数
RETRY_DELAY = 2 # 重试间隔(秒)
TIMEOUT = 10 # 请求超时时间(秒)
# ---------------------- 网络请求配置 ----------------------
def get_retry_session():
"""创建带重试机制的请求会话"""
session = requests.Session()
retry = Retry(
total=RETRY_TIMES,
backoff_factor=RETRY_DELAY,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
# ---------------------- 缓存管理函数 ----------------------
def load_cache():
"""加载缓存文件"""
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def save_cache(cache_data):
"""保存缓存到文件"""
with open(CACHE_FILE, "w", encoding="utf-8") as f:
json.dump(cache_data, f, ensure_ascii=False, indent=2)
def clean_expired_cache(cache_data):
"""清理超过 10 天的缓存"""
cutoff_date = (datetime.now() - timedelta(days=CACHE_DAYS)).strftime("%Y-%m-%d")
expired_keys = [k for k in cache_data.keys() if k < cutoff_date]
for key in expired_keys:
del cache_data[key]
return cache_data
def get_cache_key(date_str):
"""生成缓存键(只保留日期部分)"""
return date_str[:10] if len(date_str) >= 10 else date_str
# ---------------------- 数据获取函数 ----------------------
def fetch_fund_data(fund_code):
"""从 AKShare 获取基金实时数据(增加重试和超时)"""
# 替换 akshare 的默认请求会话,增加重试
ak.session = get_retry_session()
for attempt in range(RETRY_TIMES):
try:
# 设置超时时间,避免无限等待
etf_spot_df = ak.fund_etf_spot_em(timeout=TIMEOUT)
fund_data = etf_spot_df[etf_spot_df["代码"] == fund_code].iloc[0]
# 获取数据日期
data_date = fund_data.get("更新时间", fund_data.get("日期", "未知"))
# 原始数据(单位:元、份)
turnover_yuan = fund_data["成交额"]
latest_share = fund_data["最新份额"]
# 换算为亿元、亿份
turnover_yi = turnover_yuan / 100000000
share_yi = latest_share / 100000000
return {
"data_date": str(data_date),
"turnover_yi": turnover_yi,
"share_yi": share_yi
}
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
if attempt < RETRY_TIMES - 1:
print(f"⚠️ 网络请求失败(第 {attempt+1} 次),{RETRY_DELAY} 秒后重试... 错误:{e}")
time.sleep(RETRY_DELAY)
continue
raise # 最后一次失败则抛出异常
except Exception as e:
raise # 其他错误直接抛出
def get_cached_or_fetch(fund_code, cache_data):
"""获取缓存数据或从网络获取"""
today_key = get_cache_key(datetime.now().strftime("%Y-%m-%d"))
if today_key in cache_data:
print(f"📂 使用缓存数据:{today_key}")
return cache_data[today_key], True
print(f"🌐 获取网络数据...")
data = fetch_fund_data(fund_code)
today_key = get_cache_key(data["data_date"])
# 保存到缓存(保留两位小数)
cache_data[today_key] = {
"data_date": data["data_date"],
"turnover_yi": round(data["turnover_yi"], 2),
"share_yi": round(data["share_yi"], 2),
"cached_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
save_cache(cache_data)
print(f"💾 已保存缓存:{today_key}")
return cache_data[today_key], False
# ---------------------- 数据显示函数 ----------------------
def print_fund_info(fund_code, data):
"""打印基金信息"""
print("=" * 60)
print(f"📊 基金:{fund_code} | 查询时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
print(f"📅 数据日期:{data['data_date']}")
print(f"✅ 当日成交额:{data['turnover_yi']:,.2f} 亿元")
print(f"✅ 最新总份额:{data['share_yi']:,.2f} 亿份")
print("=" * 60)
# ---------------------- 主函数 ----------------------
def get_etf_data():
"""主程序入口"""
# 加载并清理缓存
cache_data = load_cache()
cache_data = clean_expired_cache(cache_data)
try:
# 获取数据(缓存或网络)
data, is_cached = get_cached_or_fetch(FUND_CODE, cache_data)
# 打印结果
print_fund_info(FUND_CODE, data)
except Exception as e:
print(f"❌ 数据获取失败:{e}")
# 如果有缓存,尝试使用最近的缓存数据
if cache_data:
latest_date = max(cache_data.keys())
latest_data = cache_data[latest_date]
print(f"\n🔧 尝试使用最近缓存({latest_date})的数据:")
print_fund_info(FUND_CODE, latest_data)
finally:
# 清理过期缓存并保存
cache_data = clean_expired_cache(cache_data)
save_cache(cache_data)
# ---------------------- 程序入口 ----------------------
if __name__ == "__main__":
get_etf_data()