23  金融文本分析(上)——从文本到结构化数据

AI 对话:ailingnan.top

本章目标
  1. 理解金融领域文本数据的来源、类型和分析价值
  2. 掌握中文文本预处理的完整流程:分词、停用词过滤、文本清洗
  3. 学会从金融文本中提取结构化信息:正则表达式和大语言模型两种路径
  4. 能够对金融文本进行词频统计和 TF-IDF 关键词分析
  5. 完成一个综合案例:从 50 条借贷公告批量提取结构化贷款数据

本章的所有操作围绕一个核心任务:把非结构化的金融文本,转化为可以纳入量化分析框架的结构化数据。


23.1 为什么金融分析师需要文本分析?

在数据清洗一章中,我们已经接触过非结构化数据:Announcement 表中的借贷公告文本,需要从中提取贷款金额、利率、银行名称等字段,才能纳入回归分析 [1]。当时我们只处理了一条公告,用一个提示词就解决了问题。

但现实中的情况远比这复杂。一家上市公司一年可能发布上百条公告,A 股市场每天产生数千条公告和数万条财经新闻,央行每个季度发布一份长达数万字的货币政策执行报告。金融市场中超过 80% 的信息以文本形式存在——公告、研报、新闻、社交媒体、监管问询函、财报电话会议纪要——而这些文本信息的时效性远高于结构化的财务报表(公告当天发布 vs 季报滞后 1-4 个月)。

这意味着,如果你只会处理结构化数据,就只能利用金融市场中不到 20% 的信息。

23.1.1 案例一:一词之差,债市震动

央行每个季度发布的《货币政策执行报告》是债券市场最重要的政策信号之一。2016 年四季度报告中,央行将此前一直使用的”保持流动性合理充裕”改为”维护流动性基本稳定”——仅仅四个字的变化,债券市场当日大幅调整,十年期国债收益率上行超过 10 个基点。

原因很简单:“合理充裕”意味着央行会主动投放流动性,而”基本稳定”则暗示央行可能减少投放甚至边际收紧。对于每天盯着央行措辞的债券交易员来说,这四个字传递的信息量,不亚于一次加息。

启示: 如果能用程序系统性地追踪央行报告的措辞变化,就有可能比手动阅读更快、更全面地捕捉政策信号。这正是文本分析的用武之地。

23.1.2 案例二:公告语气与股价异动

同一家上市公司的两份年报,一份写”公司经营稳健,业绩持续增长,市场前景广阔”,另一份写”公司面临较大挑战,行业竞争加剧,盈利能力承压”——即便两年的财务数据差距不大,市场对两份年报的反应也可能截然不同。

学术研究早已证实这一点。Loughran and McDonald(2011)对美国上市公司 10-K 年报的文本分析发现,年报中负面词汇的比例能够显著预测未来的股票收益率和盈利波动,且这种预测力独立于传统的财务指标。换句话说,文本中包含的信息,是财务报表无法完全替代的

启示: 如果能量化文本的”语气”或”情感”,并将其作为变量纳入分析模型,就能捕捉到传统财务分析遗漏的信息。

23.1.3 本章与前后章节的关系

本章的内容衔接了数据清洗章和后续的文本建模章:

  • 向前衔接数据清洗章 [1]:数据清洗章介绍了”非结构化数据的结构化转换”的基本思路——从一条公告中提取字段。本章将这个思路扩展到批量处理,并引入系统性的文本预处理方法。
  • 向前衔接爬虫章:本章使用的文本数据(借贷公告、问询函等)来自爬虫章获取的原始文件。如果你跳过了爬虫章,可以直接从课程 GitHub 仓库下载预处理好的文本数据包。
  • 向后衔接下一章(文本建模):本章的输出——预处理后的分词文本和 TF-IDF 特征——将直接成为下一章情感分析和文本分类的输入。
数据来源说明

本章使用的文本数据来自爬虫章的网络爬虫实操。如果你跳过了爬虫章,可以直接从课程 GitHub 仓库下载:

import urllib.request
url = "https://raw.githubusercontent.com/lianxhcn/dsfin/main/data/text_samples.zip"
urllib.request.urlretrieve(url, "data_raw/text_samples.zip")

23.2 金融文本数据概览

23.2.1 金融领域的常见文本数据源

在动手处理之前,先要知道金融领域有哪些文本数据可用。下表按数据来源、更新频率和典型应用场景做了归纳:

文本类型 典型来源 更新频率 典型应用场景
上市公司公告 巨潮资讯、CSMAR 事件驱动 信息提取、事件研究
年报 MD&A 巨潮资讯、CSMAR 年度 语气分析、风险披露度量
分析师研报 Wind、东方财富 不定期 情感分析、一致预期
财经新闻 新浪财经、东方财富 日度/实时 市场情绪指数
央行/监管文件 央行官网、交易所 季度/不定期 政策语气分析
交易所问询函 沪深交易所官网 事件驱动 监管强度度量、信披质量
社交媒体 雪球、东方财富股吧 实时 散户情绪
财报电话会议纪要 Wind 季度 管理层语气、信息含量
提示词:查找特定类型的金融文本数据

我正在做一个关于 [研究主题,如:上市公司借贷成本] 的分析项目。 请帮我梳理可以获取的相关文本数据类型,包括: 1. 可能包含有用信息的文本类型(如公告、研报、新闻等) 2. 每种文本的主要获取渠道(网站名称和 URL) 3. 是否可以通过 Python 批量获取(akshare 接口 / 爬虫 / 手动下载) 4. 每种文本的数据量级估计(每年大约多少条/份)

请以表格形式输出。

23.2.2 本章使用的文本素材

为了让后续各节的示例清晰可控,我们准备了以下几类文本素材。其中借贷公告和财经新闻是虚构的(与数据清洗章虚构 BasicInf、FinRatio 数据的逻辑一致 [1]——刻意植入特定问题,演示如何识别和解决),央行报告和年报 MD&A 片段则基于真实文本的风格仿写。

素材①:上市公司借贷公告(8 条)

这 8 条公告延续了数据清洗章 Announcement 的场景 [1],刻意设计了不同的变体——利率表述不同(“年化”“年息”“LPR 浮动”)、金额单位混用(亿元/万元)、银行名称有全称有简称、部分公告含 HTML 标签噪音、一条公告包含两笔贷款、以及一条精确重复。

