《AI大模型趣味实战 》第8集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 2
《AI大模型趣味实战 》第8集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 2
摘要
本文末尾介绍了如何实现新闻智能体的方法。在信息爆炸的时代,如何高效获取和筛选感兴趣的新闻内容成为一个现实问题。本文将带领读者通过Python和Flask框架,结合大模型的强大能力,构建一个个性化的新闻聚合平台,不仅能够自动收集整理各类RSS源的新闻,还能以语音播报的形式提供"新闻电台"功能。我们将重点探讨如何利用AI大模型优化新闻内容提取、自动生成标签分类,以及如何通过语音合成技术实现新闻播报功能,打造一个真正实用的个人新闻助手。
项目代码仓库:https://github.com/wyg5208/rss_news_flask

以下内容接上一个博客:《AI大模型趣味实战 》第7集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 1
9. 系统日志集成
为了更好地监控系统运行状态和调试问题,我们需要实现一个完善的日志系统,实现日志文件自动轮转和网页查看功能:
# 日志系统配置
LOG_DIR = 'logs'
if not os.path.exists(LOG_DIR):os.makedirs(LOG_DIR)# 内存缓冲区,用于在UI中显示最新日志
log_buffer = deque(maxlen=1000)# 创建自定义的日志记录器
class MemoryHandler(logging.Handler):"""将日志记录到内存缓冲区,用于Web界面显示"""def emit(self, record):log_entry = self.format(record)log_buffer.append({'time': datetime.datetime.fromtimestamp(record.created).strftime('%Y-%m-%d %H:%M:%S'),'level': record.levelname,'message': record.getMessage(),'formatted': log_entry})# 配置日志记录器
logger = logging.getLogger('rss_app')
logger.setLevel(logging.INFO)# 小时文件处理器,每小时自动创建一个新文件
hourly_handler = TimedRotatingFileHandler(filename=os.path.join(LOG_DIR, 'rss_app.log'),when='H',interval=1,backupCount=72, # 保留3天的日志encoding='utf-8'
)
# 设置日志文件后缀格式为 年-月-日_小时
hourly_handler.suffix = "%Y-%m-%d_%H"
hourly_handler.setLevel(logging.INFO)
hourly_handler.setFormatter(console_format)
logger.addHandler(hourly_handler)
为了让用户能够在Web界面上查看系统日志,我们添加了相应的路由和API端点:
@app.route('/system_logs')
@login_required
def system_logs():"""显示系统日志页面"""logger.info('访问系统日志页面')# 获取日志文件列表log_files = []try:# 获取所有日志文件并按修改时间排序log_pattern = os.path.join(LOG_DIR, 'rss_app.log*')all_log_files = glob.glob(log_pattern)all_log_files.sort(key=os.path.getmtime, reverse=True)for file_path in all_log_files:# 获取文件信息并添加到列表file_name = os.path.basename(file_path)file_stats = os.stat(file_path)file_size = file_stats.st_size / 1024 # KBfile_time = datetime.datetime.fromtimestamp(file_stats.st_mtime).strftime('%Y-%m-%d %H:%M:%S')# 格式化显示名称if file_name == 'rss_app.log':display_name = f"当前日志 ({file_size:.1f} KB) - {file_time}"else:# 解析时间戳timestamp = file_name.replace('rss_app.log.', '')try:parsed_time = datetime.datetime.strptime(timestamp, '%Y-%m-%d_%H')display_name = f"{parsed_time.strftime('%Y-%m-%d %H:00')} ({file_size:.1f} KB)"except:display_name = f"{file_name} ({file_size:.1f} KB) - {file_time}"log_files.append({'name': display_name,'path': file_path})except Exception as e:logger.error(f"获取日志文件列表出错: {str(e)}")# 统计信息stats = {'total': len(log_buffer),'error': sum(1 for log in log_buffer if log['level'] == 'ERROR'),'warning': sum(1 for log in log_buffer if log['level'] == 'WARNING'),'files': len(log_files)}return render_template('system_logs.html', log_files=log_files, stats=stats)
创建系统日志页面的模板,实现实时日志显示、日志过滤和历史日志查看功能:
<!-- templates/system_logs.html -->
{% extends "base.html" %}{% block title %}系统日志{% endblock %}{% block content %}
<div class="container-fluid"><h1 class="mb-4">系统日志</h1><div class="row mb-4"><div class="col-md-12"><div class="card"><div class="card-header"><div class="d-flex justify-content-between align-items-center"><h5 class="mb-0">实时日志监控</h5><div><button id="refreshBtn" class="btn btn-sm btn-outline-primary">刷新</button><button id="clearBtn" class="btn btn-sm btn-outline-secondary">清除显示</button><div class="btn-group ms-2"><button class="btn btn-sm btn-outline-dark dropdown-toggle" type="button" data-bs-toggle="dropdown">过滤级别</button><div class="dropdown-menu"><a class="dropdown-item log-filter active" href="#" data-level="all">全部</a><a class="dropdown-item log-filter" href="#" data-level="ERROR">错误</a><a class="dropdown-item log-filter" href="#" data-level="WARNING">警告</a><a class="dropdown-item log-filter" href="#" data-level="INFO">信息</a></div></div></div></div></div><div class="card-body"><div class="log-stats mb-3"><span class="badge bg-primary">总计: <span id="total-count">{{ stats.total }}</span></span><span class="badge bg-danger">错误: <span id="error-count">{{ stats.error }}</span></span><span class="badge bg-warning text-dark">警告: <span id="warning-count">{{ stats.warning }}</span></span></div><div id="log-container" class="log-display"><div class="text-center my-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">加载中...</span></div><p class="mt-2">加载日志数据...</p></div></div></div></div></div></div><div class="row"><div class="col-md-12"><div class="card"><div class="card-header"><h5 class="mb-0">日志文件 ({{ stats.files }})</h5></div><div class="card-body"><div class="list-group">{% for log_file in log_files %}<a href="#" class="list-group-item list-group-item-action log-file-item" data-path="{{ log_file.path }}">{{ log_file.name }}</a>{% else %}<div class="text-center py-3"><p class="text-muted mb-0">没有找到日志文件</p></div>{% endfor %}</div></div></div></div></div><!-- 日志文件查看模态框 --><div class="modal fade" id="logFileModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="logFileTitle">日志文件</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><pre id="logFileContent" class="log-file-content">加载中...</pre></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button></div></div></div></div>
</div>
{% endblock %}{% block scripts %}
<script>// 日志系统交互JavaScript代码$(document).ready(function() {// 当前过滤级别let currentLevel = 'all';// 加载日志数据function loadLogs() {$.getJSON('/api/logs', { level: currentLevel }, function(data) {const logs = data.logs;const stats = data.stats;// 更新统计信息$('#total-count').text(stats.total);$('#error-count').text(stats.error);$('#warning-count').text(stats.warning);// 清空并填充日志容器const container = $('#log-container');container.empty();if (logs.length === 0) {container.html('<p class="text-center text-muted my-5">没有日志记录</p>');return;}// 创建日志表格const table = $('<table class="table table-sm table-hover log-table"></table>');const tbody = $('<tbody></tbody>');// 添加日志行logs.forEach(log => {const row = $('<tr></tr>');// 根据日志级别设置行样式if (log.level === 'ERROR') {row.addClass('table-danger');} else if (log.level === 'WARNING') {row.addClass('table-warning');}row.append(`<td class="log-time">${log.time}</td>`);row.append(`<td class="log-level">${log.level}</td>`);row.append(`<td class="log-message">${log.message}</td>`);tbody.append(row);});table.append(tbody);container.append(table);// 滚动到底部container.scrollTop(container[0].scrollHeight);});}// 初始加载日志loadLogs();// 刷新按钮点击事件$('#refreshBtn').click(function() {loadLogs();});// 清除按钮点击事件$('#clearBtn').click(function() {$('#log-container').empty();});// 日志级别过滤器点击事件$('.log-filter').click(function(e) {e.preventDefault();// 更新选中状态$('.log-filter').removeClass('active');$(this).addClass('active');// 设置当前级别并重新加载currentLevel = $(this).data('level');loadLogs();});// 日志文件项点击事件$('.log-file-item').click(function(e) {e.preventDefault();const path = $(this).data('path');const name = $(this).text();// 设置模态框标题$('#logFileTitle').text(name);$('#logFileContent').text('加载中...');// 显示模态框const modal = new bootstrap.Modal(document.getElementById('logFileModal'));modal.show();// 加载日志文件内容$.getJSON('/api/logs/file', { file_path: path }, function(data) {if (data.status === 'success') {$('#logFileContent').text(data.content);} else {$('#logFileContent').text('加载失败: ' + data.message);}});});// 自动刷新(每10秒)setInterval(loadLogs, 10000);});
</script>
{% endblock %}
通过实现这些功能,我们的系统可以自动记录所有关键操作和错误信息,用户可以实时查看系统状态和历史日志,便于问题诊断和监控。
10. 移动设备优化与兼容性处理
针对移动设备访问,我们需要优化用户界面和交互体验,特别是在移动浏览器上的按钮功能:
// static/modal_fix.js
document.addEventListener('DOMContentLoaded', function() {console.log('modal_fix.js 已加载');// 检查jQuery和Bootstrap是否加载if (typeof jQuery === 'undefined') {console.error('jQuery 未加载!');return;}if (typeof bootstrap === 'undefined') {console.error('Bootstrap 未加载!');return;}console.log('jQuery和Bootstrap已正确加载');// 在新闻详情页初始化initNewsDetailPage();function initNewsDetailPage() {// 检查是否是新闻详情页if (!document.getElementById('news-content')) {return;}console.log('初始化新闻详情页面');// 初始化模态框const shareModal = new bootstrap.Modal(document.getElementById('shareModal'), {keyboard: true});const helpModal = new bootstrap.Modal(document.getElementById('helpModal'), {keyboard: true});// 重新绑定按钮事件$('#btnShare').off('click').on('click', function() {console.log('分享按钮被点击');shareModal.show();});$('#btnHelp').off('click').on('click', function() {console.log('帮助按钮被点击');helpModal.show();});// 语音朗读功能$('#btnSpeak').off('click').on('click', function() {try {console.log('朗读按钮被点击');const title = document.getElementById('news-title').innerText;const content = document.getElementById('news-content').innerText;console.log(`准备朗读,标题长度: ${title.length}, 内容长度: ${content.length}`);// 组合完整的朗读文本const fullText = title + '。' + content;// 尝试使用Web Speech APIif ('speechSynthesis' in window) {console.log('使用Web Speech API朗读');// 创建语音对象const speech = new SpeechSynthesisUtterance();speech.text = fullText;speech.lang = 'zh-CN';speech.rate = 1.0; // 语速speech.pitch = 1.0; // 音调speech.volume = 1.0; // 音量// 开始朗读window.speechSynthesis.speak(speech);} else {console.log('Web Speech API不可用,使用后端API');// 使用后端API生成语音$.ajax({url: '/api/text_to_speech',type: 'POST',contentType: 'application/json',data: JSON.stringify({ text: fullText }),success: function(response) {if (response.status === 'success') {console.log('语音生成成功,URL:', response.audio_url);// 创建音频元素播放const audio = new Audio(response.audio_url);audio.play();} else {console.error('语音生成失败:', response.message);alert('语音生成失败: ' + response.message);}},error: function(xhr, status, error) {console.error('API请求失败:', error);alert('无法连接到语音服务: ' + error);}});}} catch (e) {console.error('朗读过程出错:', e);alert('朗读功能出错: ' + e.message);}});// 复制内容功能$('#btnCopy').off('click').on('click', function() {try {console.log('复制按钮被点击');const title = document.getElementById('news-title').innerText;const description = document.getElementById('news-description').innerText;const content = document.getElementById('news-content').innerText;// 组合要复制的文本const textToCopy = `${title}\n\n${description}\n\n${content}`;// 使用剪贴板APInavigator.clipboard.writeText(textToCopy).then(function() {console.log('内容已复制到剪贴板');alert('内容已复制到剪贴板');}).catch(function(err) {console.error('剪贴板操作失败:', err);alert('复制失败: ' + err.message);});} catch (e) {console.error('复制过程出错:', e);alert('复制功能出错: ' + e.message);}});// 复制链接按钮$('#copyLinkBtn').off('click').on('click', function() {try {const currentUrl = window.location.href;navigator.clipboard.writeText(currentUrl).then(function() {alert('链接已复制到剪贴板');}).catch(function(err) {console.error('复制链接失败:', err);alert('复制链接失败: ' + err.message);});} catch (e) {console.error('复制链接过程出错:', e);alert('复制链接功能出错: ' + e.message);}});console.log('新闻详情页面按钮事件已重新绑定');}
});
在HTML模板中引入这个修复脚本:
<!-- templates/news_detail.html -->
{% extends "base.html" %}{% block title %}{{ news.title }}{% endblock %}{% block content %}
<div class="container"><nav aria-label="breadcrumb"><ol class="breadcrumb"><li class="breadcrumb-item"><a href="{{ url_for('dashboard') }}">首页</a></li><li class="breadcrumb-item"><a href="{{ url_for('news_list') }}">新闻列表</a></li><li class="breadcrumb-item active" aria-current="page">新闻详情</li></ol></nav><div class="card"><div class="card-header"><h1 id="news-title" class="h3">{{ news.title }}</h1><div class="mt-2 text-muted"><small>来源: {{ news.source }}</small>{% if news.pub_date %}<small class="ms-3">发布时间: {{ news.pub_date.strftime('%Y-%m-%d %H:%M') }}</small>{% endif %}<small class="ms-3">添加时间: {{ news.add_date.strftime('%Y-%m-%d %H:%M') }}</small></div></div><div class="card-body">{% if news.description %}<div id="news-description" class="lead mb-4">{{ news.description }}</div>{% endif %}<div class="mb-3"><div class="btn-group" role="group"><button id="btnSpeak" class="btn btn-outline-primary" type="button"><i class="fas fa-volume-up"></i> 朗读</button><button id="btnCopy" class="btn btn-outline-secondary" type="button"><i class="fas fa-copy"></i> 复制内容</button><button id="btnShare" class="btn btn-outline-success" type="button"><i class="fas fa-share-alt"></i> 分享</button><button id="btnHelp" class="btn btn-outline-info" type="button"><i class="fas fa-question-circle"></i> 帮助</button></div></div><div id="news-content" class="news-content">{{ news.content|safe }}</div>{% if news.tags %}<div class="mt-4"><h5>标签</h5><div class="news-tags">{% for tag in news.tags %}<a href="{{ url_for('news_list', tag='{{ tag.name }}') }}" class="badge bg-primary text-decoration-none">{{ tag.name }}</a>{% endfor %}</div></div>{% endif %}{% if news.link %}<div class="mt-4"><a href="{{ news.link }}" target="_blank" class="btn btn-sm btn-outline-dark"><i class="fas fa-external-link-alt"></i> 查看原文</a></div>{% endif %}</div></div>
</div><!-- 分享模态框 -->
<div class="modal fade" id="shareModal" tabindex="-1" aria-labelledby="shareModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="shareModalLabel">分享此新闻</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><p>复制以下链接分享:</p><div class="input-group"><input type="text" class="form-control" id="shareLink" value="{{ request.url }}" readonly><button class="btn btn-outline-secondary" type="button" id="copyLinkBtn">复制</button></div></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button></div></div></div>
</div><!-- 帮助模态框 -->
<div class="modal fade" id="helpModal" tabindex="-1" aria-labelledby="helpModalLabel" aria-hidden="true"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="helpModalLabel">功能帮助</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div><div class="modal-body"><div class="mb-3"><h6><i class="fas fa-volume-up"></i> 朗读</h6><p>使用语音朗读当前新闻内容,支持中文朗读。</p></div><div class="mb-3"><h6><i class="fas fa-copy"></i> 复制内容</h6><p>将新闻标题、描述和正文内容复制到剪贴板。</p></div><div class="mb-3"><h6><i class="fas fa-share-alt"></i> 分享</h6><p>获取当前新闻的链接,以便分享给他人。</p></div></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button></div></div></div>
</div>
{% endblock %}{% block scripts %}
<script src="{{ url_for('static', filename='speech.js') }}"></script>
<script src="{{ url_for('static', filename='modal_fix.js') }}"></script>
{% endblock %}
通过这些优化,我们解决了移动设备上按钮不响应的问题,并提高了用户交互体验。
总结与扩展思考
项目梳理
在这个项目中,我们成功构建了一个功能完整的个人新闻聚合平台,它具有以下核心功能:
-
RSS源管理与内容抓取:支持添加、删除和管理多个RSS源,自动获取最新新闻内容。
-
大模型内容优化:使用大语言模型(GLM4)对抓取的内容进行智能处理,提取关键内容并去除无关元素。
-
自动标签生成:基于大模型分析新闻内容,自动生成相关标签,便于内容分类和检索。
-
语音合成与播报:支持将新闻内容转换为语音,实现类似广播的新闻播报功能。
-
定时任务系统:实现新闻自动抓取和定时播报功能,减少手动操作。
-
系统日志与监控:完善的日志系统,支持Web界面查看系统运行状态和历史日志。
-
移动设备适配:优化移动端用户体验,确保在各种设备上都能正常使用。
通过这个项目,我们展示了如何将大语言模型应用于实际应用场景,利用其强大的文本理解与生成能力提升应用的智能化水平。同时,项目也演示了从数据获取、处理、存储到展示的完整流程,涵盖了Web开发的各个方面。
扩展思考
- 个性化推荐系统
基于用户的阅读历史和标签偏好,我们可以实现个性化的新闻推荐功能。这可以通过分析用户与标签的交互行为,构建用户画像,然后使用协同过滤或内容匹配算法实现。
def get_recommended_news(user_id, limit=10):"""获取给用户推荐的新闻"""# 获取用户标签偏好user_preferences = UserTagPreference.query.filter_by(user_id=user_id).all()preferred_tags = [pref.tag.name for pref in user_preferences]# 获取含有这些标签的近期新闻recommended_news = []if preferred_tags:# 基于标签匹配的推荐tagged_news = db.session.query(News)\.join(Tag, News.id == Tag.news_id)\.filter(Tag.name.in_(preferred_tags))\.order_by(News.add_date.desc())\.limit(limit*2)\.all()recommended_news.extend(tagged_news)# 如果推荐数量不足,添加最新新闻if len(recommended_news) < limit:recent_news = News.query.order_by(News.add_date.desc())\.limit(limit - len(recommended_news))\.all()recommended_news.extend(recent_news)# 去重并限制数量unique_news = []news_ids = set()for news in recommended_news:if news.id not in news_ids and len(unique_news) < limit:news_ids.add(news.id)unique_news.append(news)return unique_news
- 情感分析与分类
使用大模型进行新闻的情感分析与主题分类,为用户提供更多维度的内容筛选。
def analyze_news_sentiment(news):"""分析新闻情感倾向"""try:prompt = f"""
分析以下新闻文章的情感倾向,返回一个值:
正面 - 积极、乐观的内容
中性 - 客观、中立的报道
负面 - 消极、悲观的内容只返回一个词:正面、## 扩展思考### 1. 多模态内容处理当前项目主要处理文本内容,但现代新闻往往包含图片、视频等多模态内容。我们可以扩展系统以支持这些内容:```python
def extract_images_from_content(content):"""从HTML内容中提取图片链接"""try:soup = BeautifulSoup(content, 'html.parser')images = []# 查找所有图片标签for img in soup.find_all('img'):src = img.get('src')if src and not src.startswith('data:'): # 排除base64编码的图片images.append({'url': src,'alt': img.get('alt', ''),'width': img.get('width', ''),'height': img.get('height', '')})return imagesexcept Exception as e:logger.error(f"提取图片时出错: {e}")return []def analyze_images_with_llm(images):"""使用大模型分析图片内容"""if not images:return []try:# 构建promptimage_urls = [img['url'] for img in images]prompt = f"""
分析以下新闻图片链接,提供每张图片可能的内容描述。
不需要访问链接,仅根据URL和alt文本推测内容。
图片链接:
{json.dumps(image_urls, indent=2)}
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])return response['message']['content']except Exception as e:logger.error(f"分析图片时出错: {e}")return "无法分析图片内容"
2. 个性化推荐系统
基于用户阅读历史和兴趣标签,实现智能推荐功能:
class UserReadHistory(db.Model):"""用户阅读历史模型"""id = db.Column(db.Integer, primary_key=True)user_id = db.Column(db.Integer, db.ForeignKey('user.id'))news_id = db.Column(db.Integer, db.ForeignKey('news.id'))read_time = db.Column(db.DateTime, default=datetime.datetime.now)read_duration = db.Column(db.Integer, default=0) # 阅读时长(秒)user = db.relationship('User', backref=db.backref('read_history', lazy=True))news = db.relationship('News', backref=db.backref('read_by', lazy=True))def get_personalized_recommendations(user_id, limit=10):"""为用户生成个性化推荐"""# 1. 获取用户感兴趣的标签user_tags = db.session.query(TagLibrary.name)\.join(UserTagPreference, UserTagPreference.tag_id == TagLibrary.id)\.filter(UserTagPreference.user_id == user_id)\.all()user_tags = [t[0] for t in user_tags]# 2. 获取用户最近阅读的新闻中的标签recent_reads = db.session.query(News)\.join(UserReadHistory, UserReadHistory.news_id == News.id)\.filter(UserReadHistory.user_id == user_id)\.order_by(UserReadHistory.read_time.desc())\.limit(20)\.all()recent_news_ids = [n.id for n in recent_reads]recent_tags = db.session.query(Tag.name)\.filter(Tag.news_id.in_(recent_news_ids))\.group_by(Tag.name)\.all()recent_tags = [t[0] for t in recent_tags]# 3. 合并兴趣标签和最近阅读标签all_tags = set(user_tags + recent_tags)# 4. 查找包含这些标签的新闻,但用户尚未阅读if all_tags:recommended_news = db.session.query(News)\.join(Tag, Tag.news_id == News.id)\.filter(Tag.name.in_(all_tags))\.filter(~News.id.in_(recent_news_ids))\.order_by(News.add_date.desc())\.limit(limit)\.all()return recommended_newselse:# 如果没有标签信息,返回最新新闻return News.query.order_by(News.add_date.desc()).limit(limit).all()
3. 社交分享与交互功能
增加社交功能,让用户能分享和讨论新闻内容:
class Comment(db.Model):"""新闻评论模型"""id = db.Column(db.Integer, primary_key=True)content = db.Column(db.Text, nullable=False)news_id = db.Column(db.Integer, db.ForeignKey('news.id'))user_id = db.Column(db.Integer, db.ForeignKey('user.id'))created_at = db.Column(db.DateTime, default=datetime.datetime.now)news = db.relationship('News', backref=db.backref('comments', lazy=True))user = db.relationship('User', backref=db.backref('comments', lazy=True))@app.route('/news/<int:news_id>/comment', methods=['POST'])
@login_required
def add_comment(news_id):"""添加评论"""content = request.form.get('content', '').strip()if not content:flash('评论内容不能为空', 'warning')return redirect(url_for('news_detail', news_id=news_id))# 使用大模型检测不良内容is_appropriate = check_content_appropriate(content)if not is_appropriate:flash('评论内容不适当,请修改后重试', 'danger')return redirect(url_for('news_detail', news_id=news_id))# 创建评论comment = Comment(content=content,news_id=news_id,user_id=current_user.id)try:db.session.add(comment)db.session.commit()flash('评论发布成功', 'success')except Exception as e:db.session.rollback()logger.error(f"添加评论出错: {e}")flash('评论发布失败,请稍后重试', 'danger')return redirect(url_for('news_detail', news_id=news_id))def check_content_appropriate(content):"""使用大模型检查内容是否适当"""try:prompt = f"""
判断以下评论内容是否适当,不包含侮辱、歧视、暴力或政治敏感内容。
只回答"适当"或"不适当"。评论内容: {content}
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])result = response['message']['content'].strip().lower()return '适当' in resultexcept Exception as e:logger.error(f"检查内容适当性时出错: {e}")return True # 出错时默认允许
4. 语音交互与语音助手功能
将系统拓展为完整的语音助手,支持语音命令控制:
@app.route('/api/speech_command', methods=['POST'])
@login_required
def process_speech_command():"""处理语音命令"""try:data = request.get_json()if not data or 'command' not in data:return jsonify({'status': 'error', 'message': '缺少命令参数'})command = data['command']logger.info(f"接收到语音命令: {command}")# 使用大模型解析命令parsed_command = parse_command_with_llm(command)logger.info(f"解析后的命令: {parsed_command}")# 执行相应操作if parsed_command['type'] == 'read_news':# 获取特定标签或最新的新闻if parsed_command.get('tag'):news_list = get_news_by_tag(parsed_command['tag'], parsed_command.get('count', 3))else:news_list = News.query.order_by(News.add_date.desc()).limit(parsed_command.get('count', 3)).all()# 生成语音news_text = "为您播报以下新闻:\n\n"for i, news in enumerate(news_list):news_text += f"第{i+1}条:{news.title}\n{news.description or ''}\n\n"# 调用语音合成APIresult = generate_speech_response(news_text)return jsonify(result)elif parsed_command['type'] == 'search_news':# 搜索新闻keyword = parsed_command.get('keyword', '')news_list = News.query.filter(News.title.contains(keyword) | News.description.contains(keyword))\.order_by(News.add_date.desc())\.limit(5)\.all()if news_list:news_text = f"找到{len(news_list)}条关于"{keyword}"的新闻:\n\n"for i, news in enumerate(news_list):news_text += f"第{i+1}条:{news.title}\n"result = generate_speech_response(news_text)return jsonify(result)else:return jsonify({'status': 'success','message': f'没有找到关于"{keyword}"的新闻','audio_url': None})else:return jsonify({'status': 'error','message': '无法识别的命令类型'})except Exception as e:logger.error(f"处理语音命令时出错: {e}")return jsonify({'status': 'error','message': str(e)})def parse_command_with_llm(command):"""使用大模型解析语音命令"""try:prompt = f"""
解析以下语音命令,提取出命令类型和参数。返回JSON格式。
支持的命令类型:
1. read_news: 朗读新闻(可能包含标签和数量)
2. search_news: 搜索新闻(包含关键词)
3. system_status: 查询系统状态例如:
- "给我读3条最新的科技新闻" -> {{"type": "read_news", "tag": "科技", "count": 3}}
- "搜索关于人工智能的新闻" -> {{"type": "search_news", "keyword": "人工智能"}}
- "查询系统状态" -> {{"type": "system_status"}}命令: {command}
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])# 尝试解析JSON响应content = response['message']['content']# 提取JSON部分json_match = re.search(r'\{.*\}', content, re.DOTALL)if json_match:json_str = json_match.group(0)return json.loads(json_str)else:# 默认返回return {'type': 'unknown'}except Exception as e:logger.error(f"解析语音命令时出错: {e}")return {'type': 'error', 'message': str(e)}
5. 多语言支持与翻译功能
添加多语言支持,使系统能够抓取、翻译和展示不同语言的新闻:
class News(db.Model):# ... 现有字段 ...language = db.Column(db.String(10), default='zh-cn') # 新增字段translated_title = db.Column(db.String(500))translated_description = db.Column(db.Text)translated_content = db.Column(db.Text)def detect_language(text):"""检测文本语言"""try:prompt = f"""
请识别以下文本的语言,并返回相应的语言代码,如:
- 中文:zh-cn
- 英文:en
- 日文:ja
- 俄文:ru
等等。只返回语言代码,不需要其他解释。文本:
{text[:200]}
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])language_code = response['message']['content'].strip().lower()return language_codeexcept Exception as e:logger.error(f"检测语言时出错: {e}")return 'zh-cn' # 默认中文def translate_with_llm(text, from_lang, to_lang='zh-cn'):"""使用大模型翻译文本"""if not text or from_lang == to_lang:return texttry:prompt = f"""
请将以下{from_lang}文本翻译成{to_lang},保持原意,注意专业术语的准确性:原文:
{text[:5000]}只返回翻译结果,不需要添加解释。
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])translated_text = response['message']['content']return translated_textexcept Exception as e:logger.error(f"翻译文本时出错: {e}")return text
6. 智能摘要与内容浓缩
为长篇新闻生成简明摘要,方便用户快速了解内容:
def generate_summary_for_news(news, max_length=200):"""为新闻生成摘要"""try:# 获取新闻正文content_text = ""if news.content:soup = BeautifulSoup(news.content, 'html.parser')content_text = soup.get_text(separator=' ', strip=True)# 如果没有内容或内容太短,使用描述if not content_text or len(content_text) < 100:if news.description:content_text = news.description# 如果还是没有内容,返回标题if not content_text:return news.title# 使用大模型生成摘要prompt = f"""
为以下新闻生成一个简洁的摘要,不超过{max_length}个字符:标题:{news.title}
内容:{content_text[:3000]}只返回摘要,不要添加任何解释。
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])summary = response['message']['content'].strip()return summaryexcept Exception as e:logger.error(f"生成摘要时出错: {e}")# 如果出错,返回原始描述或截断的内容if news.description:return news.description[:max_length] + ('...' if len(news.description) > max_length else '')return content_text[:max_length] + ('...' if len(content_text) > max_length else '')
7. 数据分析与可视化
添加数据分析功能,生成新闻趋势报告和可视化图表:
@app.route('/analytics')
@login_required
def analytics_dashboard():"""数据分析仪表板"""# 获取时间范围days = request.args.get('days', 30, type=int)start_date = datetime.datetime.now() - datetime.timedelta(days=days)# 获取每日新闻数量daily_news_counts = db.session.query(func.date(News.add_date).label('date'),func.count().label('count')).filter(News.add_date >= start_date).group_by(func.date(News.add_date)).all()# 转换为图表数据格式dates = [item.date for item in daily_news_counts]counts = [item.count for item in daily_news_counts]# 获取热门标签popular_tags = db.session.query(Tag.name,func.count().label('count')).filter(Tag.news_id == News.id,News.add_date >= start_date).group_by(Tag.name).order_by(func.count().desc()).limit(20).all()tag_names = [item.name for item in popular_tags]tag_counts = [item.count for item in popular_tags]# 生成热门话题分析topics_analysis = generate_topics_analysis(start_date)return render_template('analytics.html',days=days,dates=dates,counts=counts,tag_names=tag_names,tag_counts=tag_counts,topics_analysis=topics_analysis)def generate_topics_analysis(start_date):"""生成热门话题分析"""# 获取期间的所有新闻recent_news = News.query.filter(News.add_date >= start_date).all()# 提取所有标题titles = [news.title for news in recent_news]# 使用大模型分析热门话题if titles:try:prompt = f"""
分析以下{len(titles)}条新闻标题,识别出5个主要热门话题,并对每个话题进行简要分析。
返回JSON格式,每个话题包含名称、相关新闻数量和简短描述。新闻标题:
{json.dumps(titles[:500], ensure_ascii=False, indent=2)}返回格式示例:
[{{"topic": "人工智能", "count": 15, "description": "主要集中在AI在医疗领域的应用,以及GPT-4的发布"}},...
]
"""# 调用Ollama APIresponse = ollama.chat(model='glm4', messages=[{'role': 'user','content': prompt}])# 提取JSON部分content = response['message']['content']json_match = re.search(r'\[.*\]', content, re.DOTALL)if json_match:json_str = json_match.group(0)return json.loads(json_str)except Exception as e:logger.error(f"生成热门话题分析时出错: {e}")return []

总结
通过本项目,我们探索了如何将大语言模型与传统Web开发技术结合,打造一个智能化的新闻聚合平台。这种结合不仅增强了用户体验,也展示了AI在实际应用场景中的巨大潜力。
项目实现了以下核心功能:
- 基于RSS的多源新闻自动抓取与管理
- 使用大模型优化内容提取与标签生成
- 语音合成与新闻播报功能
- 完善的日志系统与系统监控
- 移动设备适配与跨平台兼容
扩展思考中,我们进一步探讨了多模态内容处理、个性化推荐、社交互动、语音助手、多语言支持、智能摘要以及数据分析等更高级功能。这些拓展方向展示了如何将这个基础项目发展成一个更全面、智能的信息服务平台。
从技术角度看,本项目不仅是对Flask、SQLAlchemy等传统Web开发框架的应用,更重要的是展示了如何将新兴的AI技术(如大语言模型)无缝集成到现有系统中,实现传统技术难以达成的智能功能。
对于开发者而言,这个项目提供了一个完整的参考案例,展示了从需求分析、系统设计、功能实现到优化升级的全流程,特别适合那些希望将AI能力融入自己Web应用的开发者学习和借鉴。
人工智能的快速发展正在重塑软件开发的方式和可能性,本项目只是展示了其中的一小部分潜力。未来,随着模型能力的提升和应用场景的拓展,AI驱动的软件将变得更加智能、自然和个性化,为用户创造更大的价值。
相关文章:
《AI大模型趣味实战 》第8集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 2
《AI大模型趣味实战 》第8集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 2 摘要 本文末尾介绍了如何实现新闻智能体的方法。在信息爆炸的时代,如何高效获取和筛选感兴趣的新闻内容成为一个现实问题。本文将带领读者通过P…...
低配电脑畅玩《怪物猎人:荒野》,ToDesk云电脑优化从30帧到144帧?
《怪物猎人:荒野(Monster Hunter Wilds)》自2025年正式发售以来已取得相当亮眼的成绩,仅用三天时间便轻松突破800万销量,目前顺利蝉联周榜冠军;凭借着开放世界的宏大场景和丰富的狩猎玩法,该游戏…...
Leetcode刷题笔记1 图论part03
卡码网 101 孤岛总面积 from collections import deque directions [[0, 1], [1, 0], [0, -1], [-1, 0]] count 0def main():global countn, m map(int, input().split())grid []for _ in range(n):grid.append(list(map(int, input().split())))for i in range(n):if gri…...
【模拟面试】计算机考研复试集训(第十一天)
文章目录 前言一、专业面试1、什么是面向对象编程?2、软件工程的主要模型有哪些?3、Cache和寄存器的区别4、卷积层有哪些参数,它们代表什么?5、你有读博的打算吗?6、你的师兄/姐临近毕业,仍做不出成果&…...
查看自己的公有ip
IP 地址 112.3.88.1** 是一个 公有 IP 地址,而不是私有 IP 地址。 公有 IP 地址 vs 私有 IP 地址 公有 IP 地址: 用于在互联网上唯一标识设备。由互联网服务提供商(ISP)分配。可以在全球范围内路由和访问。例如:112.3.88.156、8.8…...
【js逆向入门】图灵爬虫练习平台 第九题
地址:aHR0cHM6Ly9zdHUudHVsaW5ncHl0b24uY24vcHJvYmxlbS1kZXRhaWwvOS8 f12进入了debugger,右击选择一律不在此处暂停, 点击继续执行 查看请求信息 查看载荷,2个加密参数,m和tt 查看启动器,打上断点 进来 往…...
NET6 WebApi第5讲:中间件(源码理解,俄罗斯套娃怎么来的?);Web 服务器 (Nginx / IIS / Kestrel)、WSL、SSL/TSL
一、NET6的启动流程 区别: .NET6 WebApi第1讲:VSCode开发.NET项目、区别.NET5框架【两个框架启动流程详解】_vscode webapi-CSDN博客 2、WebApplicationBuilder:是NET6引入的一个类,是建造者模式的典型应用 1>建造者模式的…...
Nginx及前端部署全流程:初始化配置到生产环境部署(附Nginx常用命令)
nginx&前端从初始化配置到部署(xshell) 前言下载nginx前端打包与创建具体文件夹路径配置nginx.nginx.conf文件配置项内容 配置nginx.service文件配置项内容 启动nginx常用nginx命令 前言 目标:在xshell中部署前端包。 第一步:…...
python 实现一个简单的window 任务管理器
import tkinter as tk from tkinter import ttk import psutil# 运行此代码前,请确保已经安装了 psutil 库,可以使用 pip install psutil 进行安装。 # 由于获取进程信息可能会受到权限限制,某些进程的信息可能无法获取,代码中已经…...
【AI模型】深度解析:DeepSeek的联网搜索的实现原理与认知误区
一、大模型的“联网魔法”:原来你是这样上网的! 在人工智能这个舞台上,大模型们可是妥妥的明星。像DeepSeek、QWen这些大模型,个个都是知识渊博的“学霸”,推理、生成文本那叫一个厉害。不过,要是论起上网…...
【xiaozhi赎回之路-2:语音可以自己配置就是用GPT本地API】
固件作用 打通了网络和硬件的沟通 修改固件实现【改变连接到小智服务器的】 回答逻辑LLM自定义 自定义了Coze(比较高级,自定义程度比较高,包括知识库,虚拟脚色-恋人-雅思老师-娃娃玩具{可能需要使用显卡对开源模型进行微调-产…...
WX小程序
下载 package com.sky.utils;import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.Cl…...
JavaScript案例0322
以下是一些涵盖不同高级JavaScript概念和应用的案例,每个案例都有详细解释: 案例1:实现 Promise/A 规范的手写 Promise class MyPromise {constructor(executor) {this.state pending;this.value undefined;this.reason undefined;this.o…...
Spring boot 3.4 后 SDK 升级,暨 UI API/MCP 计划
PS 写这篇文章后看到 A Deep Dive Into MCP and the Future of AI Tooling | Andreessen HorowitzWe explore what MCP is, how it changes the way AI interacts with tools, what developers are already building, and the challenges that still need solving. https://a1…...
大数据学习(78)-spark streaming与flink
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一…...
2.企业级AD活动目录架构与设计原则实战指南
一、企业级AD架构核心组件解析 1.1 多域森林架构设计 核心概念: 单域模型:适用于中小型企业(<5万用户) 多域模型:满足跨国/多部门隔离需求 森林(Forest):安全信任边界&#x…...
Linux下JDK1.8安装配置
目录 1.下载完上传到Linux系统中 2.解压JDK压缩包 3.配置JDK环境变量 4.设置环境变量生效 5.查看环境变量是否配置成功 官网下载地址:Java Downloads | Oracle 1.下载完上传到Linux系统中 2.解压JDK压缩包 tar -zxvf jdk-8u151-linux-x64.tar.gz -C /usr/local (解压…...
Python OCR文本识别详细步骤及代码示例
光学字符识别(OCR)是将图像中的文字转换为可编辑文本的技术。在Python中,我们可以利用多种库实现OCR功能。本文将详细介绍使用Tesseract和EasyOCR进行文本识别的步骤,并提供完整的代码示例。 一、OCR简介 OCR(Optical…...
OpenCV 基础模块 Python 版
OpenCV 基础模块权威指南(Python 版) 一、模块全景图 plaintext OpenCV 架构 (v4.x) ├─ 核心层 │ ├─ core:基础数据结构与操作(Mat/Scalar/Point) │ └─ imgproc:图像处理流水线(滤…...
华为HCIE网络工程师培训选机构攻略
从 官方授权机构 到 性价比黑马,结合价格、师资、通过率等维度,为你筛选出最适合的培训方案。 一、华为官方授权机构(优先推荐) 华为官方授权机构拥有 真机实验环境考官级讲师,适合预算充足、追求高通过率的学员。 机…...
Linux固定IP方法(RedHat+Net模式)
1、查看当前网关 ip route | grep default 2、配置静态IP 双击重启 3、验证...
210、【图论】课程表(Python)
题目 思路 这道题本质上是一个拓扑排序。每次先统计每个点的入度个数、然后再统计点与点之间的邻接关系,找到入度为0的点作为起始遍历点。之后每遍历到这个点之后,就把这个点后续的邻接关系边的点入度减去一。当某个点入度为0时,继续被加入其…...
使用Python开发自动驾驶技术:车道线检测模型
友友们好! 我是Echo_Wish,我的的新专栏《Python进阶》以及《Python!实战!》正式启动啦!这是专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会…...
跟着StatQuest学知识07-张量与PyTorch
一、张量tensor 张量重新命名一些数据概念,存储数据以及权重和偏置。 张量还允许与数据相关的数学计算能够相对快速的完成。 通常,张量及其进行的数学计算会通过成为图形处理单元(GPUs)的特殊芯片来加速。但还有张量处理单元&am…...
nginx配置https域名后,代理后端服务器流式接口变慢
目录 问题描述原因解决办法 问题描述 使用nginx配置域名和https的ssl证书后,代理后端web服务器,发现流式接口比原来直接用服务器外部ip后端web服务器端口变慢了很多。 原因 在于 HTTP 和 HTTPS 在 Nginx 代理中的处理方式不同。以下几点解释了为什么 …...
前端字段名和后端不一致?解锁 JSON 映射的“隐藏规则” !!!
🚀 前端字段名和后端不一致?解锁 JSON 映射的“隐藏规则” 🌟 嘿,技术冒险家们!👋 今天我们要聊一个开发中常见的“坑”:前端传来的 JSON 参数字段名和后端对象字段名不一致,会发生…...
基于springboot的新闻推荐系统(045)
摘要 随着信息互联网购物的飞速发展,国内放开了自媒体的政策,一般企业都开始开发属于自己内容分发平台的网站。本文介绍了新闻推荐系统的开发全过程。通过分析企业对于新闻推荐系统的需求,创建了一个计算机管理新闻推荐系统的方案。文章介绍了…...
2024年数维杯数学建模C题天然气水合物资源量评价解题全过程论文及程序
2024年数维杯数学建模 C题 天然气水合物资源量评价 原题再现: 天然气水合物(Natural Gas Hydrate/Gas Hydrate)即可燃冰,是天然气与水在高压低温条件下形成的类冰状结晶物质,因其外观像冰,遇火即燃&#…...
Linux与HTTP中的Cookie和Session
HTTP中的Cookie和Session 本篇介绍 前面几篇已经基本介绍了HTTP协议的大部分内容,但是前面提到了一点「HTTP是无连接、无状态的协议」,那么到底有什么无连接以及什么是无状态。基于这两个问题,随后解释什么是Cookie和Session,以…...
linux 备份工具,常用的Linux备份工具及其备份数据的语法
在Linux系统中,备份数据是确保数据安全性和完整性的关键步骤。以下是一些常用的Linux备份工具及其备份数据的语法: 1. tar命令 tar命令是Linux系统中常用的打包和压缩工具,可以将多个文件或目录打包成一个文件,并可以选择添加压…...
