ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python微博舆情聚类实战:从爬虫到TF-IDF与KMeans的完整链路

Python微博舆情聚类实战:从爬虫到TF-IDF与KMeans的完整链路 简介面向对舆情分析、自然语言处理与文本聚类感兴趣的Python学习者这份资源以微博热点话题为切入点整合了从数据获取、中文分词到聚类分析的可运行项目。实现涉及jieba、pandas、scikit-learn、matplotlib、requests等常用库可用于课程设计、毕业设计或舆情监控入门实践。资源共9个文件以5个Python程序脚本为主辅以2个文本说明、1个Markdown说明文档和1个CSV数据集整体仅50KB结构小巧、便于快速下载与二次修改。目前已有209人学习。通过阅读说明文档并运行脚本可以直观理解TF-IDF特征提取、KMeans聚类、矩阵生成、IP构建等环节同时看到基于Matplotlib的可视化输出数据文件与错误日志也便于对照调试适合希望以真实社交媒体数据完成一次完整文本挖掘流程的初中级学习者。1. 用 Python 做微博舆情聚类这个项目到底在解决什么问题微博热搜每天几十个话题人工逐条看评论根本看不过来更难判断哪些话题其实在聊同一件事。比如某个明星的负面消息可能同时挂在三四个热搜词下评论区的关键词完全不同但事件是同一个。舆情聚类分析要解决的就是这个把海量微博文本按语义相似度自动分组让运营或者分析师一眼看出当前舆论场上有几个真正的热点簇每个簇的核心议题是什么。这个项目的实现思路很直接——先用爬虫拉数据再用 jieba 分词把文本切成词接着用 TF-IDF 把每一条微博转成向量最后用 KMeans 聚成若干个簇。整条链路不复杂但每一步都有细节坑尤其是中文分词和聚类簇数的选择。适合正在学 Python 文本挖掘、想用真实微博数据练手的人也适合需要快速搭建一个舆情监测原型的从业者。下文会按照项目源码的实际文件结构把每个模块的职责、参数含义和踩坑点拆开讲。2. 项目结构与数据流从爬虫到聚类结果需要哪几个文件打开 sina_analysis-master 这个压缩包里面文件不多但每一条都有明确的分工。一次性把这几个文件的关系理清楚后面跑代码的时候才知道每一步在做什么。2.1 文件清单谁负责采集、谁负责清洗、谁负责计算项目根目录下的文件大致可以分为四类网络请求、文本处理、数值计算、数据存储。spider.py 和 buildip.py 负责数据采集前者抓取微博页面后者维护代理 IP 池td_idf.py 做分词和词频统计matrix.py 负责把文本转成向量矩阵test.py 是主流程的测试入口data 目录下放着运行所需的输入数据与中间产物比如 article.csv 存的是已经抓下来的微博正文cut.txt 是分词后的缓存结果error.txt 记录抓取过程中的失败请求。整个项目的执行顺序是spider.py 采集微博文本存入 article.csvbuildip.py 在采集过程中不断更新可用代理td_idf.py 读取 article.csv 并输出分词结果 cut.txtmatrix.py 基于 cut.txt 构建 TF-IDF 矩阵并做聚类test.py 调用上述模块并打印聚类标签。实际跑的时候不一定要严格按这个顺序因为 article.csv 已经附带在数据目录里你可以直接跳过爬虫阶段从分词开始跑。这样既省时间也避免被微博的反爬策略卡住。2.2 数据文件的作用article.csv 和 cut.txt 分别存什么article.csv 是原始语料每一行代表一条微博核心字段包括微博 ID、发布时间、正文内容、转发数、评论数等。这个文件是整个分析的地基如果语料本身质量差后面聚类出来的簇基本没有参考价值。实际项目中我一般会先看这个 CSV 的行数和正文字段确认没有大面积的缺失值再继续。cut.txt 是分词后的中间结果每一行对应 article.csv 里的一条微博词与词之间用空格隔开。这个文件的好处是分词只跑一次后续调聚类参数时不需要重新处理原始文本。如果你是第一次跑这个项目建议先检查 cut.txt 是否存在。如果存在直接进入矩阵构建如果不存在说明需要先手动执行分词脚本。error.txt 是采集阶段的失败日志主要记录因 IP 被封或者请求超时而丢失的 URL对于纯分析任务可以忽略。2.3 数据处理链路一条微博从字符串变成向量的全过程一条微博文本要进入聚类算法必须经过清洗、分词、去停用词、向量化四个环节。清洗阶段主要剔除 HTML 标签、用户名、URL 和表情符号分词阶段用 jieba 的精确模式把句子切成词序列去停用词阶段过滤掉的、了、在、是这类高频无意义词向量化阶段用 TF-IDF 统计每个词在当前文本中的重要程度输出一个稀疏矩阵矩阵的行是微博列是词值是词频乘以逆文档频率的乘积。KMeans 算法只能处理数值矩阵不能直接吃文本所以 matrix.py 是整个项目中承上启下的模块。它读入 cut.txt利用 Scikit-learn 的 TfidfVectorizer 完成向量化然后调用 KMeans 进行聚类。最终输出每个簇的编号以及每个簇内最重要的若干个词这些词直接决定了这个簇的语义标签。理解这条链路之后你再去看每个 Python 文件里的代码就会有坐标感。3. 三个核心模块拆解分词、向量化与聚类的实现细节3.1 spider.py 与 buildip.py采集端的设计思路和反爬策略spider.py 用的是 requests 库直接发送 HTTP 请求请求目标是微博的移动端搜索接口。选择移动端接口的原因很简单它的返回体比 PC 端小很多而且接口参数比较稳定适合快速抓取。requests.get() 的时候需要带 cookie 和 user-agent 头否则微博会直接拒绝请求。代码里通常会把 cookie 字符串写成一个全局变量每次请求时塞进 headers 字典。buildip.py 的作用是为 spider.py 提供代理 IP。舆情类爬虫最怕的就是账号被限制同一个 IP 高频访问会在短时间内触发微博的风控机制。buildip.py 一般会从免费代理网站上抓取一批 IP然后用多线程挨个测试连通性能用的放进一个列表供 spider 随机取用。免费代理的存活率很低通常只有百分之二三十所以项目里每次请求前都要重新获取一次代理这也是爬虫速度上不去的主要原因。实际使用中我不建议把这个代理模块用于生产环境免费 IP 的稳定性太差容易被微博误判为恶意流量。更稳妥的做法是直接用本机 IP 加请求间隔来控速或者购买高可用代理池服务。如果你只是复现整个分析流程完全可以把 spider.py 跳过直接使用已经附带的 article.csv。3.2 td_idf.pyjieba 分词与自定义词典的作用td_idf.py 这个名字有点混淆因为它实际上做的是分词而不是完整的 TF-IDF 计算。真正的 IDF 权重在 matrix.py 里由 TfidfVectorizer 计算。这个文件的核心函数是读取 article.csv 的正文列逐条调用 jieba.cut() 进行精确模式分词然后把分词结果过滤掉停用词后写入 cut.txt。关键代码如下import jieba import pandas as pd # 读取已采集的微博数据 df pd.read_csv(data/article.csv, encodingutf-8) # 加载自定义词典确保领域词汇不被错误切分 jieba.load_userdict(userdict.txt) # 定义停用词集合 stopwords set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) with open(data/cut.txt, w, encodingutf-8) as out: for content in df[content].fillna(): # 精确模式分词 words jieba.cut(content.strip()) # 过滤停用词和单字词 filtered [w for w in words if w not in stopwords and len(w) 1] out.write( .join(filtered) \n)这段代码做了三件事读取原始语料、用 jieba 精确模式分词、过滤停用词后缓存到本地文件。pd.read_csv() 里的 encoding 参数要特别注意微博数据里经常混有 emoji 或特殊符号统一用 utf-8 读取可以避免大多数编码错误。jieba.load_userdict() 是给分词器喂入领域词表的地方比如新冠疫苗双减政策这类词如果不加词典会被切成新冠和疫苗两个词直接破坏后续聚类结果。stopwords.txt 里的停用词列表是通用的中文虚词集合你可以在网上找到完整的版本也可以根据自己语料中出现的极高词频词做增量补充。3.3 matrix.py 和 test.pyTF-IDF 向量化与 KMeans 聚类的参数逻辑matrix.py 是整个项目的核心计算模块。它先读取 cut.txt使用 TfidfVectorizer 把每一行分词文本转成一个 TF-IDF 向量。这里有几个参数需要关注max_features 控制特征词的数量上限默认取 5000 或者 10000调大这个值可以保留更多低频词但矩阵维度也会变大训练速度下降min_df 表示词至少在多少条文本中出现过设置为 2 可以过滤掉只出现一次的噪声词。代码如下from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans import numpy as np # 读取分词后的文本每行一条微博 with open(data/cut.txt, r, encodingutf-8) as f: corpus [line.strip() for line in f if line.strip()] # 向量化限制特征数过滤低频词 vectorizer TfidfVectorizer(max_features5000, min_df2) X vectorizer.fit_transform(corpus) print(f矩阵形状: {X.shape}) # 设置聚类簇数并训练模型 n_clusters 8 km KMeans(n_clustersn_clusters, random_state42, n_init10) labels km.fit_predict(X) # 输出每个簇的统计信息 for i in range(n_clusters): indices np.where(labels i)[0] print(f簇 {i}: {len(indices)} 条微博)fit_transform() 一步完成词典构建和向量化输出的 X 是一个稀疏矩阵行数是微博条数列数是特征词个数。max_features5000 的意思是只保留 TF-IDF 值最高的 5000 个词这样做的好处是降低噪声维度同时避免矩阵过大导致内存溢出。n_init10 是 KMeans 的一个重要参数表示用 10 组不同的初始质心跑算法取最优结果能有效缓解聚类结果受初始化影响的问题。random_state42 固定随机种子让每次跑出来的结果可复现这一步在调试阶段非常关键否则同一个脚本跑两遍得到不同标签你会以为代码有 bug。test.py 是串联所有模块的入口脚本它做的事情是按顺序调用上述模块并输出每个簇的核心关键词。核心关键词的提取逻辑是对每个簇取簇内所有文本的 TF-IDF 向量的均值然后找出均值向量中分量最大的几个词。这些词就是簇的代表性词汇直接用中文打印出来不需要额外解释。4. 数据清洗与格式兼容编码、标点和缺失值三个高频问题数据清洗是舆情聚类项目中容易被低估的一步。很多刚接触文本挖掘的人拿到 article.csv 就直接跑分词结果聚类出来的簇里全是乱码和空行。这个项目的数据质量总体不错但以下三个问题几乎必然会遇到。4.1 编码问题utf-8 与 gbk 的冲突在什么时候出现微博数据里的中文文本在存储时通常使用 utf-8 编码但 Windows 环境下 pandas 默认读取编码可能是 gbk导致打开 CSV 文件时直接报 UnicodeDecodeError。解决办法是在 pd.read_csv() 中显式指定 encodingutf-8。如果原始文件是在 Windows 本地创建的也可能是 gbk 编码这时候需要把 encoding 参数换成 gbk 进行试读。我一般会写一个函数自动检测编码先尝试 utf-8报错就换 gbk再报错就换 utf-8-sig。def read_csv_auto(path): for enc in [utf-8, gbk, utf-8-sig]: try: df pd.read_csv(path, encodingenc) print(f使用编码: {enc}) return df except UnicodeDecodeError: continue raise ValueError(无法自动识别文件编码)utf-8-sig 和 utf-8 的区别在于前者会处理 BOM 头某些 Windows 工具导出的 CSV 会带 BOM直接按 utf-8 读取会把 BOM 字符留在第一列的表头前面导致列名匹配不上。4.2 标点与空值全角符号和 NaN 对分词结果的影响微博文本里全角标点非常常见比如中文逗号、句号、引号。jieba 分词本身能处理全角标点但分词结果里会混入这些标点符号它们没有任何语义信息却会占据词频统计的份额。停用词表通常只包含词不包含标点所以需要在过滤停用词之前先把标点符号剔除。常见做法是用正则表达式过滤掉所有非中文字符、非英文字母和非数字的符号。另一个问题是 CSV 里的正文列可能存在空值pandas 会把它读成 NaN直接传给 jieba.cut() 会抛异常所以处理前一定要用 fillna() 把空值填充为空字符串。4.3 重复数据与短文本两条相同的微博和过短的正文如何处理微博转发和刷屏会导致 article.csv 里出现大量完全重复的文本。如果不做去重这些重复文本会被 KMeans 聚成一个大簇把真正有区分度的簇给淹没。处理办法是在分词前用 drop_duplicates(subsetcontent) 去重。短文本同样会有问题正文长度不足 5 个字的微博分词后往往只剩一两个词这类文本向量化后非常稀疏容易形成噪声簇。建议把长度过滤阈值设置在 10 个字符左右过滤掉过短的文本。过滤后的语料量如果少于 500 条聚类效果会非常不稳定这种情况下可以先扩充数据再分析。5. 舆情聚类实战避坑跑通项目必须避开的五个坑5.1 现象分词结果里有大量单字聚类特征全是的、了、是原因停用词表没有覆盖项目自带的语料特征微博文本中的语气词和助词占比高比如哈哈哈真的感觉这类词在多个话题里都会高频出现导致 TF-IDF 权重被稀释。解决扩充停用词表把语料中出现频率极高但与主题无关的词加入进去。我一般会先用 CountVectorizer 统计一遍全量词频手动看一眼 Top 50 的词把明显无意义的词收录进 stopwords.txt再重新跑分词。5.2 现象KMeans 聚类结果每次跑都不一样原因没有固定随机种子。KMeans 初始化质心是随机的不设置 random_state 的话每次聚类标签都会变簇的数量和边界也会有差异。解决在 KMeans 构造函数中设置 random_state42。如果还是不稳定继续调大 n_init比如从默认的 10 改成 20让算法在多组初始质心中找到更稳定的收敛解。5.3 现象matplotlib 画图时中文全部显示成方框原因matplotlib 默认字体不支持中文坐标轴标题和图例里的中文字符全部渲染成方块。解决在绘图前指定中文字体常见做法是 plt.rcParams[font.sans-serif] [SimHei] 或者 [Microsoft YaHei]。同时设置 plt.rcParams[axes.unicode_minus] False否则负号会显示成乱码。import matplotlib.pyplot as plt # 解决中文显示问题 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False5.4 现象pandas 读取 CSV 时报错 ParserError原因article.csv 里的正文字段包含换行符或者未转义的引号导致 pandas 解析时以为一行数据提前结束。解决读取时指定 enginepython这个解析引擎对不规则格式的容忍度更高。如果文件较大也可以先检查原始文件里是否有多余的引号用文本编辑器统一处理后再读取。5.5 现象聚类结果里有一个簇几乎包含了一半的数据原因语料中存在明显的主题偏向比如数据采集时搜的关键词过于集中或者没有过滤掉与热点事件无关的泛资讯类账号。解决检查这个大类簇内部的关键词是否真的语义一致。如果关键词很杂说明聚类簇数 k 设置得太小需要调大如果关键词一致说明语料本身确实大量围绕同一个话题这时候要么扩充语料引入更多元的内容要么接受这个结果并用 sub-clustering 把大类簇再拆一层。6. 聚类效果自检用轮廓系数确定最优簇数并稳定结果跑完一次聚类之后最常被问到的问题就是k 为什么是 8不是 5 或者 20这个项目源码里 n_clusters 是写死的但实际工作中不能这样操作。确定最优簇数的方法是用轮廓系数做区间搜索轮廓系数的取值范围在 -1 到 1 之间值越大表示簇内越紧凑、簇间越分离。具体做法是写一个循环让 k 从 4 遍历到 20对每个 k 值都做一次 KMeans 聚类并计算轮廓系数最后把结果画成折线图找轮廓系数最高点对应的 k。如果曲线没有明显的峰值而是平滑下降说明语料本身就没有清晰的簇结构这时候无论 k 取多少都很难得到理想结果需要回到数据源去检查语料的质量。from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt silhouette_scores [] k_range range(4, 21) for k in k_range: km KMeans(n_clustersk, random_state42, n_init10) labels km.fit_predict(X) score silhouette_score(X, labels) silhouette_scores.append(score) print(fk{k}, 轮廓系数{score:.4f}) # 绘制轮廓系数随 k 变化的曲线 plt.figure(figsize(10, 6)) plt.plot(k_range, silhouette_scores, markero) plt.xlabel(簇数 k) plt.ylabel(轮廓系数) plt.title(不同簇数下的轮廓系数对比) plt.show()轮廓系数的计算需要用到整条微博的 TF-IDF 矩阵如果语料量过大可以抽样一部分文本先试算。这个脚本跑一次的时间取决于矩阵大小几千条微博的规模通常只需要几秒钟。选定最优 k 之后把 KMeans 的 n_clusters 参数更新成这个值再用完整的矩阵重新训练一次。轮廓系数不是越高越好比如 k 等于 4 时轮廓系数是 0.35k 等于 12 时是 0.38这时候应该选 4 而不是 12因为簇数太多会失去舆情研判的意义——你不可能让运营人员每天盯着十几个热点簇去分析。轮廓系数的用途是辅助决策不是替代决策。我自己的习惯是每次调完聚类参数后强制走一遍完整流程——重新读 cut.txt、重新构建矩阵、重新计算轮廓系数、保存聚类标签。文本聚类的坑在分词和编码不在算法本身。数据干净、流程固定结果就稳定。希望这个项目能帮你在微博舆情分析这条路上少走一段弯路。本文还有配套的精品资源点击获取
返回列表