# 8 条借贷公告文本
announcements = [
    # 公告1: 标准格式(基准样本, 与数据清洗章一致)
    "本公司于2023年6月1日,与中国建设银行股份有限公司深圳市分行签署贷款协议,"
    "贷款金额为2亿元,期限3年,利率为年化4.20%。",

    # 公告2: 利率表述变体 + 银行简称
    "本公司于2023年8月15日与工行广东省分行签订借款合同,"
    "借款金额15000万元,借款期限5年,年息4.50%。",

    # 公告3: LPR浮动利率(正则无法直接提取)
    "2022年3月10日,本公司与中国银行总行签署流动资金贷款协议。"
    "贷款金额人民币5亿元整,期限36个月,"
    "执行利率为一年期贷款市场报价利率(LPR)加80个基点。",

    # 公告4: 含HTML标签和全角字符(脏文本)
    "<p>本公司于2023年11月20日与中国农业银行股份有限公司珠海分行签署"
    "贷款合同,贷款金额为人民币<b>8000万元</b>,贷款期限为2年,"
    "贷款年利率为4.60%。</p>",

    # 公告5: 期限用"月"表示
    "中国石化于2023年9月5日与交通银行股份有限公司上海市分行"
    "签订借款合同。本次借款金额为20000万元,借款期限18个月,"
    "利率为年化3.85%。",

    # 公告6: 信息不完整(利率未确定)
    "本公司于2022年12月8日与招商银行总行营业部签署综合授信协议,"
    "获得授信额度人民币10亿元,期限2年。"
    "具体贷款利率将根据届时市场情况另行确定。",

    # 公告7: 一条公告含两笔贷款
    "万科企业股份有限公司于2023年4月分别与两家银行签署贷款协议:"
    "(1)与中国建设银行股份有限公司深圳市分行签署贷款协议,"
    "金额3亿元,期限5年,利率年化4.10%;"
    "(2)与中信银行股份有限公司深圳分行签署贷款协议,"
    "金额2亿元,期限3年,利率年化4.35%。",

    # 公告8: 与公告1完全重复(模拟爬虫重复抓取)
    "本公司于2023年6月1日,与中国建设银行股份有限公司深圳市分行签署贷款协议,"
    "贷款金额为2亿元,期限3年,利率为年化4.20%。",
]

# 对应的公司代码和年份
ann_meta = [
    {"stkcd": "000001", "year": 2023},
    {"stkcd": "000001", "year": 2023},
    {"stkcd": "000002", "year": 2022},
    {"stkcd": "000063", "year": 2023},
    {"stkcd": "600028", "year": 2023},
    {"stkcd": "600036", "year": 2022},
    {"stkcd": "000002", "year": 2023},
    {"stkcd": "000001", "year": 2023},  # 精确重复
]

素材④:财经新闻(6 条)

6 条新闻覆盖从明确正面到明确负面的完整情感光谱,其中第 3、4 条刻意包含了在金融语境下容易被误判的”歧义词”(如”不良率下降”中的”不良”和”下降”、“财务费用增长”中的”增长”),第 5 条则是正面政策信号与负面经济暗示并存的混合情感文本。这些素材将在下一章的情感分析中发挥核心作用。

news_list = [
    {
        "id": 1,
        "title": "宁德时代一季度净利润同比增长35%,动力电池市占率稳居全球第一",
        "text": "宁德时代4月25日晚间发布2024年一季报,公司实现营业收入798亿元,"
                "同比增长12.3%;归母净利润105亿元,同比大幅增长35.4%。公司表示,"
                "受益于全球新能源汽车渗透率持续提升,动力电池出货量保持强劲增长,"
                "全球市场份额进一步扩大至37.5%。多家券商维持"买入"评级,"
                "目标价上调至280元。",
        "label": "正面"
    },
    {
        "id": 2,
        "title": "某房企未能按期偿付美元债利息,标普下调评级至CC",
        "text": "受流动性持续恶化影响,某大型房地产企业未能按期偿付一笔到期美元债券利息,"
                "构成实质性违约。标普全球评级随即将该公司长期信用评级由CCC-下调至CC,"
                "展望维持"负面"。受此消息冲击,该公司港股股价单日暴跌18.3%,"
                "年内累计跌幅已超过70%。分析人士指出,公司短期内面临较大的再融资压力,"
                "债务重组方案仍存在重大不确定性。",
        "label": "负面"
    },
    {
        "id": 3,
        "title": "招商银行不良贷款率连续三个季度下降,资产质量持续改善",
        "text": "招商银行2024年一季报显示,截至3月末,全行不良贷款率为0.95%,"
                "较上年末下降0.05个百分点,连续三个季度改善。拨备覆盖率维持在450%以上"
                "的较高水平。市场人士评价称,招行在房地产风险敞口可控的前提下,"
                "资产质量表现优于同业平均水平,风险管理能力得到进一步验证。"
                "该股当日上涨2.1%。",
        "label": "正面(含歧义词:不良、风险、下降)"
    },
    {
        "id": 4,
        "title": "某制造业上市公司负债率攀升至82%,财务费用同比大幅增长",
        "text": "某制造业上市公司最新财报显示,截至报告期末,公司资产负债率已攀升至82.3%,"
                "较年初上升6.2个百分点。其中短期借款同比增加30%,财务费用同比大幅增长"
                "45.1%,对公司利润形成明显侵蚀。公司解释称负债率上升主要源于产能扩张"
                "所需的资本开支。然而在当前市场需求放缓的背景下,高杠杆经营策略面临的"
                "不确定性正在提升。",
        "label": "负面(含歧义词:增长、提升、扩张)"
    },
    {
        "id": 5,
        "title": "央行宣布降准0.5个百分点,释放长期资金约1万亿元",
        "text": "中国人民银行宣布,自2024年2月5日起,下调金融机构存款准备金率0.5个百分点。"
                "本次降准预计释放长期资金约1万亿元,有助于降低实体经济融资成本,"
                "支持经济平稳运行。市场分析认为,此举表明当前经济下行压力仍然较大,"
                "央行有必要通过货币政策工具进行逆周期调节。降准消息公布后,"
                "A股市场反应平淡,沪指微涨0.3%,债券市场则出现小幅走强。",
        "label": "混合"
    },
    {
        "id": 6,
        "title": "沪深交易所发布退市新规,财务类退市营收门槛上调至3亿元",
        "text": "沪深交易所4月30日同步发布《股票上市规则》修订稿,进一步完善退市标准体系。"
                "主要修订内容包括:将财务类退市中的营业收入指标由1亿元调整为3亿元;"
                "新增规范类退市中对控股股东资金占用的量化认定标准;交易类退市中的"
                "市值指标维持5亿元不变。新规自2024年7月1日起施行,设置一年过渡期。",
        "label": "中性"
    },
]

