首页 / 资讯中心 / 文章详情

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

微博舆情分析实战:爬虫、LDA主题与情感分析源码全流程 ★ FEATURED ARTICLE
简介基于微博数据的舆情分析项目整合微博爬虫、LDA主题分析与情感分析全流程是一份面向计算机、通信、人工智能等专业学生及从业者的完整源码资料。项目源于个人高分毕业设计答辩98分代码均已调试运行既适合新手循序渐进学习也便于进阶者二次改造。压缩包共39个文件、16.16MB以23个Python脚本为主体覆盖数据采集、文本预处理、热度计算、主题建模、情感打分等环节另有7个Markdown说明文档和7个文本语料含正负向语料、停用词表以及Word2Vec模型和结果表格目录按heat、topic similarity、emotional analysis、weibo-crawler、LDA等模块划分结构清晰。目前已有211人学习下载适合作为课程设计、大作业或毕业设计的参考模板。从中可掌握微博爬虫反爬与评论采集技巧、LDA主题数调优方法、基于API/SDK的情感分析实现以及热度与情感指标的可视化呈现思路。1. 基于微博数据的舆情分析爬虫、LDA主题与情感分析这套源码能跑通到什么程度做网络舆情分析绕不开微博文本短、带表情、带转发链、情绪浓度高是最典型的社交语料也是最难爬、最脏、最难解读的数据。标题里这套「基于微博数据的舆情分析项目」核心资产是三件事——微博爬虫拿数据、LDA主题分析看大家在聊什么、情感分析看大家是夸还是骂所谓源码资料齐全一般就是带可跑代码、停用词表、标注样本和说明文档的完整工程包。适合两类人课程设计或毕业设计需要完整落地链路的在校生以及小团队想快速搭一个舆情监控 demo 的从业者。这篇我按数据采集、主题建模、情感判定、工程交付的顺序把能直接用、能踩得住坑的方案一次讲完。2. 微博爬虫从 Cookie 维护到去重入库先把语料稳稳拿下来2.1 为什么我选 m.weibo.cn 移动端接口而不是 PC 端微博爬虫选哪个端直接决定能不能稳定跑完。PC 端 weibo.com 的接口现在基本都要登录态而且页面结构和接口签名改得频繁爬虫写出来三个月内就可能报废。我一般首选 m.weibo.cn 的移动端接口它的网络请求是普通的 JSON 接口不涉及复杂签名UA 伪装成 iPhone Safari 就能过基础风控是目前社群公开源码里用得最多、也最省力的一条路。另一个原因是移动端接口在弱网环境下做了大量字段精简返回的数据结构相对固定。/api/container/getIndex这个接口用containerid参数区分搜索、热门、主页三个场景搜索场景的 containerid 是100103type1q关键词的拼接形式翻页只要改page参数。这套结构在近几年的项目里基本稳定所以我在这类舆情项目的爬虫部分习惯性优先写移动端。选移动端还有一个现实考量反爬策略是跟着流量走的。PC 端页面承载更高的营销流量风控规则更敏感移动端反而长期保持一套「按频率封、放过低频」策略。只要请求间隔控制在 2 秒以上、单关键词单次抓取不超过 10 页大多数情况下不会触发滑块验证。2.2 按关键词抓取爬虫最小实现与字段清洗先写一个按关键词搜索微博的最小实现这一步跑通后面换热门、换用户主页都只是改参数import requests import time from urllib.parse import quote def fetch_weibo_search(keyword, page1, cookie): # m.weibo.cn 搜索接口containerid 里 q 参数是关键词page 控制翻页 containerid f100103type1q{quote(keyword)} url https://m.weibo.cn/api/container/getIndex params {containerid: containerid, page: page} headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1, Cookie: cookie, Referer: fhttps://m.weibo.cn/search?containerid100103type%3D1%26q{quote(keyword)}, } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() return data.get(data, {}).get(cards, []) def parse_cards(cards): # card_type9 是微博正文卡片其他类型广告、推荐用户直接跳过 items [] for card in cards: if card.get(card_type) ! 9: continue mblog card.get(mblog, {}) if not mblog: continue items.append({ mid: mblog.get(id), text: mblog.get(text), user: mblog.get(user, {}).get(screen_name, ), reposts: mblog.get(reposts_count, 0), comments: mblog.get(comments_count, 0), likes: mblog.get(attitudes_count, 0), pub_time: mblog.get(created_at), }) return items逻辑说明quote(keyword)对中文关键词做 URL 编码避免请求参数乱码card_type9是接口里微博正文卡片的标准标识过滤掉推荐位、广告位和用户卡片避免脏数据混进分析语料。mid是微博唯一 ID后续去重和增量全靠它。参数说明timeout建议固定 10 秒防止某个慢请求拖死整轮抓取UA 里的 iPhone 型号可以随便换但必须保留 Mobile 标识这是走移动端接口的身份前提。Cookie参数是维持登录态的关键微博搜索接口不登录也能返回部分结果但连续翻页到第 4、5 页后大概率被拦所以正式跑批前一定要填有效的登录 Cookie。拿到接口返回后text字段是带 HTML 标签的富文本包含a标签、br、缩略图链接等噪声。清洗时用正则去掉标签并保留正文即可这一步放在预处理脚本里统一做不在爬虫里做——爬虫只负责把原始字段落库清洗交给分析阶段这是舆情项目的通用分层。2.3 增量去重与分页续抓防止数据重复和断点丢失舆情分析最怕数据重复LDA 也好、情感统计也好重复样本会把权重成倍抬高。去重方案我直接用 SQLite 建表给mid加唯一索引插入时用INSERT OR IGNORE幂等又简单import sqlite3 def save_new_items(items, db_conn): new_count 0 for it in items: # mid 是唯一键重复插入会被 IGNORE 吞掉天然完成增量去重 cur db_conn.execute( INSERT OR IGNORE INTO weibo_data (mid, text, user_name, reposts, comments, likes, pub_time) VALUES (?, ?, ?, ?, ?, ?, ?), (it[mid], it[text], it[user], it[reposts], it[comments], it[likes], it[pub_time]), ) new_count cur.rowcount db_conn.commit() return new_count逻辑说明mid一旦重复SQLite 会因为唯一约束直接跳过该条rowcount只统计真实插入的行数所以返回值就是「这一轮新增了多少条」。这个数字很有用可以写进日志监控爬虫是不是空跑。参数说明建表时给mid加UNIQUE约束是前提否则INSERT OR IGNORE不会生效。pub_time不要用字符串存建议统一转成YYYY-MM-DD HH:MM:SS格式方便后面按小时、按天做舆情趋势分析。分页续抓是另一个容易翻车的点。微博接口的翻页不是一直翻到没数据而是最多返回 20 页左右之后即使page再大也返回空。所以抓取策略一般是每轮每个关键词抓 5-10 页通过max_id或时间窗口定位上次抓取位置而不是靠 page 死磕到底。小规模舆情 demo 里最实用的做法是每轮抓完记住最新一条mid对应的pub_time下一轮只抓这个时间点之后的微博。配合INSERT OR IGNORE的去重能力即便时间窗口重叠也无所谓重复数据会被静默过滤不会污染语料。注意微博抓取只建议采集公开可见的数据控制请求频率数据仅用于学术分析或内部舆情研判。大批量、高频采集会触发账号风控这个红线要心里有数。3. LDA 主题分析分词、K 值选择与主题解读让舆情可读3.1 微博语料预处理jieba 分词、自定义词典与停用词表微博文本进入 LDA 前要先过三关HTML 标签、噪声词、分词质量。文本清洗我放在preprocess.py里统一处理核心逻辑是去标签、去话题符号、用 jieba 分词后用停用词表过滤import re import jieba def clean_text(raw): text re.sub(r[^], , raw) # 去微博正文里的 HTML 标签 text re.sub(r#.*?#, , text) # 话题标签先去掉后续可按需单独统计 text re.sub(rhttps?://\S, , text) # 去链接 text re.sub(r\S, , text) # 去 提及 words jieba.lcut(text) stopwords load_stopwords() kept [] for w in words: w w.strip() if w and w not in stopwords and len(w) 1 and not w.isdigit(): kept.append(w) return kept逻辑说明清洗顺序有讲究先去掉链接和 提及再分词最后过停用词表。len(w) 1会把单字词过滤掉微博文本里单字大多是语气词或错误分词留着只会变成主题噪声。load_stopwords()从data/stopwords.txt读入停用词表这个表要重点加入微博生态词比如「转发」「微博」「网页链接」「收起」这类平台推送词不干掉它们LDA 的主题词会一直被「网页链接」这类词占据这是新手最容易踩的坑。还有一个提升分词质量的关键步骤自定义词典。微博里大量网络热词、品牌词、人名是 jieba 默认词库里没有的比如项目涉及的产品名、明星名、缩写词。分词不对后面所有分析都会跟着偏。常见做法是在项目里维护一份userdict.txt格式是「词 词频 词性」# data/userdict.txt 冰墩墩 10 nr 谷爱凌 10 nr 双减政策 10 n加载方式一行代码jieba.load_userdict(data/userdict.txt)。词频给 10 就够了表示这是一个独立词不必让 jieba 自己猜着切。这个文件也是很多「资料齐全」项目会附带的资源拿到手先看它内容是否匹配你的分析领域——如果源码项目自带的词典是电商领域的直接套用到微博舆情上分词效果会大打折扣。3.2 词袋与 LDA 模型gensim 参数这样调预处理完的文本列表转成 gensim 的Dictionary和Corpus再训练 LDA。这里最容易犯的错是直接拿字符串列表丢进模型gensim 要求的是「每个文档分词后的词列表」嵌套结构不是原始文本from gensim.corpora import Dictionary from gensim.models import LdaModel # all_seg_list 是预处理后的文本集合形如 [[冰墩墩, 可爱], [冬奥, 比赛, 精彩]] dictionary Dictionary(all_seg_list) # 去掉低于 5 条文档出现、或超过 80% 文档都出现的词 dictionary.filter_extremes(no_below5, no_above0.8) corpus [dictionary.doc2bow(doc) for doc in all_seg_list] lda LdaModel( corpuscorpus, num_topics8, id2worddictionary, alphaauto, etaauto, passes20, random_state42, ) for topic_id, topic_words in lda.print_topics(num_words10): print(topic_id, topic_words)参数说明num_topics8是先随便给的主题数后面要用困惑度和一致性去校正alphaauto和etaauto让模型自动学习文档-主题分布和主题-词分布的稀疏度对小语料效果比手动固定值好passes20是遍历语料的轮数微博语料几千条规模 20 轮够用太大会过拟合random_state42一定要固定否则每次跑出来的主题词都不一样后面做实验对比就是用户说的「黑匣子」没法复现。filter_extremes的参数要结合语料规模调no_below5表示词至少要出现在 5 条博文里低于这个数的低频词会被剔除no_above0.8表示出现在 80% 以上文档的词会被当成主题间泛化词剔除。微博语料里「转发」「视频」这类词就算不在停用词表里也会被这个阈值干掉。3.3 主题数 K 怎么定困惑度、一致性一起看主题数 K 是 LDA 新手的第一个玄学决策点。K 太小话题糊成一大锅K 太大主题高度重复、没有区分度。我一般用「困惑度 一致性」双指标选 Kfrom gensim.models import CoherenceModel # 候选 K 从 4 到 16分别训练并对比 def evaluate_k(corpus, dictionary, texts, min_k4, max_k16): results [] for k in range(min_k, max_k 1, 2): model LdaModel( corpuscorpus, num_topicsk, id2worddictionary, alphaauto, etaauto, passes15, random_state42, ) perplexity model.log_perplexity(corpus) cm CoherenceModel(modelmodel, textstexts, dictionarydictionary, coherencec_v) coherence cm.get_coherence() results.append({k: k, perplexity: perplexity, coherence: coherence}) print(fK{k}, perplexity{perplexity:.2f}, coherence{coherence:.3f}) return results逻辑说明log_perplexity越低表示模型对语料的拟合越好但它会随 K 增大单调下降单独看没有意义c_v一致性衡量主题内词之间的语义关联度越高越好但 K 太大时一致性也会虚高。正确的选择逻辑是先看一致性曲线是否在某个 K 出现明显拐点或峰值再回看该 K 下各主题词是否可读、有没有两两重叠严重。我见过很多人只盯着困惑度选了个很大 K结果主题词全是零散专有名词这就是指标单看的教训。实际项目里我在一致性曲线找了峰值后还会做一次「疲惫验证」把主题词打印出来让没参与项目的同事读一遍看能不能猜出每个主题在讲什么。能猜出来就说明 K 选对了猜不出来指标再好看也要调。3.4 主题解读落地主题词表、权重与时间趋势主题模型跑完不是终点舆情分析要回答「人在聊什么、热度怎么变」。我习惯把每个主题解释成一个短标签比如主题 0 的词是「涨价」「货源」「发货」就标成「供应链问题」然后按天统计每个主题的文档数占比画出时序曲线看话题什么时候发酵、什么时候消退。这个统计环节有个细节容易忽略用 LDA 给每篇文档推断主题分布时get_document_topics的结果是(topic_id, prob)列表只保留概率最大的那个主题作为该文档的归属def assign_topic(lda_model, corpus, doc_index): topics lda_model.get_document_topics(corpus[doc_index]) # 按概率降序排最大概率的主题作为该文档的归属 best_topic max(topics, keylambda x: x[1]) return best_topic[0]参数说明minimum_probability默认 0.01如果一条文档对所有主题的概率都低于这个值返回结果里就没有该文档的主题所以做归属统计时不能只拿返回值直接用要先判空。一般舆情项目里我对「没进入任何主题」的文档单独聚成一组不放进来避免拉低主题占比的准确性。时间趋势的维度我一般是pub_time按天截断后与主题归属做交叉统计。这一步产出两个核心可视化主题随时间的热度堆叠图和每个主题下的代表性博文列表。代表性博文可以选主题概率最高那条也可以选转发量最高那条两者表达的意思不同——前者是主题核心表达后者是出圈爆点。源码项目里如果有 pyLDAvis 的.html可视化文件那是给你交互式看主题词分布用的但正式报告里我还是建议放自己画的时序图决策者看趋势比看气泡图直观得多。4. 情感分析SnowNLP 适配微博语料从 0.5 附近拉到可信区间4.1 为什么 SnowNLP 开箱即用却又经常翻车情感分析在这类舆情项目里最常见的落地方式是 SnowNLP原因很直接不需要标注数据、几行代码出分数、库是纯本地离线推理。SnowNLP(今天的发布会真精彩).sentiments返回一个 0 到 1 的概率值大于 0.6 多数人判为正向小于 0.4 判为负向0.4 到 0.6 判为中性。但翻车现场也很经典SnowNLP 默认模型是用商品评论语料训练的迁移到微博这种短文本、高反讽、带表情的语料上情感分大量集中在 0.5 附近俗称「得分暧昧」。具体表现是一条「这服务质量真是绝了」的吐槽模型给 0.6 正分「终于买到心心念念的演唱会票了」这种明显正向的表达反而因为词库不认识「心心念念」给 0.45 分。这时候不要急着推翻模型要做的是两件事用它默认模型做人机对比找到偏差分布再做领域微调。另一个问题是 SnowNLP 对否定句和反问句的处理很弱。「不好看」「不太好看」「不是不好看」这三句的语义梯度默认模型的打分基本区分不出来因为它本质是朴素贝叶斯对词袋概率的叠加没有语法层面的反转逻辑。这就是为什么情感分析模块在真实舆情项目里要有「模型 词典修正」的复合结构。4.2 用标注样本微调 SnowNLP 情感模型解决领域偏差最直接的办法是给 SnowNLP 喂本领域的标注语料重新训练。SnowNLP 内置的sentiment模块支持训练和保存你只要准备两个文本文件每行一条语料pos.txt放正向微博neg.txt放负向微博from snownlp import sentiment # 每行一条微博短文本正负样本量建议至少各 500 条 sentiment.train(data/pos.txt, data/neg.txt) sentiment.save(models/sentiment.marshal)逻辑说明sentiment.train会把两个文件读入训练一个朴素贝叶斯分类器save把训练结果存成序列化文件。训练完要让 SnowNLP 在运行时加载这份新模型加载方式是全局替换位置要在SnowNLP类被调用之前from snownlp import sentiment, SnowNLP sentiment.load(models/sentiment.marshal) # 替换内置模型 test_sents [ 这次的活动办得真不错, 物流等了五天还没到服了, 不是不好看是根本没法看, ] for s in test_sents: score SnowNLP(s).sentiments print(s, round(score, 3))参数说明正负样本量直接决定微调效果。我实际经验是各 300 条能明显改善「得分暧昧」问题各 1000 条基本达到稳定。样本不要找太大段的文章每条控制在 50 字以内的微博原句和预测时的输入分布保持一致这是最容易忽略的分布偏差问题——用长评训练、拿短微博预测效果一定打折。微调后必须做一次回归测试。把原来预判是正向和负向的测试集各选 100 条重新打分看均值是否拉开正向样本均值应该在 0.7 以上负向样本均值应该在 0.3 以下。如果正向均值还在 0.55 附近说明训练语料里混入了中性表达需要重新清理标注文件。4.3 词典法兜底正向词、负向词与否定逻辑微调后的模型能解决大部分场景但微博里高频出现的否定表达仍然会击穿它所以我在每个舆情项目里都保留一个词典法打分器作为兜底专门处理「不 正向词」这类组合pos_words set(open(data/pos_words.txt, encodingutf-8).read().split()) neg_words set(open(data/neg_words.txt, encodingutf-8).read().split()) deny_words {不, 没, 无, 非, 莫, 勿, 别, 未} def dictionary_score(text): from jieba import lcut words [w for w in lcut(text) if w.strip()] score 0 deny_scope 0 # 否定词影响后面 2 个词 for w in words: if w in deny_words: deny_scope 2 elif deny_scope 0: deny_scope - 1 if w in pos_words: score - 1 elif w in neg_words: score 1 else: if w in pos_words: score 1 elif w in neg_words: score - 1 return score逻辑说明deny_scope模拟否定作用域。遇到「不」之后 2 个词内的正负方向都反转比如「不好看」中「好」是正向词但被「不」反转得分从 1 变 -1。这个逻辑很糙但对短文本微博来说足够用也比单纯词袋计数靠谱得多。词表资源一般项目资料里会带如果没有可以从网上的大连理工情感词汇本体库提取或者用知网情感词典。词典法和 SnowNLP 怎么协作我的策略是分成两层先用 SnowNLP 打分分不出明显正负得分在 0.4-0.6 之间的文本再交给词典法二次判定如果词典法给出的正负词命中数为 0才判定为中性。这样把「模型擅长识别复杂情感、词典擅长捕捉反转」互补起来准确率比单跑 SnowNLP 高出不少。4.4 向多模态情感分析延伸表情、图片与转发语境文本之外还有大量信号微博自带的表情符号、图片、视频封面、点赞数、转发评论比。近几年多模态情感分析思路开始进入舆情工程常见做法是给每条微博补一个「视觉情感」标签用图片的情感极性去修正文本的极性。比如一条文案是反讽的负面吐槽配图却是微笑表情模型若只读文本容易误判而图片特征能帮助识别这是反讽。不过小规模源码项目直接上多模态大模型不现实我一般分两步落地这个方向第一步是把微博文本里的 emoji 做个简单映射笑脸类属正向、哭脸类属负向作为情感分的修正项第二步才是对真正带图的微博抽帧或提取 CLIP embedding做粗粒度正负分类。表情符号映射在工程上几乎零成本效果提升却很明显——微博用户习惯用表情对冲文字表达的辛辣比如「这个人真的太好了[狗头]」读懂狗头是反讽就能避免一次严重误判。多模态情感分析真正用起来是在事件级舆情上。单条微博的多模态融合容易过拟合但把某个事件下所有文本分、图片分、互动数据一起聚合就能得到更稳定的情感曲线。5. 舆情分析项目避坑手册爬虫风控、编码乱码与情感分漂移的排查清单5.1 爬虫Cookie 失效与验证码拦截的排查路径现象抓取前 5 页正常第 6 页开始返回的cards为空或者响应里的msg变成「系统繁忙」。原因连续翻页触发频率风控或 Cookie 里的SUB字段过期。移动端接口对单 session 的请求频率限制大约在每分钟 30 次以内超过后即使换关键词也会被临时拦截。解决请求间加time.sleep(random.uniform(2, 4))每轮抓取控制在 5-6 页内下一轮换个时间窗口再抓Cookie 失效就重新登录网页版微博从开发者工具里拿新的SUB值更新到config.py里。如果整个 IP 被限流等 5-10 分钟会自动恢复千万不要在同一 IP 下强行并发重试。现象返回的数据里text字段出现乱码或变成\u开头的转义序列。原因接口对部分 emoji 和生僻字做的是 Unicode 直传JSON 解析后没做 unicode 转义还原。解决requests响应对象统一指定resp.json()解析即可它自带 unicode 解码不需要手写unicode_escape。真正容易乱码的地方是 SQLite 存储后再读出来所以建库连接要加charset参数sqlite3.connect(weibo.db)后用PRAGMA encoding UTF-8确认一次。现象入库后总条数和接口返回不一致莫名少了很多。原因card_type过滤条件写错。搜索接口里除了微博正文还有「推荐阅读」「热门话题」「搜索发现」等卡片如果过滤条件用的是card.get(card_type) 9而某些卡片 type 字段是字符串或缺失就整卡被跳过。解决先打印所有不重复的card_type值确认目标卡片的真实类型再写过滤条件。这个坑我自己也踩过后来养成了「第一轮抓取先 dump 原始 JSON看结构再写解析」的习惯。5.2 预处理与 LDA乱码、分词错误与 K 值陷阱现象LDA 打印出来的主题词里反复出现「微博」「转发」「网页链接」这类与舆情内容无关的词。原因停用词表没有覆盖平台专有词filter_extremes的no_above阈值设得过高导致这些高频泛化词进入主题。解决先补充停用词表把「微博」「转发」「链接」「图片」「视频」加进去再把no_above从默认的 0.8 调低到 0.5-0.6让出现在大多数文档里的词直接被词典过滤。过滤后再训练主题词会干净很多。现象不同时间跑同一个 LDA 脚本主题词完全不同说好的「可复现」消失了。原因LdaModel默认random_state为 None每次训练初始化不同或者语料顺序变了影响了迭代收敛路径。解决固定random_state42同时固定语料顺序预处理结果落盘成 Pickle 文件后续训练全从这份文件读不重新跑分词。这样即使过了两周重新执行也能还原同一组主题。现象困惑度还在下降但主题已经糊成一团主题数 K 总是选不对。原因只看困惑度选 K。困惑度随 K 单调下降是模型特性不代表 K 越大越好主题之间高度重叠时一致性指标会暴露出来。解决按我在 3.3 的方式跑 K4 到 16 的一致性对比选一致性曲线里的局部峰值或拐点再人工读主题词验证可解释性。如果一致性峰值对应的主题词还是不可读优先回看预处理环节八成是停用词或词典没调对。5.3 情感分析默认模型偏差与标注口径不一致现象SnowNLP 对明显负面的吐槽给出 0.6 的正分而且集中发生在「反讽」「言不由衷」的句子。原因默认模型基于电商评论训练语气直白、正负分明微博反讽式表达考验的是语用理解词袋模型天然不擅长。解决按领域微调同时引入 4.3 的词典法作为修正层。反讽类文本不要试图在朴素贝叶斯上解决那是深度学习的事词典法补一层「正向词 狗头表情」的反转规则效果在工程上更可控。现象微调后情感分整体偏高所有微博都被判成正向。原因训练语料里正负样本的标注口径不一致。比如把「还行」「一般般」标成正向样本贝叶斯学习到的概率分布就会被这种弱正向文本污染。解决标注前定义清楚三条硬规则积极词汇明确是正向消极词汇明确是负向「中性描述」不进入训练集正负样本各 500 条起步保持数量均衡训练完用一小批验证集看均值正向大于 0.7、负向小于 0.3达不到就查标注文件。现象有人工标注好的舆情样本但微调后效果反而变差。原因直接把人工标注的「正/负/中性」三分类标签丢给 SnowNLP 训练。SnowNLP 的情感训练只接受正负两类文件中性样本混进去会被强行归类反而破坏边界。解决中性样本单独存放不进训练集。后续做效果评估时再用中性样本检验区分度而不是让模型学会「输出 0.5」。6. 让舆情项目可交付工程目录、定时增量与人工抽检闭环6.1 工程目录怎么组织README 和 requirements 先对齐拿到任何舆情源码项目第一步不是读代码而是确认工程结构是否完整。一个能长期跑的舆情项目目录至少是这样weibo_analysis/ ├── config.py # 关键词、Cookie、数据库路径 ├── crawler/ │ ├── fetcher.py # 移动端搜索抓取 │ └── storage.py # 去重入库 ├── analysis/ │ ├── preprocess.py # 清洗、分词、停用词 │ ├── lda_model.py # 主题模型训练与评估 │ └── sentiment.py # 情感打分与词典修正 ├── data/ # 停用词表、自定义词典、标注语料 ├── models/ # 训练好的情感模型文件 ├── output/ # 报告、图表、pyLDAvis 文件 └── run_pipeline.py # 一键跑全流程我一般拿到项目先看README和requirements.txt确认依赖版本。SnowNLP、gensim、jieba 这三个库的版本兼容性有问题时最典型的翻车现场是 gensim 新版本 API 变动导致老代码报错用pip install -r requirements.txt时不要升级到最新版锁定项目声明版本最省事。6.2 让 Pipeline 定时跑起来用脚本驱动而非 Jupyter舆情分析的价值在持续跟踪一次性跑完的舆情没有决策意义。run_pipeline.py把爬取、预处理、LDA、情感打分、输出报告串成一条命令固定random_state保证可复现if __name__ __main__: cfg load_config() cards fetch_weibo_search(cfg.keyword, page1, cookiecfg.cookie) new_count save_new_items(parse_cards(cards), db_conn) if new_count 0: print(no new data, skip analysis) exit(0) docs load_new_docs_from_db(new_count) seg_list [clean_text(d[text]) for d in docs] lda_model train_lda(seg_list, num_topicscfg.num_topics) for doc in docs: doc[senti] round(float(SnowNLP(doc[text]).sentiments), 3) write_daily_report(docs, lda_model, output_dircfg.output_dir)调度的常见做法是 crontab比如每天凌晨 2 点跑一次30 2 * * * cd /home/weibo_analysis /usr/bin/python3 run_pipeline.py logs/pipeline.log 21。这里有个经验exit(0)的分支不要省舆情项目不是天天有新数据没有增量就跳过整个分析流程能省下大量无意义的计算时间。6.3 结果验证人工抽检、一致性指标与置信区间最后一步是验证也是很多源码项目缺的环节。我习惯在情感分析结果里随机抽 100 条人工标注一次和模型打分对比如果准确率低于 70%就把中性阈值区间从 0.4-0.6 扩大到 0.35-0.65把更多模糊样本拦在中性里避免强行分类带来的误差。主题分析则验证主题词的可解释性抽三个主题让同事描述「这个主题是什么」描述一致就说明主题切分成功。这个项目做下来最大的教训是「舆情分析的价值不在于模型多高级而在于数据干净、流程可复现、结论能解释」。爬虫和预处理往往比模型参数更值得花时间情感分也不是越接近 0 或 1 就是越准中性样本保留下来本身就是结论。希望这篇踩坑梳理能帮你少走一段弯路把这个方向做成一个可以长期跑、敢对决策负责的舆情看板。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站