第二章:数据获取与清洗——用Tushare/Akshare搞定财报数据

做量化投资,数据就是你的弹药。没有干净的数据,再牛的模型也是白搭。

这一章,我带你走一遍数据获取和清洗的全流程。说白了,就是教你怎么从Tushare和Akshare这两个工具里拿到财报数据,然后把它收拾得服服帖帖。

2.1 数据源的选择:Tushare vs Akshare

先说说这两个工具。我个人习惯是两者搭配着用。

特性 Tushare Akshare
数据质量 高,经过校验 中等,偶尔有异常
获取方式 需要token,有积分限制 免费,无需注册
更新频率 实时更新 日频更新
适用场景 正式策略、回测 快速验证、研究

嗯,这里要注意:Tushare的免费额度有限,我建议你先把Akshare用熟了,再上Tushare。我在项目中遇到过,有人一上来就买Tushare的付费套餐,结果发现数据用不上,白白浪费钱。

2.2 用Akshare获取财报数据

Akshare的接口很直观。你想想看,它就是把网页上的数据直接扒下来给你。

import akshare as ak

# 获取利润表
profit_df = ak.stock_profit_sheet_by_report_em(symbol="600519")
print(profit_df.head())

# 获取资产负债表
balance_df = ak.stock_balance_sheet_by_report_em(symbol="600519")
print(balance_df.head())

# 获取现金流量表
cashflow_df = ak.stock_cash_flow_sheet_by_report_em(symbol="600519")
print(cashflow_df.head())

你看,三行代码就把茅台的三张表都拿下来了。但有个坑——Akshare返回的字段名是中文的,而且不同接口的命名风格不统一。我建议你拿到数据后,先做个字段映射。

我的小技巧: 写一个统一的字段映射字典,把中文名转成英文名。比如"营业收入"映射成"revenue","净利润"映射成"net_profit"。这样后面做因子计算时,代码会清爽很多。

2.3 用Tushare获取财报数据

Tushare的接口更规范,但需要先注册获取token。我个人习惯把token存在环境变量里,而不是硬编码在代码中。

import tushare as ts

# 设置token
ts.set_token('your_token_here')
pro = ts.pro_api()

# 获取利润表
profit_df = pro.income(ts_code='600519.SH', start_date='20200101', end_date='20231231')
print(profit_df.head())

# 获取资产负债表
balance_df = pro.balancesheet(ts_code='600519.SH', start_date='20200101', end_date='20231231')
print(balance_df.head())

# 获取现金流量表
cashflow_df = pro.cashflow(ts_code='600519.SH', start_date='20200101', end_date='20231231')
print(cashflow_df.head())

Tushare返回的数据字段是英文的,而且命名很规范。比如"营业收入"对应"revenue","净利润"对应"n_income"。这一点比Akshare省心不少。

注意: Tushare的免费用户每天只能调用200次接口。如果你要获取全市场5000多只股票的数据,建议分批获取,或者升级到付费套餐。我曾经因为没注意这个限制,跑了一整天的脚本,结果只拿到了一半数据。

2.4 数据缺失值处理

拿到数据后,你会发现很多缺失值。为什么会这样?原因很多:有的公司刚上市,历史数据不全;有的公司财报披露延迟;还有的字段本身就是可选的。

我处理缺失值的思路是这样的:

  1. 先看缺失比例:如果某个字段缺失超过50%,直接扔掉。
  2. 再看缺失模式:是随机缺失,还是系统性缺失?
  3. 最后决定填充方式:均值填充、中位数填充,还是用行业均值?
import pandas as pd
import numpy as np

# 假设df是合并后的财报数据
def handle_missing_values(df):
    # 1. 删除缺失超过50%的列
    threshold = len(df) * 0.5
    df = df.dropna(thresh=threshold, axis=1)
    
    # 2. 对数值型字段,用行业均值填充
    for col in df.select_dtypes(include=[np.number]).columns:
        df[col] = df.groupby('industry')[col].transform(lambda x: x.fillna(x.mean()))
    
    # 3. 对类别型字段,用众数填充
    for col in df.select_dtypes(include=['object']).columns:
        df[col] = df[col].fillna(df[col].mode()[0])
    
    return df

