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  # 实在解析不了,标记为异常
避坑指南:我曾经因为没处理「0001-01-01」这种占位日期,导致回测结果全部出错。建议对极端日期(早于1990年或晚于当前日期)做标记,不要直接丢弃。

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
经验之谈:阈值不要设太高。80分是个不错的起点。我曾经设了95分,结果「中国平安」和「中国平安保险」匹配不上,白白浪费了三天人工校对。

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
我曾经踩过的坑:只做格式校验,不做业务校验。结果入库了一批「涨停板」数据——所有股票收盘价都是前一天的1.1倍。嗯,那是数据供应商的测试数据,我居然没发现。

4.4 本章知识体系

下面这张图,是我做知识库时总结的「数据清洗流水线」。你照着这个流程走,基本不会漏掉关键环节。

金融数据清洗与标准化流程 文本清洗 去除噪声 · 统一格式 实体识别与对齐 股票代码 · 公司名 质量校验 格式 · 逻辑 · 业务 字符级 格式级 语义级 代码标准化 公司名模糊匹配 格式校验 逻辑校验 业务校验 输出:标准化、高质量、可追溯的金融数据 知识库入库

你看,整个流程其实不复杂。但难在「坚持」。我见过太多团队,刚开始雄心勃勃搞清洗,三个月后就变成「先入库再说,后面再补」。结果呢?后面永远没时间补。

所以我的建议是:把清洗流程做成自动化流水线,每天跑一次。哪怕只清洗10条数据,也比攒了一万条再洗要强。嗯,这就是我做了这么多年知识库,最想跟你分享的一句话。

专注资料整理