23.2.3 从一条到一万条:思维的升级

在数据清洗章中,我们用一个提示词从一条公告中提取了贷款信息 [1]。那个方法对单条文本完全够用,但如果面对的是几百条、几千条公告,逐条手动调用 LLM 既慢又贵。

本章要解决的核心问题是:如何高效地、系统性地处理大量文本?

这需要一套完整的处理流程:

原始文本(HTML/PDF/TXT)


文本清洗(去噪音、统一编码)        ← 本章第 2


中文分词 + 停用词过滤              ← 本章第 2


信息提取(正则 + LLM)            ← 本章第 3


词频统计 / TF-IDF 关键词分析       ← 本章第 4


结构化数据(可纳入回归/建模)      ← 本章第 5 节(综合案例)

下面我们从流程的第一步——中文文本预处理——开始。


23.3 中文文本预处理

23.3.1 为什么中文文本需要特殊处理?

英文文本有天然的空格分隔单词——The central bank cut interest rates 中每个词都被空格隔开,计算机可以直接识别。但中文不是这样。看这句话:

中国人民银行决定下调金融机构存款准备金率0.5个百分点

哪里是词与词的边界?“中国人民银行”是一个词还是五个字?“存款准备金率”应该拆成”存款”+“准备金率”还是保持整体?计算机无法像读英文一样自动判断。

这就是为什么中文文本分析的第一步必须是分词——把连续的汉字序列切分成有意义的词语。分词的质量直接决定了后续所有分析(词频统计、情感分析、文本分类)的准确性。

23.3.2 分词:jieba 库

jieba 是目前 Python 生态中最成熟、使用最广泛的中文分词工具。它的名字来源于”结巴”——暗示中文分词就像在句子中”结巴”地停顿,找到词与词的边界。

# 安装(只需一次)
# !pip install jieba

import jieba

text = "中国人民银行决定下调金融机构存款准备金率0.5个百分点"
words = jieba.lcut(text)
print(words)
['中国人民银行', '决定', '下调', '金融', '机构', '存款', '准备金', '率', '0.5', '个', '百分点']

jieba.lcut() 返回一个列表,每个元素是一个词。大部分词切得不错,但注意两个问题:

  • “金融机构”被拆成了”金融”和”机构”——这在一般语境下没问题,但如果你在分析央行文件,“金融机构”作为一个整体可能更合适
  • “存款准备金率”被拆成了”存款”“准备金”“率”——对于金融分析来说,这个拆分破坏了术语的完整性

这就引出了下一个关键步骤:自定义词典

23.3.3 自定义词典:让分词工具认识金融术语

jieba 允许加载自定义词典,告诉它哪些词应该作为整体保留。词典是一个纯文本文件,每行一个词,格式为:词语 词频 词性(词频和词性可以省略)。

# 方法一:加载词典文件
# jieba.load_userdict('data_raw/finance_dict.txt')

# 方法二:逐个添加词语(适合少量词语)
finance_terms = [
    "中国人民银行", "存款准备金率", "贷款市场报价利率",
    "中期借贷便利", "常备借贷便利", "逆回购",
    "定向降准", "金融机构", "同业拆借",
    "不良贷款率", "拨备覆盖率", "资产负债率",
]

for term in finance_terms:
    jieba.add_word(term)

# 再次分词,对比效果
words_v2 = jieba.lcut(text)
print(words_v2)
['中国人民银行', '决定', '下调', '金融机构', '存款准备金率', '0.5', '个', '百分点']

加载自定义词典后,“金融机构”和”存款准备金率”都被正确识别为整体了。

我们用素材②中的一段央行报告来进一步验证效果:

pboc_text = (
    "2020年一季度,面对新冠肺炎疫情对经济社会发展带来的严峻冲击,"
    "中国人民银行坚决贯彻党中央、国务院决策部署,加大逆周期调节力度,"
    "综合运用多种货币政策工具,保持流动性合理充裕。一季度累计开展"
    "中期借贷便利(MLF)操作11000亿元,下调MLF利率30个基点至2.95%,"
    "引导贷款市场报价利率(LPR)相应下行。"
)

# 对比:不加词典 vs 加词典
words_before = jieba.lcut(pboc_text)  # 已加载词典的状态
print("分词结果(已加载金融词典):")
print(words_before)
提示词:构建金融领域自定义分词词典

我需要对中国央行的货币政策执行报告进行文本分析,使用 jieba 分词。 请帮我整理一份金融领域的自定义分词词典,包含以下类别的专有名词:

  1. 货币政策工具名称(如逆回购、MLF、SLF、定向降准等)
  2. 金融机构类型(如政策性银行、股份制商业银行等)
  3. 金融市场术语(如银行间同业拆借、社会融资规模等)
  4. 常见金融监管术语(如宏观审慎、系统性风险等)
  5. 常见金融指标名称(如不良贷款率、拨备覆盖率、资本充足率等)

每行格式为:词语 词频 词性 请至少列出 50 个词条,按类别分组排列。

23.3.4 停用词过滤

分词之后,结果中会包含大量对分析没有帮助的”功能词”——“的”“了”“在”“是”“和”“为”等。这些词被称为停用词(stop words),在做词频统计和文本建模之前需要过滤掉,否则它们会占据高频词的大部分位置,淹没真正有意义的内容词。

# 一个基础的中文停用词表(实际使用中会更长,通常包含上千个词)
basic_stopwords = set([
    "的", "了", "在", "是", "和", "与", "为", "对", "以", "及",
    "等", "中", "将", "把", "被", "由", "从", "到", "也", "都",
    "就", "又", "而", "但", "或", "个", "各", "这", "那", "之",
    "其", "她", "他", "我", "你", "们", "着", "过", "不", "没",
    "有", "要", "会", "能", "可", "所", "日", "月", "年",
    "(", ")", ",", "。", "、", ";", ":", """, """,
])

# 过滤停用词
text_sample = "中国人民银行决定下调金融机构存款准备金率0.5个百分点"
words = jieba.lcut(text_sample)
words_filtered = [w for w in words if w not in basic_stopwords and len(w) > 1]

print(f"过滤前({len(words)} 个词):{words}")
print(f"过滤后({len(words_filtered)} 个词):{words_filtered}")
过滤前(8 个词):['中国人民银行', '决定', '下调', '金融机构', '存款准备金率', '0.5', '个', '百分点']
过滤后(5 个词):['中国人民银行', '决定', '下调', '金融机构', '存款准备金率']
金融语境下的停用词需要谨慎处理

