4. 数据清洗与标准化:金融文本清洗、实体识别与质量校验
金融数据,说白了就是「金矿里的沙子」。
我做了这么多年金融知识库,见过太多团队花80%的时间在洗数据上。为什么?因为金融文本太「脏」了。公告里夹杂着乱码,财报里数字格式不统一,公司名一会儿全称一会儿简称。嗯,今天我们就来聊聊怎么把这些「沙子」淘成「金子」。
4.1 金融文本清洗:去除噪声,统一格式
我个人习惯把文本清洗分成三个层次:字符级、格式级、语义级。
4.1.1 字符级清洗
金融文本里最常见的噪声是什么?HTML标签、特殊符号、全半角混用。我在项目中遇到过一份年报,里面居然嵌了十几处「 」和「
」标签,直接导致后续的NLP模型跑偏。
import re
def clean_financial_text(text):
# 去除HTML标签
text = re.sub(r'<[^>]+>', '', text)
# 统一全半角
text = text.replace(',', ',').replace('。', '.').replace('(', '(').replace(')', ')')
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
4.1.2 格式级清洗
金融数据里,日期格式是最乱的。2023-01-01、2023/01/01、2023年1月1日、甚至还有「23-01-01」。你想想看,如果入库时不统一,后面做时间序列分析就是灾难。
from datetime import datetime
def standardize_date(date_str):
formats = ['%Y-%m-%d', '%Y/%m/%d', '%Y年%m月%d日', '%y-%m-%d']
for fmt in formats:
try:
return datetime.strptime(date_str, fmt).strftime('%Y-%m-%d')
except:
continue
return None # 实在解析不了,标记为异常
4.2 实体识别与对齐:股票代码、公司名
金融知识库的核心实体就两个:股票代码和公司名。但麻烦的是,同一个公司可能有多个代码(A股、H股),同一个代码在不同语境下可能指代不同公司(比如退市后重新上市)。
4.2.1 股票代码标准化
我建议统一采用「市场代码+6位数字」的格式。比如上证就是SH600000,深证就是SZ000001。为什么?因为这样可以直接对接Wind、Tushare等数据源。
def normalize_stock_code(code, market='SH'):
# 去除前缀中的字母
code = re.sub(r'[A-Za-z]', '', code)
# 补足6位
code = code.zfill(6)
return f'{market}{code}'
核心原则:实体对齐不是简单的字符串匹配,而是「知识图谱对齐」。同一个公司,在A股叫「贵州茅台」,在港股叫「贵州茅台酒股份有限公司」,在英文公告里叫「Kweichow Moutai」。你需要维护一张「实体映射表」。
4.2.2 公司名模糊匹配
纯靠字符串匹配,你会漏掉大量实体。我常用的方法是:先做分词,再提取关键词,最后用编辑距离做模糊匹配。
from fuzzywuzzy import fuzz
def match_company_name(name, entity_list, threshold=80):
best_match = None
best_score = 0
for entity in entity_list:
score = fuzz.token_sort_ratio(name, entity)
if score > best_score:
best_score = score
best_match = entity
return best_match if best_score >= threshold else None
4.3 数据质量校验规则
数据清洗完了,不代表就干净了。你需要一套「质量门禁」来把关。我一般设三道关卡:
| 关卡 | 校验规则 | 示例 |
|---|---|---|
| 第一关:格式校验 | 检查字段类型、长度、格式 | 股票代码必须6位数字,日期必须YYYY-MM-DD |
| 第二关:逻辑校验 | 检查数据间的逻辑关系 | 收盘价不能为负,成交量不能为0 |
| 第三关:业务校验 | 检查是否符合业务常识 | 市盈率超过1000倍需要标记异常 |
def quality_check(row):
errors = []
# 格式校验
if not re.match(r'^\d{6}$', row['stock_code']):
errors.append('股票代码格式错误')
# 逻辑校验
if row['close_price'] <= 0:
errors.append('收盘价不能为负或零')
# 业务校验
if row['pe_ratio'] > 1000:
errors.append('市盈率异常,请人工复核')
return errors
4.4 本章知识体系
下面这张图,是我做知识库时总结的「数据清洗流水线」。你照着这个流程走,基本不会漏掉关键环节。
你看,整个流程其实不复杂。但难在「坚持」。我见过太多团队,刚开始雄心勃勃搞清洗,三个月后就变成「先入库再说,后面再补」。结果呢?后面永远没时间补。
所以我的建议是:把清洗流程做成自动化流水线,每天跑一次。哪怕只清洗10条数据,也比攒了一万条再洗要强。嗯,这就是我做了这么多年知识库,最想跟你分享的一句话。