福卫兵-多源异构数据采集与融合应用综合实践

这个项目属于哪个课程 <首页 - 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聊天窗口

  • 图片分析

  • 微博评论分析

  • 贴吧评论分析

  • 用户自爬取数据和报告式分析
    img
    img

四.个人工作

  • 前端页面的布局安排以及美化设计

    • 左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';
        }
    });
});
posted @ 2024-12-15 23:57  chenoojkk  阅读(9)  评论(0编辑  收藏  举报