""" 龙虎榜采集器 主方案:AkShare stock_lhb_detail_daily_sina(已实测可用,含上榜原因) 备选:东财 datacenter 接口(报表名不确定,暂时注释备用) """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) import akshare as ak import pandas as pd from datetime import datetime, timedelta from src.storage.db import upsert_df, upsert_log, get_last_fetch def fetch_lhb_date(trade_date: str) -> pd.DataFrame: """ 获取单日龙虎榜 trade_date: "YYYY-MM-DD" 或 "YYYYMMDD" """ # 统一为 YYYYMMDD 格式 ds = trade_date.replace("-", "") try: df = ak.stock_lhb_detail_daily_sina(date=ds) except Exception as e: print(f" [WARN] LHB Sina failed for {trade_date}: {e}") return pd.DataFrame() if df is None or df.empty: return pd.DataFrame() # Sina 返回列(实测):序号, 股票代码, 股票名称, 收盘价, 涨跌幅, 成交额, 成交额, 上榜原因 # 编码问题导致中文列名显示乱码,用位置索引映射 # 0=序号, 1=代码, 2=名称, 3=收盘, 4=涨跌幅, 5=成交额, 6=??? 7=上榜原因 df.columns = ["no", "ts_code", "name", "close", "pct_change", "amount", "amount2", "reason"] df["trade_date"] = trade_date.replace("-", "") # 格式化为 YYYY-MM-DD df["trade_date"] = pd.to_datetime(df["trade_date"]).dt.strftime("%Y-%m-%d") # 保留有用字段 keep = ["trade_date", "ts_code", "name", "close", "pct_change", "amount", "reason"] df = df[[c for c in keep if c in df.columns]] return df def fetch_lhb_range(start_date: str, end_date: str = None) -> pd.DataFrame: """抓取日期区间的龙虎榜(逐日)""" end_date = end_date or datetime.today().strftime("%Y-%m-%d") all_days = [] cur = datetime.strptime(start_date.replace("-", ""), "%Y%m%d") end = datetime.strptime(end_date.replace("-", ""), "%Y%m%d") while cur <= end: ds = cur.strftime("%Y-%m-%d") df = fetch_lhb_date(ds) if df is not None and not df.empty: all_days.append(df) cur += timedelta(days=1) if all_days: return pd.concat(all_days, ignore_index=True) return pd.DataFrame() def incremental_lhb(source: str = "lhb_daily") -> pd.DataFrame: """增量采集:只抓上次之后的新交易日""" last = get_last_fetch(source) start = last.get("last_date") or (datetime.today() - timedelta(days=7)).strftime("%Y-%m-%d") end = datetime.today().strftime("%Y-%m-%d") df = fetch_lhb_range(start, end) if df is not None and not df.empty: upsert_df(df, "lhb_daily") upsert_log(source, param="", last_date=end) return df if __name__ == "__main__": import fire fire.Fire({ "date": lambda d: upsert_df(fetch_lhb_date(d), "lhb_daily"), "range": lambda s, e=None: upsert_df(fetch_lhb_range(s, e), "lhb_daily"), "incr": lambda: incremental_lhb(), })