V1
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user