资讯详情

微博舆情分析全流程:爬虫、LDA主题建模与情感分析实战

📅 2026/10/13 0:42:23 | 华诺云谱 👁 阅读
微博舆情分析全流程:爬虫、LDA主题建模与情感分析实战
简介基于微博数据的舆情分析项目整合微博爬虫、LDA主题分析与情感分析三大模块面向计算机、通信、人工智能等专业学生及从业者适用于课程设计、大作业或毕业设计等场景。资源包内含三十九个文件以Python脚本、Markdown说明文档和TXT语料为主另附训练好的word2vec模型及Excel数据表整体体积约十六兆结构清晰便于按模块查阅。项目为个人高分作品答辩评审达九十八分所有代码均经过调试测试可直接运行涵盖热度计算、评论情感均值分析、主题余弦相似度、多日期降维等功能并附正向负向语料、近义词表等资料方便替换数据快速复现。目前已有211人学习下载兼具完整性与可扩展性既能支撑入门实践也可在此基础上二次开发具有较高的学习借鉴价值。1. 微博舆情分析项目爬虫、LDA与情感分析的完整流水线这个项目把微博舆情分析完整串成了一条流水线先用微博爬虫把评论和用户信息拉下来再做数据清洗和分词然后兵分三路——LDA跑主题分析、情感分析打正负标签、热度计算给事件排序最后汇总成图表和结论。对于准备毕业设计或者想一口气把爬虫、NLP、可视化全过一遍的人这套源码比零散教程省事得多。项目全用 Python 实现核心模块分为 weibo-crawler、LDA、emotional analysis、heat calculation 四组中间夹着停用词表、正负向语料和训练好的 w2v.model代码按目录结构复原后即可运行。下面我从爬虫层开始把每个模块的选型逻辑、参数设置和踩坑点逐一拆开说。2. 微博爬虫模块评论抓取、用户信息与随机化策略weibo-crawler 目录下放了 5 个脚本comment crawler.py 负责通用评论抓取comments-crawler_random.py 在它的基础上加了随机延迟comments-crawler_random仅针对去年的评论.py 把抓取范围限定到指定年份user information crawler.py 收集博主主页信息data cleaning.py 统一做清洗requirement.txt 锁依赖。这样的分工意味着你不用改一个脚本跑到底爬评论、爬用户、补旧数据是三种独立任务可以分开调度。2.1 评论爬虫m.weibo.cn 接口与字段解析评论爬虫的核心是移动端接口m.weibo.cn/api/comments/show。微博 PC 端网页版结构复杂频繁请求容易触发验证码而移动端接口相对稳定返回的 JSON 字段够用。常见做法是直接拿微博 mid 和 page 两个参数翻页一次拉 20 条。# comment crawler.py 核心逻辑示意 import requests def fetch_comments(mid, page1): url https://m.weibo.cn/api/comments/show params {id: mid, page: page} headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X), Referer: fhttps://m.weibo.cn/detail/{mid}, } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() if not data.get(data): return [] result [] for c in data[data]: result.append({ comment_id: c.get(id), user_id: c[user][id], nickname: c[user][screen_name], content: c.get(text, ), created_at: c.get(created_at, ), like_count: c.get(like_count, 0), }) return result逻辑说明params里的id就是微博正文的 midpage从 1 开始递增返回列表为空就说明翻到底。headers里带Referer是为了模拟从微博详情页跳转过来的流量缺了这个头经常出现 200 但data为空的情况。timeout10防止单条请求卡死整个流程。like_count是可选字段但后续热度计算会用到尽量保留。调用时在外面套一个 while 循环直到返回结果少于 20 条或者为空就停止。created_at是英文时区格式比如Fri Dec 22 12:00:00 0800 2023这一步不用急着转标准格式清洗阶段统一处理。2.2 用户信息爬虫从用户 ID 到地域与认证信息拿到评论里的user_id之后需要去补用户维度信息时就走container/getIndex接口。它的参数是containerid100505{uid}返回体里包含性别、认证状态、粉丝数、所在地。# user information crawler.py 核心逻辑示意 def fetch_user_info(uid): url https://m.weibo.cn/api/container/getIndex params {containerid: f100505{uid}} headers { User-Agent: Mozilla/5.0 (Linux; Android 10), Referer: https://m.weibo.cn/, } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json().get(data, {}) user data.get(userInfo, {}) return { user_id: uid, gender: user.get(gender, ), verified: user.get(verified, False), followers_count: user.get(followers_count, 0), location: user.get(location, ), }逻辑说明containerid拼接规则是固定前缀100505加用户 ID这是移动端用户主页的标准参数。返回的数据层级要注意用户资料嵌在data.userInfo里不是直接挂在data下。location字段可能是北京、海外这类文本后续做地域分布聚合时可以直接 groupby。这里有个容易被忽略的点抓用户信息属于二次请求请求量和评论爬取不在一个量级。如果评论有 1 万条去重后的用户可能只有几千建议先把user_id去重存进集合再逐批请求。2.3 随机化与时间窗口requests 请求的频率控制comments-crawler_random.py存在的意义很明确固定频率请求是爬虫翻车的第一大原因。微博服务端对单 IP 单位时间内的请求数有隐式限制连发几十个请求后必然被限流。随机化的核心不是随意 sleep而是让每次请求间隔落在一个合理区间内。# comments-crawler_random.py 频率控制片段 import time import random def crawl_with_random_delay(crawl_fn, max_pages10): for page in range(1, max_pages 1): try: items crawl_fn(page) if not items: break delay random.uniform(2.0, 5.0) # 随机延迟 print(fpage{page}, sleep{delay:.1f}s) time.sleep(delay) except requests.exceptions.RequestException: time.sleep(10) # 网络异常时退避逻辑说明random.uniform(2.0, 5.0)是经验值。太短容易被识别为脚本太长的话单微博几百页评论要等很久2 到 5 秒是多数人实测的折中区间。异常分支单独睡 10 秒是为了在接口临时抖动时让流量降下来而不是立刻重试。如果你只爬几十条做演示这段可以砍掉如果要爬完整条微博的评论它是必需项。另一个脚本comments-crawler_random仅针对去年的评论.py加的是时间窗口过滤把created_at字符串转成 datetime判断年份是否落在指定区间。这样爬出来的评论天然按年分类做同比分析时省一道筛选。# 仅针对去年的评论.py 时间窗口判断 from datetime import datetime def in_target_year(date_str, target_year): dt datetime.strptime(date_str, %a %b %d %H:%M:%S %z %Y) return dt.year target_year参数说明target_year由外部传入比如 2023。注意strptime的格式串里%z对应0800时区后缀格式写错会直接抛 ValueError。数据清理阶段我用data cleaning.py做二次加工去 HTML 标签、去重复评论、统一日期格式这些坑在第 5 章专门展开。3. LDA 主题分析分词处理与超参调整的落地流程LDA 目录下有 8 个文件LDA.py 是标准流程LDA超参.py 是可调参版本分词处理.py 负责把文本切成词袋excel转txt.py 把 Excel 里的文本列抽成纯文本自建词表.txt、近义词表.txt、停用词表.txt 是三份词表资源。它们的分工很清晰先用词表武装分词器再把语料转成中间格式最后喂给 gensim 建模。3.1 分词处理jieba、自建词表与近义词合并LDA 之前必须做中文分词。项目里用的是 jieba但直接对微博文本跑默认 jieba 会出问题——网络新词、领域词会被拆得四分五裂。所以分词处理.py 的加载顺序是先加载自建词表再过滤停用词最后做近义词合并。# 分词处理.py 核心逻辑示意 import jieba jieba.load_userdict(自建词表.txt) # 领域词一次性装入 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: group line.strip().split() if len(group) 2: for word in group[1:]: synonym_map[word] group[0] def tokenize(text): words [] for w in jieba.lcut(text): w w.strip() if not w or w in stopwords: continue words.append(synonym_map.get(w, w)) return words逻辑说明load_userdict把“数字经济”“双减政策”这类复合词整体锁定分词时才不会被拆成“数字/经济”。停用词过滤放在 jieba 切词之后凡是命中停用词表的直接丢弃。近义词合并在最后一步例如把“房价走势”“房价波动”同时映射到“房价”LDA 的稀疏矩阵会变紧凑主题词收敛更明显。这里有个值得注意的点微博文本里大量出现的“网页链接”“转发微博”这类噪声词靠通用停用词表覆盖不到。运行项目前建议打开停用词表.txt把这些平台泛化词手动追加进去否则 LDA 的主题会被这些高频词主导。3.2 Excel 转 TXT从新增.xlsx 到 LDA 输入格式LDA 输入不关心 Excel 格式只认纯文本。excel转txt.py 的作用就是把新增.xlsx里的文本列逐行抽出来写成一个干净的行式文本文件。这个中间步骤看似多余实际很有必要因为 Excel 列里的换行符和特殊字符会污染后续的词典构建。# excel转txt.py 核心逻辑示意 import pandas as pd df pd.read_excel(新增.xlsx) # 假设文本列名为 content按需修改 with open(corpus.txt, w, encodingutf-8) as f: for text in df[content].dropna(): f.write(str(text) \n)逻辑说明read_excel底层依赖 openpyxl 引擎如果环境里没装会直接报 ModuleNotFoundError安装 requirement.txt 时要把 openpyxl 一并装好。写入文件时务必指定encodingutf-8否则某些 Windows 环境下默认编码写出的文件LDA 读取时会出现乱码。dropna()把空评论过滤掉避免空行进入 LDA 语料。3.3 LDA 建模与超参调整num_topics、passes 与 alphaLDA.py 使用 gensim 跑标准流程LDA超参.py 则把关键超参暴露成变量方便批量实验。gensim 的 LdaModel 核心参数有四个num_topics、passes、iterations 和 alpha/eta。# LDA超参.py 核心逻辑示意 from gensim import corpora, models texts [] with open(corpus.txt, encodingutf-8) as f: for line in f: tokens tokenize(line) if tokens: texts.append(tokens) dictionary corpora.Dictionary(texts) corpus [dictionary.doc2bow(doc) for doc in texts] lda models.LdaModel( corpus, num_topics6, id2worddictionary, passes20, iterations200, alphaauto, etaauto, random_state42, ) for topic_id in range(lda.num_topics): words [w for w, _ in lda.show_topic(topic_id, topn10)] print(f主题{topic_id}: { .join(words)})参数说明num_topics6是我在这个项目里跑了一圈后选出来的起始值不一定对所有数据都成立。常见做法是从 4 跑到 10逐个看show_topic输出的主题词是否互斥再定。passes20表示整个语料库被训练 20 轮小规模语料 20 足够语料大了可以加到 50但耗时线性上升。iterations200是内部迭代的默认值短文本场景不用改。alphaauto和etaauto让模型自己估计文档-主题和主题-词分布比手工指定固定值更稳这也是中文短文本 LDA 的常规做法。LDA超参.py 的核心价值在于批量对比循环尝试不同主题数输出每轮的主题词和困惑度。这里强调一点LDA 是概率模型困惑度低不代表主题可解释性强最终要以人工读主题词为准。我在实际项目里会把自动对比结果当参考主题词看着像一回事才算数。4. 情感分析实现API 版与 SDK 版的双路线解读emotional analysis 目录下同时存在两个版本情感分析_API版.py 走外部通用接口情感分析_SDK版.py 跑本地语料打分。配套的还有正向语料.txt、负向语料.txt、正向比重.py、折线图绘制.py、多日期降维.py、修改日期格式.py。有一份双版本代码的好处是网络环境不稳时切 SDK 版离线跑需要高精度时切 API 版互不干扰。4.1 API 版与 SDK 版两条路线的取舍先看对比表再展开讲怎么选。维度API 版SDK 版依赖requests、接口密钥jieba、本地正负向语料共性对网络流行语覆盖一般依赖领域语料质量可扩展性能每条一次网络请求慢本地计算秒级批量处理适用小批量、容错高的场景离线批量分析、课程答辩演示API 版的实际流程就是读一行文本、调用接口、取回标签和置信度、写入结果表。SDK 版是我更常用的一条路因为批量数据分析场景里逐条打网络请求太慢本地语料打分把一轮几万条评论处理完也就是几十秒。两版配合使用的方式很常见先用 SDK 版快速刷完全量数据挑出得分接近 0 的模糊样本再用 API 版复核这一小部分。4.2 正负向语料与情感得分从词典到加权窗口SDK 版最大的资产是那两份语料文件——正向语料.txt 和负向语料.txt里面每行一个情感词。情感分析_SDK版.py 把这两个文件加载成集合再对每条微博分词后逐词命中统计。# 情感分析_SDK版.py 核心逻辑示意 import jieba def read_vocab(path): with open(path, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) pos_words read_vocab(正向语料.txt) neg_words read_vocab(负向语料.txt) def sentiment_score(text): score 0 for word in jieba.lcut(text): if word in pos_words: score 1 elif word in neg_words: score - 1 return score逻辑说明计分逻辑直接正向词加一、负向词减一。它不考虑否定词和程度副词比如“不太高兴”会被算成负向因为命中了“高兴”“很不满意”还是负向因为“满意”是正向词抵消不了“不”的语义反转。这是词典法的已知短板但胜在逻辑透明答辩时一句“这是词典法的典型取舍”就能讲清楚。使用这两份语料时注意正向语料.txt 是一份基础集直接拿来跑通用话题问题不大但如果你分析的是具体行业比如地产或教育强烈建议往里补领域情感词。比如分析楼市舆情“上涨”“松绑”在语境里是正向分析就业舆情“裁员”“降薪”必须进负向语料。4.3 情感均值、正向比重与折线图把文本变成时间序列单条评论的情感得分没意义按天聚合后的趋势才有价值。情感均值.py 和 Comment mean.py 做同一件事——按日期分组计算平均分正向比重.py 多算一列正向占比折线图绘制.py 最后把两条线画出来。# 情感均值.py 折线图绘制.py 组合逻辑 import pandas as pd import matplotlib.pyplot as plt df pd.read_excel(新增.xlsx, parse_dates[created_at]) df[score] df[content].apply(sentiment_score) df[date] df[created_at].dt.date daily df.groupby(date).agg( mean_score(score, mean), positive_ratio(score, lambda x: (x 0).mean()), ) daily.plot(y[mean_score, positive_ratio], kindline) plt.savefig(sentiment_trend.png, dpi150)逻辑说明这里有两个容易踩的地方。第一parse_dates[created_at]告诉 pandas 把该列解析成日期类型否则 groupby 按字符串聚合会把同一天的数据拆成不同 key。第二positive_ratio的 lambda 表达式计算当天得分大于 0 的评论占比它和均值一纵一横一个看负面程度、一个看负面范围。多日期降维.py 就是在这个聚合结果上按月份或季度再做一次降维适合跨年对比的场景。5. 避坑笔记从爬虫数据到 LDA 主题的常见坑点与排查这里我把项目跑通过程中最容易翻车的 5 个问题按“现象 → 原因 → 解决”整理成记录每一条都是实际踩过的。5.1 评论里全是 HTML 标签现象爬回来的 content 字段带着a href... relnofollow、br/、span class...之类标签分词后主题词全被标签拆散。原因m.weibo.cn 返回的text字段是富文本不是纯文本微博在评论内容里嵌了表情和短链。解决在 data cleaning.py 里做两层清洗先正则去标签再压缩空白。import re content re.sub(r[^], , content) content re.sub(r\s, , content).strip()这个坑不解决后面 LDA 的词表会被http、class这类残片污染主题可解释性直接崩盘。5.2 excel转txt 后 LDA 读文件乱码现象excel转txt.py 生成的 corpus.txt 在 LDA 读取时报UnicodeDecodeError或者读进来全是框框。原因Excel 文件在不同版本另存时的默认编码不一致写出的 TXT 可能是 ANSI 或带 BOM 的 UTF-8而读文件的代码指定了纯utf-8。解决读取时统一用utf-8-sig并加errorsignore兜底。with open(corpus.txt, encodingutf-8-sig, errorsignore) as f: texts [tokenize(line) for line in f]utf-8-sig能自动处理 BOM 头errorsignore保证个别脏字节不会让整个任务中断。从那以后我写任何中文文本读取都会默认考虑编码问题。5.3 日期格式不统一导致按天聚合失败现象groupby 日期后数据全挤在同一天或者聚合结果出现一堆 NaN。原因爬虫返回的created_at是含时区的英文格式例如Fri Dec 22 12:00:00 0800 2023而 Excel 里手工录入的日期可能是2023/12/22或2023-12-22两种格式混在一起。解决用修改日期格式.py 统一转成%Y-%m-%d后再聚合。from datetime import datetime def normalize_date(s): return datetime.strptime(s, %a %b %d %H:%M:%S %z %Y).strftime(%Y-%m-%d)如果来源复杂就用pd.to_datetime(s, formatmixed)做自动推断解析不了的丢到异常列表人工补。日期是时间序列分析的命脉这一步值得多花几分钟验证。5.4 LDA 主题词全混在一起每个主题都像在说同一件事现象跑完 LDA5 个主题里 4 个都包含“转发”“微博”“视频”“链接”这类词主题之间无法区分。原因停用词表覆盖不全平台泛化词把主题词挤掉了。微博文本里的高频噪声词不是自然语言是功能词。解决手动扩展停用词表把转发、微博、网页链接、视频、展开这类词全部加进去重新跑分词处理.py 后再建 LDA。这一步是纯体力活但值得做主题模型的输出质量有很大一部分取决于词表质量。近义词表.txt 同理领域词不合并主题就永远是散的。5.5 爬虫爬到一半被限流现象请求连续返回 401 或空列表手动刷新浏览器正常过几分钟脚本又恢复。原因请求频率过于固定User-Agent 和 Referer 不匹配触发了微博服务端的接口频控。解决随机延迟时间间隔并准备多个 UA 交替使用。ua_list [ Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X), Mozilla/5.0 (Linux; Android 10), Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7), ] headers[User-Agent] random.choice(ua_list)核心不是单个 UA 多强而是请求特征分布要像真人。固定 2 秒一发的规律请求最容易被识别。6. 热度计算与主题验证用余弦相似度给分析兜底6.1 热度三层加权从转发、评论、赞到时间衰减heat calculation 目录下的热度_1.py、热度_2.py、热度_3.py 代表了三种热度模型逐级递进。第一层是线性加权把转发、评论、点赞按固定权重相加第二层加时间衰减第三层引入多参数因子。实际分析中用最多的是带时间衰减的那个版本因为舆情事件的生命周期差异很大微博发布 10 分钟和发布 3 天后的热度意义完全不同。# 热度_2.py 时间衰减热度示意 import math def heat_with_decay(like_count, comment_count, repost_count, hours_since_publish, weights(1.0, 2.0, 3.0), decay_factor0.5): raw ( weights[0] * like_count weights[1] * comment_count weights[2] * repost_count ) decay math.exp(-decay_factor * hours_since_publish / 24) return raw * decay参数说明weights(1, 2, 3)表示转发权重最高评论次之点赞最低这是从“行为成本”角度取的默认值转发需要二次创作评论成本居中点赞是一键行为。decay_factor0.5是每 24 小时的衰减系数数值越大热度掉得越快。分析单日舆情用 0.5 合适分析一周维度的演化建议降到 0.2 左右不然旧微博的热度会迅速归零曲线全塌在一侧。热度_3.py 的模型更接近行业里用的“多因子合成”在时间衰减之外引入了原创率、用户等级等修正项。但要注意热度不是越高越好要结合情感倾向看——高热负向才是真正的舆情风险点这个联动分析思路可以写在报告里。6.2 用 Word2Vec 与主题余弦相似度做一致性验证主题余弦相似度.py 是整个项目里最容易被忽略但价值最高的一个脚本。它做的事情是把 LDA 输出的主题词通过 word2vc.py 训练好的 w2v.model 映射成向量再计算主题之间的余弦相似度用来判断主题是不是重叠了。# word2vc.py 训练词向量 from gensim.models import Word2Vec sentences [jieba.lcut(line) for line in open(corpus.txt, encodingutf-8-sig)] model Word2Vec(sentences, vector_size128, window5, min_count2, workers4) model.save(w2v.model)参数说明vector_size128是词向量的维度短文本场景下 128 到 256 都够用维度太小丢失语义太大训练慢且容易过拟合。window5表示只看词前后各 5 个词内的共生关系min_count2过滤掉只出现一次的生僻词能显著降低训练噪声。训练语料直接用 LDA 的输入 corpus.txt保证两个模型在同一语义空间。# 主题余弦相似度.py 计算主题间重叠度 import numpy as np from sklearn.metrics.pairwise import cosine_similarity model Word2Vec.load(w2v.model) def topic_vector(topic_words, model): vecs [model.wv[w] for w in topic_words if w in model.wv] if not vecs: return np.zeros(model.vector_size) return np.mean(vecs, axis0) topic_a lda.show_topic(0, topn10) topic_b lda.show_topic(1, topn10) sim cosine_similarity([topic_vector(topic_a, model)], [topic_vector(topic_b, model)])[0][0] print(f主题相似度: {sim:.2f})逻辑说明相似度超过 0.7 基本可以判定两个主题高度重叠说明主题数设少了需要回 LDA超参.py 调大 num_topics反过来如果所有主题相似度都很低说明主题数设大了有一部分主题只是把同一批词拆散。这就是整个分析流程的兜底验证步骤。这套流程跑完后我习惯用 map 目录下的 Number of new employees.py 结合新增.xlsx 做一次地域维度扩展把不同地区的正负向均值叠到地图上看空间分布情绪密度和地域分布一旦对上报告的落地感会强很多。从那以后每次换数据源我都强制走一遍“爬虫 → 清洗 → 分词 → LDA → 主题一致性校验 → 情感均值 → 热度关联”闭环直到主题相似度全部低于 0.7 才收工。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