
体育赛事新闻标题是典型的高情绪密度短文本。比如一条标题中同时出现“0冠1亚”“止步16强”“包揽男女单冠军”读者还没看正文就能感受到强烈的惋惜、意外和对比。对开发者来说这类标题是中文文本情感分析的好样本文本短、情绪词集中、事件词明确但同样也容易因为分词、停用词和情感词典差异而判断不准。这篇文章用一个可运行的 Python 示例拆解从数据准备、分词、关键词提取到情感打分和可视化的完整流程。学完后你可以把同一套方法用到舆情监控、内容标签、新闻聚合等场景也可以把它改造成一个定时跑批的文本分析小工具。整个示例围绕“体育新闻标题”展开不评价具体比赛结果只把标题当作一份文本语料来处理。这样既能避免情绪化判断也能让技术方案保持可复现、可扩展。1. 为什么把体育新闻标题作为情感分析样本1.1 体育标题的三层信息结构从信息结构看体育新闻标题包含三层。事件层是“谁在什么赛事拿了什么结果”例如“冠军赛冠军”“男单16强”“亚军”情绪层是“天啊”“最差记录”“爆冷”“遗憾”等主观表达修辞层则包括“包揽”“全军覆没”“三打一”等带有对比和夸张色彩的词汇。这些词语会影响读者的情绪感知自然也成为情感分析模型关注的输入。中文文本情感分析也叫意见挖掘是让程序判断一段自然语言表达的是积极情感、消极情感还是中性情感。对新闻标题而言分析的粒度一般只需要到句子级或段落级不要求识别具体对象。体育标题的特别之处在于字数少事件词和情绪词混在一起标点和叹词使用频繁适合检验分词、停用词和情感词典的效果。如果把标题单纯当作字符串处理程序只能统计字符频率无法理解“冠军”和“止步”之间的语义差异。只有经过分词、关键词提取、情感打分这一条链路才能把一个标题转化成可供后续展示或聚合的数字特征。1.2 情感分析在真实场景里解决什么问题情感分析在体育资讯平台里有几种常见用途舆情监控快速判断一段时间内用户对某支队伍、某场比赛的讨论偏积极还是偏消极。内容分类把报道标题自动标记为“战报”“综述”“遗憾出局”“惊喜夺冠”等类别。智能推荐对情绪倾向相近的文章做聚合提升专题页的编辑效率。辅助写作对编辑拟好的标题给出情绪强度提示避免全部标题都过于夸张。这里不要过度承诺。情感分析分数是模型输出不是业务结论。比如“天啊”在口语里可以表示惊讶也可以表示吐槽模型不一定能区分。上线到生产环境前必须用人工标注样本验证分数和业务判断是否一致。1.3 本文示例的目标和整体流程本文提供一个最小可运行示例流程如下准备若干条体育新闻标题包括一条带有“0冠1亚”“最差记录”的标题。对标题做清洗去除标点、多余空格、不可见字符。使用 jieba 做分词并过滤停用词。使用 jieba.analyse 提取 TF-IDF 关键词。使用 SnowNLP 计算每条标题的情感分数。将结果保存为 CSV并使用 matplotlib 绘制情绪分布图。技术栈选择 Python 3.9、pandas、jieba、SnowNLP、matplotlib。这套组合的好处是安装简单、代码量少、适合学习。如果后续要做更大规模的文本分析可以把处理逻辑封装成函数再接入调度平台。2. 环境准备和示例数据2.1 安装依赖建议使用虚拟环境避免污染系统 Python。创建项目目录后先准备 requirements.txt 文件pandas2.0.3 jieba0.42.1 snownlp0.12.3 matplotlib3.7.2安装命令pip install -r requirements.txt如果你的网络源较慢可以使用国内镜像源pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里要注意版本兼容性。pandas 2.x 需要 Python 3.8 以上snownlp 是纯 Python 实现安装一般比较顺利。如果安装时提示缺少 scikit-learn 或 numpy先根据报错信息补装对应依赖再重新安装。2.2 准备标题样本为了复现分析过程把标题整理成列表。下面语料中包含一条“0冠1亚”的标题以及几条用于对比的积极、中性和消极标题。这些标题只作为文本示例不构成对真实比赛的判断。import pandas as pd raw_titles [ 天啊0冠1亚国乒再次无缘冠军赛冠军创下最差记录, 张本智和兄妹包揽男女单冠军国乒男单全军覆没止步16强女单获得亚军。, 中国队包揽全部五项冠军年轻球员表现出色。, 爆冷头号种子首轮出局球迷直呼不敢相信。, 老将逆袭绝境翻盘晋级决赛。, 遗憾出局但拼尽全力未来可期。, ] df_raw pd.DataFrame(raw_titles, columns[raw_title]) df_raw.to_csv(titles.csv, indexFalse, encodingutf-8-sig) print(df_raw)把标题保存为 CSV 的好处是后续可以脱离代码独立维护语料新增标题时不需要修改 Python 文件。保存时使用utf-8-sig编码避免用 Excel 打开文件时出现中文乱码。2.3 文本清洗原始标题包含叹号、逗号、句号等标点这些标点对情感判断有一定作用但会影响 jieba 的关键词提取。实际项目中通常先做一层清洗把标点替换为空格再对连续空格做压缩。import re def clean_text(text): text str(text).strip() text re.sub(r\s, , text) text re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) return text.lower() df_raw[clean_title] df_raw[raw_title].apply(clean_text) print(df_raw[[raw_title, clean_title]].to_string(indexFalse))清洗逻辑里有三个关键点保留中文字符、英文字母和数字其余字符替换为空格。英文字母转小写让“ABC”和“abc”统一。数字和英文保留是为了保留“0冠1亚”中的数字信息。如果你处理的文本里数字没有业务意义也可以把数字去掉正则改成[^\u4e00-\u9fa5]。清洗后会得到类似下面的结果天啊 0冠1亚 国乒再次无缘冠军赛冠军创下最差记录 张本智和兄妹包揽男女单冠军 国乒男单全军覆没止步16强 女单获得亚军注意清洗会去掉叹号这可能削弱“天啊”等口语词的情绪强度。后面情感分析时可以对原始标题和清洗后标题分别计算分数再对比差异。3. 分词和关键词提取3.1 为什么要先分词中文标题没有空格程序无法直接统计“词”。如果不分词整条标题会被当成一个长字符串关键词提取和情感分析都很难展开。分词就是把连续的汉字序列切分成有语义边界的词语。jieba 是目前常用的中文分词库支持精确模式、全模式和搜索引擎模式。本文使用默认的精确模式适合文本分析场景。3.2 用 jieba 分词并过滤停用词定义一组基础停用词过滤“的、了、在、是”等没有实际意义的词import jieba stopwords { 的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 再次, 创下, 获得, 成为, 进入 } def cut_words(text): words jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords and len(w) 1] df_raw[words] df_raw[clean_title].apply(cut_words) print(df_raw[[raw_title, words]].to_string(indexFalse))过滤停用词时要小心。比如“再次”“创下”在普通文本中可能是停用词但在体育新闻标题里“再次无缘冠军”和“创下最差记录”是重要的情绪信号。因此停用词表必须结合业务语料反复调整不能直接套用网上的通用停用词表。3.3 用 TF-IDF 提取关键词jieba 提供analyse.extract_tags可以基于 TF-IDF 算法提取关键词。短文本标题的 IDF 统计不一定准确但在示例级别够用。import jieba.analyse def extract_keywords(title, topK3): return jieba.analyse.extract_tags(title, topKtopK, withWeightFalse) df_raw[keywords] df_raw[clean_title].apply(extract_keywords) print(df_raw[[raw_title, keywords]].to_string(indexFalse))如果需要查看关键词权重可以改为withWeightTrue返回格式是“关键词: 权重”的列表。3.4 专有名词分词不准的常见问题在输入标题里“张本智和”是人名。默认情况下jieba 可能把它切成“张本”“智和”两个词。解决办法是加载自定义词典。在项目目录中创建userdict.txt张本智和 1 nr 国乒 2 nz 冠军赛 3 n加载方式jieba.load_userdict(userdict.txt)加载词典必须在第一次 jieba 分词之前完成。如果分词已经执行过最好重启 Python 进程再测试。自定义词典格式是“词语 词频 词性”词频数字越大分词时越倾向于保留完整词。加载后可以用下面方式验证print(jieba.lcut(张本智和兄妹包揽男女单冠军)) # 预期输出包含“张本智和”或“张本智和”和“兄妹”这是一个很容易踩的坑词典文件路径写错、格式不对、或者 load_userdict 调用太晚都会导致词典不生效。生产环境里建议把自定义词典路径外置到配置文件方便更新。3.5 查看关键词结果运行后可能得到类似下面的表格结构raw_titlewordskeywords天啊0冠1亚国乒再次无缘冠军赛冠军创下最差记录天啊, 0冠, 1亚, 国乒, 无缘, 冠军赛, 冠军, 最差, 记录冠军赛, 国乒, 最差张本智和兄妹包揽男女单冠军国乒男单全军覆没止步16强女单获得亚军。张本智和, 兄妹, 包揽, 男女, 单冠军, 国乒, 男单, 全军覆没, 止步, 16强, 女单, 亚军张本智和, 全军覆没, 男单中国队包揽全部五项冠军年轻球员表现出色。中国队, 包揽, 全部, 五项, 冠军, 年轻, 球员, 表现出色中国队, 表现出色, 冠军注意这里展示的是示例结构不是唯一输出。你运行时的分词结果会受 jieba 版本、自定义词典、停用词表影响。关键是观察关键词能否反映标题的核心事件和情绪倾向。4. 情感打分和结果分析4.1 SnowNLP 情感分数的含义SnowNLP 的中文情感分析基于贝叶斯模型sentiments属性返回 0 到 1 之间的浮点数。越接近 1 表示模型认为情感越积极越接近 0 表示越消极0.5 附近表示中性。这个模型的训练语料偏向电商评论对新闻标题这种短文本不一定准确。比如“爆冷”在体育语境里表示意外情感倾向可能是惊讶或惋惜但模型可能把它识别为中性词。因此分数只能作为参考不能作为绝对标准。4.2 逐条计算情感分数分别对原始标题和清洗后标题计算情感分数from snownlp import SnowNLP def sentiment_score(text): if not text.strip(): return 0.5 return SnowNLP(text).sentiments df_raw[sentiment_clean] df_raw[clean_title].apply(sentiment_score) df_raw[sentiment_raw] df_raw[raw_title].apply(sentiment_score) print(df_raw[[raw_title, sentiment_clean, sentiment_raw]].to_string(indexFalse))这里把两个结果都列出来是为了观察清洗过程是否改变了情感信号。如果一条标题在清洗前被识别为消极清洗后变成中性就可能是因为叹号或逗号被替换后模型少了一部分语气信息。实际使用时可以只保留其中一个分数。如果业务上更关注标题的原始表达建议使用sentiment_raw。4.3 划分情绪类别把连续分数转换成业务可读的标签。这里设定一个简单规则小于 0.4 判为消极。大于 0.6 判为积极。其他判为中性。def emotion_label(score): if score 0.4: return 消极 elif score 0.6: return 积极 else: return 中性 df_raw[emotion_clean] df_raw[sentiment_clean].apply(emotion_label) df_raw[emotion_raw] df_raw[sentiment_raw].apply(emotion_label) print(df_raw[[raw_title, sentiment_raw, emotion_raw]].to_string(indexFalse))阈值可以根据业务调整。如果希望更灵敏地捕捉“遗憾出局”这类略带消极的表达可以把阈值改成 0.45如果只希望标记强情绪标题可以分别收紧到 0.3 和 0.7。阈值的选取应该基于人工标注样本而不是凭感觉。4.4 生成统计汇总使用 pandas 的groupby统计各情绪类别的数量summary df_raw.groupby(emotion_raw).size().reset_index(namecount) print(summary)同时保存完整结果到 CSVdf_raw.to_csv(title_sentiment_result.csv, indexFalse, encodingutf-8-sig)保存结果文件是排查问题的重要步骤。如果后续发现某个标题的情感分数不符合预期可以追溯到处理前的文本、清洗后的文本、分词结果和关键词结果定位是哪一步出了问题。5. 可视化呈现5.1 解决 matplotlib 中文乱码matplotlib 默认字体不包含中文字符直接绘制中文标题会出现方块。绘图前需要设置中文字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] FalseWindows 上通常有 Microsoft YaHei 或 SimHeimacOS 上可以设置 Arial Unicode MSLinux 服务器上可能都没有需要先安装中文字体或者使用字体文件路径方式加载。5.2 绘制情绪类别柱状图用柱状图展示消极、中性、积极标题数量fig, ax plt.subplots(figsize(8, 5)) colors {消极: #d9534f, 中性: #f0ad4e, 积极: #5cb85c} order [消极, 中性, 积极] summary[emotion_raw] pd.Categorical(summary[emotion_raw], categoriesorder, orderedTrue) summary summary.sort_values(emotion_raw) ax.bar( summary[emotion_raw], summary[count], color[colors.get(e, #999999) for e in summary[emotion_raw]], ) ax.set_title(体育新闻标题情绪分布) ax.set_xlabel(情绪类别) ax.set_ylabel(标题数量) plt.tight_layout() plt.savefig(emotion_distribution.png, dpi150) plt.show()这个图适合快速展示整体分布。如果样本量很少比如只有 6 条标题不要因为柱子高低得出过强结论。5.3 绘制每条标题情感分数横向条形图横向条形图适合观察单条标题的分数排序plot_df df_raw[[raw_title, sentiment_raw]].copy() plot_df[short_title] plot_df[raw_title].str.slice(0, 18) plot_df plot_df.sort_values(sentiment_raw) fig, ax plt.subplots(figsize(10, len(plot_df) * 0.6)) ax.barh(plot_df[short_title], plot_df[sentiment_raw], color#5bc0de) ax.axvline(0.5, colorgray, linestyle--, linewidth1) ax.set_xlim(0, 1) ax.set_title(各标题情感分数对比) ax.set_xlabel(SnowNLP sentiment) ax.set_ylabel(标题) plt.tight_layout() plt.savefig(sentiment_bar.png, dpi150) plt.show()图中 0.5 处的竖线是中性参考线。可以快速看出哪些标题分数明显低于 0.5哪些高于 0.5。5.4 词云扩展如果你希望看到标题中的高频词可以安装 wordcloudpip install wordcloud生成词云的代码from wordcloud import WordCloud all_words .join(df_raw[clean_title]) font_path C:/Windows/Fonts/msyh.ttc wc WordCloud( font_pathfont_path, width800, height400, background_colorwhite ).generate(all_words) wc.to_file(title_wordcloud.png)font_path必须指向系统真实存在的中文字体文件否则生成图片全是方块。在 Linux 上可以先运行fc-list :langzh查找可用字体。6. 常见问题排查6.1 安装依赖失败现象pip install提示依赖冲突或找不到包。常见原因Python 版本过低、pip 源不稳定、缺少底层编译工具。检查方式python --version pip --version处理方法优先使用 Python 3.9 以上环境在虚拟环境中安装使用国内镜像源。如果snownlp安装报错先安装它依赖的 numpy、scikit-learn再重试。6.2 分词结果不理想现象人名被切碎队伍名被拆开“张本智和”变成“张本”“智和”。常见原因jieba 默认词典没有收录该专有名词自定义词典未加载词典加载后没有重启进程。检查方式打印分词结果确认是否在jieba.load_userdict之后执行分词。处理方法加载自定义词典或针对个别词调用jieba.add_word(张本智和)。建议把自定义词典单独维护为一个文件方便团队共享。6.3 SnowNLP 情感分数偏差大现象明显消极的标题分数接近 0.8明显积极的标题分数反而低于 0.4。常见原因模型训练语料与体育新闻标题差异太大预处理把叹号等语气符号删除阈值设置不合理。检查方式先对原始标题计算一次分数再对清洗后标题计算一次分数对比差异。打印分词结果确认“最差”“爆冷”“遗憾”等词是否保留了。处理方法不要直接依赖默认模型。可以收集几百条体育标题人工标注积极/消极/中性然后用 SnowNLP 的训练接口做领域微调或者改用基于词典的情绪词加权方法。更重要的是分数只是参考业务判断要结合人工规则。6.4 matplotlib 中文乱码现象图中中文显示为方框或空白。常见原因系统缺少中文字体或font.sans-serif配置的字体名不正确。检查方式fc-list :langzh在 Windows 上可以检查C:/Windows/Fonts/下是否存在 msyh.ttc、simhei.ttf。处理方法plt.rcParams[font.sans-serif]配置成系统实际存在的字体名称或者用font_manager.FontProperties指定字体文件路径。6.5 CSV 文件乱码现象用 Excel 打开 CSV中文显示乱码。常见原因保存时用了utf-8而 Excel 默认按本地编码打开。处理方法to_csv时设置encodingutf-8-sig。这个前缀会让 Excel 正确识别 UTF-8 文件。6.6 关键词全是“冠军”“比赛”等高频词现象提取出的关键词没有区分度每条标题都包含相同词。常见原因停用词表太小没有过滤掉体育领域高频词样本量太少TF-IDF 的 IDF 统计失真。处理方法维护一个体育领域停用词表把“比赛”“冠军”“球员”“获得”等业务词加入停用词或低频调整也可以收集更大规模的语料重新计算 IDF。问题现象常见原因检查方式处理建议安装依赖失败Python 版本过低或 pip 源不稳定python --version查看 pip 报错使用虚拟环境、升级 Python、换镜像源人名被切碎默认词典缺少专有名词打印分词结果加载 userdict 或调用 add_word情感分数偏差模型训练语料差异大对比原始标题和清洗后分数调整阈值、领域微调或结合人工规则matplotlib 中文乱码系统无中文字体或未配置fc-list :langzh设置正确字体名或字体文件路径CSV Excel 乱码保存为 utf-8 而非 utf-8-sig用文本编辑器打开保存时使用 utf-8-sig关键词无区分度停用词表不完整打印每条关键词扩充领域停用词表或增大样本量7. 最佳实践和扩展方向7.1 学习环境与生产环境差异本文示例适合在本地直接运行。学习环境里文件和代码放在同一个目录运行完看输出和图片即可。生产环境需要考虑下面几点把清洗、分词、关键词、情感打分封装成独立函数方便单元测试。输入数据来源从本地 CSV 换成数据库表或消息队列。增加日志记录每条处理记录的原始文本、清洗文本、得分和异常信息。使用缓存避免重复计算相同标题。维护自定义词典和模型版本确保部署环境与训练环境一致。一个可复用的函数接口示例def analyze_batch(texts): records [] for text in texts: clean clean_text