4. 金融数据源解析:结构化、半结构化与非结构化数据的获取与处理

金融知识图谱的构建质量,80% 取决于底层数据的质量与覆盖度。金融数据源按结构化程度可分为三类:结构化数据(财报、行情)、半结构化数据(公告、新闻)、非结构化数据(研报、舆情)。本章将逐一解析每类数据的获取方式、处理要点及在知识图谱中的映射策略。

4.1 结构化数据:财报与行情

结构化数据以明确的表格、行、列形式存在,字段类型固定,是知识图谱中实体属性与数值关系最直接的来源。

4.1.1 财报数据获取与处理

  • 获取来源:交易所官网(上交所、深交所)、巨潮资讯网、Wind/Choice 终端 API、上市公司季报/年报 PDF 中的 XBRL 或 CSV 附件。
  • 核心字段:股票代码、报告期、营业收入、净利润、每股收益、资产负债率、现金流等。
  • 处理流程:
    1. 字段对齐:不同交易所的财报科目名称存在差异(如“营业收入”vs“营业总收入”),需建立统一科目映射表。
    2. 时间标准化:将“2023-12-31”、“2023/12/31”、“2023年12月31日”统一为 ISO 8601 格式。
    3. 单位归一化:将“万元”、“亿元”、“元”统一转换为“元”或“亿元”,并记录原始单位。
    4. 缺失值处理:对关键财务指标缺失,采用前向填充(FFill)或行业均值插补,并标记数据质量标签。
实战提示: 财报数据中经常出现“调整后净利润”与“归母净利润”混用的情况。在知识图谱中,建议将两种口径分别建模为独立属性,并通过 hasAdjustmentType 关系区分,避免数值冲突。

4.1.2 行情数据获取与处理

  • 获取来源:交易所 Level-1/Level-2 行情、Tushare/AkShare 开源库、彭博/路透终端。
  • 核心字段:时间戳、开盘价、收盘价、最高价、最低价、成交量、成交额、涨跌幅、换手率。
  • 处理要点:
    • 复权处理:前复权与后复权对历史回测影响巨大,图谱中应同时存储“不复权”、“前复权”两个版本。
    • 停牌与异常值:停牌日数据为 NaN,需标记为 tradingStatus: suspended;涨跌幅超过 10% 的异常值需人工复核。
    • 时间对齐:不同交易所(A股、港股、美股)交易时段不同,跨市场图谱需统一转换为 UTC+8 并标注时区。
数据类型 典型字段 常见问题 图谱处理策略
财报 营业收入、净利润、报告期 科目名称不统一、单位不一致 建立科目映射表 + 单位归一化
行情 收盘价、成交量、时间戳 复权差异、停牌缺失、时区混乱 多版本存储 + 停牌标记 + 时区统一
结构化数据解析流程 结构化数据解析流程 财报/行情源 字段对齐 清洗归一 图谱映射 质量标记 关键:字段对齐是结构化数据入图谱的瓶颈,建议维护全局字段映射字典

4.2 半结构化数据:公告与新闻

半结构化数据具有自描述标签(如 XML、JSON、HTML),但结构不固定,字段数量与嵌套层级随内容变化。公告和新闻是金融知识图谱中事件抽取与关系发现的主要来源。

4.2.1 公告数据获取与处理

  • 获取来源:巨潮资讯网 PDF 公告、交易所信息披露平台、上市公司官网投资者关系栏目。
  • 处理难点:
    • PDF 解析:公告多为 PDF 格式,需使用 PyMuPDF、pdfplumber 提取文本,但表格和复杂排版容易错位。
    • 公告分类:需按“重大资产重组”、“关联交易”、“分红派息”、“业绩预告”等类别进行自动分类(基于标题关键词 + 正文特征)。
    • 关键信息抽取:使用正则或命名实体识别(NER)抽取“交易金额”、“交易对手”、“生效日期”等结构化字段。
公告处理最佳实践: 先通过公告标题正则匹配确定公告类型,再调用对应的抽取模板。例如“关于……的公告”匹配“关联交易”后,使用 re.search(r'交易金额[::]\s*([\d,]+\.?\d*)', text) 抽取金额。

4.2.2 新闻数据获取与处理

  • 获取来源:财经新闻网站(东方财富、新浪财经)、RSS 订阅、新闻聚合 API(如 NewsAPI)。
  • 处理流程:
    1. 网页抓取:使用 Requests + BeautifulSoup 或 Scrapy 抓取新闻正文,注意处理反爬和动态加载。
    2. 正文提取:使用 Readability、Newspaper3k 等库去除广告、导航栏,提取纯净正文。
    3. 实体识别:识别新闻中涉及的股票代码、公司名称、人名、金额、时间等实体。
    4. 事件分类:将新闻归类为“正面/负面/中性”情绪,并映射到“利好/利空”事件类型。
