You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
73 lines
2.2 KiB
73 lines
2.2 KiB
"""东方财富 CHOICE API 管理层讨论与分析(MD&A)抓取工具。
|
|
- 输入: 股票代码列表 (如 ['603233.SH'])
|
|
- 输出: pandas DataFrame (年报/中报的董事会报告索引, 含标题与页码)
|
|
- 不需要登录, 不需要 cookie, 无文件落盘
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pandas as pd
|
|
|
|
from utils._emchoice import fetch_report
|
|
|
|
|
|
_REPORT_NAME = "RPT_BUSINESSANALYSIS"
|
|
# 列清单沿用 docs/api_docs.md 抓包值
|
|
_COLUMNS = (
|
|
"ORG_CODE,SECUCODE,SECURITY_CODE,SECURITY_INNER_CODE,SECURITY_NAME_ABBR,"
|
|
"REPORT_DATE,REPORT_NAME,NOTICE_DATE,SOURCE,RELINFOCODE,TITLE,PAGE"
|
|
)
|
|
# 只取年报/中报的讨论与分析
|
|
_REPORT_NAMES = '"年报","中报"'
|
|
|
|
|
|
def _build_params(
|
|
secucode: str,
|
|
start_date: str,
|
|
end_date: str,
|
|
) -> dict[str, str]:
|
|
"""构造 RPT_BUSINESSANALYSIS 查询参数."""
|
|
return {
|
|
"source": "CHOICE",
|
|
"reportName": _REPORT_NAME,
|
|
"columns": _COLUMNS,
|
|
"quoteColumns": "",
|
|
"filter": (
|
|
f'(SECUCODE="{secucode}")(REPORT_NAME in ({_REPORT_NAMES}))'
|
|
f"(REPORT_DATE<='{end_date}')(REPORT_DATE>='{start_date}')"
|
|
),
|
|
"pageNumber": "1",
|
|
"pageSize": "50",
|
|
"sortTypes": "-1",
|
|
"sortColumns": "REPORT_DATE",
|
|
"client": "SW",
|
|
}
|
|
|
|
|
|
def _fetch_one(secucode: str, years: int) -> pd.DataFrame:
|
|
"""拉一只股票的管理层讨论与分析."""
|
|
today = pd.Timestamp.today()
|
|
start = (today - pd.DateOffset(years=years)).strftime("%Y-%m-%d")
|
|
end = today.strftime("%Y-%m-%d")
|
|
|
|
rows = fetch_report(_build_params(secucode, start, end))
|
|
df = pd.DataFrame(rows)
|
|
if not df.empty:
|
|
df["SECUCODE"] = secucode
|
|
return df
|
|
|
|
|
|
def get_mda(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_one(code, years)
|
|
if not df.empty:
|
|
frames.append(df)
|
|
|
|
if not frames:
|
|
return pd.DataFrame()
|
|
return pd.concat(frames, ignore_index=True)
|
|
|