第3章:数据获取与清洗——从公开渠道拿到财报,再用Pandas给它“洗个澡”
做投资分析,最怕什么?
不是行情看不懂,而是数据本身就有问题。
我见过太多人,拿着从网上扒下来的财报数据直接跑模型,结果算出来的ROE、毛利率全是错的。为什么?因为原始数据里夹杂着单位、逗号、空行,甚至还有“—”(表示无数据)。
所以这一章,咱们就聊聊:怎么从公开渠道拿到靠谱的财报数据,以及怎么用Python把它洗干净。
3.1 公开财报数据从哪里拿?
我个人习惯,首选两个渠道:
- 巨潮资讯网(cninfo.com.cn):证监会指定的信息披露网站,所有A股公司的年报、季报PDF都能免费下载。数据最权威,但格式不统一,需要自己解析。
- 东方财富Choice / 同花顺iFinD:有API接口,可以批量拉取结构化数据。适合做量化分析,但部分功能需要付费。
如果你只是做个人研究,我建议先从巨潮资讯网下载PDF,再用Python的pdfplumber或tabula-py提取表格。虽然麻烦点,但数据源可靠。
3.2 数据清洗为什么这么重要?
你想想看,原始财报数据长什么样?
- 数字里带着逗号:
1,234,567.89 - 空值显示为“—”或“--”
- 单位不统一:有的用“万元”,有的用“元”
- 表头重复:每页PDF都有表头,合并时变成重复行
如果不处理这些,你算出来的指标全是错的。我曾经在项目里遇到过,因为没注意到某列数据是“万元”单位,直接当“元”用了,结果算出来的营收差了1万倍……嗯,从那以后我再也不敢跳过数据清洗这一步。
3.3 Pandas入门:清洗财报数据的核心操作
Pandas是Python里处理表格数据的利器。说白了,它就像Excel的“超级加强版”,但能自动化处理成千上万行数据。
3.3.1 读取数据
假设你从巨潮下载了一份CSV格式的财报数据:
import pandas as pd
# 读取CSV文件
df = pd.read_csv('财报数据.csv', encoding='utf-8')
# 看一眼前5行
print(df.head())
这里要注意:编码问题。很多中文CSV文件用的是gbk编码,如果你用utf-8读会报错。我建议先试utf-8,不行就换gbk或gb2312。
3.3.2 处理缺失值
财报里常见的缺失值有“—”、“--”、“None”等。我们需要把它们统一替换成NaN:
# 把常见的缺失值替换为NaN
df.replace(['—', '--', 'None', ''], pd.NA, inplace=True)
# 查看每列的缺失值数量
print(df.isnull().sum())
为什么要替换成NaN?因为Pandas对NaN有专门的处理函数,比如dropna()(删除缺失行)和fillna()(填充缺失值)。
3.3.3 去除逗号和单位
数字里的逗号必须去掉,否则Pandas会把它当成字符串:
# 假设'营业收入'列里有逗号
df['营业收入'] = df['营业收入'].str.replace(',', '')
# 如果列里有“万元”字样,也去掉
df['营业收入'] = df['营业收入'].str.replace('万元', '')
# 转成数值类型
df['营业收入'] = pd.to_numeric(df['营业收入'], errors='coerce')
errors='coerce'的意思是:如果转换失败(比如遇到“—”),就变成NaN。这样不会报错,也不会中断程序。
3.3.4 统一单位
不同年份的财报,单位可能不一样。比如2022年是“万元”,2023年变成了“元”。我们需要统一:
# 假设有一列'单位',值为'万元'或'元'
df.loc[df['单位'] == '万元', '营业收入'] = df['营业收入'] * 10000
# 然后删除'单位'列
df.drop('单位', axis=1, inplace=True)
3.3.5 去除重复行
PDF解析时,经常会把表头行也当成数据行读进来。这些重复行必须删掉:
# 删除完全重复的行
df.drop_duplicates(inplace=True)
# 或者根据某列去重(比如'股票代码'和'年份')
df.drop_duplicates(subset=['股票代码', '年份'], keep='last', inplace=True)
3.4 实战:清洗一份真实的财报数据
下面是一个完整的清洗流程,我拿贵州茅台2023年年报的数据举例:
import pandas as pd
# 1. 读取原始数据
df = pd.read_csv('茅台2023年报.csv', encoding='gbk')
# 2. 替换缺失值
df.replace(['—', '--', 'None', ''], pd.NA, inplace=True)
# 3. 去除数字中的逗号
for col in ['营业收入', '净利润', '总资产']:
df[col] = df[col].astype(str).str.replace(',', '')
df[col] = pd.to_numeric(df[col], errors='coerce')
# 4. 统一单位(假设原始数据是万元)
for col in ['营业收入', '净利润', '总资产']:
df[col] = df[col] * 10000
# 5. 删除重复行
df.drop_duplicates(inplace=True)
# 6. 重置索引
df.reset_index(drop=True, inplace=True)
print(df.info())
print(df.head())
跑完这段代码,你的数据就基本干净了。可以放心地拿去算ROE、毛利率、资产负债率等指标。
3.5 本章知识体系图
下面这张图,帮你理清数据获取与清洗的完整流程:
3.6 避坑指南
最后,分享几个我踩过的坑:
- 编码问题:中文CSV文件,优先试
gbk,不行再试utf-8。我遇到过文件明明是gbk,但部分字符用了utf-8,结果读出来全是乱码。这时候可以用chardet库自动检测编码。 - 单位陷阱:有些财报的“营业收入”列,表头写的是“元”,但实际数字是“万元”。一定要先看几行数据,确认单位后再处理。
- 表头重复:PDF解析时,每页的表头都会被读进来。合并多个PDF时,记得用
drop_duplicates()清理。 - 数据类型:清洗完后,一定要用
df.dtypes检查每列的数据类型。数值列必须是float64或int64,不能是object。