# 处理缺失值
df_clean = handle_missing_values(df)
避坑指南: 我曾经用全局均值填充,结果发现金融行业的市盈率被非金融行业拉低了。后来改用行业均值填充,效果好了很多。记住:不同行业的财务指标差异很大,一定要分组处理。

2.5 行业分类标准化

行业分类是个大问题。不同数据源用的分类标准不一样:Tushare用申万行业,Akshare用证监会行业,还有的用GICS行业分类。

我建议统一用申万一级行业分类。为什么?因为它是国内最常用的,而且很多因子研究都是基于这个分类做的。

# 行业分类映射
industry_map = {
    '银行': '银行',
    '证券': '非银金融',
    '保险': '非银金融',
    '房地产开发': '房地产',
    '汽车整车': '汽车',
    '半导体': '电子',
    '白酒': '食品饮料',
    '医药商业': '医药生物',
    # ... 更多映射
}

def standardize_industry(df, industry_col='industry'):
    df['industry_std'] = df[industry_col].map(industry_map)
    return df

# 应用行业标准化
df_clean = standardize_industry(df_clean)

你想想看,如果不做标准化,你在做行业中性化处理时,就会遇到"银行"和"商业银行"被当成两个行业的问题。这会导致因子计算出现偏差。

注意: 行业分类映射不是一劳永逸的。每年都会有新的行业出现,比如"新能源"、"人工智能"这些。我建议你定期更新映射表,至少每半年检查一次。

2.6 数据清洗的完整流程

好了,我们把上面这些步骤串起来,形成一个完整的数据清洗流程。

def data_pipeline(start_date, end_date):
    # 1. 获取股票列表
    stock_list = get_stock_list()
    
    # 2. 获取财报数据
    financial_data = []
    for stock in stock_list:
        try:
            profit = ak.stock_profit_sheet_by_report_em(symbol=stock)
            balance = ak.stock_balance_sheet_by_report_em(symbol=stock)
            cashflow = ak.stock_cash_flow_sheet_by_report_em(symbol=stock)
            
            # 合并三张表
            merged = pd.concat([profit, balance, cashflow], axis=1)
            financial_data.append(merged)
        except:
            continue
    
    # 3. 合并所有股票数据
    df = pd.concat(financial_data, ignore_index=True)
    
    # 4. 字段映射
    df = map_fields(df)
    
    # 5. 处理缺失值
    df = handle_missing_values(df)
    
    # 6. 行业标准化
    df = standardize_industry(df)
    
    # 7. 去重
    df = df.drop_duplicates()
    
    return df

# 执行数据清洗
df_clean = data_pipeline('20200101', '20231231')

这个流程看起来简单,但实际跑起来会遇到各种问题。比如网络超时、数据格式不一致、字段名变化等等。我建议你加上异常处理和日志记录,方便排查问题。

核心要点:
  • 数据获取:Tushare和Akshare搭配使用,取长补短
  • 缺失值处理:先看比例,再看模式,最后决定填充方式
  • 行业标准化:统一用申万一级行业分类,定期更新映射表
  • 完整流程:获取→合并→映射→清洗→标准化→去重
数据获取与清洗流程图 数据获取 Tushare / Akshare 数据合并 三张表合并 字段映射 中文→英文 缺失值处理 行业均值填充 行业标准化 申万一级分类 去重 删除重复行 输出 干净 数据

这张图把整个流程串起来了。你从数据获取开始,经过合并、映射、清洗、标准化、去重,最后得到干净的数据。每一步都很关键,缺一不可。

好了,数据获取和清洗就讲到这里。记住:数据质量决定了你的策略上限。花80%的时间在数据上,剩下的20%做模型,这才是量化投资的正确姿势。

专注资料整理