"""东方财富 CHOICE API 分红数据抓取工具 (分红明细 + 分红统计)。 - 输入: 股票代码列表 (如 ['603233.SH']) - 输出: pandas DataFrame (按年度的分红明细 / 分红统计) - 不需要登录, 不需要 cookie, 无文件落盘 """ from __future__ import annotations import pandas as pd from utils._emchoice import fetch_report _DETAIL_REPORT_NAME = "RPT_HSF9_ASSIGNPLAN_DETAIL" # 列清单沿用 docs/api_docs.md 抓包值 _DETAIL_COLUMNS = ( "SECUCODE,SECURITY_CODE,SECURITY_NAME_ABBR,REPORT_TYPE,ISSUE_YEAR," "IMPL_PLAN_PROFILE,ASSIGN_PROGRESS,ASSIGN_OBJECT,PRETAX_BONUS_RMB," "AFTERTAX_BONUS_RMB,BONUS_RATIO,IT_RATIO,CASHAMT,DIVSHAREBASE," "DAT_YAGGR,GMDECISION_NOTICE_DATE,EQUITY_RECORD_DATE,EX_DIVIDEND_DATE," "PAY_CASH_DATE,EQUITY_BASEDATE,NOTICE_DATE,NOTICE_ADDRES,INT_YEAR" ) _STATICS_REPORT_NAME = "RPT_HSF9_ASSIGNPLAN_STATICS" _STATICS_COLUMNS = ( "SECURITY_NAME_ABBR,SECURITY_CODE,SECUCODE,SECURITY_NAME,YEAR," "AUALACCMDIV_ARD,PARENTNETPROFIT,SFCFJCXG,NDLJMGGLSQ,CLOSE_PRICE," "GXL,SUM_AUALACCMDIV_ARD,SUM_PARENTNETPROFIT,ZFL" ) def _year_range(years: int) -> tuple[int, int]: """按年份数算出 (起始年, 结束年), 结束年 = 当前年.""" end_year = pd.Timestamp.today().year return end_year - years + 1, end_year def _build_detail_params( secucode: str, start_year: int, end_year: int, ) -> dict[str, str]: return { "reportName": _DETAIL_REPORT_NAME, "columns": _DETAIL_COLUMNS, "quoteColumns": "", "filter": f'(SECUCODE="{secucode}")(INT_YEAR>={start_year})(INT_YEAR<={end_year})', "pageNumber": "", "pageSize": "", "sortTypes": "-1,-1", "sortColumns": "INT_YEAR,NOTICE_DATE", "source": "CHOICE", "client": "SW", } def _build_statics_params( secucode: str, start_year: int, end_year: int, ) -> dict[str, str]: return { "reportName": _STATICS_REPORT_NAME, "columns": _STATICS_COLUMNS, "quoteColumns": "", "filter": f'(SECUCODE="{secucode}")(YEAR>={start_year})(YEAR<={end_year})', "pageNumber": "", "pageSize": "", "sortTypes": "-1", "sortColumns": "YEAR", "source": "CHOICE", "client": "SW", } def _fetch_detail(secucode: str, years: int) -> pd.DataFrame: """拉一只股票的分红明细.""" start_year, end_year = _year_range(years) rows = fetch_report(_build_detail_params(secucode, start_year, end_year)) df = pd.DataFrame(rows) if not df.empty: df["SECUCODE"] = secucode return df def _fetch_statics(secucode: str, years: int) -> pd.DataFrame: """拉一只股票的分红统计.""" start_year, end_year = _year_range(years) rows = fetch_report(_build_statics_params(secucode, start_year, end_year)) df = pd.DataFrame(rows) if not df.empty: df["SECUCODE"] = secucode return df def get_dividend_detail(secucodes: list[str], years: int = 5) -> pd.DataFrame: """抓取指定股票的分红明细 (按年度) 并按股票合并。""" codes = [code.strip() for code in secucodes if code.strip()] if not codes: return pd.DataFrame() frames: list[pd.DataFrame] = [] for code in codes: df = _fetch_detail(code, years) if not df.empty: frames.append(df) if not frames: return pd.DataFrame() return pd.concat(frames, ignore_index=True) def get_dividend_statics(secucodes: list[str], years: int = 5) -> pd.DataFrame: """抓取指定股票的分红统计 (按年度) 并按股票合并。""" codes = [code.strip() for code in secucodes if code.strip()] if not codes: return pd.DataFrame() frames: list[pd.DataFrame] = [] for code in codes: df = _fetch_statics(code, years) if not df.empty: frames.append(df) if not frames: return pd.DataFrame() return pd.concat(frames, ignore_index=True)