ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微博舆情分析系统毕设实战:从数据采集到情感分析可视化

微博舆情分析系统毕设实战:从数据采集到情感分析可视化 简介这份资源是面向计算机相关专业学生与项目实战学习者的微博舆情分析系统毕业设计完整源码包采用PythonDjangoVue3技术栈配套爬虫代码与数据源数据库使用MySQL适合做大作业、毕业设计或需要舆情分析项目练手的读者。压缩包共约2000个文件整体约547.61MB其中1797个jpg与16个png为界面截图和图表素材75个py与34个pyc构成后端与爬虫逻辑28个vue负责前端页面另有12个csv、4个sql、7个json等数据与配置文件以及2个pth模型文件目录结构清晰便于按模块查阅。该资源经导师指导并评审通过源码均本地编译运行并严格调试已有127人学习下载。读者可获得可运行的完整项目、爬虫与数据源、数据库脚本及文档说明便于快速理解舆情采集、话题分析与可视化展示的实现思路也可作为二次开发与答辩准备的参考。1. 从一条热搜到一套能跑的系统微博舆情分析到底在做什么刷到“微博舆情分析系统”这个毕业设计题目时很多人第一反应是去搜现成源码结果下载下来发现跑不起来——数据库连不上、依赖版本对不上、爬虫一跑就封 IP。我带过几届学生的毕设也帮朋友改过类似项目血泪经验是能跑通的系统从来不是靠抄源码而是靠把数据流、存储、分析、展示这四段拆开每段单独验证。这套系统要解决的核心问题很具体给定一批微博文本公开话题下的帖子、评论自动判断情绪倾向、提取高频关键词、按时间画出热度曲线最后用一个 Web 界面把结果呈现出来。适合谁适合正在做计算机毕业设计、需要一套完整可演示项目的人也适合刚学完 Python 想找个真实场景练爬虫和文本分析的新手。它不涉及任何敏感数据获取所有数据都来自公开可访问的页面分析逻辑也完全基于词频和情感词典不碰用户隐私。下面我从环境搭建讲到部署排错每一步都给出可复现的命令和参数。2. 环境准备与数据采集把微博公开数据变成结构化 CSV2.1 为什么选 requests BeautifulSoup 而不是 Scrapy毕业设计项目最怕“过度工程”。Scrapy 适合大规模分布式采集但配置复杂、调试成本高一个异步管道报错能卡你两天。我一般会建议用requests加BeautifulSoup做单机采集配合time.sleep控制频率代码量少、逻辑透明答辩时也好讲清楚每一行在干什么。Python 版本选 3.9 或 3.10太新的 3.12 在某些 Windows 环境下装lxml会报编译错误。安装依赖直接用 pippip install requests2.31.0 beautifulsoup44.12.2 lxml4.9.3 pandas2.0.3 jieba0.42.1 snownlp0.12.3 flask2.3.2这里锁版本是为了避免“昨天还能跑今天报错”的玄学问题。snownlp是中文情感分析库虽然精度一般但胜在轻量、无需训练适合毕设演示。jieba做分词flask做后端接口。2.2 采集脚本翻页逻辑与反爬规避微博公开话题页的结构经常变但核心思路不变找到包含帖子文本的 DOM 节点提取时间、内容、点赞数然后翻页。下面是一个最小可运行示例采集某个公开话题的前 5 页import requests from bs4 import BeautifulSoup import time import csv import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(url): try: resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() return resp.text except requests.RequestException as e: print(f请求失败: {e}) return None def parse_page(html): soup BeautifulSoup(html, lxml) items [] # 注意微博页面 class 名会变需根据实际页面调整 for card in soup.select(div.card-wrap): content card.select_one(p.txt) time_tag card.select_one(p.from a) if content and time_tag: items.append({ content: content.get_text(stripTrue), time: time_tag.get_text(stripTrue), }) return items def save_to_csv(rows, filenameweibo_data.csv): with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[content, time]) writer.writeheader() writer.writerows(rows) if __name__ __main__: all_rows [] base_url https://s.weibo.com/weibo?q%23话题名称%23page{} for page in range(1, 6): url base_url.format(page) html fetch_page(url) if html: rows parse_page(html) all_rows.extend(rows) print(f第 {page} 页采集到 {len(rows)} 条) time.sleep(random.uniform(2, 5)) # 随机间隔降低被封风险 save_to_csv(all_rows) print(f总计 {len(all_rows)} 条已保存)逻辑说明fetch_page负责发请求并处理异常parse_page用 CSS 选择器定位帖子卡片save_to_csv用utf-8-sig编码防止 Excel 打开乱码。参数方面timeout10避免卡死random.uniform(2,5)让间隔不固定比固定sleep(3)更自然。注意微博未登录状态下只能看前几页这是平台限制不是代码问题。如果采集量不够可以换用公开的微博数据集如 NLPCC 提供的情感分析语料但毕设演示用几百条公开数据足够了。提示采集前先手动打开目标页面按 F12 检查div.card-wrap是否存在不同时期 class 名可能不同需要同步修改选择器。3. 文本清洗与情感分析让 jieba 和 snownlp 跑出可解释的结果3.1 清洗规则去噪比分词更重要原始微博文本里混着大量噪声用户名、话题标签、表情符号、URL、重复转发内容。如果不洗分词结果里全是“转发”“微博”“网页链接”这类无意义词情感分析也会被带偏。我一般写一个clean_text函数按顺序做五件事去 URL、去 、去话题标签、去表情符号、去多余空格。import re def clean_text(text): # 去除 URL text re.sub(rhttp[s]?://\S, , text) # 去除 用户名 text re.sub(r[\w\u4e00-\u9fa5], , text) # 去除 #话题# text re.sub(r#.*?#, , text) # 去除表情符号常见 Unicode 范围 text re.sub(r[\U00010000-\U0010ffff], , text) # 去除多余空白 text re.sub(r\s, , text).strip() return text参数说明\U00010000-\U0010ffff覆盖了大部分 emoji\s把连续空格、换行合并成一个空格。清洗后文本长度如果小于 5 个字符建议直接丢弃因为太短没有分析价值。3.2 情感分析snownlp 的阈值怎么定snownlp返回 0 到 1 之间的情感分数越接近 1 越积极越接近 0 越消极。但直接按 0.5 分正负太粗糙实际数据里大量中性文本会落在 0.4 到 0.6 之间。我的经验是设两个阈值小于 0.3 判消极大于 0.7 判积极中间判中性。这样分类结果更符合人工直觉答辩时也好解释。from snownlp import SnowNLP def analyze_sentiment(text): if not text or len(text) 5: return 中性, 0.5 score SnowNLP(text).sentiments if score 0.3: return 消极, score elif score 0.7: return 积极, score else: return 中性, score逻辑说明SnowNLP(text).sentiments内部基于朴素贝叶斯模型对短文本效果尚可对反讽、双重否定容易翻车。比如“呵呵真是太好了”会被判积极这是已知局限毕设里可以在文档中说明不必强行解决。参数方面阈值 0.3 和 0.7 可以根据你的数据集微调如果积极文本明显偏少就把上限降到 0.65。3.3 关键词提取TF-IDF 与 TextRank 怎么选jieba.analyse提供两种关键词提取方式extract_tags基于 TF-IDFtextrank基于 TextRank。TF-IDF 依赖全局语料适合有大量背景文本的场景TextRank 只依赖单篇文本适合短文本。微博帖子普遍短我一般用 TextRank取 top 20 个词并过滤掉停用词。import jieba.analyse STOPWORDS set([的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个, 上, 也, 很, 到, 说, 要, 去, 你, 会, 着, 没有, 看, 好, 自己, 这]) def extract_keywords(text, topK20): words jieba.analyse.textrank(text, topKtopK, withWeightFalse) return [w for w in words if w not in STOPWORDS and len(w) 1]参数说明topK20是经验值太多会混入噪声太少覆盖不全。len(w) 1过滤单字词因为单字词在中文里歧义太大。停用词表可以按你的数据补充比如“转发”“微博”这类平台词一定要加进去。4. 存储与可视化用 Flask ECharts 把分析结果端到端串起来4.1 SQLite 表结构设计三张表就够了毕设项目不需要上 MySQLSQLite 零配置、单文件、方便打包。建三张表weibo存原始数据sentiment存情感分析结果keyword存关键词。用sqlite3标准库操作不引入 ORM代码更直观。import sqlite3 def init_db(db_pathweibo.db): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS weibo ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, publish_time TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) cursor.execute( CREATE TABLE IF NOT EXISTS sentiment ( id INTEGER PRIMARY KEY AUTOINCREMENT, weibo_id INTEGER, label TEXT, score REAL, FOREIGN KEY (weibo_id) REFERENCES weibo(id) ) ) cursor.execute( CREATE TABLE IF NOT EXISTS keyword ( id INTEGER PRIMARY KEY AUTOINCREMENT, word TEXT, weight REAL, weibo_id INTEGER, FOREIGN KEY (weibo_id) REFERENCES weibo(id) ) ) conn.commit() conn.close()逻辑说明weibo_id作为外键关联三张表查询时用 JOIN 就能拿到一条微博的完整分析结果。created_at记录入库时间方便排查数据是哪天跑的。参数方面SQLite 默认并发能力弱但毕设演示单用户访问完全够用。4.2 Flask 接口两个路由返回 JSON前端用 ECharts 画图后端只需要提供两个接口/api/sentiment_dist返回积极、中性、消极的数量/api/keyword_top返回 top 10 关键词及权重。from flask import Flask, jsonify import sqlite3 app Flask(__name__) def query_db(sql): conn sqlite3.connect(weibo.db) cursor conn.cursor() cursor.execute(sql) rows cursor.fetchall() conn.close() return rows app.route(/api/sentiment_dist) def sentiment_dist(): rows query_db(SELECT label, COUNT(*) FROM sentiment GROUP BY label) return jsonify({label: count for label, count in rows}) app.route(/api/keyword_top) def keyword_top(): rows query_db(SELECT word, SUM(weight) as total FROM keyword GROUP BY word ORDER BY total DESC LIMIT 10) return jsonify([{word: w, weight: round(t, 2)} for w, t in rows]) if __name__ __main__: app.run(debugTrue, host0.0.0.0, port5000)逻辑说明query_db封装了连接和关闭避免每次写重复代码。sentiment_dist用GROUP BY统计三类情感数量keyword_top用SUM(weight)聚合同一关键词在不同微博中的权重。参数方面host0.0.0.0让局域网内其他设备也能访问方便答辩时用手机演示。debugTrue仅限开发阶段部署时要关掉。4.3 ECharts 前端饼图加柱状图十分钟搞定前端不需要框架一个 HTML 文件引入 ECharts CDN 即可。饼图展示情感分布柱状图展示关键词权重。数据通过fetch从 Flask 接口获取。!DOCTYPE html html head meta charsetutf-8 title微博舆情分析看板/title script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div idpie stylewidth: 600px; height: 400px;/div div idbar stylewidth: 600px; height: 400px;/div script fetch(/api/sentiment_dist).then(r r.json()).then(data { const chart echarts.init(document.getElementById(pie)); chart.setOption({ title: { text: 情感分布 }, series: [{ type: pie, data: Object.entries(data).map(([name, value]) ({ name, value })) }] }); }); fetch(/api/keyword_top).then(r r.json()).then(data { const chart echarts.init(document.getElementById(bar)); chart.setOption({ title: { text: 关键词 Top 10 }, xAxis: { type: category, data: data.map(d d.word) }, yAxis: { type: value }, series: [{ type: bar, data: data.map(d d.weight) }] }); }); /script /body /html逻辑说明fetch拿到 JSON 后直接映射成 ECharts 需要的格式饼图用Object.entries把对象转成数组柱状图用map提取字段。参数方面echarts5.4.3是稳定版本CDN 加载慢的话可以下载到本地。图表容器宽高必须显式设置否则 ECharts 不渲染。5. 避坑与排查那些让系统跑不起来的真实原因5.1 采集返回空列表但浏览器能打开页面现象requests.get返回 200但BeautifulSoup解析出来 0 条数据。原因微博对未登录请求返回的是简化版页面DOM 结构和登录后不同或者触发了验证码跳转。解决先打印resp.text[:500]看实际返回内容如果是登录页或验证页就换用公开数据集或者手动保存几页 HTML 再解析。不要试图绕过平台验证机制毕设用公开数据完全合规。5.2 snownlp 安装后导入报错“No module named”现象pip install snownlp成功但from snownlp import SnowNLP报错。原因Python 环境混乱pip 装到了系统环境而 IDE 用的是虚拟环境。解决在项目目录下执行python -m venv venv创建虚拟环境激活后重新安装。Windows 用venv\Scripts\activateMac/Linux 用source venv/bin/activate。装完用pip list确认 snownlp 在列表里。5.3 中文分词结果全是单字现象jieba.cut出来的词都是“我”“的”“了”这种单字。原因没有加载自定义词典或者文本被清洗成了无空格的长串。解决先确认清洗后的文本保留了正常标点和空格然后加载一个自定义词典补充领域词汇。jieba.load_userdict(userdict.txt)文件里每行一个词格式为“词 词频 词性”词频可省略。5.4 Flask 接口跨域报错 CORS现象前端 HTML 直接打开file://协议请求 Flask 接口浏览器控制台报 CORS 错误。原因file://和http://localhost:5000不同源。解决把 HTML 文件放到 Flask 的static目录下通过http://localhost:5000/static/index.html访问或者安装flask-cors并配置CORS(app)。最简单的方法是前者不用改代码。5.5 SQLite 数据库被锁现象采集脚本还在写数据时Flask 查询报“database is locked”。原因SQLite 默认写操作会锁库并发读写冲突。解决采集和查询分开跑先跑完采集再启动 Flask或者在连接时设置timeout10让写操作等待而不是立即失败。sqlite3.connect(weibo.db, timeout10)就能缓解。6. 让系统更耐看增量采集与情感阈值调优的两个技巧答辩时老师最爱问两个问题“数据量够不够”和“情感分析准不准”这两个问题其实可以用同一个思路解决增量采集 动态阈值。增量采集的意思是每次只采集最新几页按发布时间去重后追加到数据库这样系统可以持续运行数据量随时间自然增长。实现上给weibo表加一个publish_time唯一索引插入时用INSERT OR IGNORE重复数据自动跳过。cursor.execute(CREATE UNIQUE INDEX IF NOT EXISTS idx_time ON weibo(publish_time)) cursor.execute(INSERT OR IGNORE INTO weibo (content, publish_time) VALUES (?, ?), (content, pub_time))情感阈值调优则更取巧先人工标注 50 条数据作为验证集然后写一个循环把积极阈值从 0.6 到 0.8 以 0.05 步长遍历看哪个阈值下分类准确率最高。下面这段代码可以直接跑def tune_threshold(texts, labels): best_acc, best_th 0, 0.7 for th in [0.6, 0.65, 0.7, 0.75, 0.8]: correct 0 for text, true_label in zip(texts, labels): score SnowNLP(text).sentiments pred 积极 if score th else (消极 if score 1 - th else 中性) if pred true_label: correct 1 acc correct / len(texts) if acc best_acc: best_acc, best_th acc, th return best_th, best_acc参数说明1 - th保证消极阈值和积极阈值对称避免中性区间偏移。验证集 50 条是经验值太少波动大太多标注成本高。跑完把best_th写回analyze_sentiment函数即可。我自己的习惯是每次换数据集都重新跑一遍这个调优因为不同话题的用词风格差异很大固定阈值一定会翻车。希望这套流程能帮你把毕设稳稳跑通少熬两个通宵。本文还有配套的精品资源点击获取
返回列表