Python中文文本分析入门:从数据清洗到情感分析实战指南
1. 文本分析的门槛到底在哪里为什么这一期笔记值得记写这篇笔记的时候我正在跟一组物流客服评价数据较劲。表格里的数值型字段配送时长、退款金额、客户等级处理起来很顺手一看到“评价内容”那一列的文本就犯愁——几百条中文短句挤在一起既没法直接求平均值也画不了折线图。身边的同事大多直接跳过这类文本字段理由是“太麻烦”但我心里清楚这些评价文字里藏着真实的问题线索客户反复提到的“包装破损”“速度慢”“客服态度”都是最直观的业务信号。这就是文本分析的价值。它不是在跟传统的数据分析比谁更高端而是解决一个真实问题把自然语言这种非结构化数据转换成可以被统计、被建模、被可视化的结构化特征。学会这一套东西复盘投诉工单、分析问卷填空题、梳理社交平台的用户反馈都能用上。这期笔记是我“数据分析学习总结笔记”系列的第17篇核心记录一次完整的文本分析入门案例——从原始文本入手完成数据清洗、分词、关键词提取、词频统计、词云可视化和朴素情感打分形成一套可以反复套用的基础流程。整个案例没有高级算法、没有深度模型用的都是常见Python库适合刚入门数据分析、想挑战“非数值型数据”的学习者。先给完全没接触过文本分析的读者一个直观对比感受一下文本数据分析和传统的表格数据分析在流程上的差异环节传统结构化数据分析文本分析数据形态数值、日期、分类字段任意长度的句子、词组第一步工作缺失值、异常值检查清洗噪音、去重、统一编码核心变换聚合、透视、归一化分词、向量化、特征提取常用输出统计表、趋势图词频榜、词云、情感倾向分布很多人在这一关被劝退是因为习惯性地用“处理数值”的思路去套文本。看到一列句子先问“能不能转数字”而不是问“这句子里有哪些内容单元值得统计”。一旦把视角切换到“内容单元”你自然会进入到分词和词频的世界。2. 案例选型与整体路线用商品评论文本做一次完整的分析闭环我给自己定的案例目标是拿到一批商品评论原始文本经过一整套流程输出三个结论——整体口碑倾向好评多还是差评多、用户高频关注点大家最常提什么、代表性主题词正面和负面各自的关键特征。刚开始我也想过用学术范儿的数据集练手比如新闻分类、情感标注语料。实践之后发现入门阶段最合适的其实是商品评论原因有三数据量可控、业务语境足够自然不需要额外背景知识就能判断一条评论的情感倾向。比如“物流很快包装结实”是好评“用了三天就出问题”是差评任何人都有这样的生活直觉可以随时验证模型输出是否合理。我把完整的分析链路拆成了六个阶段后面几章逐一展开构造或采集一批原始评论文本入门阶段直接用模拟数据更容易聚焦流程数据清洗去重、去除无意义字符、统一文本格式中文分词把一条长评切成有意义的词语序列过滤停用词去掉“的”“了”“就”这类高频但无业务含义的词关键词提取与可视化按词频排序和TF-IDF加权画出词云朴素情感打分用正向词表、负向词表和程度词组合判断评论情绪这个流程看起来很朴素但恰恰适合入门。它覆盖了文本分析的绝大部分基础技能而且每一步都能看到直观的输出——看到词云图谱逐渐成型、情感分数和人工判断基本一致的那一刻比任何教科书都更能建立信心。我用的工具就是Python jieba pandas wordcloud scikit-learn没有上大模型。原因很简单入门阶段如果直接套深度学习方案模型像一个黑盒子数据在里面的每一步变形都看不透出了问题也无法判断是数据问题还是模型问题。先用规则和统计手段跑通一个基础流程后续哪怕上手更复杂的模型你也能知道那些训练参数到底在优化什么。3. 环境准备与数据清洗编码乱码和噪音文本是怎么处理的3.1 环境清单与安装实操环境是Python 3.10建议直接用Anaconda或miniconda管理依赖省去不少版本冲突的烦恼。需要用到的核心库pip install pandas jieba wordcloud matplotlib scikit-learn版本说明一下jieba基础分词用0.42.1版本即可wordcloud需要注意图形渲染依赖Pillow和matplotlibpandas建议选1.5以上版本能减少一些字符串处理的警告噪音。首次运行wordcloud如果你遇到中文字体变成方块不要慌这不是代码问题而是缺少中文字体配置。后面我会专门提到字体参数的设置。3.2 模拟评论数据的构造因为这篇笔记聚焦流程本身我给自己准备了一份模拟评论文件comments.csv包含三个字段评论ID、评论时间、评论文本。文本内容参考了真实场景中常见的说法比如“物流很快第二天就到了”“包装有破损盒子都压扁了”“音质还可以但是这个价位来说已经很值了”“连接不稳定经常断连重连”等等总共造了300条。你也可以用Excel表格手写生成这样一份数据字段不用多够演示即可。实操中你面对的真实数据往往比自己造的数据“脏得多”。Excel表格里偶尔混入全角空格、换行符网页爬来的文本夹杂HTML标签这些都必须在清洗阶段处理干净否则分词和统计结果会被严重干扰。3.3 清洗操作去重、去符号、去空值加载数据后执行下列操作import pandas as pd import re df pd.read_csv(comments.csv, encodingutf-8) print(df.shape) print(df.head()) # 1. 去除完全重复的记录 df df.drop_duplicates(subset[comment]) # 2. 删除空评论 df df.dropna(subset[comment]) # 3. 去除首尾空格、把全角空格和换行符统一规整 df[comment_clean] df[comment].str.replace(r\s, , regexTrue) df[comment_clean] df[comment_clean].str.strip() # 4. 去除无意义的纯符号评论 def is_noise(text): text_no_symbols re.sub(r[^\u4e00-\u9fa5a-zA-Z0-9], , text) return len(text_no_symbols) 2 df df[~df[comment_clean].apply(is_noise)]这里有一个容易忽略的老坑字符集编码问题。pandas读取CSV时默认会用系统编码如果你在Windows上读别人发来的文件经常碰到gbk和utf-8互相打架的情况爆出一堆乱码。建议读取时显式指定编码比如encodingutf-8或encodinggbk去试也可以直接把数据源统一为CSV先把编码固定再导入后续处理。清洗完的文本我建议单独存一份df[[comment_clean]].to_csv(comments_cleaned.csv, indexFalse, encodingutf-8)这是一个非常容易被忽视的好习惯。比如分词发现异常时你可以快速反查原始文本是清洗的问题还是本身内容的问题不需要从头再跑一遍全流程。4. 分词与停用词机器是如何“读懂”一段中文的4.1 为什么中文必须分词英文单词之间天然有空格分隔分词基本是白送。但中文不同“物流很快”四个汉字连在一起机器并不知道哪里是词边界。如果按单字处理“快”字在文本里的含义非常模糊根本无法承载业务判断。分词就是把连续汉字序列切分成有意义的词语序列这一步的准确性直接决定后面所有统计的质量。jieba是目前入门最顺的中文分词工具它内部结合了基于前缀词典的扫描和基于汉字成词能力的隐马尔可夫模型能够识别大多数常见词汇。对于“我来到北京清华大学”这种句子它能正确切分为“我/来到/北京/清华大学”中文个性词汇的识别率在一般场景下表现都不错。4.2 jieba的三种分词模式实践中我主要用两种模式import jieba text 这个耳机音质不错但是蓝牙连接不够稳定 seg1 jieba.cut(text) # 精确模式默认 seg2 jieba.cut(text, cut_allFalse) # 精确模式等价于默认 seg3 jieba.cut_for_search(text) # 搜索引擎模式粒度更细精确模式适合做文本分析——它倾向于给出看起来最自然的词语切分“搜索引擎模式”会把长词进一步拆细一般用于检索场景入门阶段不需要优先考虑。4.3 自定义词典解决“人名品牌词”被拆碎的问题实际跑数据会遇到一个让人抓狂的情况明明“蓝牙耳机”是一个完整业务词分词结果却是“蓝牙/耳机”“某品牌”被拆成了几个字。根本原因是jieba默认词典没有收录这些词或者词频权重不够导致它按通用规则拆分。解决办法是给jieba添加自定义词典。我建了一个userdict.txt每行一个词后面可以加词频和词性蓝牙耳机 10 n 连接稳定性 5 n 降噪效果 10 n 客服态度 5 njieba.load_userdict(userdict.txt)加完自定义词典再看同一句话的分词结果业务相关词组能够被完整保留。这一步在真实项目中很重要尤其是处理产品名词、行业术语、人名地名时没有自定义词典几乎没法得到有意义的关键词统计。4.4 停用词表把“的、了、就”这类噪音词踢出去分词之后你会得到一堆词语但直接数词频的话排前几名的往往是“的”“了”“很”“也”“就”。这些词在语法上有用但是在主题分析里属于纯噪音必须过滤。我整理了一份最小停用词表直接以列表形式写在代码里stopwords set([ 的, 了, 和, 是, 就, 都, 而, 及, 与, 着, 或, 一个, 在, 我, 你, 它, 这, 那, 有, 很, 也, 但, 并, 等, 还, 挺, 比较, 非常, 有些, 这个, 那个, 因为, 所以, 但是, 不过, 以及, 对于, 虽然, 不然 ]) def filter_stopwords(words): return [w for w in words if w not in stopwords and w.strip()]网上可以下载到更完整的停用词表比如常见的“哈工大停用词表”处理公开场合的文本完全够用。入门阶段我的经验是先下载通用词表再根据自己数据的业务语境做增量扩充比如本次案例把“耳机”“商品”“东西”也加入停用词因为这些词在商品评论场景中太过通用对主题区分没有帮助。注意停用词表不是越多越好。把业务关键词误加到停用词里会导致关键主题直接消失。筛选停用词时多问一句这个词对区分评论主题有没有帮助4.5 完整的分词流水线我把清洗、分词、过滤封装成一个函数方便对整列数据批量处理def seg_text(text): # 精确模式分词 seg_list jieba.cut(text, cut_allFalse) # 过滤停用词和空白 words [w.lower() for w in seg_list if len(w.strip()) 0 and w not in stopwords] return .join(words) df[seg_comment] df[comment_clean].apply(seg_text)分词结果保存完整文本后面统计词频直接按空格拆分就行。补充一个我在第一版跑实验时栽过的跟头早期我把英文大小写混在一起统计结果“A”和“a”被当成两个词词频榜被切成了一堆碎片。建议分词后统一转小写英文缩写另做归一化处理避免无意义的重复统计。5. 从词频到TF-IDF给关键词做一次“通票还是枪手”的判定5.1 词频统计先看整体热点清洗并分词后第一步最简单直观的统计是整体词频。把每条评论的词语序列拼在一起数一数每个词出现了多少次from collections import Counter all_words .join(df[seg_comment]).split( ) word_freq Counter(all_words) top_30 word_freq.most_common(30)执行后我自己复盘了一次结果排名靠前的词多半是“快”“音质”“包装”“稳定”“价格”这些词确实能反映用户对物流、产品性能和价格的关注度。但整体词频有一个先天问题它只考虑“出现次数”不考虑“词在哪些文本里出现”。如果一个词在所有评论里都出现它反而说明不了什么。比如“东西”这种词在商品评论里到处都是但对主题区分毫无意义。5.2 TF-IDF给“到处都在的词”降权在分词场景里TF-IDF是一种更聪明的加权方式。它的想法很简单词频TF一个词在本条评论里出现多次说明它与本条评论关系紧密逆文档频率IDF一个词在所有评论里普遍存在说明它区分度低权重应该被压低把两者相乘留下的是“在本条评论里频繁出现、但在整体评论中不那么普遍”的词。这类词才是区分评论主题的关键。jieba内置了analyse.extract_tags可以直接输出每条评论的TF-IDF关键词import jieba.analyse # 给每条评论提取3个关键词 df[keywords] df[comment_clean].apply( lambda x: ,.join(jieba.analyse.extract_tags(x, topK3)) )如果想拿到全局视角的关键词热度把整列清洗后的评论文本拼接成一个超长字符串再丢给extract_tags就能得到所有评论中综合权重最高的关键词。这与直接用词频做完后再人工筛一遍停用词的思路相比工作量更小。5.3 生成词云注意中文字体否则方块满天飞词云是文本分析中最有视觉冲击力的输出也是最容易“看起来做完”的环节。我用wordcloud库生成词云一个核心参数是font_path——指定中文字体文件from wordcloud import WordCloud import matplotlib.pyplot as plt word_freq_dict dict(word_freq) wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # 换成你机器上的中文字体路径 width800, height600, max_words100, background_colorwhite ) wc.generate_from_frequencies(word_freq_dict) plt.figure(figsize(10, 8)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.show()如果你是在Linux服务器上跑没有Windows字体目录就需要下载一个开源中文字体放到项目里再填对应的font_path。这一步不做词云上全是“豆腐块”非常挫败。提示词云展现的全局词频信息适合向业务方汇报时快速传递“大家最常讨论什么主题”。但如果你要做严谨的分析结论更要参考TF-IDF提取的重点词而不是只看词云里字号最大的那几个词。6. 情感分析用一套朴素情感词典给评论打正负分6.1 从规则开始不急着上一堆训练模型很多入门教程一进到情感分析就搬出训练好的深度学习模型我建议先绕开。第一版流程应该足够透明你能清楚地解释为什么某条评论被判成“正向”。我搭的方案是基于情感词典的得分累加模型包含三张词表正向情感词表比如“好”“快”“稳”“满意”“舒适”“赞”“不错”“清晰”“好用”负向情感词表比如“慢”“差”“破损”“返修”“封”“断”“失望”“辣鸡”否定词表比如“不”“没”“没有”“无法”“不太”“不足”打分逻辑是遍历评论分词结果碰到正向词加1分碰见负向词减1分如果在情绪词前面碰到了否定词就反转极性比如“不稳定”最终算负分。为了对程度差异做一点区分我再加了一个小的程度词表比如“非常”“太”把分数权重变成2“有点”“稍微”变成0.5。我演示用的小型词表直接写在代码里即可pos_words set([好, 快, 稳, 满意, 值得, 流畅, 清晰, 合适, 不错, 赞, 舒适, 实用]) neg_words set([慢, 差, 破损, 坏, 断, 失望, 垃圾, 卡, 贵, 劣质, 问题, 退货]) negate_words set([不, 没, 没有, 无法, 不太, 并不是]) degree_words {非常: 2, 太: 2, 很: 2, 有点: 0.5, 稍微: 0.5, 比较: 1.2}打分函数实现如下def sentiment_score(text): words [w for w in jieba.cut(text, cut_allFalse) if w.strip()] score 0 polarity 1 for i, w in enumerate(words): if w in degree_words: polarity degree_words[w] continue if w in negate_words: polarity -polarity continue if w in pos_words: score 1 * polarity elif w in neg_words: score - 1 * polarity # 遇到普通词重置程度但注意否定词连续出现的情况 if w not in degree_words: polarity 1 return score把函数应用到每条评论上分数大于0判为正向小于0判为负向等于0判为中性。拿模拟数据跑一遍随机抽20条人工核对绝大多数判断是合理的前提是词表覆盖了评论里的情绪词。6.2 朴素方案的偏差比想象中要小最初我担心这个方案太粗糙但经过对比后发现在短文本、口语化评论、业务场景集中的数据里它的命中率很高。因为用户的评论语言高度重复情绪表达方式有限不像长文里那样充满隐喻和反讽。对入门案例来说这个方案够用了。不过也要清楚它的边界。看这句评论“产品本身不错但是售后响应速度太慢了有点失望”。这个词表方案会把“不错”记为正分、“慢”和“失望”记为负分最后得分为负勉强判成负向。可是如果人工看这句话其实是“产品认可、售后不满”的混合评价。朴素情感词典解决不了这种多维度情绪共存的问题后续要细化到“属性级情感分析”就得上带方面抽取的模型了。如果是真实业务场景我更推荐把方案稍作升级按评论里的句式进行分段打分或者只统计负向词的个数来做差评预警。规则方案的优点是可解释、可调整方便你在词表里持续加入业务词汇让模型效果越用越准。6.3 结果的可视化从情感维度看整体口碑拿到每条评论的情感分后统计正、中、负三类评论的占比用饼图或柱状图展示。我画了一个简单的占比柱状图def label_sentiment(score): if score 0: return positive elif score 0: return negative else: return neutral df[sentiment] df[comment_clean].apply(lambda x: label_sentiment(sentiment_score(x))) df[sentiment].value_counts().plot(kindbar)这个分布比例本身就是可以放进日报里的结论。你可以继续把它拆着看负面评论里出现频率最高的词语是什么用第5章的方法分别对正向评论和负向评论做关键词提取能进一步定位“让用户不满的核心点在哪里”。这个拆解动作很推荐做因为它把文本分析从“统计层”推进到了“业务归因层”是后续撰写分析报告的重要素材。7. 文本分析入门避坑清单编码、分词、词表、业务背景这四个坑7.1 第一个坑数据源的编码和格式混乱最常见的开场雷就是乱码。某次我从一个Excel导出的文件里读评论pandas直接报编码错误试了gbk和utf-8都不行。后来发现文件被用“UTF-8-BOM”格式保存过开头多了三个不可见字节解析才失败。处理办法很简单读完CSV后检查列名是否带奇怪的\ufeff前缀若有就做一次列名重命名或者统一先另存为纯UTF-8文本。此外网页或软件导出的评论经常夹带中文引号、省略号、特殊符号。正则清洗时保留汉字、英文字符和数字其余全部替换为空格即可把特殊符号当作分隔噪音处理。7.2 第二个坑不维护停用词表词云一塌糊涂很多新手第一次跑词云结果输出的最大字号是“的”“是”“就”“这个”。搞笑归搞笑但确实说明停用词表没跟上。我给自己的规矩是每个新项目前两轮跑完词频后必须增量修改一次停用词表。跑完看看前50名里哪些词是业务通用的空词就把它加进去。关键词每换一批新数据都要按业务语境调整一次停用词表和自定义词典。停用词表不是一次配好终身使用的东西。7.3 第三个坑业务词汇被分词切碎统计结果失真“降噪深度”“续航时间”“售后服务”这类复合业务词如果不加自定义词典很容易被拆成“降噪/深度”“续航/时间”“售后/服务”。单个分出来的词虽然也有意义但组合词表达的业务概念更具体、更有指向性。很多入门教材不讲这个导致你做完词频后发现词表里全是“服务”“速度”“质量”这类泛化词分析报告写出来没深度。我的操作方法是先跑一次词频把前100个词扫一遍圈出所有带有业务含义的复合词然后集中加进userdict.txt再重新分词。两轮迭代之后词频结果质量会有质的提升。7.4 第四个坑重新审视“词频高重要”这是文本分析最容易犯的认知坑。某次我做的数据里“价格”出现了80次“客服”出现60次按词频排名“价格”似乎是用户最关心的点。但在逐条阅读评论后发现提到“价格”的句子多是“价格还可以”“价格不算贵”而提到“客服”的句子几乎都是“客服不回复”“客服态度差”。词频确实能讲出部分真相但只有结合情感分析和语境判断才能还原完整故事。所以我坚持在文本分析的项目中保留一个“原始文本抽查”环节无论跑出多少漂亮图表最后都要抽取30条左右原始评论逐条阅读。真正决定分析质量的重点不是算法选得多高级而是你对业务语境的敏感度。7.5 一个小技巧把清洗后的文本保存下来做三层备份我自己的习惯是保存三个版本原始数据、清洗后的干净文本、分词后的词序列。每次分析报告里有疑问我都能快速回溯是哪一层出了问题。这个习惯救过我很多次比如有一次客户质疑某产品的正面评价比例偏高我直接从中间结果查到是自定义词典把某品牌的负面复合词误分词成中性词汇几分钟内定位了根因。文本分析入门的最大收获不是学会了某个库的API而是建立一种处理文本数据的思维框架任何一列看似不能分析的文字只要经过清洗、分词、过滤、特征提取这几步都可以变成可用于统计和可视化的结构化数据。这套流程的每一步都不难难点在于对细节较真——编码是否统一、词表是否匹配业务、结果是否有可解释性。当你把这些细节逐一打磨到位文本分析的方法论会像表格透视一样自然地融入你的日常数据分析工具箱。