ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微博舆情分析实战:爬虫、LDA主题模型与情感分析全链路解析

微博舆情分析实战:爬虫、LDA主题模型与情感分析全链路解析 简介基于微博数据的舆情分析项目整合微博爬虫、LDA主题分析与情感分析三大模块源码与配套资料齐全适合作为毕业设计或期末课程设计参考。项目曾获答辩评审98分代码均经过调试测试可直接运行覆盖计算机、通信、人工智能、自动化等相关专业的学习人群新手可对照源码快速上手基础较好的学习者也能在此基础上改进扩展。压缩包共39个文件包含23个Python源码脚本、7份Markdown说明文档、7个TXT语料文本以及词向量模型和Excel表格数据整体仅16.16MB目录按热度计算、主题相似度、情感分析、微博爬虫、LDA等模块划分并附有自建词表、近义词表、停用词表及正负向语料结构清晰便于按模块检索与查阅。已有211人学习下载可从中获得爬虫采集、文本预处理、分词、主题建模、情感计算与可视化的完整实现思路借助自带语料与模型快速复现实验在此基础上修改调整以扩展功能整体具有较高的学习借鉴与二次开发价值。1. 拆一套能跑通的微博舆情分析爬虫、LDA、情感分析缺一不可做舆情分析最折磨人的不是算法本身而是把「采集—清洗—分析—可视化」整条链路拼起来。单独搜微博爬虫、LDA 主题分析、情感分析每块都有教程可轮到自己动手光是登录态 Cookie、词表处理、日期格式这几个细节就能耗掉一个周末。这套基于微博数据的舆情分析项目把链路一次性补齐weibo-crawler 负责采集评论和用户信息LDA 模块产出主题分布emotional analysis 模块做 API/SDK 双版本情感打分和时间序列聚合最后还有三个版本的热度计算脚本收尾。源码经过调试跑通过课程答辩适合期末课程设计、大作业、毕业设计也适合想快速搭一条舆情分析基线流程的从业者直接参考。下面按模块逐个拆开讲。2. 微博爬虫脚本三种采集方式的取舍与翻页参数2.1 项目里的爬虫文件是怎么分的打开 weibo-crawler 文件夹第一眼会被一堆相近的脚本名搞懵comment crawler.py、comments-crawler_random.py、comments-crawler_random仅针对去年的评论.py、user information crawler.py、data cleaning.py外加一个 requirement.txt。第一次看容易以为只是重复造轮子其实分工很明确——基础版是固定爬指定微博的评论随机版用于批量采样仅针对去年的版本锁定的是时间窗口适合做年度复盘或事件回顾。跑任何脚本之前先把依赖装上。requirement.txt 里把 requests、pandas 这些核心依赖列清楚了我一般直接用pip install -r requirement.txt一次装完。装完建议先跑一遍 data cleaning.py 确认环境没问题再动爬虫。清洗脚本依赖的是爬虫产出的原始文件如果你还没爬到数据它会直接报文件不存在这不是代码坏了是执行顺序不对——先爬后洗别倒着跑。2.2 评论爬虫与用户信息爬虫登录态与翻页逻辑微博评论区接口现在基本都要求带登录态 Cookie不带的话很容易拿到空列表或者被重定向到登录页。核心请求长这样import requests import time import random def fetch_comments(weibo_id, cookie, cursor0): url https://weibo.com/ajax/comments/show params { id: str(weibo_id), # 微博的 mid不是分享链接里的整串数字 cursor: cursor # 游标翻页第一页传 0 } headers { Cookie: cookie, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() return data.get(data, []), data.get(next_cursor, 0)这里有两个最容易翻车的参数。第一个是 weibo_id它取的是微博 mid不是分享链接里那串长数字。很多人直接把链接里的字符串塞进去结果永远返回空数据——需要先在浏览器里打开一条微博从 URL 里找到 mid 单独截出来。第二个是翻页机制新版评论接口不是传统的 page 数字翻页而是用 next_cursor 游标第一页传 0拿到响应后再用返回的 next_cursor 继续翻直到 next_cursor 变成 0 为止。每页大概能拿 20 条评论评论区上万条的微博要循环翻很多次才能拿全。我一般会在每页之间 sleep 一个随机值比如time.sleep(random.uniform(1, 3))避免请求频率过高把自己账号搭进去。翻页循环里建议加一个最大页数保护比如最多翻 50 页就停防止某条微博评论数异常多时脚本无限跑下去。user information crawler.py 抓的是用户主页字段粉丝数、关注数、微博数、性别、所在地等。这些字段在后面热度归一化里非常关键。跨账号对比热度时光看转发评论数会严重偏向大 V通常要除以粉丝量级才能对比user information crawler 抓的粉丝数就是给热度_3.py 用的。2.3 随机评论爬虫为什么还要单独做一个仅针对去年的版本comments-crawler_random.py 和基础版的最大区别是采样逻辑先从一个话题或搜索结果里拿到一批微博 id再随机抽一部分做评论文本采集而不是死磕某一条微博。这样拿到的数据覆盖了多个发布者做主题分析时文本多样性更好不容易出现整个语料都在聊同一件事的假象。import random weibo_ids load_search_result_ids() # 从搜索结果页拿到的全量微博 id sample_ids random.sample(weibo_ids, k200) for wid in sample_ids: comments, _ fetch_comments(wid, cookie) time.sleep(random.uniform(1, 2))k 值决定采样规模。我一般会根据后续 LDA 的需求反推如果计划最后做主题分析采样的评论总数最好别低于 2000 条否则主题数稍微多一点每个主题分到的文本就不够看了跑出来全是重叠词。至于仅针对去年的评论那个版本本质是在采集时加了时间窗口过滤把发布时间不在目标年份内的评论直接丢弃。做年度舆情复盘、或者对比去年和今年同一事件的热度这类场景必须用这个版本否则会把今年新产生的评论混进老微博里时间序列直接就失真了。跑完之后用 data cleaning.py 做字段抽取、去重和空值处理再进 LDA 环节——这一步是连接爬虫和分析的核心桥梁别跳。3. LDA 主题分析词表、分词与超参调优的完整链路3.1 预处理三件套自建词表、近义词表、停用词表LDA 能不能跑出可读的主题七成取决于分词。这个项目里给了三张表自建词表.txt、近义词表.txt、停用词表.txt三张表各管一件事。自建词表里放的是网络新词和领域词比如内卷躺平破防元宇宙这类jieba 默认词库里可能没有或分词时被切得稀碎。用jieba.add_word注册之后分词结果才稳定。近义词表用来做归并例如新冠和新冠肺炎在主题词里其实是同一个意思不归并的话主题词会拆得很散。停用词表过滤的了在这类高频无意义词。处理顺序是先加领域词再过滤停用词最后做近义词替换import jieba # 加载自建词表把领域词注册进 jieba with open(自建词表.txt, encodingutf-8) as f: for line in f: word line.strip() if word: jieba.add_word(word) stopwords set() with open(停用词表.txt, encodingutf-8) as f: for line in f: stopwords.add(line.strip()) # 近义词表每行两个词用空格分隔前一个替换成后一个 synonym_map {} with open(近义词表.txt, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 2: synonym_map[parts[0]] parts[1] def clean_text(text): words [] for w in jieba.cut(text): if w in stopwords or len(w) 1: continue words.append(synonym_map.get(w, w)) return wordslen(w) 1这个过滤很关键。单字词在主题模型里几乎都是噪声保留它会把好热这类字塞进每一个主题的高频词列表主题区分度直接下降。近义词替换放在停用词过滤之后也有讲究——先去掉无意义词再归并实质词替换表命中率更高不容易误伤。分词处理.py 把清洗结果按行存成新文本文件每行一条评论。这一步完成后语料质量基本定型后面 LDA 调参改的都是数字改不动内容了。3.2 LDA.py 与 LDA超参.py固定主题数与自动寻优项目里给了两个训练脚本。LDA.py 是固定主题数直接训练适合先快速看效果from gensim import corpora, models dictionary corpora.Dictionary(tokenized_docs) corpus [dictionary.doc2bow(doc) for doc in tokenized_docs] lda models.LdaModel( corpuscorpus, id2worddictionary, num_topics6, passes20, random_state42 ) for topic in lda.print_topics(num_words10): print(topic)num_topics6 是拍脑袋先定的初值passes20 是数据迭代轮数random_state42 固定随机种子。重点强调 random_stateLDA 有随机初始化过程不固定种子的话每次跑出的主题词可能都不一样论文截图和复现都对不上。我见过不少项目改一行参数就重新训练结果主题顺序全变最后截图对不上这就是没固定种子的典型症状。LDA超参.py 做的是自动寻优常见做法是遍历候选主题数从 2 到 15 每个 k 都训练一遍记录困惑度或主题一致性然后画一条曲线挑拐点from gensim.models import CoherenceModel results [] for k in range(2, 16): model models.LdaModel( corpuscorpus, id2worddictionary, num_topicsk, passes20, random_state42 ) cm CoherenceModel(modelmodel, textstokenized_docs, dictionarydictionary) results.append((k, cm.get_coherence())) for k, score in results: print(f主题数 k{k:2d}, 一致性{score:.4f})主题一致性越高不代表越好实际经验是曲线会在某个 k 值出现转折后面增长变缓甚至下跌这个转折点就是相对合理的主题数。几百条评论可能 3 个主题就够几万条才能切出 8 个。代码跑出来的 k 只是参考最终拍板还要看每个主题的高频词能不能用一句人话概括——概括不了就减主题数。3.3 主题余弦相似度用 w2v 验证主题之间的关联topic similarity 文件夹里有 w2v.model、word2vc.py 和主题余弦相似度.py。word2vc.py 训练 Word2Vec 模型训练语料直接复用 LDA 的清洗结果。主题余弦相似度的思路是把每个主题下权重最高的 Top-N 个词取出来用 w2v 词向量求平均得到主题向量然后计算两两主题的余弦相似度import numpy as np from gensim.models import Word2Vec from sklearn.metrics.pairwise import cosine_similarity model Word2Vec.load(w2v.model) def topic_vector(topic_words): vecs [model.wv[w] for w in topic_words if w in model.wv] return np.mean(vecs, axis0) if vecs else np.zeros(model.vector_size) topics [[w for w, _ in lda.show_topic(i, topn10)] for i in range(lda.num_topics)] vecs [topic_vector(t) for t in topics] for i in range(len(vecs)): for j in range(i 1, len(vecs)): sim cosine_similarity([vecs[i]], [vecs[j]])[0][0] print(f主题{i}与主题{j}的相似度: {sim:.3f})为什么用词向量平均而不是直接看词重叠因为两个主题可能完全没有相同的词但语义相近——比如一个主题聊疫情防护另一个聊口罩供应用词重叠算出来是 0用 w2v 才能捕捉到它们实际在聊同一件事。如果两个主题相似度超过 0.7基本说明该减少主题数或加大近义词表归并力度。这个检查在答辩时几乎必被提问你的主题之间区分度多大没有这组数据会很被动。4. 情感分析与时间序列API 版、SDK 版和多日期聚合4.1 情感分析_API版.py 与 情感分析_SDK版.py接口调用方式的取舍emotional analysis 文件夹里有两个并列的情感分析脚本。API 版是直接拿 HTTP 请求调第三方情感分析接口传文本后拿回情感极性、置信分适合先验证链路通不通。SDK 版是用官方 SDK 封装代码量更少自动处理签名、鉴权、超时重试适合正式跑批量数据。两个版本核心参数逻辑一致差异在工程封装层。用 API 版时请求体一般长这样import requests def sentiment_score(text, api_key): url https://api.example.com/sentiment # 以项目内实际配置为准 payload { text: text, apikey: api_key } resp requests.post(url, jsonpayload, timeout5) data resp.json() return { label: data.get(sentiment), # positive / negative / neutral score: data.get(confidence, 0) # 置信度通常在 0~1 之间 }接口类情感分析最常踩的坑是文本长度上限。很多公开接口单条文本限制 200 字或 512 字超过会被截断截断后语义不完整判断很容易翻车。跑批之前先统计评论文本长度分布把超长文本做分段def split_long_text(text, limit200): if len(text) limit: return [text] return [text[i:i limit] for i in range(0, len(text), limit)]分段后再分别请求最后按各段长度加权平均得到整条评论的情感倾向。这个处理不算精细但对接口类情感分析已经够用。SDK 版的好处是这些细节大部分被封装掉了但你还是要知道底层有截断逻辑——换了一个 SDK 版本默认长度变了结果可能悄然变化。正向语料.txt 和负向语料.txt 是拿来校验的抽 100 条正向和 100 条负向逐条过接口看判定准确率在什么水平这个步骤能防止接口输出和你的业务场景完全对不上。4.2 正向比重与情感均值从单条评论得分到整体倾向单条评论的得分是散的要变成舆情结论需要聚合。正向比重.py 算的是情感极性为正的评论占全部评论的比例这在事件舆情里比均值更直觉比如某个话题正向比重只有 0.21基本可以判定舆论偏负面如果接近 0.5说明争议很大。情感均值则按天或按话题聚合。def positive_ratio(scores, threshold0.6): pos sum(1 for s in scores if s threshold) return pos / len(scores) def daily_mean(df): return df.groupby(date)[sentiment_score].mean()threshold 这个参数值得单独调。有的接口打分偏保守分数全部挤在 0.4 到 0.7 之间这时候把正负分界线定在 0.6 会漏掉大量偏正样本。我一般会先画一张分数分布直方图看是不是双峰形态再根据波谷位置定阈值——直接套默认值是最省事但也是最容易让结论失真的一种做法。根目录 analysis-main 里的 Emotional mean.py 和 Comment mean.py 跟这里的脚本是配套的一个按日期聚合情感均值一个按评论维度聚合跑哪个取决于你要画什么图。这条链路的信息要完整记录用了哪个接口、阈值多少、语料多少条答辩时这些数字比结论本身更能扛住追问。4.3 多日期降维与折线图绘制把情感分数变成时间序列舆情分析最终要给结论结论最好有趋势图。多日期降维.py 做的事是把每条评论一个时间戳的明细数据压缩成每天一个情感均值的时间序列。常见做法是先把日期格式统一再 groupby 聚合。修改日期格式.py 解决的是时间字符串不统一的问题。爬下来的时间有的长这样2024-01-05 08:30:00有的短这样01-05还有带小时前昨天这种相对时间的。不先统一格式groupby 出来的日期会碎成几百个桶。import pandas as pd df[date] pd.to_datetime(df[created_at], formatmixed) df[date] df[date].dt.strftime(%Y-%m-%d) daily df.groupby(date)[sentiment_score].mean().reset_index()pd.to_datetime 加 formatmixed 会让 pandas 自动识别混合格式比手动写正则省心。折线图绘制.py 用 matplotlib 把降维后的数据画出来import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(daily[date], daily[sentiment_score], markero, linewidth1.5) plt.xlabel(日期) plt.ylabel(情感均值) plt.xticks(rotation45) plt.tight_layout() plt.savefig(sentiment_trend.png, dpi150)画折线图有个隐藏细节如果情感均值接近 0曲线会紧贴横轴看不出波动。我一般会根据分数范围设plt.ylim(0, 1)或者做一次 min-max 归一化趋势才明显。这个图在答辩里几乎必被提问多花十分钟调整比现场被问住划算得多。5. 舆情项目避坑指南限流、主题漂移与分数异常的排查顺序5.1 爬虫第一轮就超时或返回空列表现象按 README 跑 comment crawler.py返回数据是空列表或者前几条评论拿到了翻几页后一直超时。原因第一是 Cookie 没带全或已过期第二是翻页间隔太短被服务端限流。微博评论接口对匿名请求基本是直接拒绝登录态 Cookie 里包含的 uid 和 sub token 缺一不可。解决先用浏览器登录微博打开开发者工具复制完整 Cookie 替换到脚本头部。请求之间加随机 sleep至少 1 秒起步翻页越频繁间隔越长。如果短时间需要爬大量数据跑到 500 条就停一下换个时间再继续。别跟限流较劲把账号搭进去得不偿失。5.2 LDA 每次生成的主题都不一样现象同一个语料、同一个参数隔一天再跑主题词完全换了一套论文截图没法复现。原因gensim 的 LdaModel 随机初始化默认每次运行拿到不同的初始分布迭代轮次不足时结果抖动更明显。很多误用是加了 num_topics 但没设 random_state也没调 passes。解决训练时固定random_state42这类整数种子把 passes 从默认值调到 20 或 30alpha 和 eta 先用默认值跑一版。如果换了 Python 或 gensim 版本导致结果不一致在代码里记录版本号答辩时能直接答出来。顺手把训练参数写进输出文件的文件名比如lda_k6_pass20_seed42.txt后续对比就不用手动翻日志。5.3 情感分析返回的分数全是同一个值现象跑完情感分析几百条评论得分全是 0.5 或同一个结果正向比重怎么算都是 0.5。原因最常见的是文本传参带上了空白字符或换行符接口直接判定为无效输入并返回默认值另一种是文本被截断到只剩前缀语义信息丢失。解决请求前先做text.strip()并过滤空内容超长文本按 split_long_text 逻辑分段处理。如果还不行打印一次实际请求的 payload看接口到底收到了什么。90% 的分全是 0问题一眼就能从 payload 里看出来这个打印动作很多人不做反而去怀疑模型有问题。5.4 文件名乱码与日期格式不对齐现象LDA 文件夹里有个文件名显示为excelתtxt.pyWindows 下双击能打开换到 Linux 就报错新爬的数据里日期有的是昨天、有的是01-05。原因这个文件在打包时编码出了问题实际功能是把 Excel 数据转成 txt 供分词脚本使用日期字段则是微博接口对近期数据返回相对时间、对远期数据返回绝对时间。解决文件名改回excel_to_txt.py再运行即可。日期字段先跑一遍修改日期格式.py把相对时间换算成绝对时间再统一成%Y-%m-%d格式。做这一步时留一列原始值后面发现换算偏差还能回溯。5.5 脚本一运行就 FileNotFoundError现象直接运行 LDA.py报错说找不到停用词表.txt但文件明明就在同一个文件夹里。原因源码包的路径用的是相对路径依赖当前工作目录。用 IDE 运行时工作目录可能指向项目根目录而不是脚本所在目录相对路径自然就错位了。解决脚本开头切换基准目录import os os.chdir(os.path.dirname(os.path.abspath(__file__)))这两句解决大半路径问题。后面数据文件多了建议把所有文件路径收敛到一个配置文件里统一管理不要散落在各脚本中。这是我从这套代码里看到的最实用的习惯之一改数据目录时只动一个地方。6. 热度计算的三个版本怎么选把整个项目串成流水线heat calculation 文件夹里有热度_1.py、热度_2.py、热度_3.py 三个版本第一次看会以为是迭代关系实际上三个版本对应三种数据场景。热度_1.py 是最简单的加权求和转发权重最高、评论次之、点赞最低。适合数据量小、时间跨度短、不需要跨账号对比的场景一段代码马上出数。def heat_1(retweets, comments, likes): return retweets * 3 comments * 2 likes * 1如果数据跨了多天继续用热度_1 会出现几天前的高互动微博一直压着新微博的问题这时候需要热度_2 的时间衰减import math def heat_2(retweets, comments, likes, hours_since_publish): base retweets * 3 comments * 2 likes * 1 return base * math.exp(-0.05 * hours_since_publish)0.05 这个衰减系数要按自己的数据调系数越大热度的时效性越强。做话题热点榜时它会比热度_1 合理得多。热度_3.py 做粉丝归一化解决大 V 和普通博主同台比较的问题。转发 1000 的百万粉账号和转发 100 的千粉账号哪个话题性更强分母加对数粉丝数是一种常见处理def heat_3(retweets, comments, likes, followers): base retweets * 3 comments * 2 likes * 1 return base / (math.log10(followers 1) 1)三个版本可以独立用也可以在一条流水线里分阶段切换爬虫拿数据data cleaning 清洗LDA 分主题情感分析打分多日期降维做时间线最后用热度_2 排序、折线图呈现。整套代码跑通一次后换新话题只需要改爬虫的微博 id 列表和词表其余环节不用动。我最早做热度分析时直接拿热度_1 跑全量数据结果一个百万粉账号把其他所有话题全压成了零那版结论差点写进报告。从那以后我每次处理跨账号数据都强制先过一遍热度_3把量级差异消掉再切回榜单逻辑。希望帮到你。本文还有配套的精品资源点击获取
返回列表