AKShare 新浪财报踩坑实战:字段大面积缺失,切换 Tushare 彻底解决(附完整源码)
一、前言做 A 股多因子基本面回测需要批量提取单季度财务数据滚动计算 TTM 指标扣非净利润 TTM、营收 TTM、经营现金流 TTM 等。最初选用 AKShare 新浪季度财报接口批量抓取几十只标的运行后发现大量核心财务字段全为 0调试日志定位 3 个致命缺陷新浪季度利润表无原生扣非净利润、归母净利润字段只能用营业利润临时替代数据失真银行、保险类个股资产负债表权益列名特殊通用匹配列表匹配失败equity整列归零绝大多数消费、制造、新能源个股现金流量表不披露「分配股利支付现金」分红 TTM 无有效值。本文提供两套完整可运行代码基于 AKShare 的临时兼容修复方案扩充银行权益列名营业利润 TTM 替代扣非根治方案Tushare 标准化财报抓取原生齐全财务字段无缺失、无需字段适配。二、AKShare 新浪财报原生存在的问题2.1 核心盈利字段缺失新浪季度利润表仅返回营业收入、营业利润没有量化回测必备的扣非净利润、归母净利润导出 Excel 中deduct_profit_ttm全部为 0。2.2 金融行业权益列名不兼容银行资产负债表权益表头为「归属于本行股东权益合计」通用匹配列表无法命中银行股equity列全 0无法计算 PB 估值。2.3 分红现金流披露不全非金融企业季度现金流表不披露分红支出dividend_ttm恒为 0无法统计分红指标。2.4 运行日志报错示例【600036.SH 资产负债表字段匹配】 ✅ 匹配成功|候选:股东权益 原始列:股东权益 【调试统计600036.SH】 equity非空行数0【600519.SH 现金流量表字段匹配】 ❌ 全部候选无匹配[分配股利、利润支付的现金,分配股利、利润或偿付利息支付的现金]三、临时兼容方案AKShare 改造完整源码3.1 修复思路扩充银行、保险专用权益候选列名优先匹配金融行业资产负债表提取营业利润单季度数据滚动 4 季度求和生成 TTM临时赋值给deduct_profit_ttm统一日期对齐逻辑三张表外连接合并空值填充 0输出标准化 Excel。3.2 完整可运行代码importakshareasakimportpandasaspdimportnumpyasnpimporttimefrompathlibimportPath# 路径配置RAW_FOLDERPath(./finance_ttm)DEBUG_FOLDERPath(./debug_logs)RAW_FOLDER.mkdir(exist_okTrue)DEBUG_FOLDER.mkdir(exist_okTrue)# 股票池stock_codes[600519.SH,600887.SH,600036.SH,000001.SZ,601318.SH]# 扩充银行专用字段匹配列表COL_CONFIG{equity:[归属于本行股东权益合计,本行股东权益总额,本行所有者权益,归属于母公司股东权益合计,股东权益合计,所有者权益合计,归属母公司股东权益,所有者权益总计,股东权益总计,净资产合计,所有者权益(或股东权益)合计,归属于母公司股东的所有者权益合计,股东权益总额,归属母公司所有者权益,净资产,所有者权益净额,股东权益净额,归属于母公司净资产,母公司所有者权益合计,归属母公司净资产合计,股东权益],inventory:[存货,存货净额,库存商品],receivable:[应收账款,应收票据及应收账款,应收账款净额],revenue_single:[营业收入,营业总收入],gross_profit_single:[营业利润,主营业务利润],cfo_single:[经营活动产生的现金流量净额,经营活动现金净流量],dividend_single:[分配股利、利润支付的现金,分配股利、利润或偿付利息支付的现金,分配股利支付的现金,股息红利支付现金,分配股利或利润支付的现金]}defsafe_get_column(df,col_candidates):多候选列名匹配自动去除首尾空格col_map{c.strip():cforcindf.columns}fortargetincol_candidates:t_striptarget.strip()ift_stripincol_map:print(f✅ 匹配成功 | 候选:{target}原始列:{col_map[t_strip]})returndf[col_map[t_strip]].astype(float)print(f❌ 全部候选无匹配{col_candidates})returnpd.Series(np.zeros(len(df)),indexdf.index)defwrite_debug_txt(code,bal_df,inc_df,cash_df):输出三张表原生表头用于后续补充字段symcode.split(.)[0]txt_pathDEBUG_FOLDER/f{sym}_cols.txtwithopen(txt_path,w,encodingutf-8)asf:f.write( 资产负债表列名 \n)f.write(\n.join(bal_df.columns))f.write(\n\n 利润表列名 \n)f.write(\n.join(inc_df.columns))f.write(\n\n 现金流量表列名 \n)f.write(\n.join(cash_df.columns))defcalc_ttm(df,val_col,date_colend_date):滚动4季度TTM计算按财报结束日期升序df_sortdf.sort_values(date_col).reset_index(dropTrue)ttm_list[]foriinrange(len(df_sort)):start_idxmax(0,i-3)ttm_sumdf_sort.loc[start_idx:i,val_col].sum()ttm_list.append(ttm_sum)df_sort[f{val_col}_ttm]ttm_listreturndf_sortdeffetch_single_stock(code):sym,marketcode.split(.)sina_marketshifmarketSHelseszprint(f\n 开始抓取{code})try:# 1. 抓取三大报表bal_dfak.stock_financial_analysis_indicator_em(symbolsym)inc_dfak.stock_profit_sheet_by_yearly_em(symbolsym)cash_dfak.stock_cash_flow_sheet_by_yearly_em(symbolsym)time.sleep(0.8)# 输出原生表头用于调试write_debug_txt(code,bal_df,inc_df,cash_df)# 统一财报日期列 end_datebal_df[end_date]pd.to_datetime(bal_df[报告期])inc_df[end_date]pd.to_datetime(inc_df[报告期])cash_df[end_date]pd.to_datetime(cash_df[报告期])# 提取资产负债表字段bal_rawpd.DataFrame()bal_raw[end_date]bal_df[end_date]bal_raw[equity]safe_get_column(bal_df,COL_CONFIG[equity])bal_raw[inventory]safe_get_column(bal_df,COL_CONFIG[inventory])bal_raw[receivable]safe_get_column(bal_df,COL_CONFIG[receivable])# 提取利润表单季度字段inc_rawpd.DataFrame()inc_raw[end_date]inc_df[end_date]inc_raw[revenue_single]safe_get_column(inc_df,COL_CONFIG[revenue_single])inc_raw[gross_profit_single]safe_get_column(inc_df,COL_CONFIG[gross_profit_single])# 提取现金流单季度字段cash_rawpd.DataFrame()cash_raw[end_date]cash_df[end_date]cash_raw[cfo_single]safe_get_column(cash_df,COL_CONFIG[cfo_single])cash_raw[dividend_single]safe_get_column(cash_df,COL_CONFIG[dividend_single])# 三表合并merge_1pd.merge(bal_raw,inc_raw,onend_date,howouter)all_dfpd.merge(merge_1,cash_raw,onend_date,howouter)all_dfall_df.sort_values(end_date).reset_index(dropTrue)all_dfall_df.fillna(0.0)# 计算TTM指标all_dfcalc_ttm(all_df,revenue_single)all_dfcalc_ttm(all_df,gross_profit_single)all_dfcalc_ttm(all_df,cfo_single)all_dfcalc_ttm(all_df,dividend_single)# 营业利润TTM临时替代扣非净利润TTMall_df[deduct_profit_ttm]all_df[gross_profit_single_ttm]# 输出调试统计print(f【调试统计{code}】)print(fequity非空行数{(all_df[equity]!0).sum()})print(frevenue_single非空行数{(all_df[revenue_single]!0).sum()})print(f营业利润非空行数{(all_df[gross_profit_single]!0).sum()})print(f{code}合并总行数{len(all_df)}有效行数{len(all_df)})# 输出Excelout_cols[end_date,equity,inventory,receivable,revenue_single_ttm,deduct_profit_ttm,cfo_single_ttm,dividend_single_ttm]save_dfall_df[out_cols].copy()save_pathRAW_FOLDER/f{sym}_fin.xlsxsave_df.to_excel(save_path,indexFalse)print(f✅{code}财报已保存至{save_path})exceptExceptionase:print(f❌{code}抓取失败{str(e)})if__name____main__:forstockinstock_codes:fetch_single_stock(stock)print(\n 全部财报抓取执行完毕 )3.3 修复后效果白酒、制造、医药、券商标的equity全部匹配成功无归零银行股新增专用权益表头部分银行可正常读取净资产deduct_profit_ttm不再全 0由营业利润滚动 TTM 填充满足临时回测需求完整输出 Excel 标准化表格和日线行情表可按end_date关联。3.4 方案局限性deduct_profit_ttm是营业利润替代并非真实扣非净利润多因子回测存在偏差大部分非银个股分红现金流无数据dividend_ttm恒为 0每日 PB、PE、总市值需要额外拼接日线数据新浪无每日估值指标接口。四、根治方案切换 Tushare 标准化财报抓取4.1 Tushare 核心优势完美解决全部痛点利润表income接口原生返回扣非净利润、归母净利润无需替代资产负债表balancesheet字段标准化银行、券商、制造业表头统一不存在匹配失败现金流量表cashflow完整披露分配股利支付现金分红指标真实有效daily_basic日线基础指标直接返回每日pe_ttm、pb、market_cap无需自行计算数据权威、字段齐全适合严谨基本面量化回测。PS:Tushare 极简抓取示例代码importtushareastsimportpandasaspd# 1. 配置Tushare token个人中心复制TOKEN你的Tushare Tokenprots.pro_api(TOKEN)defget_stock_finance(ts_code,start_year20100101):# 1. 资产负债表bal_dfpro.balancesheet(ts_codets_code,start_datestart_year,report_type1)bal_dfbal_df[[end_date,total_equity,inventory,accounts_receiv]]bal_df.rename(columns{total_equity:equity,accounts_receiv:receivable},inplaceTrue)# 2. 利润表原生扣非净利润inc_dfpro.income(ts_codets_code,start_datestart_year,report_type1)inc_dfinc_df[[end_date,revenue,operate_profit,deduct_profit]]inc_df.rename(columns{revenue:revenue_single},inplaceTrue)# 3. 现金流量表cash_dfpro.cashflow(ts_codets_code,start_datestart_year,report_type1)cash_dfcash_df[[end_date,n_cashflow_operate,pay_div]]cash_df.rename(columns{n_cashflow_operate:cfo_single,pay_div:dividend_single},inplaceTrue)# 三表合并merge_1pd.merge(bal_df,inc_df,onend_date,howouter)all_dfpd.merge(merge_1,cash_df,onend_date,howouter)all_df[end_date]pd.to_datetime(all_df[end_date])all_dfall_df.sort_values(end_date).reset_index(dropTrue)all_dfall_df.fillna(0)# 复用前文calc_ttm函数计算滚动指标defcalc_ttm(df,val_col,date_colend_date):df_sortdf.sort_values(date_col).reset_index(dropTrue)ttm_list[]foriinrange(len(df_sort)):start_idxmax(0,i-3)ttm_sumdf_sort.loc[start_idx:i,val_col].sum()ttm_list.append(ttm_sum)df_sort[f{val_col}_ttm]ttm_listreturndf_sort all_dfcalc_ttm(all_df,revenue_single)all_dfcalc_ttm(all_df,deduct_profit)all_dfcalc_ttm(all_df,cfo_single)all_dfcalc_ttm(all_df,dividend_single)all_df[deduct_profit_ttm]all_df[deduct_profit_ttm]returnall_df# 调用示例if__name____main__:test_code600519.SHfin_dataget_stock_finance(test_code)print(fin_data.head())fin_data.to_excel(tushare_finance.xlsx,indexFalse)