福卫兵-多源异构数据采集与融合应用综合实践
这个项目属于哪个课程 | <首页 - 2024数据采集与融合技术实践 - 福州大学 - 班级博客 - 博客园 (cnblogs.com)> |
---|---|
这个作业要求在哪里 | <综合设计 - 作业 - 2024数据采集与融合技术实践 - 班级博客 - 博客园 (cnblogs.com)> |
组名、项目简介 | <组名:福小兵,项目需求:实时舆情监控系统,项目目标:为福州大学提供舆情监控与决策辅助工具,技术路线:使用 Flask 后端、Memfire(PostgreSQL)数据库和 Vue 前端技术栈,建立从数据采集到情感分析再到可视化的完整系统> |
团队成员学号 | <102202141黄昕怡, 102202112刘莹,102202145谢含, 102202101马鑫,102202106王强,102202126陈家凯,102202153来再提·叶鲁别克,102202124 阿依娜孜·赛日克> |
这个项目目标 | <设计并实现一个多源异构数据采集系统,通过情感分析和大数据技术总结和展示舆情,增强学校管理者对校园舆情的理解和控制力度。> |
其他参考文献 | [1] Kumar, A., et al. "Real-Time Sentiment Analysis of Twitter Data." Journal of Big Data. 2021. [2]马茜, 谷峪, 张天成, & 于戈. (2013). 一种基于数据质量的异构多源多模态感知数据获取方法. 计算机学报, 36(10), 12. [3]司俊勇, & 付永华. (2024). 多模态数据融合的在线学习情感计算研究. 图书与情报(3), 69-80. [4]何炎祥, 孙松涛, 牛菲菲, & 李飞. (2017). 用于微博情感分析的一种情感语义增强的深度学习模型. 计算机学报, 40(4), 18.[5] 张玲, 王磊, & 李铭. (2021). 面向社交媒体情感分析的多模态数据融合方法研究. 图书情报工作, 65(8), 21-28.[6] 赵妍妍, 秦兵, 刘挺.文本情感分析[J].软件学报 |
Github项目仓库:https://github.com/fufubuff/fu_police
一.项目背景
随着信息技术的飞速发展和互联网的广泛普及,社交媒体成为了公众表达意见和情感的主要渠道。在这一背景下,网络舆情的影响力逐渐增大,尤其在高校等具有较高社会关注度的机构中,舆情的波动往往直接关系到学校的声誉与形象。近年来,社交平台如微博、知乎、贴吧等成为了用户分享观点、交流信息的重要场所,同时也成为了舆情产生和扩散的主要阵地。
在中国,高校是知识创新和社会舆论的重要发源地,因此高校的舆情管理和监控具有特殊意义。舆情事件,尤其是负面舆情的蔓延,可能会对学校的形象、声誉乃至长期发展产生深远影响。如何及时识别、分析和响应网络舆情,成为了高校管理者亟待解决的课题。
二.项目结构
1. 数据采集层
数据采集层主要负责从多个社交平台(如微博、知乎、贴吧等)获取与福州大学相关的多模态内容(包括文本、图片、视频链接等)。采集的主要手段包括:
- 爬虫技术:利用 Python 的爬虫框架(如 Scrapy、Selenium 等),定期从指定平台抓取相关数据。
- 官方 API 接口:通过调用各平台提供的开放 API(如微博 API、知乎 API 等),自动化获取平台公开的与福州大学相关的内容。
所有采集到的数据将存储在 Memfire 提供的云端 PostgreSQL 数据库 中,确保数据的高效查询与存储。同时,数据库设计考虑到未来可能需要扩展的数据量,采用分表分库、索引优化等手段,提升数据检索的效率。
2. 后端层(Flask)
后端层采用 Flask 框架,提供灵活、易扩展的 RESTful API 接口,供前端进行数据交互。具体的功能包括:
2.1 关键词搜索
- 用户通过关键词检索,后端会在 PostgreSQL 数据库中查找与福州大学相关的内容。
- 支持模糊搜索和精准搜索,返回相关文本、图片及视频链接等信息。
2.2 情感分析
- 利用基于 深度学习模型 的情感分析算法,对文本内容进行多类别情感识别(如正面、负面、中性等)。
- 情感分析结果通过 概率分布 返回,帮助管理人员了解舆情的整体情绪倾向。
2.3 舆情整合
- 关键词提取:采用自然语言处理(NLP)技术提取与福州大学相关的高频关键词。
- 话题检测:通过聚类算法分析文本内容,识别高频话题,帮助发现舆情热点。
- 情感分析统计:统计不同情感类别的数量,生成舆情的情感分布报告。
- AI 总结:集成大语言模型接口,对检索到的结果进行二次生成,深入总结相关舆情的核心内容。
3. 前端层(Vue + HTML/CSS)
前端层采用 Vue.js 框架进行开发,提供直观、交互性强的用户界面。前端分为两个主要部分:
3.1 前端一:报告式分析界面
- 搜索输入框:用户可以输入关键词,搜索与福州大学相关的内容。
- 情感分析可视化:通过 Chart.js 等图表库展示情感分析结果,直观显示正面、负面、中性情感的比例。
- AI 总结输出:展示由后端大语言模型生成的总结报告,帮助用户快速了解舆情的概况。
3.2 前端二:聊天式舆论分析界面
- 用户聊天交互:用户可以通过类似聊天窗口的方式,与系统进行舆情分析对话。系统根据用户输入的查询内容,返回相关的分析结果。
- 全数据库整合:整合数据库中的所有数据,支持分页展示。用户可以通过点击不同的分页查看舆情内容。
- 数据分析展示:通过柱状图、饼图、词云等多种可视化方式展示数据分析结果,帮助用户快速了解热点话题、舆情趋势等信息。
- AI 聊天窗口:通过大语言模型的接口,用户可以在聊天窗口中实时提问,获取关于福州大学的舆情分析结果。
3.3 交互功能
- 数据表格:所有的数据都以表格形式展示,用户可以按需求筛选、排序。
- 词云翻页:支持词云的翻页展示,帮助用户查看不同时间段的高频关键词。
- 音乐播放器:在舆情报告中嵌入音乐播放器,作为用户的互动彩蛋。
- 日历打卡:用户可以查看并记录每天的舆情热点,并进行日历打卡,增强参与感。
所有前端界面都通过 Axios 与后端的 Flask API 进行数据交互,动态获取数据并展示在前端界面上,确保数据实时更新和高效展示。
4. 技术栈总结
- 数据采集:Python 爬虫框架(Scrapy、Selenium)、平台 API 接口。
- 后端:Flask、PostgreSQL 数据库、深度学习模型(情感分析)。
- 前端:Vue.js、HTML/CSS、Chart.js、Axios。
- 可视化工具:柱状图、饼图、词云、日历、音乐播放器等。
- 大语言模型:集成大语言模型接口,进行二次生成和深度总结。
5. 系统优化与展望
- 优化数据存储:随着数据量的增大,可考虑采用分布式数据库架构来提升存储和查询性能。
- 情感分析模型优化:通过持续优化情感分析模型,提升其对不同类型文本的识别准确度。
- 实时舆情监控:结合流式数据处理技术,实现舆情的实时监控和预警系统。
- 智能推荐:基于用户行为数据,提供个性化的舆情分析推荐,提升系统的智能化水平。
通过以上优化和扩展,福州大学舆情监控系统将不仅具备强大的数据分析能力,还能为管理者提供及时、精确的决策支持。
三.项目展示
项目页面的功能展示:
-
爬取内容的数据库的数据展示,方式有柱形图,饼图,词云图,分页表格,还包含音乐播放器,日历。此外还有ai聊天窗口
-
图片分析
-
微博评论分析
-
贴吧评论分析
-
用户自爬取数据和报告式分析
四.个人工作
-
前端页面的布局安排以及美化设计
- 左bar:AI窗口的前端设计与代码编写,右bar:音乐播放器 日历的样式设计以及各个功能的实现
style样式 .music-back.rotating { animation: rotate 2s linear infinite; } .right-sidebar { grid-area: right; background-color: #f8e9e9; padding: 10px; box-shadow: 0 4px 8px rgba(192, 57, 43, 0.2); display: flex; flex-direction: column; align-items: center; } .ai-window { display: flex; flex-direction: column; justify-content: flex-end; width: 100%; height: 700px; position: relative; border: 1px solid #ccc; margin-bottom: 20px; border-radius: 10px; padding: 20px; box-sizing: border-box; overflow: hidden; background: url('img/aibg.jpg') no-repeat center center; /* Add your background image here */ background-size: cover; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } #chatContainer { overflow-y: auto; max-height: 90%; display: flex; flex-direction: column; margin-top: auto; padding: 10px; background: rgba(255, 255, 255, 0.8); border-radius: 10px; box-shadow: inset 0 4px 8px rgba(0, 0, 0, 0.1); } .sent-message, .reply-message { width: fit-content; max-width: 80%; padding: 10px 15px; margin: 5px 0; border-radius: 20px; font-weight: bold; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .sent-message { align-self: flex-end; background-color: #c0392b; color: #fff; } .reply-message { align-self: flex-start; background-color: #ecf0f1; color: #333; } #userInput { width: calc(100% - 40px); padding: 10px; margin-top: 10px; border: 1px solid #ccc; border-radius: 20px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); font-size: 16px; } .button-container { position: relative; z-index: 10; /* 保持按钮容器较高的层级 */ text-align: center; /* 使按钮在水平方向居中 */ margin-bottom: -10px; margin-top: -20px; } .clickable-image { display: inline-block; cursor: pointer; transition: transform 0.3s; } .clickable-image:hover { transform: scale(1.1); /* Slightly enlarge the image on hover */ } @keyframes fadeIn { from { opacity: 0; /* 初始透明度为0,完全透明 */ } to { opacity: 1; /* 最终透明度为1,完全显示 */ } } img { cursor: pointer; width: 80px; height: auto; border-radius: 5px; transition: transform 0.2s; } img:hover { transform: scale(1.1); } .calendar-container { margin-top: 20px; display: flex; justify-content: center; align-items: center; } #calendar { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; width: 100%; max-width: 400px; background-color: #fff; padding: 10px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); } .calendar-day { display: flex; justify-content: center; align-items: center; padding: 10px; border-radius: 5px; font-weight: bold; cursor: pointer; position: relative; /* Add this line */ } .calendar-day.today { background-color: #77fffa; } .calendar-day.marked { background-color: #fa8377; color: #fff; } .calendar-container button { background-color: #c0392b; color: white; border: none; padding: 10px 20px; font-size: 14px; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; margin: 0 10px; } .calendar-container button:hover { background-color: #a93226; } .stamp { position: absolute; width: 30px; height: 30px; bottom: 5px; right: 5px; animation: stampAnimation 1s forwards; } @keyframes stampAnimation { 0% { transform: scale(0) rotate(0deg); opacity: 0; } 50% { transform: scale(1.2) rotate(20deg); opacity: 1; } 100% { transform: scale(1) rotate(0deg); opacity: 1; } } .small-stamp { position: absolute; width: 20px; height: 20px; bottom: 0; right: -3px; z-index: 10; }
html结构 <div class="left-sidebar"> <div class="ai-window"> <div id="chatContainer"></div> <input type="text" id="userInput" placeholder="福兵,开启今日的巡查吧!"> </div> <div class="button-container"> <img src="img/fu.png" alt="Clickable Image" class="clickable-image" onclick="sendText()"> </div> </div> <div class="right-sidebar"> <!-- 音乐播放器 --> <div class="player"> <!-- 歌曲封面 --> <div class="cover"> <img src="music/back.jpg" alt="" class="music-back"> <div class="song"> <p> 纯音乐,请欣赏<br> </p> </div> </div> <!-- 歌词设置 --> <h2>清澈的眼睛</h2> <!-- audio标签 --> <div class="mus"> <audio src="music/bgm.mp3" controls autoplay loop></audio> </div> </div> <div class="calendar-container"> <div id="calendar"></div> </div> </div> <table> <thead> <tr> <th>序号</th> <th>昵称</th> <th>内容</th> <th>情感</th> </tr> </thead> <tbody id="commentsTableBody"></tbody> </table>
script document.addEventListener('DOMContentLoaded', function () { const calendar = document.getElementById('calendar'); const today = new Date(); const year = today.getFullYear(); const month = today.getMonth(); const date = today.getDate(); let isStamped = false; // Flag to track if the stamp has been applied function generateCalendar(year, month) { calendar.innerHTML = ''; const firstDay = new Date(year, month, 1).getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); for (let i = 0; i < firstDay; i++) { const emptyCell = document.createElement('div'); calendar.appendChild(emptyCell); } for (let day = 1; day <= daysInMonth; day++) { const dayCell = document.createElement('div'); dayCell.textContent = day; dayCell.classList.add('calendar-day'); if (day === date) { dayCell.classList.add('today'); } calendar.appendChild(dayCell); } } generateCalendar(year, month); const clickableImage = document.querySelector('.clickable-image'); clickableImage.addEventListener('click', function () { if (!isStamped) { // Check if the stamp has already been applied const todayCell = document.querySelector('.calendar-day.today'); if (todayCell) { const stamp = document.createElement('img'); stamp.src = 'img/fu.png'; stamp.classList.add('stamp'); todayCell.appendChild(stamp); setTimeout(() => { todayCell.classList.add('marked'); stamp.remove(); const smallImage = document.createElement('img'); smallImage.src = 'img/fu.png'; // Path to the small image smallImage.classList.add('small-stamp'); todayCell.appendChild(smallImage); }, 1000); // Adjust the timeout duration as needed isStamped = true; // Set the flag to true after stamping } } }); }); function handleKeyPress(event) { if (event.key === 'Enter') { sendText(); } } function sendText() { const userInput = document.getElementById('userInput').value.trim(); const chatContainer = document.getElementById('chatContainer'); if (userInput !== '') { // 显示用户发送的消息 const userMessage = document.createElement('div'); userMessage.textContent = userInput; userMessage.classList.add('sent-message'); chatContainer.appendChild(userMessage); // 清空输入框 document.getElementById('userInput').value = ''; // 滚动到聊天底部 chatContainer.scrollTop = chatContainer.scrollHeight; // 发送请求到后端处理 fetch('http://localhost:5000/api/chat', { // 请根据实际后端地址修改 method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: userInput }) }) .then(response => response.json()) .then(data => { // 显示 AI 助手的回复 const aiMessage = document.createElement('div'); aiMessage.textContent = data.reply; // 假设后端返回 { reply: "回复内容" } aiMessage.classList.add('reply-message'); chatContainer.appendChild(aiMessage); // 滚动到聊天底部 chatContainer.scrollTop = chatContainer.scrollHeight; }) .catch(error => { console.error('Error:', error); const errorMessage = document.createElement('div'); errorMessage.textContent = '抱歉,出现了一个错误。请稍后再试。'; errorMessage.classList.add('reply-message'); chatContainer.appendChild(errorMessage); chatContainer.scrollTop = chatContainer.scrollHeight; }); } } function showMessage() { $('#message').show(); $('#message').css('animation-play-state', 'running'); // 触发动画开始播放 }
- 前端导航栏功能实现 以及小彩蛋设计
.popup {
display: none;
position: fixed;
z-index: 1;
left: 50%;
transform: translateX(-50%);
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border: 2px solid black;
text-align: center;
width: 300px;
}
.qrcode {
width: 100%;
height: auto;
}
.popup button {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
}
.player {
margin-top: 10px;
height: 300px;
width: 400px;
background-color: rgba(216, 218, 221, 0.5);
border-radius: 10px;
}
.cover {
height: 200px;
width: 400px;
margin-left: 20px;
margin-top: 15px;
overflow: hidden;
border-radius: 10px;
display: flex;
/* 水平方向布局,两端对齐 */
justify-content: flex-start;
/* 垂直方向居中对齐 */
align-items: stretch;
<div class="navbar">
<div class="logo-container">
<img src="img/logo.png" alt="Logo">
<span>福卫兵</span>
</div>
<a href="#">主页</a>
<div class="dropdown">
<a href="#" class="dropbtn">菜单</a>
<div class="dropdown-content">
<a href="index.html">图片分析</a>
<a href="comment_analyse.html">贴吧评论分析</a>
<a href="analyse_demo.html">微博评论分析</a>
</div>
</div>
<a id="aboutButton">关于我们</a>
<a id="contactButton">联系</a>
</div>
<div id="aboutModal" class="modal">
<p>大数据福卫兵兵团感谢你的喜欢</p>
</div>
<div id="easterEggModal" class="modal">
<p>维护福大网络环境 人人有责 快来加入福卫兵吧</p>
</div>
<div id="contactModal" class="popup">
<img src="img/qrcode.png" alt="QR Code" class="qrcode">
<button onclick="closeContactModal()">×</button>
</div>
document.addEventListener('DOMContentLoaded', function () {
let aboutClickCount = 0;
const aboutModal = document.getElementById('aboutModal');
const easterEggModal = document.getElementById('easterEggModal');
const contactModal = document.getElementById('contactModal');
const aboutButton = document.getElementById('aboutButton');
const contactButton = document.getElementById('contactButton');
aboutButton.addEventListener('click', function () {
aboutClickCount++;
aboutModal.style.display = 'block';
setTimeout(function () {
aboutModal.style.display = 'none';
}, 5000); // 5 seconds total (0.5s fade in + 2s fade out + 2.5s display time)
if (aboutClickCount % 10 === 0) {
easterEggModal.style.display = 'block';
setTimeout(function () {
easterEggModal.style.display = 'none';
}, 5000); // 5 seconds display time
}
});
contactButton.addEventListener('click', function () {
contactModal.style.display = 'block';
setTimeout(function () {
contactModal.style.display = 'none';
}, 5000); // 5 seconds display time
});
window.addEventListener('click', function (event) {
if (event.target === aboutModal) {
aboutModal.style.display = 'none';
}
if (event.target === easterEggModal) {
easterEggModal.style.display = 'none';
}
if (event.target === contactModal) {
contactModal.style.display = 'none';
}
});
});