
背景量化基本面数据采集需求搭建 A 股多因子选股模型需要批量获取个股单季度财务数据计算滚动 TTM 指标营收 TTM、扣非净利润 TTM、经营现金流 TTM、净资产、存货、应收账款等因子。最开始选择 AKShare 新浪免费财报接口批量抓取几十只标的导出 Excel 后发现大量核心财务字段全部为 0无法用于回测。AKShare 新浪财报三大无法根治的缺陷2.1 核心盈利字段缺失无原生扣非净利润新浪季度利润表仅返回营业收入、营业利润量化必备的扣非净利润、归母净利润完全无数据只能用营业利润 TTM 临时替代盈利因子存在天然失真。运行日志示例【600519.SH 现金流量表字段匹配】 ❌ 全部候选无匹配[分配股利、利润支付的现金,分配股利、利润或偿付利息支付的现金]2.2 银行、保险权益表头特殊匹配失败银行资产负债表权益表头为「归属于本行股东权益合计」通用匹配列表无法识别银行股equity净资产整列归零无法计算 PB、ROE 估值因子。【600036.SH 资产负债表字段匹配】 ✅ 匹配成功|候选:股东权益 原始列:股东权益 【调试统计600036.SH】 equity非空行数02.3 非金融企业分红现金流披露空白消费、新能源、制造业季度现金流不披露分红支出dividend_ttm恒等于 0无法构建分红类基本面因子。3. 临时兼容方案AKShare 扩充字段适配源码3.1 优化思路扩充金融行业专用权益候选列名适配银行、保险特殊表头编写通用滚动 TTM 计算函数营业利润 TTM 临时替代扣非净利润三张财报按报告期合并自动输出标准化 Excel附带调试日志保存原始表头。importakshareasakimportpandasaspdimportnumpyasnpimporttimefrompathlibimportPath# 目录初始化out_pathPath(./ak_fin_output)debug_pathPath(./debug_header)out_path.mkdir(exist_okTrue)debug_path.mkdir(exist_okTrue)stock_pool[600519.SH,600036.SH,000001.SZ,601318.SH]# 扩充银行专用字段匹配列表col_config{equity:[归属于本行股东权益合计,本行股东权益总额,归属于母公司股东权益合计,股东权益合计,所有者权益合计,净资产合计,股东权益],inventory:[存货,存货净额],receivable:[应收账款,应收票据及应收账款],rev_q:[营业收入,营业总收入],op_profit_q:[营业利润],cfo_q:[经营活动产生的现金流量净额],div_q:[分配股利、利润或偿付利息支付的现金]}defget_col_data(df,candidates):col_map{c.strip():cforcindf.columns}fornameincandidates:ifname.strip()incol_map:print(f✅ 匹配成功{name}-{col_map[name.strip()]})returndf[col_map[name.strip()]].astype(float)print(f❌ 无匹配字段{candidates})returnpd.Series(np.zeros(len(df)))defcalc_ttm(data,val_col,date_colend_date):datadata.sort_values(date_col).reset_index(dropTrue)ttm_list[]foriinrange(len(data)):startmax(0,i-3)ttm_list.append(data.loc[start:i,val_col].sum())data[f{val_col}_ttm]ttm_listreturndatadeffetch_stock(code):sym,mktcode.split(.)print(f\n 抓取{code})try:balak.stock_financial_analysis_indicator_em(symbolsym)incak.stock_profit_sheet_by_yearly_em(symbolsym)cashak.stock_cash_flow_sheet_by_yearly_em(symbolsym)time.sleep(0.6)# 保存原始表头用于调试withopen(debug_path/f{sym}_cols.txt,w,encodingutf-8)asf:f.write(资产负债表\n\n.join(bal.columns))f.write(\n利润表\n\n.join(inc.columns))f.write(\n现金流表\n\n.join(cash.columns))# 统一日期列bal[end_date]pd.to_datetime(bal[报告期])inc[end_date]pd.to_datetime(inc[报告期])cash[end_date]pd.to_datetime(cash[报告期])bal_dfpd.DataFrame({end_date:bal[end_date]})bal_df[equity]get_col_data(bal,col_config[equity])bal_df[inventory]get_col_data(bal,col_config[inventory])bal_df[receivable]get_col_data(bal,col_config[receivable])inc_dfpd.DataFrame({end_date:inc[end_date]})inc_df[rev_q]get_col_data(inc,col_config[rev_q])inc_df[op_profit_q]get_col_data(inc,col_config[op_profit_q])cash_dfpd.DataFrame({end_date:cash[end_date]})cash_df[cfo_q]get_col_data(cash,col_config[cfo_q])cash_df[div_q]get_col_data(cash,col_config[div_q])# 合并三表merge1pd.merge(bal_df,inc_df,howouter,onend_date)all_dfpd.merge(merge1,cash_df,howouter,onend_date)all_dfall_df.fillna(0).sort_values(end_date)# 计算TTMall_dfcalc_ttm(all_df,rev_q)all_dfcalc_ttm(all_df,op_profit_q)all_dfcalc_ttm(all_df,cfo_q)all_dfcalc_ttm(all_df,div_q)all_df[deduct_profit_ttm]all_df[op_profit_q_ttm]# 输出统计print(fequity非空行数{(all_df[equity]!0).sum()})print(f总数据行数{len(all_df)})all_df.to_excel(out_path/f{sym}_fin.xlsx,indexFalse)print(f✅{code}数据保存完成)exceptExceptionase:print(f抓取失败{e})if__name____main__:forsinstock_pool:fetch_stock(s)print(全部抓取完成)3.3 过渡方案局限性deduct_profit_ttm为营业利润替代不是真实扣非净利润回测存在偏差绝大多数个股分红现金流无数据分红因子无法使用每日 PB、PE、总市值需要额外拼接行情数据无原生接口。4. 根治方案Tushare 标准化财报采集4.1 Tushare 核心优势完美解决全部痛点income利润表接口原生返回扣非净利润、归母净利润无需替代balancesheet资产负债表字段全局标准化银行、制造、消费表头统一不存在匹配失败cashflow现金流完整披露分红支付现金分红指标数据完整daily_basic每日指标直接返回 PE_TTM、PB、总市值省去行情拼接步骤。4.2 Tushare 极简采集代码importtushareastsimportpandasaspd# 填入你的Tushare Tokentoken你的个人tokenprots.pro_api(token)defget_tushare_fin(ts_code):# 资产负债表balpro.balancesheet(ts_codets_code,report_type1)balbal[[end_date,total_equity,inventory,accounts_receiv]]bal.rename(columns{total_equity:equity,accounts_receiv:receivable},inplaceTrue)# 利润表原生扣非净利润incpro.income(ts_codets_code,report_type1)incinc[[end_date,revenue,deduct_profit]]inc.rename(columns{revenue:rev_q},inplaceTrue)# 现金流表cashpro.cashflow(ts_codets_code,report_type1)cashcash[[end_date,n_cashflow_operate,pay_div]]cash.rename(columns{n_cashflow_operate:cfo_q,pay_div:div_q},inplaceTrue)df_mergepd.merge(bal,inc,onend_date,howouter)df_mergepd.merge(df_merge,cash,onend_date,howouter)df_merge[end_date]pd.to_datetime(df_merge[end_date])df_mergedf_merge.fillna(0).sort_values(end_date)# 复用TTM计算函数defcalc_ttm(data,val_col):datadata.sort_values(end_date).reset_index(dropTrue)ttm_list[data.loc[max(0,i-3):i,val_col].sum()foriinrange(len(data))]data[f{val_col}_ttm]ttm_listreturndata df_mergecalc_ttm(df_merge,rev_q)df_mergecalc_ttm(df_merge,deduct_profit)df_mergecalc_ttm(df_merge,cfo_q)df_mergecalc_ttm(df_merge,div_q)returndf_mergeif__name____main__:resget_tushare_fin(600519.SH)res.to_excel(tushare_fin_result.xlsx,indexFalse)print(res.head())