“增长”“下降”“提高”“降低”这些词,在通用的停用词表中通常不会被收录。但在特定的分析场景中,你需要思考是否要保留它们:

  • 如果你做的是情感分析(判断文本的正面/负面语气),这些词是重要的情感信号词,必须保留
  • 如果你做的是主题分析(判断文本在讨论什么话题),这些词可能是噪音——因为几乎每篇央行报告都会提到”增长”,它不能帮助你区分不同话题

原则:停用词表不是一成不变的,需要根据分析目标调整。

23.3.5 文本清洗 pipeline

在实际获取的金融文本中,原始数据往往包含各种噪音:从网页爬取的文本可能带有 HTML 标签,数据库导出的文本可能混有全角字符和多余空格,港股公告可能是繁体字。这些噪音如果不清理,会严重影响分词和后续分析的效果。

我们用素材①中的公告 4(脏文本)来演示:

dirty_text = (
    "<p>本公司于2023年11月20日与中国农业银行股份有限公司珠海分行签署"
    "贷款合同,贷款金额为人民币<b>8000万元</b>,贷款期限为2年,"
    "贷款年利率为4.60%。</p>"
)
print("原始文本:")
print(dirty_text)

这段文本有三类噪音:HTML 标签(<p><b>)、全角数字(2023)、全角标点。我们逐步清洗:

import re
import unicodedata

def clean_text(text):
    """金融文本清洗 pipeline"""

    # Step 1: 去除 HTML 标签
    text = re.sub(r'<[^>]+>', '', text)

    # Step 2: 全角字符转半角
    result = []
    for char in text:
        code = ord(char)
        if 0xFF01 <= code <= 0xFF5E:       # 全角 ASCII 字符范围
            result.append(chr(code - 0xFEE0))
        elif code == 0x3000:                # 全角空格
            result.append(' ')
        else:
            result.append(char)
    text = ''.join(result)

    # Step 3: 去除多余空白
    text = re.sub(r'\s+', ' ', text).strip()

    return text

clean = clean_text(dirty_text)
print("清洗后:")
print(clean)
清洗后:
本公司于2023年11月20日与中国农业银行股份有限公司珠海分行签署贷款合同,贷款金额为人民币8000万元,贷款期限为2年,贷款年利率为4.60%。

清洗后,全角数字 2023 变成了半角 2023,HTML 标签被去除,文本变得干净规整。现在可以对它做正则提取或分词了。

中文文本清洗的常见陷阱
  • 数字的处理要谨慎: 在贷款公告中,“2亿元”“4.2%”这些数字是关键信息,不能盲目删除。只有在做词频统计、情感分析等”语义分析”任务时,才考虑去除数字。在做信息提取时,数字必须保留。
  • 标点符号有时承载语义: 问号可能暗示不确定性(“能否实现盈利目标?”),感叹号可能暗示强调或惊讶。在情感分析中,这些信息值得保留。
  • 先清洗,后分词: 全角字符和 HTML 标签会干扰 jieba 的分词效果。务必先做清洗,再做分词。

23.3.6 封装完整的预处理函数

将上述所有步骤封装为一个可复用的函数:

import jieba
import re

def preprocess_chinese_text(text, stopwords=None, user_dict=None, remove_numbers=False):
    """
    中文金融文本预处理完整 pipeline

    参数:
        text: 原始文本字符串
        stopwords: 停用词集合(set), 默认为 None
        user_dict: 自定义词典文件路径, 默认为 None
        remove_numbers: 是否去除纯数字词, 默认 False

    返回:
        words: 预处理后的词语列表
    """
    # Step 1: 去 HTML 标签
    text = re.sub(r'<[^>]+>', '', text)

    # Step 2: 全角转半角
    result = []
    for char in text:
        code = ord(char)
        if 0xFF01 <= code <= 0xFF5E:
            result.append(chr(code - 0xFEE0))
        elif code == 0x3000:
            result.append(' ')
        else:
            result.append(char)
    text = ''.join(result)

    # Step 3: 去多余空白
    text = re.sub(r'\s+', ' ', text).strip()

    # Step 4: 加载自定义词典
    if user_dict:
        jieba.load_userdict(user_dict)

    # Step 5: 分词
    words = jieba.lcut(text)

    # Step 6: 过滤停用词和短词
    if stopwords:
        words = [w for w in words if w not in stopwords]
    words = [w for w in words if len(w) > 1]

    # Step 7: 可选-去除纯数字
    if remove_numbers:
        words = [w for w in words if not re.match(r'^[\d.]+$', w)]

    return words

验证效果:

# 对公告4(脏文本)做完整预处理
result = preprocess_chinese_text(
    dirty_text,
    stopwords=basic_stopwords,
    remove_numbers=False  # 保留数字,因为后续要提取金额和利率
)
print(result)
['本公司', '2023', '11', '20', '中国农业银行', '股份有限公司', '珠海', '分行',
 '签署', '贷款', '合同', '贷款', '金额', '人民币', '8000', '万元', '贷款',
 '期限', '贷款', '年利率', '4.60']
提示词:批量预处理文本数据

