更新BK缓存策略

This commit is contained in:
Sakurasan
2026-07-08 00:33:15 +08:00
parent 1a50f7680a
commit 015aeb989a
3 changed files with 55 additions and 22 deletions
+41 -16
View File
@@ -318,14 +318,14 @@ def _get_sector_session() -> AsyncSession:
global _sector_session global _sector_session
if _sector_session is None: if _sector_session is None:
_sector_session = AsyncSession( _sector_session = AsyncSession(
impersonate="chrome120", impersonate="chrome131",
headers={ headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Referer": "https://data.eastmoney.com/bkzj/hy.html", "Referer": "https://data.eastmoney.com/bkzj/hy.html",
"Accept": "*/*", "Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9", "Accept-Language": "zh-CN,zh;q=0.9",
}, },
timeout=10, timeout=5,
) )
return _sector_session return _sector_session
@@ -370,7 +370,7 @@ async def fetch_sector_list(sector_type: str) -> list[dict]:
set_cache(cache_key, json.dumps(push2_data, ensure_ascii=False), ttl_hours=ttl) set_cache(cache_key, json.dumps(push2_data, ensure_ascii=False), ttl_hours=ttl)
return push2_data return push2_data
# push2 失败,用 akshare # push2 失败,用 akshare(不写磁盘缓存)
akshare_data = await akshare_task akshare_data = await akshare_task
if akshare_data: if akshare_data:
_sector_cache[sector_type] = (akshare_data, now) _sector_cache[sector_type] = (akshare_data, now)
@@ -385,14 +385,14 @@ async def _fetch_push2(sector_type: str) -> list[dict]:
session = _get_sector_session() session = _get_sector_session()
ut = await get_em_ut() ut = await get_em_ut()
url = (
f"https://push2.eastmoney.com/api/qt/clist/get"
f"?fs={fs}&fields={SECTOR_FIELDS}"
f"&fid=f62&po=1&pz=500&pn=1&np=1&fltt=2"
f"&invt=2&ut={ut}"
)
for attempt in range(2): for attempt in range(2):
url = (
f"https://push2.eastmoney.com/api/qt/clist/get"
f"?fs={fs}&fields={SECTOR_FIELDS}"
f"&fid=f62&po=1&pz=500&pn=1&np=1&fltt=2"
f"&invt=2&ut={ut}"
)
try: try:
resp = await session.get(url) resp = await session.get(url)
if resp.status_code != 200: if resp.status_code != 200:
@@ -432,6 +432,11 @@ async def _fetch_push2(sector_type: str) -> list[dict]:
if "disconnect" in err.lower() or "refused" in err.lower() or attempt == 1: if "disconnect" in err.lower() or "refused" in err.lower() or attempt == 1:
await get_em_ut(force_refresh=True) await get_em_ut(force_refresh=True)
ut = _em_ut ut = _em_ut
# 先尝试更新现有会话的 headers
try:
session.headers.update({"Referer": "https://data.eastmoney.com/bkzj/hy.html"})
except Exception:
pass
# 重建会话(TLS 指纹可能会被缓存) # 重建会话(TLS 指纹可能会被缓存)
global _sector_session global _sector_session
_sector_session = None _sector_session = None
@@ -445,10 +450,12 @@ import akshare as ak
async def _fetch_akshare(sector_type: str) -> list[dict]: async def _fetch_akshare(sector_type: str) -> list[dict]:
"""akshare 降级方案(同花顺数据源)""" """akshare 降级方案(东方财富数据源)"""
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
code_map_key = f"board_codes:{sector_type}"
def _get(): def _build_code_map():
"""获取板块代码映射(HTTP 较慢,结果单独缓存 24h)"""
code_map = {} code_map = {}
try: try:
if sector_type == "industry": if sector_type == "industry":
@@ -469,16 +476,34 @@ async def _fetch_akshare(sector_type: str) -> list[dict]:
code_map[str(r.get("name", ""))] = str(r.get("code", "")) code_map[str(r.get("name", ""))] = str(r.get("code", ""))
except Exception: except Exception:
pass pass
return code_map
def _get_fund_flow():
if sector_type == "industry": if sector_type == "industry":
df = ak.stock_fund_flow_industry() return ak.stock_fund_flow_industry()
else: else:
df = ak.stock_fund_flow_concept() return ak.stock_fund_flow_concept()
return code_map, df # 1. 尝试从缓存读取 code_map
code_map = {}
cached_map = get_cache(code_map_key)
if cached_map is not None:
code_map = json.loads(cached_map)
try: try:
code_map, df = await loop.run_in_executor(None, _get) if code_map:
# 已有缓存,只需获取资金流
df = await loop.run_in_executor(None, _get_fund_flow)
else:
# 首次:code_map + 资金流并发获取
map_data, df = await asyncio.gather(
loop.run_in_executor(None, _build_code_map),
loop.run_in_executor(None, _get_fund_flow),
)
if map_data:
code_map = map_data
set_cache(code_map_key, json.dumps(code_map, ensure_ascii=False), ttl_hours=24)
if df is None or df.empty: if df is None or df.empty:
return [] return []
df = df.sort_values("净额", ascending=False) df = df.sort_values("净额", ascending=False)
+2 -2
View File
@@ -458,12 +458,12 @@ export interface SectorResponse {
* 获取东方财富板块列表(按主力净流入排序) * 获取东方财富板块列表(按主力净流入排序)
* @param type industry=行业板块, concept=概念板块 * @param type industry=行业板块, concept=概念板块
*/ */
export async function fetchSectors(type: SectorType): Promise<SectorItem[]> { export async function fetchSectors(type: SectorType, signal?: AbortSignal): Promise<SectorItem[]> {
const baseUrl = getApiBaseUrl(); const baseUrl = getApiBaseUrl();
const url = `${baseUrl}/api/sectors?type=${type}`; const url = `${baseUrl}/api/sectors?type=${type}`;
try { try {
const resp = await fetch(url, { method: "GET" }); const resp = await fetch(url, { method: "GET", signal });
if (!resp.ok) return []; if (!resp.ok) return [];
const result: SectorResponse = await resp.json(); const result: SectorResponse = await resp.json();
return result.data || []; return result.data || [];
+12 -4
View File
@@ -1,5 +1,5 @@
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
import { useState, useEffect, useMemo } from "react"; import { useState, useEffect, useMemo, useRef } from "react";
import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api"; import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { ArrowLeft, TrendingUp, TrendingDown, RefreshCw } from "lucide-react"; import { ArrowLeft, TrendingUp, TrendingDown, RefreshCw } from "lucide-react";
@@ -21,17 +21,25 @@ function SectorsPage() {
const [data, setData] = useState<SectorItem[]>([]); const [data, setData] = useState<SectorItem[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [sort, setSort] = useState<SortMode>("mainNetInflow"); const [sort, setSort] = useState<SortMode>("mainNetInflow");
const abortRef = useRef<AbortController | null>(null);
const loadData = (t: SectorType) => { const loadData = (t: SectorType) => {
// 取消上次未完成的请求
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
setLoading(true); setLoading(true);
fetchSectors(t).then((items) => { fetchSectors(t, controller.signal).then((items) => {
setData(items); if (!controller.signal.aborted) {
setLoading(false); setData(items);
setLoading(false);
}
}); });
}; };
useEffect(() => { useEffect(() => {
loadData(tab); loadData(tab);
return () => abortRef.current?.abort();
}, [tab]); }, [tab]);
const sorted = useMemo(() => { const sorted = useMemo(() => {