数据源 格式特点 核心处理工具 图谱映射目标
公告 PDF、标题结构化、正文半结构化 PyMuPDF、正则、分类模型 事件节点 + 交易关系
新闻 HTML、动态加载、噪声多 Scrapy、Newspaper3k、NER 事件节点 + 情绪属性

4.3 非结构化数据:研报与舆情

非结构化数据以自由文本、图像、语音等形式存在,没有预定义的数据模型。研报和舆情是金融知识图谱中深度语义理解与推理的核心挑战。

4.3.1 研报数据获取与处理

  • 获取来源:券商研究所官网、慧博资讯、东方财富研报中心、付费 API(如 Choice 研报接口)。
  • 处理难点:
    • 长文本分割:研报通常 20-50 页,需按章节(摘要、行业分析、公司分析、盈利预测)进行智能分割。
    • 表格与图表提取:研报中的财务预测表格、股价走势图需 OCR 或表格识别模型(如 Table Transformer)处理。
    • 观点抽取:从“我们认为……”、“预计……”、“维持买入评级”等句式抽取分析师观点与评级。
注意: 研报中的“盈利预测”表格经常出现跨页断裂。建议在 PDF 解析时先检测表格边界,若表格跨页则合并两页的表格行,再按行解析为图谱中的 FinancialForecast 节点。

4.3.2 舆情数据获取与处理

  • 获取来源:雪球、股吧、微博、微信公众号、贴吧等社交平台。
  • 处理流程:
    1. 爬虫采集:使用模拟登录、API 代理池采集帖子标题、正文、评论、转发数、点赞数。
    2. 文本清洗:去除表情符号、@用户、URL、重复刷屏内容。
    3. 情感分析:基于金融领域情感词典(如 Loughran-McDonald 词典)或预训练模型(如 FinBERT)计算情感得分。
    4. 舆情热度计算:综合发帖量、阅读量、情感波动率,构建舆情热度指数。
非结构化数据图谱映射架构 非结构化数据图谱映射架构 研报 PDF 舆情文本 OCR + 分割 情感分析 观点节点 舆情热度节点 非结构化数据入图谱的核心挑战:从自由文本中提取结构化三元组 建议采用“预训练模型 + 规则模板”混合策略,平衡召回率与精确率

4.4 三类数据源对比与融合策略

维度 结构化数据 半结构化数据 非结构化数据
数据格式 CSV、XBRL、数据库表 PDF、HTML、JSON 纯文本、图片、语音
解析难度 低 中 高
图谱映射方式 直接映射为属性 规则 + 模型抽取事件 NLP 深度理解
数据质量 高(经过审计) 中(存在噪声) 低(主观性强)
更新频率 季度/日 日/实时 实时
融合策略 作为基准事实 补充事件线索 提供市场情绪
融合策略建议: 在知识图谱中,以结构化数据(财报、行情)作为实体属性的“黄金标准”,半结构化数据(公告、新闻)用于构建事件节点与关系,非结构化数据(研报、舆情)用于丰富实体描述与情绪标签。三者通过 hasEvent、hasSentiment 等关系进行关联,形成完整的金融知识网络。

4.5 代码示例:财报数据清洗与图谱映射

以下示例展示从 CSV 财报数据到知识图谱三元组的转换过程:

import pandas as pd
import re

# 1. 读取原始财报数据
df = pd.read_csv('financial_report_raw.csv')
print("原始字段:", df.columns.tolist())

# 2. 字段对齐:统一科目名称
subject_mapping = {
    '营业总收入': 'revenue',
    '营业收入': 'revenue',
    '净利润': 'net_profit',
    '归属于母公司所有者的净利润': 'net_profit_parent'
}
df['subject_standard'] = df['subject'].map(subject_mapping)

# 3. 单位归一化:将“万元”转为“元”
def normalize_unit(value, unit):
    if unit == '万元':
        return value * 10000
    elif unit == '亿元':
        return value * 100000000
    return value

df['value_normalized'] = df.apply(
    lambda row: normalize_unit(row['value'], row['unit']), axis=1
)

# 4. 生成图谱三元组 (subject, predicate, object)
triples = []
for _, row in df.iterrows():
    triple = (
        f"stock:{row['stock_code']}",
        f"hasFinancialMetric",
        f"metric:{row['subject_standard']}_{row['report_date']}"
    )
    triples.append(triple)

# 5. 输出前 5 条三元组
for t in triples[:5]:
    print(f"({t[0]}) -- [{t[1]}] --> ({t[2]})")
代码要点: 上述代码中,subject_mapping 是字段对齐的核心字典,需要根据实际数据源持续维护。图谱映射时,建议将 stock_code 和 report_date 组合作为唯一标识,避免重复节点。