我有一个 DataFrame df,其中 text 列包含上市公司公告的原始文本(可能含 HTML 标签和全角字符)。 请帮我:

  1. text 列的每条文本做以下预处理:
    • 去除 HTML 标签
    • 全角字符转半角
    • 使用 jieba 分词(需先加载金融自定义词典 data_raw/finance_dict.txt
    • 过滤停用词(停用词表路径为 data_raw/stopwords.txt
    • 去除长度为 1 的单字词
  2. 将分词结果存储在新列 text_tokenized 中(词语列表格式)
  3. 新增一列 text_joined,将词语列表用空格连接为字符串(供后续 TF-IDF 使用)
  4. 输出预处理前后的前 3 行对比

请使用 tqdm 显示处理进度条。


23.4 文本信息提取

预处理解决了”把文本变干净”的问题,但很多时候我们需要的不是干净的文本本身,而是从中提取出特定的结构化字段——金额、利率、日期、银行名称。这就是信息提取的任务。

本节介绍两种互补的方法:正则表达式大语言模型(LLM)

23.4.1 正则表达式:规则明确时的首选

正则表达式(Regular Expression, regex)是一种用特定符号描述文本模式的工具。你可以把它理解为一种”模板匹配”——告诉计算机你要找的文本长什么样,它就帮你把符合模式的内容找出来。

我们从素材①的公告 1(基准样本)开始:

import re

text = ("本公司于2023年6月1日,与中国建设银行股份有限公司深圳市分行"
        "签署贷款协议,贷款金额为2亿元,期限3年,利率为年化4.20%。")

# 提取利率
rate_match = re.search(r'[利率年息]+[为是]?年化?(\d+\.?\d*)%', text)
if rate_match:
    print(f"利率: {rate_match.group(1)}%")

# 提取金额和单位
amount_match = re.search(r'金额[为是]?(?:人民币)?(\d+\.?\d*)(万元|亿元|元)', text)
if amount_match:
    print(f"金额: {amount_match.group(1)} {amount_match.group(2)}")

# 提取期限
term_match = re.search(r'期限[为是]?(\d+)(年|个月)', text)
if term_match:
    print(f"期限: {term_match.group(1)} {term_match.group(2)}")
利率: 4.20%
金额: 2 亿元
期限: 3 年

正则表达式的语法初看有些晦涩,但核心模式并不多。下表列出了上面用到的关键符号:

符号 含义 示例
\d 匹配数字(0-9) \d+ 匹配 “2023”
+ 前面的字符出现 1 次或多次 \d+ 匹配一个或多个数字
? 前面的字符出现 0 次或 1 次 \.? 匹配有或没有小数点
\. 匹配字面上的句号/小数点 \d+\.?\d* 匹配 “4.20” 或 “4”
* 前面的字符出现 0 次或多次 \d* 匹配零个或多个数字
() 捕获组——提取括号内匹配的内容 (\d+)% 从 “4.20%” 中提取 “4.20”
[...] 匹配方括号内的任意一个字符 [为是] 匹配 “为” 或 “是”
(?:...) 非捕获组——匹配但不提取 (?:人民币)? 匹配可选的 “人民币”
\| 或(二选一) (万元\|亿元) 匹配 “万元” 或 “亿元”
你不需要记住正则表达式的所有语法

正则表达式的完整语法非常复杂,专业程序员也经常需要查手册。在本课程中,你需要掌握的是:

  1. 能看懂上面这些基本符号的含义
  2. 能向 AI 描述你想匹配的文本模式,让 AI 帮你生成正则表达式
  3. 能验证 AI 生成的正则是否在你的数据上正确工作

第 2 点是最重要的——精确地描述”你要找什么”,比自己写正则更有效率。

现在我们测试正则表达式在不同表述变体上的表现。用素材①的公告 2(利率表述变体):

text2 = ("本公司于2023年8月15日与工行广东省分行签订借款合同,"
         "借款金额15000万元,借款期限5年,年息4.50%。")

# 使用更宽泛的正则
rate_match = re.search(r'(?:利率|年息|年化)[为是]?年?化?(\d+\.?\d*)%', text2)
print(f"利率: {rate_match.group(1)}%" if rate_match else "利率: 未找到")

amount_match = re.search(r'(?:贷款|借款)?金额[为是]?(?:人民币)?(\d+\.?\d*)(万元|亿元|元)', text2)
print(f"金额: {amount_match.group(1)} {amount_match.group(2)}" if amount_match else "金额: 未找到")
利率: 4.50%
金额: 15000 万元

正则表达式也有明显的局限。用公告 3(LPR 浮动利率):

text3 = ("2022年3月10日,本公司与中国银行总行签署流动资金贷款协议。"
         "贷款金额人民币5亿元整,期限36个月,"
         "执行利率为一年期贷款市场报价利率(LPR)加80个基点。")

rate_match = re.search(r'(?:利率|年息|年化)[为是]?年?化?(\d+\.?\d*)%', text3)
print(f"利率: {rate_match.group(1)}%" if rate_match else "利率: 未找到")
利率: 未找到

“LPR 加 80 个基点”——正则表达式找不到一个直接的百分比数字,因为利率被表述为一个浮动的公式。这正是需要 LLM 介入的场景。

提示词:生成金融文本信息提取的正则表达式

我有一批上市公司借贷公告文本。请帮我写 Python 正则表达式,从中提取以下字段:

  1. 贷款金额和单位:可能的表述包括”X亿元”“X万元”“X元”“人民币X亿元整”
  2. 年化利率:可能的表述包括”年化X%““年息X%”“利率X%”“利率为X%”
  3. 贷款期限:可能的表述包括”X年”“X个月”“期限X年”“借款期限X个月”
  4. 贷款日期:格式可能是”YYYY年M月D日”或”YYYY年MM月DD日”

对每个字段,请: - 考虑至少 3 种常见的表达变体 - 使用捕获组提取数值部分 - 提供 3 个测试用例,包括能匹配和不能匹配的情况 - 如果某种表述正则难以处理(如”LPR+80BP”),请明确说明并建议改用 LLM

23.4.2 大语言模型(LLM)提取:复杂文本的利器

当文本的表述方式多变、信息散落在多个句子中、或者需要语义理解才能提取时,正则表达式就力不从心了。此时可以调用大语言模型来完成提取。

这个方法在数据清洗章已经使用过——当时我们用一个提示词从公告文本中提取了贷款信息 [1]。现在我们把它扩展为一个可批量调用的函数:

# 以下代码展示调用逻辑,实际运行需要配置 API
# 支持 OpenAI、智谱清言、DeepSeek 等 API

def extract_loan_info_llm(text, api_client):
    """
    使用 LLM 从借贷公告中提取结构化信息

    参数:
        text: 公告文本
        api_client: 已配置的 API 客户端

    返回:
        dict: 提取的结构化信息
    """
    prompt = f"""
    你是金融信息提取助手。请从以下上市公司借贷公告中提取结构化信息。

    输出为 JSON 格式,包含以下字段:
    - loan_date: 贷款签署日期,格式 YYYY-MM-DD
    - bank_name: 银行法人机构标准化名称(如"建设银行"而非"建行深圳分行")
    - bank_level: 银行层级,取值为"总行""省级分行""地级分行"之一
    - loan_amount: 贷款金额原始数值(浮点数)
    - loan_unit: 金额单位,取值为"元""万元""亿元"之一
    - loan_rate: 年化利率(%),如为浮动利率请注明公式
    - loan_rate_type: "固定"或"浮动"
    - loan_term_year: 贷款期限(年),如原文为月则换算
    - warnings: 列表,记录无法提取的字段及原因

    若一条公告包含多笔贷款,请返回一个 JSON 列表。

    公告文本:{text}
    """

    response = api_client.chat(prompt)
    return response

我们用公告 3(LPR 浮动利率)和公告 7(一文多笔)来说明 LLM 的优势:

# 公告3的LLM提取结果(示例)
{
    "loan_date": "2022-03-10",
    "bank_name": "中国银行",
    "bank_level": "总行",
    "loan_amount": 5.0,
    "loan_unit": "亿元",
    "loan_rate": "一年期LPR+80BP",
    "loan_rate_type": "浮动",
    "loan_term_year": 3,
    "warnings": ["利率为浮动利率,具体数值取决于LPR当期报价"]
}

# 公告7的LLM提取结果(示例)——返回列表
[
    {
        "loan_date": "2023-04-01",
        "bank_name": "建设银行",
        "bank_level": "地级分行",
        "loan_amount": 3.0,
        "loan_unit": "亿元",
        "loan_rate": 4.10,
        "loan_rate_type": "固定",
        "loan_term_year": 5,
        "warnings": ["日期仅精确到月份,默认取该月1日"]
    },
    {
        "loan_date": "2023-04-01",
        "bank_name": "中信银行",
        "bank_level": "地级分行",
        "loan_amount": 2.0,
        "loan_unit": "亿元",
        "loan_rate": 4.35,
        "loan_rate_type": "固定",
        "loan_term_year": 3,
        "warnings": ["日期仅精确到月份,默认取该月1日"]
    }
]

公告 3 中正则无法提取的浮动利率,LLM 正确识别并标注了利率类型。公告 7 中两笔贷款被正确拆分为两条记录——而简单的正则很容易把两组数据混在一起。

23.4.3 正则 vs LLM:什么时候用哪个?

正则 vs LLM 的选择策略
场景 推荐方法 原因
格式固定的数字提取(利率、金额、日期) 正则表达式 速度快、确定性高、免费
银行名称标准化(“建行深圳分行”→“建设银行”) LLM 需要语义理解和世界知识
一条文本包含多笔贷款 LLM 需要理解文本结构
浮动利率、条件性表述 LLM 需要语义推理
大批量处理(>10000 条) 正则为主 + LLM 抽样验证 成本与效率的平衡
探索性分析(不确定文本有哪些模式) 先用 LLM 看几条,总结模式后写正则 效率最优的组合策略

实务中最常用的策略:先用正则处理能直接匹配的字段(通常能覆盖 80-90% 的样本),再用 LLM 处理正则失败的样本和需要语义理解的字段。


23.5 词频统计与关键词分析

完成了预处理和信息提取之后,我们来看另一类常见的文本分析任务:从文本中发现”什么词最重要”

23.5.1 词频统计:最直接的文本画像

统计每个词出现了多少次,是理解一篇文本”在说什么”的最简单方法。

我们用素材④的三条新闻来演示:

from collections import Counter
import jieba

# 选取新闻1(正面)、新闻2(负面)、新闻6(中性)
texts = {
    "正面(宁德时代)": news_list[0]["text"],
    "负面(房企违约)": news_list[1]["text"],
    "中性(退市新规)": news_list[5]["text"],
}

for label, text in texts.items():
    words = preprocess_chinese_text(text, stopwords=basic_stopwords, remove_numbers=True)
    top_words = Counter(words).most_common(8)
    print(f"\n{label}】Top 8 高频词:")
    for word, count in top_words:
        print(f"  {word}: {count}")

仅从高频词就能看出三条新闻的主题差异:正面新闻的高频词是”增长”“电池”“市场份额”,负面新闻是”违约”“评级”“下调”“压力”,中性新闻是”退市”“营业收入”“指标”。

23.5.2 TF-IDF:找到”真正重要”的词

词频统计有一个明显的局限:如果你分析的是 10 份年报,“公司”“经营”“报告期”这些词在每份年报中都会高频出现——它们出现频率高,但并不能帮助你区分不同公司或不同行业。

TF-IDF(Term Frequency - Inverse Document Frequency) 解决的就是这个问题。它的直觉是:

一个词在当前文档中频繁出现(TF 高),但在其他文档中很少出现(IDF 高),说明它对当前文档特别重要。

  • TF(词频)= 该词在当前文档中的出现次数 / 当前文档的总词数
  • IDF(逆文档频率)= log(文档总数 / 包含该词的文档数)
  • TF-IDF = TF × IDF

“公司”这个词在每份年报中都出现,IDF 接近 0,TF-IDF 值很低——它虽然频繁,但不重要。而”芯片”可能只在科技公司的年报中出现,IDF 很高,TF-IDF 值也高——它是区分这家公司与其他公司的关键词。

我们用素材③的三段年报 MD&A 来演示:

from sklearn.feature_extraction.text import TfidfVectorizer
import pandas as pd

# 三段年报 MD&A(已分词,用空格连接)
mda_texts = {
    "招商银行(银行业)": (
        "报告期内 本行 坚持 轻型银行 战略 方向 持续 深化 零售金融 护城河 "
        "全年 实现 营业收入 同比 增长 归属 股东 净利润 同比 增长 "
        "资产质量 保持 稳健 不良贷款率 较上年末 下降 个百分点 "
        "拨备覆盖率 风险 抵补 能力 充足 本行 零售 客户 总数 突破 "
        "管理 零售 客户 总资产 达 万亿元 财富 管理 手续费 佣金 收入 "
        "连续三年 保持 两位数 增长 面对 复杂 多变 外部 环境 "
        "本行 继续 坚守 合规 底线 强化 风险管理 能力 高质量 发展"
    ),
    "中兴通讯(通信设备)": (
        "报告期内 全球 通信设备 市场 竞争格局 进一步 分化 5G 网络建设 "
        "大规模 部署 阶段 逐步 过渡 深度覆盖 行业 应用 拓展 阶段 "
        "运营商 资本开支 增速 有所 放缓 公司 实现 营业收入 "
        "同比 下降 研发投入 占 营业收入 比例 继续 保持 行业 领先 水平 "
        "产品 层面 公司 第三代 自研 芯片 已 完成 流片 验证 "
        "进入 小批量 商用 阶段 服务器 存储 产品 全球 市场份额 "
        "稳步 提升 海外 收入 占比 地缘政治 风险 部分 市场 拓展 "
        "构成 不确定性 公司 持续 加大 研发投入 力度 聚焦 核心技术 突破"
    ),
    "万科 A(房地产业)": (
        "报告期内 房地产市场 深度 调整 态势 延续 行业 整体 面临 "
        "前所未有 挑战 受 商品房 销售 大幅 下滑 影响 公司 全年 "
        "实现 营业收入 同比 下降 归属 上市公司 股东 净利润 "
        "同比 下降 公司 部分 城市 项目 去化 速度 不及 预期 "
        "存货 跌价 准备 计提 金额 较上年 显著 增加 流动性 方面 "
        "公司 通过 加快 销售 回款 处置 非核心 资产 压缩 资本开支 "
        "多措并举 全力 保障 债务 如期 偿付 公司 有息负债 总额 "
        "一年内 到期 债务 占比 短期 偿债 压力 较为 突出 "
        "公司 继续 活下去 首要 目标 主动 作为 积极 应对 严峻 考验"
    ),
}

# 构建 TF-IDF 矩阵
vectorizer = TfidfVectorizer(max_features=200)
tfidf_matrix = vectorizer.fit_transform(mda_texts.values())

# 提取每份文档的 Top 10 关键词
feature_names = vectorizer.get_feature_names_out()
for i, (company, _) in enumerate(mda_texts.items()):
    scores = tfidf_matrix[i].toarray().flatten()
    top_indices = scores.argsort()[-10:][::-1]
    top_words = [(feature_names[j], round(scores[j], 3)) for j in top_indices]
    print(f"\n{company}】TF-IDF Top 10:")
    for word, score in top_words:
        print(f"  {word}: {score}")

TF-IDF 的结果比简单词频更有区分度:

  • 招商银行的关键词是”零售”“财富”“管理”“护城河”“拨备覆盖率”——精准反映了其”零售银行”的战略定位
  • 中兴通讯的关键词是”5G”“芯片”“流片”“运营商”“研发投入”——典型的通信设备制造商特征
  • 万科的关键词是”去化”“活下去”“偿债”“跌价”“回款”——直指房地产行业的困境

而”报告期内”“公司”“实现”“营业收入”等在三份文档中都出现的词,TF-IDF 得分很低,不会出现在关键词列表中。这正是 TF-IDF 相比简单词频的价值所在。

提示词:TF-IDF 关键词提取

我有若干份上市公司年报的 MD&A 文本(已分词,词之间用空格分隔),存储在一个字典 mda_texts 中,键为公司名称,值为分词后的文本字符串。

请帮我: 1. 用 sklearn 的 TfidfVectorizer(max_features=500)构建 TF-IDF 矩阵 2. 输出每份文档中 TF-IDF 得分最高的 10 个关键词及其得分 3. 找出只在一份文档中出现的高 TF-IDF 词(即该文档的”独有特征词”) 4. 将结果整理为 DataFrame 格式,列为:公司名称、排名、关键词、TF-IDF 得分


23.6 综合案例:从 50 条借贷公告到结构化贷款数据表

前面几节分别介绍了预处理、正则提取、LLM 提取和关键词分析。本节将这些方法串联起来,完成一个完整的实操案例:从 50 条借贷公告的原始文本出发,批量提取结构化的贷款信息,最终输出一份可以直接用于回归分析的干净数据表。

这个案例直接衔接数据清洗章的 Announcement 场景 [1]——数据清洗章只处理了一条公告,这里扩展到 50 条,走完从”拿到文本”到”输出 CSV”的全流程。

23.6.1 数据来源

这 50 条公告来自爬虫章从巨潮资讯网获取的上市公司临时公告。每条公告已经保存为 data_raw/announcements/ 目录下的 .txt 文件,文件名格式为 {stkcd}_{year}_{seq}.txt(如 000001_2023_01.txt)。

Note

如果你跳过了爬虫章,可以从课程 GitHub 仓库下载这 50 条公告的文本包。在本节的代码演示中,我们使用素材①中的 8 条公告作为精简示例来展示完整流程。

23.6.2 Step 1:批量读取与文本清洗

import pandas as pd

# 构建 DataFrame(使用素材①的 8 条公告)
df_ann = pd.DataFrame({
    "stkcd": [m["stkcd"] for m in ann_meta],
    "year":  [m["year"]  for m in ann_meta],
    "text":  announcements,
})

print(f"原始数据:{len(df_ann)} 条公告")
print(f"公司数量:{df_ann['stkcd'].nunique()}")
df_ann[["stkcd", "year", "text"]].head(3)
# 批量清洗
df_ann["text_clean"] = df_ann["text"].apply(clean_text)

# 对比清洗前后(以公告4为例)
idx = 3  # 公告4
print("【清洗前】")
print(df_ann.loc[idx, "text"][:80], "...")
print("\n【清洗后】")
print(df_ann.loc[idx, "text_clean"][:80], "...")

23.6.3 Step 2:正则提取(可直接匹配的字段)

import re

def extract_by_regex(text):
    """用正则从借贷公告中提取结构化字段"""
    result = {
        "loan_amount": None, "loan_unit": None,
        "loan_rate": None, "loan_term": None, "loan_term_unit": None,
        "regex_warnings": []
    }

    # 金额
    m = re.search(r'(?:贷款|借款)?金额[为是]?(?:人民币)?(\d+\.?\d*)(万元|亿元|元)', text)
    if m:
        result["loan_amount"] = float(m.group(1))
        result["loan_unit"] = m.group(2)
    else:
        result["regex_warnings"].append("金额未提取")

    # 利率
    m = re.search(r'(?:利率|年息|年利率)[为是]?年?化?(\d+\.?\d*)%', text)
    if m:
        result["loan_rate"] = float(m.group(1))
    else:
        result["regex_warnings"].append("利率未提取")

    # 期限
    m = re.search(r'(?:贷款|借款)?期限[为是]?(\d+)(年|个月)', text)
    if m:
        result["loan_term"] = int(m.group(1))
        result["loan_term_unit"] = m.group(2)
    else:
        result["regex_warnings"].append("期限未提取")

    return result

# 批量提取
regex_results = df_ann["text_clean"].apply(extract_by_regex).apply(pd.Series)
df_ann = pd.concat([df_ann, regex_results], axis=1)

# 统计提取成功率
total = len(df_ann)
for field in ["loan_amount", "loan_rate", "loan_term"]:
    success = df_ann[field].notna().sum()
    print(f"{field}: {success}/{total} 条成功 ({success/total*100:.0f}%)")
loan_amount: 6/8 条成功 (75%)
loan_rate: 5/8 条成功 (62%)
loan_term: 6/8 条成功 (75%)

公告 3(LPR 浮动利率)和公告 6(利率未确定)的利率提取失败;公告 7(一文多笔)只提取到了第一笔的信息;公告 8(重复)的结果与公告 1 相同。这些都是预期内的——正则表达式处理了能处理的部分,剩下的交给 LLM。

23.6.4 Step 3:LLM 补充提取

# 找出正则提取不完整的记录
needs_llm = df_ann[
    df_ann["regex_warnings"].apply(len) > 0
].index.tolist()

print(f"需要 LLM 补充提取的记录:{len(needs_llm)} 条")
for idx in needs_llm:
    print(f"  公告 {idx+1}{df_ann.loc[idx, 'regex_warnings']}")
需要 LLM 补充提取的记录:3 条
  公告 3:['利率未提取']
  公告 6:['利率未提取']
  公告 7:['金额未提取', '利率未提取', '期限未提取']
提示词:LLM 补充提取正则失败的公告

我正在从上市公司借贷公告中批量提取贷款信息。以下 3 条公告的部分字段正则提取失败,请帮我提取。

输出为 JSON 格式,每条公告对应一个 JSON 对象(若一条公告含多笔贷款则返回列表),字段包括: - loan_date: 日期 (YYYY-MM-DD) - bank_name: 银行标准化名称 - bank_level: 银行层级(总行/省级分行/地级分行) - loan_amount: 金额数值 - loan_unit: 单位(元/万元/亿元) - loan_rate: 利率(%),浮动利率请注明公式 - loan_rate_type: 固定/浮动 - loan_term_year: 期限(年) - warnings: 无法提取的字段及原因

公告 3:{df_ann.loc[2, ‘text_clean’]} 公告 6:{df_ann.loc[5, ‘text_clean’]} 公告 7:{df_ann.loc[6, ‘text_clean’]}

23.6.5 Step 4:结果验证与清洗

# 4.1 去除精确重复(公告1和公告8)
print(f"去重前:{len(df_ann)} 条")
df_ann = df_ann.drop_duplicates(subset=["text_clean"])
print(f"去重后:{len(df_ann)} 条")

# 4.2 金额单位统一换算为万元(复用数据清洗章的逻辑 [1])
unit_map = {"元": 0.0001, "万元": 1, "亿元": 10000}
df_ann["loan_amount_wan"] = (
    df_ann["loan_amount"] * df_ann["loan_unit"].map(unit_map)
)

# 4.3 期限统一换算为年
df_ann["loan_term_year"] = df_ann.apply(
    lambda row: row["loan_term"] / 12 if row["loan_term_unit"] == "个月"
                else row["loan_term"],
    axis=1
)

# 4.4 stkcd 格式统一(复用数据清洗章方法 [1])
df_ann["stkcd"] = df_ann["stkcd"].astype(str).str.zfill(6)

23.6.6 Step 5:聚合为公司-年层面

同一家公司在同一年可能有多笔贷款(如公告 7 中万科的两笔),需要聚合为公司-年层面,才能与 FinRatio 数据做合并 [1]。

# 按 (stkcd, year) 聚合(复用数据清洗章的长表聚合逻辑 [1])
df_loans = (
    df_ann
    .groupby(["stkcd", "year"])
    .apply(lambda g: pd.Series({
        "loan_count": len(g),
        "total_loan_wan": g["loan_amount_wan"].sum(),
        "avg_loan_rate": (  # 以金额为权重的加权平均利率
            (g["loan_rate"] * g["loan_amount_wan"]).sum()
            / g["loan_amount_wan"].sum()
            if g["loan_rate"].notna().any() else None
        ),
        "avg_loan_term": (  # 以金额为权重的加权平均期限
            (g["loan_term_year"] * g["loan_amount_wan"]).sum()
            / g["loan_amount_wan"].sum()
            if g["loan_term_year"].notna().any() else None
        ),
    }))
    .reset_index()
)

print(f"聚合后:{len(df_loans)} 个公司-年观测")
df_loans

23.6.7 Step 6:输出

# 保存到 data_clean/ 目录
df_loans.to_csv("data_clean/loans_extracted.csv", index=False)
print("已保存至 data_clean/loans_extracted.csv")
print(f"可直接与 FinRatio 数据通过 (stkcd, year) 合并")

这份数据的结构与数据清洗章中 FinRatio 表的主键完全一致——都是 (stkcd, year) [1],可以直接 merge,将文本提取的贷款特征纳入回归分析。


23.7 本章小结

本章介绍了金融文本分析的基础工具和流程,核心内容可以用下图概括:

金融文本(公告/研报/新闻/央行报告)
  │
  ├─→ 文本清洗(去HTML、全角转半角、去噪音)
  │
  ├─→ 中文分词(jieba + 金融自定义词典)
  │     │
  │     └─→ 停用词过滤
  │
  ├─→ 信息提取
  │     ├─→ 正则表达式(格式明确的数字字段)
  │     └─→ LLM(语义复杂、正则失败的字段)
  │
  └─→ 词频/关键词分析
        ├─→ 词频统计(Counter)
        └─→ TF-IDF(区分文档的关键词)
              │
              ▼
        结构化数据(可纳入回归/建模)

关键要点:

  1. 先清洗,后分词,再分析——顺序不能乱
  2. 金融术语需要自定义词典——否则分词结果不可信
  3. 正则和 LLM 互补——正则处理大部分,LLM 处理正则搞不定的
  4. TF-IDF 优于简单词频——它能发现真正有区分度的关键词

下一章将在本章预处理的基础上,进入文本分析的核心应用——情感分析与文本建模:用词典方法、机器学习和大语言模型来量化文本的”语气”和”情感”,并将文本指标纳入金融量化分析框架。


23.7.1 章末练习

  1. 基础题: 使用 akshare 获取某家上市公司最近 20 条公告标题(ak.stock_notice_report()),对标题做分词和词频统计,绘制 Top 15 高频词的条形图。

  2. 应用题: 对本章提供的 8 条借贷公告,分别用正则表达式和 LLM 提取贷款信息,将两种方法的结果整理为 DataFrame 进行对比。哪些字段两种方法结果一致?哪些存在差异?差异的原因是什么?

  3. 应用题: 对本章提供的 6 条财经新闻,使用 TF-IDF 提取每条新闻的 Top 5 关键词。哪些词在多条新闻中都出现但 TF-IDF 得分很低?哪些词只在一条新闻中出现但得分很高?

  4. 思考题: 如果你要分析央行货币政策报告的语气变化趋势,请设计一个从”获取文本”到”输出语气指数时序图”的完整分析方案。画出流程图,列出每个步骤需要的工具和方法,以及可能遇到的挑战。


23.7.2 参考文献

  • Loughran, T., & McDonald, B. (2011). When is a liability not a liability? Textual analysis, dictionaries, and 10-Ks. Journal of Finance, 66(1), 35–65. Link, Google.