Merge pull request #22 from apeng0406/main

系统语言中英切换优化
This commit is contained in:
戒酒的李白
2025-03-10 12:39:23 +08:00
committed by GitHub
21 changed files with 5725 additions and 5364 deletions
+15 -3
View File
@@ -142,15 +142,27 @@ def not_found_error(error):
@app.errorhandler(500)
def internal_error(error):
return render_template('500.html'), 500
return render_template('error.html',
error_code=500,
error_title='服务器错误',
error_message='服务器遇到了一个问题,请稍后再试。',
error_i18n_key='serverError'), 500
@app.errorhandler(403)
def forbidden_error(error):
return render_template('403.html'), 403
return render_template('error.html',
error_code=403,
error_title='禁止访问',
error_message='您没有权限访问此页面。',
error_i18n_key='forbidden'), 403
@app.errorhandler(400)
def bad_request_error(error):
return render_template('400.html'), 400
return render_template('error.html',
error_code=400,
error_title='错误请求',
error_message='服务器无法理解您的请求。',
error_i18n_key='badRequest'), 400
# 数据库配置
DB_CONFIG = {
+239
View File
@@ -0,0 +1,239 @@
// 多语言支持文件
const translations = {
'zh': {
// 导航菜单
'home': '首页',
'hotWord': '热词统计',
'tableData': '微博舆情统计',
'articleChar': '文章分析',
'ipChar': 'IP分析',
'commentChar': '评论分析',
'yuqingChar': '舆情分析',
'yuqingpredict': '舆情预测',
'articleCloud': '文章内容词云图',
'dataVisualization': '数据可视化',
'weiboSystem': '微博舆情分析系统',
'wordCloud': '词云图',
// 首页
'articleCount': '文章个数',
'articleCrawlRule': '文章爬取规则',
'nextCrawlTime': '下次爬取时间',
'articlePublishTimeCount': '文章发布时间个数',
'commentLikeCountTopFore': '评论点赞量 Top Fore',
'viewAll': '查看全部',
'articleTypeRatio': '文章类型占比',
'commentUserWordCloud': '评论用户名词云图',
'commentUserTimeRatio': '评论用户时间占比',
// 热词页面
'hotWordStatistics': '热词统计页',
'hotWordCloud': '热词词云图',
'hotWordRanking': '热词查询表格',
'wordFrequency': '词频',
'hotWordSelection': '热词选择',
'hotWordName': '热词名称',
'occurrenceCount': '出现次数',
'hotWordSentiment': '热词情感',
'hotWordYearTrend': '热词年份变化趋势',
'queryCommentsByHotWord': '根据选择的热词从而查询出评论数据',
'hotWordTimeDistribution': '热词出现时间分布个数',
// 舆情分析页面
'hotWordSentimentTrendBar': '热词情感趋势柱状图',
'hotWordSentimentTrendTree': '热词情感趋势树形图',
'articleCommentSentimentTrendPie': '文章内容与评论内容舆情趋势饼状图',
// 舆情预测页面
'topicStatisticsPage': '话题统计页',
// 文章分析页面
'articleCharPage': '文章分析页',
'typeSelection': '类型选择',
'articleLikeAnalysis': '文章点赞量分析 👍',
'articleCommentAnalysis': '文章评论量分析 🔥',
'articleForwardAnalysis': '文章转发量分析 🥇',
'likeRangeStatistics': '点赞区间统计',
'rangeCount': '区间个数',
// 评论分析页面
'commentLikeRangeChart': '评论点赞次数区间图',
'commentUserGenderRatio': '评论用户性别占比',
'userCommentWordCloud': '用户评论词云图',
// IP分析页面
'articleIpLocationAnalysis': '文章IP位置分析图',
'commentIpLocationAnalysis': '评论IP位置分析图',
// 评论相关
'commentUser': '评论用户',
'commentGender': '评论性别',
'commentAddress': '评论地址',
'commentContent': '评论内容',
'likeCount': '点赞量',
// 微博舆情统计页面
'weiboArticleStatTable': '微博文章统计表格 - 舆情 情感分类',
'sentimentClassification': '情感分类',
'articleId': '文章ID',
'articleIp': '文章IP',
'articleTitle': '文章标题',
'articleLike': '点赞量',
'articleForward': '转发量',
'articleComment': '评论量',
'articleType': '类型',
'articleContent': '内容',
'articleTime': '发布时间',
// 通用
'switchToEnglish': '切换到英文',
'switchToChinese': '切换到中文',
'semester': '网安小学期',
// 错误页面
'pageNotFound': '页面未找到',
'backToHome': '返回首页',
'serverError': '服务器错误',
'forbidden': '禁止访问',
'badRequest': '错误请求'
},
'en': {
// Navigation menu
'home': 'Home',
'hotWord': 'Hot Words',
'tableData': 'Weibo Public Opinion Stats',
'articleChar': 'Article Analysis',
'ipChar': 'IP Analysis',
'commentChar': 'Comment Analysis',
'yuqingChar': 'Public Opinion Analysis',
'yuqingpredict': 'Opinion Prediction',
'articleCloud': 'Article Content Word Cloud',
'dataVisualization': 'Data Visualization',
'weiboSystem': 'Weibo Public Opinion Analysis System',
'wordCloud': 'Word Cloud',
// Home page
'articleCount': 'Article Count',
'articleCrawlRule': 'Article Crawl Rule',
'nextCrawlTime': 'Next Crawl Time',
'articlePublishTimeCount': 'Article Publish Time Count',
'commentLikeCountTopFore': 'Comment Like Count Top Four',
'viewAll': 'View All',
'articleTypeRatio': 'Article Type Ratio',
'commentUserWordCloud': 'Comment User Word Cloud',
'commentUserTimeRatio': 'Comment User Time Ratio',
// Hot word page
'hotWordStatistics': 'Hot Word Statistics',
'hotWordCloud': 'Hot Word Cloud',
'hotWordRanking': 'Hot Word Ranking',
'wordFrequency': 'Word Frequency',
'hotWordSelection': 'Hot Word Selection',
'hotWordName': 'Hot Word Name',
'occurrenceCount': 'Occurrence Count',
'hotWordSentiment': 'Hot Word Sentiment',
'hotWordYearTrend': 'Hot Word Year Trend',
'queryCommentsByHotWord': 'Query comments based on selected hot word',
'hotWordTimeDistribution': 'Hot Word Time Distribution Count',
// Public opinion analysis page
'hotWordSentimentTrendBar': 'Hot Word Sentiment Trend Bar Chart',
'hotWordSentimentTrendTree': 'Hot Word Sentiment Trend Tree Chart',
'articleCommentSentimentTrendPie': 'Article and Comment Sentiment Trend Pie Chart',
// Opinion prediction page
'topicStatisticsPage': 'Topic Statistics Page',
// Article analysis page
'articleCharPage': 'Article Analysis Page',
'typeSelection': 'Type Selection',
'articleLikeAnalysis': 'Article Like Analysis 👍',
'articleCommentAnalysis': 'Article Comment Analysis 🔥',
'articleForwardAnalysis': 'Article Forward Analysis 🥇',
'likeRangeStatistics': 'Like Range Statistics',
'rangeCount': 'Range Count',
// Comment analysis page
'commentLikeRangeChart': 'Comment Like Range Chart',
'commentUserGenderRatio': 'Comment User Gender Ratio',
'userCommentWordCloud': 'User Comment Word Cloud',
// IP analysis page
'articleIpLocationAnalysis': 'Article IP Location Analysis',
'commentIpLocationAnalysis': 'Comment IP Location Analysis',
// Comment related
'commentUser': 'Comment User',
'commentGender': 'Gender',
'commentAddress': 'Address',
'commentContent': 'Content',
'likeCount': 'Likes',
// Weibo public opinion stats page
'weiboArticleStatTable': 'Weibo Article Statistics Table - Sentiment Classification',
'sentimentClassification': 'Sentiment Classification',
'articleId': 'Article ID',
'articleIp': 'Article IP',
'articleTitle': 'Article Title',
'articleLike': 'Likes',
'articleForward': 'Forwards',
'articleComment': 'Comments',
'articleType': 'Type',
'articleContent': 'Content',
'articleTime': 'Publish Time',
// Common
'switchToEnglish': 'Switch to English',
'switchToChinese': 'Switch to Chinese',
'semester': 'Network Security Semester',
// Error pages
'pageNotFound': 'Page Not Found',
'backToHome': 'Back to Home',
'serverError': 'Server Error',
'forbidden': 'Forbidden',
'badRequest': 'Bad Request'
}
};
// 获取当前语言
function getCurrentLanguage() {
return localStorage.getItem('language') || 'zh';
}
// 设置语言
function setLanguage(lang) {
localStorage.setItem('language', lang);
location.reload();
}
// 翻译函数
function t(key) {
const lang = getCurrentLanguage();
return translations[lang][key] || key;
}
// 页面加载时应用翻译
document.addEventListener('DOMContentLoaded', function() {
// 应用当前语言
applyTranslations();
// 添加语言切换按钮事件
const langSwitcher = document.getElementById('language-switcher');
if (langSwitcher) {
langSwitcher.addEventListener('click', function() {
const currentLang = getCurrentLanguage();
const newLang = currentLang === 'zh' ? 'en' : 'zh';
setLanguage(newLang);
});
}
});
// 应用翻译到页面元素
function applyTranslations() {
const elements = document.querySelectorAll('[data-i18n]');
elements.forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = t(key);
});
}
+57 -28
View File
@@ -1,29 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>404页面</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css"> </head>
<body class=" ">
<div class="wrapper">
<div class="container">
<div class="row no-gutters height-self-center">
<div class="col-sm-12 text-center align-self-center">
<div class="iq-error position-relative">
<img src="/static/picture/Datum_404.png" class="img-fluid iq-error-img iq-error-img-dark mx-auto" alt="">
<img src="/static/picture/Datum_404.png" class="img-fluid iq-error-img mb-0" alt="">
<h2 class="mb-0">噢!该页面没有找到..</h2>
<p>本次请求没有任何反应.</p>
<a class="btn btn-primary d-inline-flex align-items-center mt-3" href="/user/login"><i class="ri-home-4-line mr-2"></i>回到登录页</a>
</div>
</div>
</div>
</div>
</div>
/
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - 页面未找到</title>
<link rel="stylesheet" href="/static/css/backend.css">
<!-- 添加语言支持脚本 -->
<script src="/static/js/i18n.js"></script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f8f9fa;
}
.error-container {
text-align: center;
padding: 2rem;
}
.error-code {
font-size: 6rem;
font-weight: bold;
color: #dc3545;
}
.error-message {
font-size: 1.5rem;
margin: 1rem 0;
}
.back-link {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25rem;
}
</style>
</head>
<body>
<div class="error-container">
<div class="error-code">404</div>
<div class="error-message" data-i18n="pageNotFound">页面未找到</div>
<p>您请求的页面不存在或已被移除。</p>
<a href="/" class="back-link" data-i18n="backToHome">返回首页</a>
</div>
<!-- 初始化语言支持 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
applyTranslations();
});
</script>
</body>
</html>
+57 -32
View File
@@ -1,33 +1,58 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>错误页面</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css"> </head>
<body class=" ">
<div class="wrapper">
<div class="mt-5 iq-maintenance">
<div class="container-fluid p-0">
<div class="row no-gutters">
<div class="col-sm-12 text-center">
<div class="iq-maintenance">
<img src="/static/picture/maintenance.png" class="img-fluid" alt="">
<h3 class="mt-4 mb-2">{{ errorMsg }}</h3>
<p class="mb-2">请回去再次检查问题并且修改问题.</p>
<p><a href="/user/login" class="btn btn-primary">回到登录页</a></p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Backend Bundle JavaScript -->
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ error_code }} - {{ error_title }}</title>
<link rel="stylesheet" href="/static/css/backend.css">
<!-- 添加语言支持脚本 -->
<script src="/static/js/i18n.js"></script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f8f9fa;
}
.error-container {
text-align: center;
padding: 2rem;
}
.error-code {
font-size: 6rem;
font-weight: bold;
color: #dc3545;
}
.error-message {
font-size: 1.5rem;
margin: 1rem 0;
}
.back-link {
display: inline-block;
margin-top: 1rem;
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 0.25rem;
}
</style>
</head>
<body>
<div class="error-container">
<div class="error-code">{{ error_code }}</div>
<div class="error-message" data-i18n="{{ error_i18n_key }}">{{ error_title }}</div>
<p>{{ error_message }}</p>
<a href="/" class="back-link" data-i18n="backToHome">返回首页</a>
</div>
<!-- 初始化语言支持 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
applyTranslations();
});
</script>
</body>
</html>
+380 -380
View File
@@ -1,381 +1,381 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>爬虫控制面板</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
<style>
.topic-item {
margin: 5px;
padding: 8px 15px;
border-radius: 20px;
background-color: #f8f9fa;
display: inline-block;
cursor: pointer;
}
.topic-item.selected {
background-color: #0d6efd;
color: white;
}
.custom-topic-input {
margin: 10px 0;
}
.parameter-section {
margin: 20px 0;
padding: 20px;
border-radius: 10px;
background-color: #f8f9fa;
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">爬虫控制面板</h2>
<!-- 话题选择区域 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">选择话题类型</h5>
</div>
<div class="card-body">
<div id="predefinedTopics" class="mb-3">
<!-- 预定义话题将通过JavaScript动态加载 -->
</div>
<div class="custom-topic-input">
<h6>添加自定义话题</h6>
<div class="input-group">
<input type="text" class="form-control" id="customTopic" placeholder="输入自定义话题">
<button class="btn btn-primary" onclick="addCustomTopic()">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</div>
<div id="selectedTopics" class="mt-3">
<h6>已选择的话题:</h6>
<div id="selectedTopicsList" class="mt-2">
<!-- 已选择的话题将在这里显示 -->
</div>
</div>
</div>
</div>
<!-- 爬虫参数配置 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">爬虫参数配置</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="crawlDepth" class="form-label">爬取深度</label>
<input type="number" class="form-control" id="crawlDepth" value="3" min="1" max="10">
<small class="text-muted">每个话题爬取的页数(1-10</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="interval" class="form-label">爬取间隔(秒)</label>
<input type="number" class="form-control" id="interval" value="5" min="1">
<small class="text-muted">每次请求之间的间隔时间</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="maxRetries" class="form-label">最大重试次数</label>
<input type="number" class="form-control" id="maxRetries" value="3" min="1">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="timeout" class="form-label">请求超时时间(秒)</label>
<input type="number" class="form-control" id="timeout" value="30" min="1">
</div>
</div>
</div>
</div>
</div>
<!-- AI配置助手 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-robot"></i> AI配置助手
</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="aiPrompt" class="form-label">用自然语言描述您的爬虫需求</label>
<textarea class="form-control" id="aiPrompt" rows="3"
placeholder="例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。"></textarea>
</div>
<div class="d-flex justify-content-between align-items-center">
<button class="btn btn-primary" onclick="generateConfig()">
<i class="fas fa-magic"></i> 生成配置
</button>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoApply" checked>
<label class="form-check-label" for="autoApply">
自动应用生成的配置
</label>
</div>
</div>
<div id="aiResponse" class="mt-3" style="display: none;">
<div class="alert alert-info">
<h6 class="alert-heading">AI助手建议:</h6>
<p id="aiSuggestion" class="mb-0"></p>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="d-flex justify-content-between mb-5">
<button class="btn btn-primary" onclick="startCrawling()">
<i class="fas fa-play"></i> 开始爬取
</button>
<button class="btn btn-secondary" onclick="saveConfig()">
<i class="fas fa-save"></i> 保存配置
</button>
</div>
<!-- 爬虫状态和日志 -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">爬虫状态</h5>
</div>
<div class="card-body">
<div class="progress mb-3">
<div id="crawlProgress" class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<div class="border p-3 bg-light" style="height: 200px; overflow-y: auto;">
<pre id="crawlLog" class="mb-0"></pre>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/js/bootstrap.bundle.min.js"></script>
<script>
// 预定义话题列表
const predefinedTopics = [
'热门', '社会', '科技', '娱乐', '体育', '财经',
'教育', '健康', '军事', '文化', '汽车', '美食'
];
// 已选择的话题
let selectedTopics = new Set();
// 初始化页面
window.onload = function() {
loadPredefinedTopics();
};
// 加载预定义话题
function loadPredefinedTopics() {
const topicsDiv = document.getElementById('predefinedTopics');
predefinedTopics.forEach(topic => {
const topicElement = document.createElement('span');
topicElement.className = 'topic-item';
topicElement.textContent = topic;
topicElement.onclick = () => toggleTopic(topic, topicElement);
topicsDiv.appendChild(topicElement);
});
}
// 切换话题选择状态
function toggleTopic(topic, element) {
if (selectedTopics.has(topic)) {
selectedTopics.delete(topic);
element.classList.remove('selected');
} else {
selectedTopics.add(topic);
element.classList.add('selected');
}
updateSelectedTopicsList();
}
// 添加自定义话题
function addCustomTopic() {
const input = document.getElementById('customTopic');
const topic = input.value.trim();
if (topic) {
selectedTopics.add(topic);
input.value = '';
updateSelectedTopicsList();
}
}
// 更新已选择的话题列表
function updateSelectedTopicsList() {
const listDiv = document.getElementById('selectedTopicsList');
listDiv.innerHTML = '';
selectedTopics.forEach(topic => {
const topicElement = document.createElement('span');
topicElement.className = 'topic-item selected';
topicElement.textContent = topic;
topicElement.onclick = () => {
selectedTopics.delete(topic);
updateSelectedTopicsList();
};
listDiv.appendChild(topicElement);
});
}
// 开始爬取
function startCrawling() {
if (selectedTopics.size === 0) {
alert('请至少选择一个话题!');
return;
}
const config = {
topics: Array.from(selectedTopics),
parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value)
}
};
// 发送爬虫配置到后端
fetch('/api/spider/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateCrawlLog('爬虫任务已启动...');
} else {
updateCrawlLog('启动失败:' + data.message);
}
})
.catch(error => {
updateCrawlLog('错误:' + error.message);
});
}
// 保存配置
function saveConfig() {
const config = {
topics: Array.from(selectedTopics),
parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value)
}
};
fetch('/api/spider/save-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('配置已保存!');
} else {
alert('保存失败:' + data.message);
}
})
.catch(error => {
alert('保存出错:' + error.message);
});
}
// 更新爬虫日志
function updateCrawlLog(message) {
const log = document.getElementById('crawlLog');
const timestamp = new Date().toLocaleTimeString();
log.innerHTML += `[${timestamp}] ${message}\n`;
log.scrollTop = log.scrollHeight;
}
// WebSocket连接用于实时更新爬虫状态
const ws = new WebSocket(`ws://${window.location.host}/ws/spider-status`);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'progress') {
document.getElementById('crawlProgress').style.width = data.value + '%';
} else if (data.type === 'log') {
updateCrawlLog(data.message);
}
};
// AI配置生成
async function generateConfig() {
const prompt = document.getElementById('aiPrompt').value.trim();
if (!prompt) {
alert('请输入您的爬虫需求描述!');
return;
}
const aiResponse = document.getElementById('aiResponse');
const aiSuggestion = document.getElementById('aiSuggestion');
try {
const response = await fetch('/api/spider/ai-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
});
const data = await response.json();
if (data.success) {
// 显示AI建议
aiSuggestion.textContent = data.suggestion;
aiResponse.style.display = 'block';
// 如果选择自动应用配置
if (document.getElementById('autoApply').checked) {
// 清除现有选择
selectedTopics.clear();
// 应用新的话题
data.config.topics.forEach(topic => {
selectedTopics.add(topic);
});
// 更新参数
document.getElementById('crawlDepth').value = data.config.parameters.crawlDepth;
document.getElementById('interval').value = data.config.parameters.interval;
document.getElementById('maxRetries').value = data.config.parameters.maxRetries;
document.getElementById('timeout').value = data.config.parameters.timeout;
// 更新UI
updateSelectedTopicsList();
// 添加提示
updateCrawlLog('AI配置已自动应用');
}
} else {
throw new Error(data.message);
}
} catch (error) {
aiSuggestion.textContent = '生成配置时出错:' + error.message;
aiResponse.style.display = 'block';
}
}
</script>
</body>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>爬虫控制面板</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
<style>
.topic-item {
margin: 5px;
padding: 8px 15px;
border-radius: 20px;
background-color: #f8f9fa;
display: inline-block;
cursor: pointer;
}
.topic-item.selected {
background-color: #0d6efd;
color: white;
}
.custom-topic-input {
margin: 10px 0;
}
.parameter-section {
margin: 20px 0;
padding: 20px;
border-radius: 10px;
background-color: #f8f9fa;
}
</style>
</head>
<body>
<div class="container mt-5">
<h2 class="mb-4">爬虫控制面板</h2>
<!-- 话题选择区域 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">选择话题类型</h5>
</div>
<div class="card-body">
<div id="predefinedTopics" class="mb-3">
<!-- 预定义话题将通过JavaScript动态加载 -->
</div>
<div class="custom-topic-input">
<h6>添加自定义话题</h6>
<div class="input-group">
<input type="text" class="form-control" id="customTopic" placeholder="输入自定义话题">
<button class="btn btn-primary" onclick="addCustomTopic()">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</div>
<div id="selectedTopics" class="mt-3">
<h6>已选择的话题:</h6>
<div id="selectedTopicsList" class="mt-2">
<!-- 已选择的话题将在这里显示 -->
</div>
</div>
</div>
</div>
<!-- 爬虫参数配置 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">爬虫参数配置</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="crawlDepth" class="form-label">爬取深度</label>
<input type="number" class="form-control" id="crawlDepth" value="3" min="1" max="10">
<small class="text-muted">每个话题爬取的页数(1-10</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="interval" class="form-label">爬取间隔(秒)</label>
<input type="number" class="form-control" id="interval" value="5" min="1">
<small class="text-muted">每次请求之间的间隔时间</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="maxRetries" class="form-label">最大重试次数</label>
<input type="number" class="form-control" id="maxRetries" value="3" min="1">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="timeout" class="form-label">请求超时时间(秒)</label>
<input type="number" class="form-control" id="timeout" value="30" min="1">
</div>
</div>
</div>
</div>
</div>
<!-- AI配置助手 -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-robot"></i> AI配置助手
</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="aiPrompt" class="form-label">用自然语言描述您的爬虫需求</label>
<textarea class="form-control" id="aiPrompt" rows="3"
placeholder="例如:我想爬取最近一周关于人工智能的热门微博,重点关注转发量超过1000的内容,每个话题爬取前5页内容。"></textarea>
</div>
<div class="d-flex justify-content-between align-items-center">
<button class="btn btn-primary" onclick="generateConfig()">
<i class="fas fa-magic"></i> 生成配置
</button>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="autoApply" checked>
<label class="form-check-label" for="autoApply">
自动应用生成的配置
</label>
</div>
</div>
<div id="aiResponse" class="mt-3" style="display: none;">
<div class="alert alert-info">
<h6 class="alert-heading">AI助手建议:</h6>
<p id="aiSuggestion" class="mb-0"></p>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="d-flex justify-content-between mb-5">
<button class="btn btn-primary" onclick="startCrawling()">
<i class="fas fa-play"></i> 开始爬取
</button>
<button class="btn btn-secondary" onclick="saveConfig()">
<i class="fas fa-save"></i> 保存配置
</button>
</div>
<!-- 爬虫状态和日志 -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">爬虫状态</h5>
</div>
<div class="card-body">
<div class="progress mb-3">
<div id="crawlProgress" class="progress-bar" role="progressbar" style="width: 0%"></div>
</div>
<div class="border p-3 bg-light" style="height: 200px; overflow-y: auto;">
<pre id="crawlLog" class="mb-0"></pre>
</div>
</div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/js/bootstrap.bundle.min.js"></script>
<script>
// 预定义话题列表
const predefinedTopics = [
'热门', '社会', '科技', '娱乐', '体育', '财经',
'教育', '健康', '军事', '文化', '汽车', '美食'
];
// 已选择的话题
let selectedTopics = new Set();
// 初始化页面
window.onload = function() {
loadPredefinedTopics();
};
// 加载预定义话题
function loadPredefinedTopics() {
const topicsDiv = document.getElementById('predefinedTopics');
predefinedTopics.forEach(topic => {
const topicElement = document.createElement('span');
topicElement.className = 'topic-item';
topicElement.textContent = topic;
topicElement.onclick = () => toggleTopic(topic, topicElement);
topicsDiv.appendChild(topicElement);
});
}
// 切换话题选择状态
function toggleTopic(topic, element) {
if (selectedTopics.has(topic)) {
selectedTopics.delete(topic);
element.classList.remove('selected');
} else {
selectedTopics.add(topic);
element.classList.add('selected');
}
updateSelectedTopicsList();
}
// 添加自定义话题
function addCustomTopic() {
const input = document.getElementById('customTopic');
const topic = input.value.trim();
if (topic) {
selectedTopics.add(topic);
input.value = '';
updateSelectedTopicsList();
}
}
// 更新已选择的话题列表
function updateSelectedTopicsList() {
const listDiv = document.getElementById('selectedTopicsList');
listDiv.innerHTML = '';
selectedTopics.forEach(topic => {
const topicElement = document.createElement('span');
topicElement.className = 'topic-item selected';
topicElement.textContent = topic;
topicElement.onclick = () => {
selectedTopics.delete(topic);
updateSelectedTopicsList();
};
listDiv.appendChild(topicElement);
});
}
// 开始爬取
function startCrawling() {
if (selectedTopics.size === 0) {
alert('请至少选择一个话题!');
return;
}
const config = {
topics: Array.from(selectedTopics),
parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value)
}
};
// 发送爬虫配置到后端
fetch('/api/spider/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
})
.then(response => response.json())
.then(data => {
if (data.success) {
updateCrawlLog('爬虫任务已启动...');
} else {
updateCrawlLog('启动失败:' + data.message);
}
})
.catch(error => {
updateCrawlLog('错误:' + error.message);
});
}
// 保存配置
function saveConfig() {
const config = {
topics: Array.from(selectedTopics),
parameters: {
crawlDepth: parseInt(document.getElementById('crawlDepth').value),
interval: parseInt(document.getElementById('interval').value),
maxRetries: parseInt(document.getElementById('maxRetries').value),
timeout: parseInt(document.getElementById('timeout').value)
}
};
fetch('/api/spider/save-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('配置已保存!');
} else {
alert('保存失败:' + data.message);
}
})
.catch(error => {
alert('保存出错:' + error.message);
});
}
// 更新爬虫日志
function updateCrawlLog(message) {
const log = document.getElementById('crawlLog');
const timestamp = new Date().toLocaleTimeString();
log.innerHTML += `[${timestamp}] ${message}\n`;
log.scrollTop = log.scrollHeight;
}
// WebSocket连接用于实时更新爬虫状态
const ws = new WebSocket(`ws://${window.location.host}/ws/spider-status`);
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
if (data.type === 'progress') {
document.getElementById('crawlProgress').style.width = data.value + '%';
} else if (data.type === 'log') {
updateCrawlLog(data.message);
}
};
// AI配置生成
async function generateConfig() {
const prompt = document.getElementById('aiPrompt').value.trim();
if (!prompt) {
alert('请输入您的爬虫需求描述!');
return;
}
const aiResponse = document.getElementById('aiResponse');
const aiSuggestion = document.getElementById('aiSuggestion');
try {
const response = await fetch('/api/spider/ai-config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
});
const data = await response.json();
if (data.success) {
// 显示AI建议
aiSuggestion.textContent = data.suggestion;
aiResponse.style.display = 'block';
// 如果选择自动应用配置
if (document.getElementById('autoApply').checked) {
// 清除现有选择
selectedTopics.clear();
// 应用新的话题
data.config.topics.forEach(topic => {
selectedTopics.add(topic);
});
// 更新参数
document.getElementById('crawlDepth').value = data.config.parameters.crawlDepth;
document.getElementById('interval').value = data.config.parameters.interval;
document.getElementById('maxRetries').value = data.config.parameters.maxRetries;
document.getElementById('timeout').value = data.config.parameters.timeout;
// 更新UI
updateSelectedTopicsList();
// 添加提示
updateCrawlLog('AI配置已自动应用');
}
} else {
throw new Error(data.message);
}
} catch (error) {
aiSuggestion.textContent = '生成配置时出错:' + error.message;
aiResponse.style.display = 'block';
}
}
</script>
</body>
</html>
Binary file not shown.
+548 -548
View File
File diff suppressed because it is too large Load Diff
+344 -344
View File
@@ -1,345 +1,345 @@
{% extends 'base_page.html' %}
{% block title %}
文章分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block card-stretch card-height">
<div class="card-body">
<div class="form-group">
<label>类型选择</label>
<select onchange="typeChange(event)" class="form-control mb-3">
{% for i in typeList %}
{% if defaultType == i %}
<option selected value="{{ i }}">{{ i }}</option>
{% else %}
<option value="{{ i }}">{{ i }}</option>
{% endif %}
{% endfor %}
</select>
<script>
function typeChange(e){
window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value
}
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章点赞量分析 👍</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章评论量分析 🔥</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章转发量分析 🥇</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc']
option = {
title: {
text: '点赞区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['区间个数']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data:{{ xData |tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '区间个数',
type: 'bar',
data: {{ yData }},
itemStyle:{
color:function(params){
return colors[params.dataIndex % colors.length];
}
},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
title: {
text: '文章评论区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
},
xAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
yAxis: {
type: 'value'
},
series: [
{
name:"区间个数",
data: {{ y1Data }},
type: 'line',
symbol: 'triangle',
symbolSize: 20,
lineStyle: {
color: '#5470C6',
width: 4,
type: 'dashed'
},
itemStyle: {
borderWidth: 3,
borderColor: '#EE6666',
color: 'yellow'
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '转发量分析区间图'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x2Data | tojson }}
},
series: [
{
name: '区间个数',
type: 'bar',
data: {{ y2Data }}
}
]
};
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="articleChar">文章分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block card-stretch card-height">
<div class="card-body">
<div class="form-group">
<label data-i18n="typeSelection">类型选择</label>
<select onchange="typeChange(event)" class="form-control mb-3">
{% for i in typeList %}
{% if defaultType == i %}
<option selected value="{{ i }}">{{ i }}</option>
{% else %}
<option value="{{ i }}">{{ i }}</option>
{% endif %}
{% endfor %}
</select>
<script>
function typeChange(e){
window.location.href = 'http://127.0.0.1:5000/page/articleChar?type=' + e.target.value
}
</script>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleLikeAnalysis">文章点赞量分析 👍</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCommentAnalysis">文章评论量分析 🔥</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleForwardAnalysis">文章转发量分析 🥇</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66cc99','#ffcc66','#ff6666','#6699cc']
option = {
title: {
text: t('likeRangeStatistics')
},
tooltip: {
trigger: 'axis'
},
legend: {
data: [t('rangeCount')]
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data:{{ xData |tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '区间个数',
type: 'bar',
data: {{ yData }},
itemStyle:{
color:function(params){
return colors[params.dataIndex % colors.length];
}
},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
title: {
text: '文章评论区间统计'
},
tooltip: {
trigger: 'axis'
},
legend: {
},
xAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
yAxis: {
type: 'value'
},
series: [
{
name:"区间个数",
data: {{ y1Data }},
type: 'line',
symbol: 'triangle',
symbolSize: 20,
lineStyle: {
color: '#5470C6',
width: 4,
type: 'dashed'
},
itemStyle: {
borderWidth: 3,
borderColor: '#EE6666',
color: 'yellow'
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '转发量分析区间图'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x2Data | tojson }}
},
series: [
{
name: '区间个数',
type: 'bar',
data: {{ y2Data }}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
+143 -143
View File
@@ -1,144 +1,144 @@
{% extends 'base_page.html' %}
{% block title %}
文章内容词云图
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章内容词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 750px;text-align: center">
<img style="width:60%" src="/static/contentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="articleCloud">文章内容词云图</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="articleCharPage">文章分析页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCloud">文章内容词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 750px;text-align: center">
<img style="width:60%" src="/static/contentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% endblock %}
+504 -477
View File
@@ -1,478 +1,505 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}首页{% endblock %}</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css">
<meta name="referrer" content="no-referrer" />
</head>
<body class=" ">
<!-- loader Start -->
<div id="loading">
<div id="loading-center">
</div>
</div>
<!-- loader END -->
<!-- Wrapper Start -->
<div class="wrapper">
<div class="iq-sidebar sidebar-default ">
<div class="iq-sidebar-logo d-flex align-items-end justify-content-between">
<a href="" class="header-logo">
<img src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/202407051027268.png" class="img-fluid rounded-normal light-logo" alt="logo">
<span>微博舆情分析系统</span>
</a>
<div class="side-menu-bt-sidebar-1">
<svg xmlns="http://www.w3.org/2000/svg" class="text-light wrapper-menu" width="30" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
</div>
<div class="data-scrollbar" data-scroll="1">
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
<div class="pt-5 pb-5"></div>
</div>
</div>
<div class="content-page">
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">首页</h4>
</div>
</div>
<div class="col-lg-8 col-md-12">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">文章个数</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">文章爬取规则</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary">下次爬取时间</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">7-5-18:00</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h4 class="font-weight-bold">文章发布时间个数</h4>
</div>
<div id="main" style="width:100%;height: 350px" class="custom-chart"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8">
<div class="card card-block card-stretch card-height">
<div class="card-header card-header-border d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论点赞量 Top Fore</h4>
</div>
</div>
<div class="card-body-list">
<ul class="list-style-3 mb-0">
{% for i in commentsLikeCountTopFore %}
<li class="p-3 list-item d-flex justify-content-start align-items-center">
<div class="avatar">
<p>
🧑‍ {{ i[5] }}
</p>
<p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap">
{{ i[4] }}
</p>
</div>
<div class="list-style-action d-flex justify-content-end ml-auto">
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6>
</div>
</li>
{% endfor %}
<div class="d-flex justify-content-end align-items-center border-top-table p-3">
<a href="/page/tableData" class="btn btn-secondary btn-sm">查看全部</a>
</div>
</ul>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">文章类型占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论用户名词云图</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px;text-align: center">
<img style="width:85%" src="/static/authorNameCloud.jpg" alt="">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">评论用户时间占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainThree" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!-- Wrapper End-->
<footer class="iq-footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
</div>
<div class="col-lg-6 text-right">
<span class="mr-1">网安小学期 &copy; 2024.<a target="_blank" href="#">郭航江</a></span>
</div>
</div>
</div>
</footer>
<script src="/static/echarts.min.js"></script>
<script src="/static/china.js"></script>
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myCharts = echarts.init(chartDom);
var option;
var xData = {{ xData | tojson }};
var yData = {{ yData }};
var xRes = []
var yRes = []
for(var i = 0;i < 8;i++){
xRes.push(xData[i])
yRes.push(yData[i])
}
option = {
tooltip:{
trigger:"axis"
},
legend:{},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xRes
},
yAxis: {
type: 'value'
},
series: [
{
name:"日期个数",
data: yRes,
type: 'bar',
areaStyle: {
color:"rgba(0,128,255,0.2)"
},
smooth:true,
lineStyle:{
width:5
},
emphasis:{
focus:'series'
}
}
]
};
let count = 8;
setInterval(()=>{
if(count >= xData.length) count=0
xRes.shift()
xRes.push(xData[count])
yRes.shift()
yRes.push(yData[count])
count++
myCharts.setOption({
xAxis:[{
data:xRes
}],
series:[{
data:yRes
}]
})
},2000)
option && myCharts.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '各微博类型占比饼状图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
padding:[10,20,30,20],
},
series: [
{
name: '博客类型占比',
type: 'pie',
radius: '50%',
data: {{ typeChart |tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left:10,
right:10,
type: 'scroll',
},
series: [
{
name: '评论时间发布个数',
type: 'pie',
radius: ['60%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: {{ createAtChart | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
<script src="/static/js/backend-bundle.min.js"></script>
<script src="/static/js/customizer.js"></script>
<script src="/static/js/sidebar.js"></script>
<script src="/static/js/flex-tree.min.js"></script>
<script src="/static/js/tree.js"></script>
<script src="/static/js/table-treeview.js"></script>
<script src="/static/js/sweetalert.js"></script>
<script src="/static/js/vector-map-custom.js"></script>
<script src="/static/js/chart-custom.js"></script>
<script src="/static/js/01.js"></script>
<script src="/static/js/02.js"></script>
<script src="/static/js/slider.js"></script>
<script src="/static/js/index.js" type="module"></script>
<script src="/static/js/app.js">
</script>
</body>
</html>
<style>
body {
background-color: #add8e6; /* 浅蓝色 */
}
.dark .card {
background-color: #3a4d76;
border-radius: 0px;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{% block title %}首页{% endblock %}</title>
<link rel="stylesheet" href="/static/css/backend-plugin.min.css">
<link rel="stylesheet" href="/static/css/backend.css">
<meta name="referrer" content="no-referrer" />
<script src="/static/js/i18n.js"></script>
</head>
<body class=" ">
<!-- loader Start -->
<div id="loading">
<div id="loading-center">
</div>
</div>
<!-- loader END -->
<!-- Wrapper Start -->
<div class="wrapper">
<div class="iq-sidebar sidebar-default ">
<div class="iq-sidebar-logo d-flex align-items-end justify-content-between">
<a href="" class="header-logo">
<img src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/202407051027268.png" class="img-fluid rounded-normal light-logo" alt="logo">
<span data-i18n="weiboSystem">微博舆情分析系统</span>
</a>
<div class="side-menu-bt-sidebar-1">
<svg xmlns="http://www.w3.org/2000/svg" class="text-light wrapper-menu" width="30" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
</div>
<div class="data-scrollbar" data-scroll="1">
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2" data-i18n="ipChar">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2" data-i18n="commentChar">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="yuqingChar">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="yuqingpredict">舆情预测</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
<li class="sidebar-layout">
<a href="javascript:void(0)" id="language-switcher" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"></path>
</svg>
</i>
<span class="ml-2" id="language-text">切换语言</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
<div class="pt-5 pb-5"></div>
</div>
</div>
<div class="content-page">
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="home">首页</h4>
</div>
</div>
<div class="col-lg-8 col-md-12">
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="articleCount">文章个数</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">{{ articleLenMax }}个</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="articleCrawlRule">文章爬取规则</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">每 5 小时更新一次爬取内容</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="">
<p class="mb-2 text-secondary" data-i18n="nextCrawlTime">下次爬取时间</p>
<div class="d-flex flex-wrap justify-content-start align-items-center">
<h5 class="mb-0 font-weight-bold">7-5-18:00</h5>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h4 class="font-weight-bold" data-i18n="articlePublishTimeCount">文章发布时间个数</h4>
</div>
<div id="main" style="width:100%;height: 350px" class="custom-chart"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-8">
<div class="card card-block card-stretch card-height">
<div class="card-header card-header-border d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentLikeCountTopFore">评论点赞量 Top Fore</h4>
</div>
</div>
<div class="card-body-list">
<ul class="list-style-3 mb-0">
{% for i in commentsLikeCountTopFore %}
<li class="p-3 list-item d-flex justify-content-start align-items-center">
<div class="avatar">
<p>
🧑‍ {{ i[5] }}
</p>
<p class="mb-0" style="color:#ccc;width:320px;overflow: hidden;text-overflow: ellipsis;white-space:nowrap">
{{ i[4] }}
</p>
</div>
<div class="list-style-action d-flex justify-content-end ml-auto">
<h6 class="font-weight-bold text-danger">👍 {{ i[2] }}</h6>
</div>
</li>
{% endfor %}
<div class="d-flex justify-content-end align-items-center border-top-table p-3">
<a href="/page/tableData" class="btn btn-secondary btn-sm" data-i18n="viewAll">查看全部</a>
</div>
</ul>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="articleTypeRatio">文章类型占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentUserWordCloud">评论用户名词云图</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainTwo" style="width:100%;height:350px;text-align: center">
<img style="width:85%" src="/static/authorNameCloud.jpg" alt="">
</div>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="card card-block card-stretch card-height">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="commentUserTimeRatio">评论用户时间占比</h4>
</div>
</div>
<div class="card-body p-0">
<div id="mainThree" style="width:100%;height:350px">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</div>
</div>
<!-- Wrapper End-->
<footer class="iq-footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-6">
</div>
<div class="col-lg-6 text-right">
<span class="mr-1"><span data-i18n="semester">网安小学期</span> &copy; 2024.<a target="_blank" href="#">郭航江</a></span>
</div>
</div>
</div>
</footer>
<script src="/static/echarts.min.js"></script>
<script src="/static/china.js"></script>
<!-- 添加语言切换脚本 -->
<script>
// 页面加载完成后初始化语言切换功能
document.addEventListener('DOMContentLoaded', function() {
// 更新语言切换按钮文本
updateLanguageSwitcherText();
});
// 更新语言切换按钮文本
function updateLanguageSwitcherText() {
const currentLang = getCurrentLanguage();
const langText = document.getElementById('language-text');
if (langText) {
langText.textContent = currentLang === 'zh' ? '切换到英文' : 'Switch to Chinese';
}
}
</script>
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myCharts = echarts.init(chartDom);
var option;
var xData = {{ xData | tojson }};
var yData = {{ yData }};
var xRes = []
var yRes = []
for(var i = 0;i < 8;i++){
xRes.push(xData[i])
yRes.push(yData[i])
}
option = {
tooltip:{
trigger:"axis"
},
legend:{},
toolbox: {
show: true,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
dataView: { readOnly: false },
magicType: { type: ['line', 'bar'] },
restore: {},
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xRes
},
yAxis: {
type: 'value'
},
series: [
{
name:"日期个数",
data: yRes,
type: 'bar',
areaStyle: {
color:"rgba(0,128,255,0.2)"
},
smooth:true,
lineStyle:{
width:5
},
emphasis:{
focus:'series'
}
}
]
};
let count = 8;
setInterval(()=>{
if(count >= xData.length) count=0
xRes.shift()
xRes.push(xData[count])
yRes.shift()
yRes.push(yData[count])
count++
myCharts.setOption({
xAxis:[{
data:xRes
}],
series:[{
data:yRes
}]
})
},2000)
option && myCharts.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '各微博类型占比饼状图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left',
padding:[10,20,30,20],
},
series: [
{
name: '博客类型占比',
type: 'pie',
radius: '50%',
data: {{ typeChart |tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left:10,
right:10,
type: 'scroll',
},
series: [
{
name: '评论时间发布个数',
type: 'pie',
radius: ['60%', '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: 20,
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: {{ createAtChart | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
<script src="/static/js/backend-bundle.min.js"></script>
<script src="/static/js/customizer.js"></script>
<script src="/static/js/sidebar.js"></script>
<script src="/static/js/flex-tree.min.js"></script>
<script src="/static/js/tree.js"></script>
<script src="/static/js/table-treeview.js"></script>
<script src="/static/js/sweetalert.js"></script>
<script src="/static/js/vector-map-custom.js"></script>
<script src="/static/js/chart-custom.js"></script>
<script src="/static/js/01.js"></script>
<script src="/static/js/02.js"></script>
<script src="/static/js/slider.js"></script>
<script src="/static/js/index.js" type="module"></script>
<script src="/static/js/app.js">
</script>
</body>
</html>
<style>
body {
background-color: #add8e6; /* 浅蓝色 */
}
.dark .card {
background-color: #3a4d76;
border-radius: 0px;
}
</style>
+316 -316
View File
@@ -1,317 +1,317 @@
{% extends 'base_page.html' %}
{% block title %}
评论分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">评论分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论点赞次数区间图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论用户性别占比</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">用户评论词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px;text-align: center">
<img style="width:84%" src="/static/commentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论点赞量区间折线图',
left: '1%'
},
tooltip: {
trigger: 'axis'
},
grid: {
left: '5%',
right: '15%',
bottom: '10%'
},
legend:{},
xAxis: {
data: {{ xData | tojson }}
},
yAxis: {},
toolbox: {
right: 10,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
show: true,
start: 80,
end: 100
},
{
type: 'inside',
start: 80,
end: 100
}
],
visualMap: {
top: 50,
right: 10,
pieces: [
{
gt: 0,
lte: 50,
color: '#93CE07'
},
{
gt: 50,
lte: 100,
color: '#FBDB0F'
},
{
gt: 100,
lte: 150,
color: '#FC7D02'
},
{
gt: 150,
lte: 200,
color: '#FD0100'
},
{
gt: 200,
lte: 300,
color: '#AA069F'
},
{
gt: 300,
color: '#AC3B2A'
}
],
outOfRange: {
color: '#999'
}
},
series: {
name: '点赞个数',
type: 'line',
data: {{ yData }},
markLine: {
silent: true,
lineStyle: {
color: '#333'
},
data: [
{
yAxis: 50
},
{
yAxis: 100
},
{
yAxis: 150
},
{
yAxis: 200
},
{
yAxis: 300
}
]
}
}
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论性别占比饼图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '性别个数',
type: 'pie',
radius: '50%',
data: {{ genderPieData | tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="commentChar">评论分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="commentChar">评论分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentLikeRangeChart">评论点赞次数区间图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentUserGenderRatio">评论用户性别占比</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="userCommentWordCloud">用户评论词云图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px;text-align: center">
<img style="width:84%" src="/static/commentCloud.jpg" alt="">
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论点赞量区间折线图',
left: '1%'
},
tooltip: {
trigger: 'axis'
},
grid: {
left: '5%',
right: '15%',
bottom: '10%'
},
legend:{},
xAxis: {
data: {{ xData | tojson }}
},
yAxis: {},
toolbox: {
right: 10,
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
dataZoom: [
{
show: true,
start: 80,
end: 100
},
{
type: 'inside',
start: 80,
end: 100
}
],
visualMap: {
top: 50,
right: 10,
pieces: [
{
gt: 0,
lte: 50,
color: '#93CE07'
},
{
gt: 50,
lte: 100,
color: '#FBDB0F'
},
{
gt: 100,
lte: 150,
color: '#FC7D02'
},
{
gt: 150,
lte: 200,
color: '#FD0100'
},
{
gt: 200,
lte: 300,
color: '#AA069F'
},
{
gt: 300,
color: '#AC3B2A'
}
],
outOfRange: {
color: '#999'
}
},
series: {
name: '点赞个数',
type: 'line',
data: {{ yData }},
markLine: {
silent: true,
lineStyle: {
color: '#333'
},
data: [
{
yAxis: 50
},
{
yAxis: 100
},
{
yAxis: 150
},
{
yAxis: 200
},
{
yAxis: 300
}
]
}
}
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title: {
text: '评论性别占比饼图',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '性别个数',
type: 'pie',
radius: '50%',
data: {{ genderPieData | tojson }},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
option && myChart.setOption(option);
</script>
{% endblock %}
File diff suppressed because it is too large Load Diff
+30 -27
View File
@@ -1,27 +1,30 @@
<!--
_oo0oo_
o8888888o
88" . "88
(| -_- |)
0\ = /0
___/`---'\___
.' \\| |// '.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' |_/ |
\ .-\__ '-' ___/-. /
___'. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' >' "".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `_. \_ __\ /__ _/ .-` / /
=====`-.____`.___ \_____/___.-`___.-'=====
`=---='
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
佛祖保佑 代码永无BUG
-->
{% extends 'base_page.html' %}
<!--
_oo0oo_
o8888888o
88" . "88
(| -_- |)
0\ = /0
___/`---'\___
.' \\| |// '.
/ \\||| : |||// \
/ _||||| -:- |||||- \
| | \\\ - /// | |
| \_| ''\---/'' |_/ |
\ .-\__ '-' ___/-. /
___'. .' /--.--\ `. .'___
."" '< `.___\_<|>_/___.' >' "".
| | : `- \`.;`\ _ /`;.`/ - ` : | |
\ \ `_. \_ __\ /__ _/ .-` / /
=====`-.____`.___ \_____/___.-`___.-'=====
`=---='
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
佛祖保佑 代码永无BUG
-->
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="home">首页</span>
{% endblock %}
+274 -274
View File
@@ -1,275 +1,275 @@
{% extends 'base_page.html' %}
{% block title %}
IP分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">IP分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">评论IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:50,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ articleRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:200,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ commentRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="ipChar">IP分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="ipChar">IP分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleIpLocationAnalysis">文章IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="commentIpLocationAnalysis">评论IP位置分析图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 750px"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:50,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ articleRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
title:{
text:'文章IP发布地址地图',
left:'center',
textStyle:{
color:"#333",
fontWeight:"bold"
}
},
tooltip:{
trigger:'item',
formatter:function(params){
return params.name + '<br>微博发布个数: ' + params.value + ' 个'
}
},
visualMap:{
min:0,
max:200,
text:['高','低'],
realtime:true,
calulable:true,
inRange:{
color:['orange','green']
}
},
series:[{
type:'map',
map:"china",
label:{
normal:{
show:true,
color:"white",
fontSize:'12'
}
},
emphasis:{
label:{
show:true
},
},
data:{{ commentRegionData | tojson }},
itemStyle:{
normal: {
areaColor:"skyblue",
borderColor:"#fff"
},
emphasis:{
areaColor: '#2b91b7'
}
},
zoom:1.4,
roam:true
}]
}
option && myChart.setOption(option);
</script>
{% endblock %}
+227 -224
View File
@@ -1,225 +1,228 @@
{% extends 'base_page.html' %}
{% block title %}
微博舆情统计页
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">微博舆情统计页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title">微博文章统计表格 - 舆情 情感分类
</h4>
</div>
<div class="header-action">
<i data-toggle="collapse" data-target="#datatable-1" aria-expanded="false">
<svg width="20" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
</svg>
</i>
</div>
</div>
<div class="card-body">
<p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm">情感分类</a></p>
<div class="table-responsive">
<div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row">
<table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info">
<thead>
<tr>
<th style="font-weight: bold">
文章ID
</th>
<th style="font-weight: bold">
文章IP
</th>
<th style="font-weight: bold">
点赞量
</th>
<th style="font-weight: bold">
转发量
</th>
<th style="font-weight: bold">
评论量
</th>
<th style="font-weight: bold">
类型
</th>
<th style="font-weight: bold">
内容
</th>
<th style="font-weight: bold">
发布时间
</th>
{% if defaultFlag %}
<th style="font-weight: bold">
情感分类
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for article in tableData %}
<tr class="even">
<td style="width:330px" class="sorting_1">
<a href="{{ article[9] }}">
{{ article[0] }}
</a>
</td>
<td style="width:90px">{{ article[4] }}</td>
<td style="width:90px">👍{{ article[1] }}</td>
<td style="width:90px">🥇{{ article[2] }}</td>
<td style="width:90px">🔥{{ article[3] }}</td>
<td style="width:90px" class="text-right">{{ article[8] }}</td>
<td style="width:155px" class="text-right">{{ article[5] }}</td>
<td style="width:90px" class="text-right">{{ article[7] }}</td>
{% if defaultFlag %}
<td style="width:90px">
{% if article[-1] == '正面' %}
<span class="text-success">
{{ article[-1] }}
</span>
{% else %}
<span class="text-danger">
{{ article[-1] }}
</span>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="tableData">微博舆情统计页</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="tableData">微博舆情统计页</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-header d-flex justify-content-between">
<div class="header-title">
<h4 class="card-title" data-i18n="weiboArticleStatTable">微博文章统计表格 - 舆情 情感分类</h4>
</div>
<div class="header-action">
<i data-toggle="collapse" data-target="#datatable-1" aria-expanded="false">
<svg width="20" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
</svg>
</i>
</div>
</div>
<div class="card-body">
<p><a href="/page/tableData?flag=True" class="btn btn-primary btn-sm" data-i18n="sentimentClassification">情感分类</a></p>
<div class="table-responsive">
<div id="datatable-1_wrapper" class="dataTables_wrapper dt-bootstrap4">
<div class="row">
<table id="datatable-1" class="table data-table table-striped table-bordered dataTable" role="grid" aria-describedby="datatable-1_info">
<thead>
<tr>
<th style="font-weight: bold" data-i18n="articleId">
文章ID
</th>
<th style="font-weight: bold" data-i18n="articleIp">
文章IP
</th>
<th style="font-weight: bold" data-i18n="articleTitle">
文章标题
</th>
<th style="font-weight: bold" data-i18n="articleLike">
点赞量
</th>
<th style="font-weight: bold" data-i18n="articleForward">
转发量
</th>
<th style="font-weight: bold" data-i18n="articleComment">
评论量
</th>
<th style="font-weight: bold" data-i18n="articleType">
类型
</th>
<th style="font-weight: bold" data-i18n="articleContent">
内容
</th>
<th style="font-weight: bold" data-i18n="articleTime">
发布时间
</th>
{% if defaultFlag %}
<th style="font-weight: bold" data-i18n="sentimentClassification">
情感分类
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for article in tableData %}
<tr class="even">
<td style="width:330px" class="sorting_1">
<a href="{{ article[9] }}">
{{ article[0] }}
</a>
</td>
<td style="width:90px">{{ article[4] }}</td>
<td style="width:90px" class="text-right">{{ article[5] }}</td>
<td style="width:90px">👍{{ article[1] }}</td>
<td style="width:90px">🥇{{ article[2] }}</td>
<td style="width:90px">🔥{{ article[3] }}</td>
<td style="width:90px" class="text-right">{{ article[8] }}</td>
<td style="width:155px" class="text-right">{{ article[5] }}</td>
<td style="width:90px" class="text-right">{{ article[7] }}</td>
{% if defaultFlag %}
<td style="width:90px">
{% if article[-1] == '正面' %}
<span class="text-success">
{{ article[-1] }}
</span>
{% else %}
<span class="text-danger">
{{ article[-1] }}
</span>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
</script>
{% endblock %}
+400 -400
View File
@@ -1,401 +1,401 @@
{% extends 'base_page.html' %}
{% block title %}
舆情分析
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold">舆情分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词情感趋势柱状图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词情感趋势树形图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">文章内容与评论内容舆情趋势饼状图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词TOP10</h4>
</div>
</div>
<div class="card-body">
<div id="mainFore" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group">
<label for="modelSelect">选择分析模型:</label>
<select class="form-control" id="modelSelect" onchange="updateModel(this.value)">
<optgroup label="基础模型">
<option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option>
</optgroup>
<optgroup label="OpenAI 模型">
<option value="gpt-3.5-turbo" {% if model_type == 'gpt-3.5-turbo' %}selected{% endif %}>GPT-3.5-Turbo</option>
<option value="gpt-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option>
</optgroup>
<optgroup label="Claude 模型">
<option value="claude-3-opus-20240229" {% if model_type == 'claude-3-opus-20240229' %}selected{% endif %}>Claude-3 Opus</option>
<option value="claude-3-sonnet-20240229" {% if model_type == 'claude-3-sonnet-20240229' %}selected{% endif %}>Claude-3 Sonnet</option>
<option value="claude-3-haiku-20240307" {% if model_type == 'claude-3-haiku-20240307' %}selected{% endif %}>Claude-3 Haiku</option>
</optgroup>
<optgroup label="DeepSeek 模型">
<option value="deepseek-chat" {% if model_type == 'deepseek-chat' %}selected{% endif %}>DeepSeek-V3</option>
<option value="deepseek-reasoner" {% if model_type == 'deepseek-reasoner' %}selected{% endif %}>DeepSeek-R1</option>
</optgroup>
</select>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66CC99', '#FFCC66', '#FF6666'];
option = {
title: {
text: '热词情感分析柱状图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['Rainfall']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data: {{ xData | tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '舆情个数',
type: 'bar',
data: {{ yData }},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
},
itemStyle: {
color: function (parmas) {
return colors[parmas.dataIndex % colors.length];
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
series: [
{
type: 'treemap',
data: {{ biedata | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
data: [
'正面',
'负面',
'中性'
]
},
series: [
{
name: '评论舆情结果',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: {{ biedata1 | tojson }}
},
{
name: '文章舆情结果',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: {{ biedata2 | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainFore');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
series: [
{
name: '热词出现个数',
type: 'bar',
data:{{ y1Data }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
function updateModel(value) {
window.location.href = '/page/yuqingChar?model=' + value;
}
</script>
{% extends 'base_page.html' %}
{% block title %}
<span data-i18n="yuqingChar">舆情分析</span>
{% endblock %}
{% block nav %}
<nav class="iq-sidebar-menu">
<ul id="iq-sidebar-toggle" class="side-menu">
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="home">首页</span>
</li>
<li class=" sidebar-layout">
<a href="/page/home" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>
</svg>
</i>
<span class="ml-2" data-i18n="home">首页</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/hotWord" class="svg-icon ">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="hotWord">热词统计</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/tableData" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="tableData">微博舆情统计</span>
</a>
</li>
<li class="px-3 pt-3 pb-2 ">
<span class="text-uppercase small font-weight-bold" data-i18n="dataVisualization">数据可视化</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"></path>
</svg>
</i>
<span class="ml-2" data-i18n="articleChar">文章分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/ipChar" class="svg-icon">
<i class="">
<svg class="icon line" width="18" id="receipt" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" stroke="currentColor">
<path d="M17,16V3L13,5,10,3,7,5,3,3V17.83A3.13,3.13,0,0,0,5.84,21,3,3,0,0,0,9,18V17a1,1,0,0,1,1-1H20a1,1,0,0,1,1,1v1a3,3,0,0,1-3,3H6" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path>
<line x1="8" y1="10" x2="12" y2="10" style="fill: none; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></line>
</svg>
</i>
<span class="ml-2">IP分析</span>
</a>
</li>
<li class=" sidebar-layout">
<a href="/page/commentChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path>
</svg>
</i><span class="ml-2">评论分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingChar" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情分析</span>
</a>
</li>
<li class="active sidebar-layout">
<a href="/page/yuqingpredict" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</i>
<span class="ml-2">舆情预测</span>
</a>
</li>
<li class="px-3 pt-3 pb-2">
<span class="text-uppercase small font-weight-bold">词云图</span>
</li>
<li class=" sidebar-layout">
<a href="/page/articleCloud" class="svg-icon">
<i class="">
<svg xmlns="http://www.w3.org/2000/svg" width="18" fill="none" viewbox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
</svg>
</i><span class="ml-2" data-i18n="articleCloud">文章内容词云图</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12 mb-4 mt-1">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h4 class="font-weight-bold" data-i18n="yuqingChar">舆情分析</h4>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendBar">热词情感趋势柱状图</h4>
</div>
</div>
<div class="card-body">
<div id="main" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="hotWordSentimentTrendTree">热词情感趋势树形图</h4>
</div>
</div>
<div class="card-body">
<div id="mainTwo" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0" data-i18n="articleCommentSentimentTrendPie">文章内容与评论内容舆情趋势饼状图</h4>
</div>
</div>
<div class="card-body">
<div id="mainThree" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card card-block">
<div class="card-header d-flex justify-content-between pb-0">
<div class="header-title">
<h4 class="card-title mb-0">热词TOP10</h4>
</div>
</div>
<div class="card-body">
<div id="mainFore" style="width: 100%;height: 450px"></div>
</div>
</div>
</div>
</div>
<div class="col-lg-12 mb-3">
<div class="form-group">
<label for="modelSelect">选择分析模型:</label>
<select class="form-control" id="modelSelect" onchange="updateModel(this.value)">
<optgroup label="基础模型">
<option value="basic" {% if model_type == 'basic' %}selected{% endif %}>SnowNLP</option>
</optgroup>
<optgroup label="OpenAI 模型">
<option value="gpt-3.5-turbo" {% if model_type == 'gpt-3.5-turbo' %}selected{% endif %}>GPT-3.5-Turbo</option>
<option value="gpt-4" {% if model_type == 'gpt-4' %}selected{% endif %}>GPT-4</option>
</optgroup>
<optgroup label="Claude 模型">
<option value="claude-3-opus-20240229" {% if model_type == 'claude-3-opus-20240229' %}selected{% endif %}>Claude-3 Opus</option>
<option value="claude-3-sonnet-20240229" {% if model_type == 'claude-3-sonnet-20240229' %}selected{% endif %}>Claude-3 Sonnet</option>
<option value="claude-3-haiku-20240307" {% if model_type == 'claude-3-haiku-20240307' %}selected{% endif %}>Claude-3 Haiku</option>
</optgroup>
<optgroup label="DeepSeek 模型">
<option value="deepseek-chat" {% if model_type == 'deepseek-chat' %}selected{% endif %}>DeepSeek-V3</option>
<option value="deepseek-reasoner" {% if model_type == 'deepseek-reasoner' %}selected{% endif %}>DeepSeek-R1</option>
</optgroup>
</select>
</div>
</div>
</div>
{% endblock %}
{% block echarts %}
<script>
var chartDom = document.getElementById('main');
var myChart = echarts.init(chartDom);
var option;
var colors = ['#66CC99', '#FFCC66', '#FF6666'];
option = {
title: {
text: '热词情感分析柱状图'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['Rainfall']
},
toolbox: {
show: true,
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
calculable: true,
xAxis: [
{
type: 'category',
// prettier-ignore
data: {{ xData | tojson }}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '舆情个数',
type: 'bar',
data: {{ yData }},
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
},
itemStyle: {
color: function (parmas) {
return colors[parmas.dataIndex % colors.length];
}
}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainTwo');
var myChart = echarts.init(chartDom);
var option;
option = {
series: [
{
type: 'treemap',
data: {{ biedata | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainThree');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
data: [
'正面',
'负面',
'中性'
]
},
series: [
{
name: '评论舆情结果',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: {{ biedata1 | tojson }}
},
{
name: '文章舆情结果',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: {{ biedata2 | tojson }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
var chartDom = document.getElementById('mainFore');
var myChart = echarts.init(chartDom);
var option;
option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: {{ x1Data | tojson }}
},
series: [
{
name: '热词出现个数',
type: 'bar',
data:{{ y1Data }}
}
]
};
option && myChart.setOption(option);
</script>
<script>
function updateModel(value) {
window.location.href = '/page/yuqingChar?model=' + value;
}
</script>
{% endblock %}
File diff suppressed because it is too large Load Diff
+370 -370
View File
@@ -1,371 +1,371 @@
from flask import Blueprint, jsonify, request, render_template
import json
import os
from datetime import datetime
import threading
from queue import Queue
import asyncio
import websockets
import logging
from spider.spiderData import SpiderData
from openai import OpenAI
from anthropic import Anthropic
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from ratelimit import limits, sleep_and_retry
from tenacity import retry, stop_after_attempt, wait_exponential
# 创建蓝图
spider_bp = Blueprint('spider', __name__)
# 创建日志记录器
logger = logging.getLogger('spider_control')
logger.setLevel(logging.INFO)
# 存储WebSocket连接的集合
websocket_connections = set()
# 创建消息队列
message_queue = Queue()
# 创建线程池
thread_pool = ThreadPoolExecutor(max_workers=3)
# 创建异步事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 默认配置
DEFAULT_CONFIG = {
'crawlDepth': 3,
'interval': 5,
'maxRetries': 3,
'timeout': 30,
'maxConcurrent': 2
}
# 限流装饰器
@sleep_and_retry
@limits(calls=100, period=60) # 每分钟最多100个请求
def rate_limited_request():
pass
class SpiderWorker:
def __init__(self, topics, parameters):
self.topics = topics
self.parameters = parameters
self.total_topics = len(topics)
self.completed_topics = 0
self.spider = SpiderData()
self.message_buffer = []
self.message_buffer_size = 10
self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent']))
async def send_message(self, message):
"""异步发送消息,使用缓冲区优化"""
self.message_buffer.append(message)
if len(self.message_buffer) >= self.message_buffer_size:
await self.flush_messages()
async def flush_messages(self):
"""刷新消息缓冲区"""
if not self.message_buffer:
return
try:
await broadcast_message(self.message_buffer)
self.message_buffer.clear()
except Exception as e:
logger.error(f"发送消息失败: {e}")
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def crawl_single_topic(self, topic):
"""爬取单个话题"""
try:
rate_limited_request()
await self.send_message({
'type': 'log',
'message': f'开始爬取话题: {topic}'
})
async with self.semaphore:
await asyncio.get_event_loop().run_in_executor(
thread_pool,
self.spider.crawl_topic,
topic,
self.parameters['crawlDepth'],
self.parameters['interval'],
self.parameters['maxRetries'],
self.parameters['timeout']
)
self.completed_topics += 1
progress = int((self.completed_topics / self.total_topics) * 100)
await self.send_message({
'type': 'progress',
'value': progress
})
await self.send_message({
'type': 'log',
'message': f'话题 {topic} 爬取完成'
})
except Exception as e:
logger.error(f"爬取话题 {topic} 失败: {e}")
await self.send_message({
'type': 'log',
'message': f'爬取话题 {topic} 时出错: {str(e)}'
})
raise
async def run(self):
"""运行爬虫任务"""
try:
tasks = [self.crawl_single_topic(topic) for topic in self.topics]
await asyncio.gather(*tasks)
await self.flush_messages()
await self.send_message({
'type': 'log',
'message': '所有话题爬取完成'
})
except Exception as e:
logger.error(f"爬虫任务执行出错: {e}")
await self.send_message({
'type': 'log',
'message': f'爬虫任务执行出错: {str(e)}'
})
finally:
await self.flush_messages()
async def broadcast_message(messages):
"""广播消息到所有WebSocket连接"""
if not websocket_connections:
return
for websocket in websocket_connections.copy():
try:
if isinstance(messages, list):
for message in messages:
await websocket.send(json.dumps(message))
else:
await websocket.send(json.dumps(messages))
except websockets.exceptions.ConnectionClosed:
websocket_connections.remove(websocket)
except Exception as e:
logger.error(f"发送WebSocket消息失败: {e}")
websocket_connections.remove(websocket)
@spider_bp.route('/spider/control')
def spider_control():
"""渲染爬虫控制页面"""
return render_template('spider_control.html')
@spider_bp.route('/api/spider/start', methods=['POST'])
async def start_spider():
"""启动爬虫任务"""
try:
data = request.get_json()
topics = data.get('topics', [])
parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})}
if not topics:
return jsonify({
'success': False,
'message': '请选择至少一个话题'
})
# 创建爬虫工作器
worker = SpiderWorker(topics, parameters)
# 在事件循环中运行爬虫任务
asyncio.create_task(worker.run())
return jsonify({
'success': True,
'message': '爬虫任务已启动'
})
except Exception as e:
logger.error(f"启动爬虫任务失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.route('/api/spider/save-config', methods=['POST'])
def save_spider_config():
"""保存爬虫配置"""
try:
config = request.get_json()
if save_config(config):
return jsonify({
'success': True,
'message': '配置保存成功'
})
else:
return jsonify({
'success': False,
'message': '配置保存失败'
})
except Exception as e:
logger.error(f"保存配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.websocket('/ws/spider-status')
async def spider_status_socket(websocket):
"""WebSocket连接处理"""
try:
websocket_connections.add(websocket)
logging.info("新的WebSocket连接已建立")
try:
while True:
# 等待消息,保持连接活跃
message = await websocket.receive()
if message is None:
break
except websockets.exceptions.ConnectionClosed:
logging.info("WebSocket连接已关闭")
finally:
websocket_connections.remove(websocket)
logging.info("WebSocket连接已移除")
except Exception as e:
logger.error(f"WebSocket连接处理失败: {e}")
if websocket in websocket_connections:
websocket_connections.remove(websocket)
def get_ai_client():
"""获取可用的AI客户端"""
# 按优先级尝试不同的AI服务
if os.getenv('ANTHROPIC_API_KEY'):
return {
'type': 'anthropic',
'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
}
elif os.getenv('OPENAI_API_KEY'):
return {
'type': 'openai',
'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
}
else:
raise ValueError("未找到可用的AI API密钥")
def parse_ai_response(response_text):
"""解析AI响应中的JSON配置"""
try:
# 查找JSON内容
start = response_text.find('{')
end = response_text.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("未找到有效的JSON配置")
json_str = response_text[start:end]
config = json.loads(json_str)
# 验证配置格式
if not isinstance(config.get('topics'), list):
raise ValueError("配置必须包含话题列表")
parameters = config.get('parameters', {})
if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']):
raise ValueError("配置缺少必要的参数")
# 提取建议文本(JSON之前的部分)
suggestion = response_text[:start].strip()
return config, suggestion
except Exception as e:
raise ValueError(f"解析AI响应失败: {str(e)}")
@spider_bp.route('/api/spider/ai-config', methods=['POST'])
def generate_ai_config():
"""使用AI生成爬虫配置"""
try:
prompt = request.json.get('prompt', '')
if not prompt:
return jsonify({
'success': False,
'message': '请提供爬虫需求描述'
})
# 构建AI提示
system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。
配置应包含以下内容
1. 要爬取的话题列表
2. 爬虫参数爬取深度间隔时间重试次数超时时间
请先用通俗易懂的语言解释你的配置建议然后在最后提供一个JSON格式的具体配置
注意
- 爬取深度(crawlDepth)范围1-10
- 间隔时间(interval)范围3-30
- 重试次数(maxRetries)范围1-5
- 超时时间(timeout)范围10-60
- 所有参数都必须是整数
示例输出格式
根据您的需求我建议...
{
"topics": ["话题1", "话题2"],
"parameters": {
"crawlDepth": 5,
"interval": 5,
"maxRetries": 3,
"timeout": 30
}
}"""
# 获取AI客户端
ai = get_ai_client()
try:
if ai['type'] == 'anthropic':
response = ai['client'].messages.create(
model="claude-3-sonnet-20240229",
max_tokens=1000,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.content[0].text
else: # OpenAI
response = ai['client'].chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.choices[0].message.content
# 解析AI响应
config, suggestion = parse_ai_response(response_text)
return jsonify({
'success': True,
'config': config,
'suggestion': suggestion
})
except Exception as e:
logger.error(f"AI服务调用失败: {e}")
return jsonify({
'success': False,
'message': f"AI配置生成失败: {str(e)}"
})
except Exception as e:
logger.error(f"生成配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
from flask import Blueprint, jsonify, request, render_template
import json
import os
from datetime import datetime
import threading
from queue import Queue
import asyncio
import websockets
import logging
from spider.spiderData import SpiderData
from openai import OpenAI
from anthropic import Anthropic
import aiohttp
from concurrent.futures import ThreadPoolExecutor
from ratelimit import limits, sleep_and_retry
from tenacity import retry, stop_after_attempt, wait_exponential
# 创建蓝图
spider_bp = Blueprint('spider', __name__)
# 创建日志记录器
logger = logging.getLogger('spider_control')
logger.setLevel(logging.INFO)
# 存储WebSocket连接的集合
websocket_connections = set()
# 创建消息队列
message_queue = Queue()
# 创建线程池
thread_pool = ThreadPoolExecutor(max_workers=3)
# 创建异步事件循环
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# 默认配置
DEFAULT_CONFIG = {
'crawlDepth': 3,
'interval': 5,
'maxRetries': 3,
'timeout': 30,
'maxConcurrent': 2
}
# 限流装饰器
@sleep_and_retry
@limits(calls=100, period=60) # 每分钟最多100个请求
def rate_limited_request():
pass
class SpiderWorker:
def __init__(self, topics, parameters):
self.topics = topics
self.parameters = parameters
self.total_topics = len(topics)
self.completed_topics = 0
self.spider = SpiderData()
self.message_buffer = []
self.message_buffer_size = 10
self.semaphore = asyncio.Semaphore(parameters.get('maxConcurrent', DEFAULT_CONFIG['maxConcurrent']))
async def send_message(self, message):
"""异步发送消息,使用缓冲区优化"""
self.message_buffer.append(message)
if len(self.message_buffer) >= self.message_buffer_size:
await self.flush_messages()
async def flush_messages(self):
"""刷新消息缓冲区"""
if not self.message_buffer:
return
try:
await broadcast_message(self.message_buffer)
self.message_buffer.clear()
except Exception as e:
logger.error(f"发送消息失败: {e}")
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
async def crawl_single_topic(self, topic):
"""爬取单个话题"""
try:
rate_limited_request()
await self.send_message({
'type': 'log',
'message': f'开始爬取话题: {topic}'
})
async with self.semaphore:
await asyncio.get_event_loop().run_in_executor(
thread_pool,
self.spider.crawl_topic,
topic,
self.parameters['crawlDepth'],
self.parameters['interval'],
self.parameters['maxRetries'],
self.parameters['timeout']
)
self.completed_topics += 1
progress = int((self.completed_topics / self.total_topics) * 100)
await self.send_message({
'type': 'progress',
'value': progress
})
await self.send_message({
'type': 'log',
'message': f'话题 {topic} 爬取完成'
})
except Exception as e:
logger.error(f"爬取话题 {topic} 失败: {e}")
await self.send_message({
'type': 'log',
'message': f'爬取话题 {topic} 时出错: {str(e)}'
})
raise
async def run(self):
"""运行爬虫任务"""
try:
tasks = [self.crawl_single_topic(topic) for topic in self.topics]
await asyncio.gather(*tasks)
await self.flush_messages()
await self.send_message({
'type': 'log',
'message': '所有话题爬取完成'
})
except Exception as e:
logger.error(f"爬虫任务执行出错: {e}")
await self.send_message({
'type': 'log',
'message': f'爬虫任务执行出错: {str(e)}'
})
finally:
await self.flush_messages()
async def broadcast_message(messages):
"""广播消息到所有WebSocket连接"""
if not websocket_connections:
return
for websocket in websocket_connections.copy():
try:
if isinstance(messages, list):
for message in messages:
await websocket.send(json.dumps(message))
else:
await websocket.send(json.dumps(messages))
except websockets.exceptions.ConnectionClosed:
websocket_connections.remove(websocket)
except Exception as e:
logger.error(f"发送WebSocket消息失败: {e}")
websocket_connections.remove(websocket)
@spider_bp.route('/spider/control')
def spider_control():
"""渲染爬虫控制页面"""
return render_template('spider_control.html')
@spider_bp.route('/api/spider/start', methods=['POST'])
async def start_spider():
"""启动爬虫任务"""
try:
data = request.get_json()
topics = data.get('topics', [])
parameters = {**DEFAULT_CONFIG, **data.get('parameters', {})}
if not topics:
return jsonify({
'success': False,
'message': '请选择至少一个话题'
})
# 创建爬虫工作器
worker = SpiderWorker(topics, parameters)
# 在事件循环中运行爬虫任务
asyncio.create_task(worker.run())
return jsonify({
'success': True,
'message': '爬虫任务已启动'
})
except Exception as e:
logger.error(f"启动爬虫任务失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.route('/api/spider/save-config', methods=['POST'])
def save_spider_config():
"""保存爬虫配置"""
try:
config = request.get_json()
if save_config(config):
return jsonify({
'success': True,
'message': '配置保存成功'
})
else:
return jsonify({
'success': False,
'message': '配置保存失败'
})
except Exception as e:
logger.error(f"保存配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
@spider_bp.websocket('/ws/spider-status')
async def spider_status_socket(websocket):
"""WebSocket连接处理"""
try:
websocket_connections.add(websocket)
logging.info("新的WebSocket连接已建立")
try:
while True:
# 等待消息,保持连接活跃
message = await websocket.receive()
if message is None:
break
except websockets.exceptions.ConnectionClosed:
logging.info("WebSocket连接已关闭")
finally:
websocket_connections.remove(websocket)
logging.info("WebSocket连接已移除")
except Exception as e:
logger.error(f"WebSocket连接处理失败: {e}")
if websocket in websocket_connections:
websocket_connections.remove(websocket)
def get_ai_client():
"""获取可用的AI客户端"""
# 按优先级尝试不同的AI服务
if os.getenv('ANTHROPIC_API_KEY'):
return {
'type': 'anthropic',
'client': Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
}
elif os.getenv('OPENAI_API_KEY'):
return {
'type': 'openai',
'client': OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
}
else:
raise ValueError("未找到可用的AI API密钥")
def parse_ai_response(response_text):
"""解析AI响应中的JSON配置"""
try:
# 查找JSON内容
start = response_text.find('{')
end = response_text.rfind('}') + 1
if start == -1 or end == 0:
raise ValueError("未找到有效的JSON配置")
json_str = response_text[start:end]
config = json.loads(json_str)
# 验证配置格式
if not isinstance(config.get('topics'), list):
raise ValueError("配置必须包含话题列表")
parameters = config.get('parameters', {})
if not all(key in parameters for key in ['crawlDepth', 'interval', 'maxRetries', 'timeout']):
raise ValueError("配置缺少必要的参数")
# 提取建议文本(JSON之前的部分)
suggestion = response_text[:start].strip()
return config, suggestion
except Exception as e:
raise ValueError(f"解析AI响应失败: {str(e)}")
@spider_bp.route('/api/spider/ai-config', methods=['POST'])
def generate_ai_config():
"""使用AI生成爬虫配置"""
try:
prompt = request.json.get('prompt', '')
if not prompt:
return jsonify({
'success': False,
'message': '请提供爬虫需求描述'
})
# 构建AI提示
system_prompt = """你是一个专业的爬虫配置助手。请根据用户的自然语言描述,生成合适的微博爬虫配置。
配置应包含以下内容
1. 要爬取的话题列表
2. 爬虫参数爬取深度间隔时间重试次数超时时间
请先用通俗易懂的语言解释你的配置建议然后在最后提供一个JSON格式的具体配置
注意
- 爬取深度(crawlDepth)范围1-10
- 间隔时间(interval)范围3-30
- 重试次数(maxRetries)范围1-5
- 超时时间(timeout)范围10-60
- 所有参数都必须是整数
示例输出格式
根据您的需求我建议...
{
"topics": ["话题1", "话题2"],
"parameters": {
"crawlDepth": 5,
"interval": 5,
"maxRetries": 3,
"timeout": 30
}
}"""
# 获取AI客户端
ai = get_ai_client()
try:
if ai['type'] == 'anthropic':
response = ai['client'].messages.create(
model="claude-3-sonnet-20240229",
max_tokens=1000,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.content[0].text
else: # OpenAI
response = ai['client'].chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
)
response_text = response.choices[0].message.content
# 解析AI响应
config, suggestion = parse_ai_response(response_text)
return jsonify({
'success': True,
'config': config,
'suggestion': suggestion
})
except Exception as e:
logger.error(f"AI服务调用失败: {e}")
return jsonify({
'success': False,
'message': f"AI配置生成失败: {str(e)}"
})
except Exception as e:
logger.error(f"生成配置失败: {e}")
return jsonify({
'success': False,
'message': str(e)
})
Binary file not shown.
@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>微博舆情分析系统 | 登录</title>
<link rel="icon" href="../../../static/原神启动/favicon.ico" />
<script src="/static/js/i18n.js"></script>
</head>
<style>
@@ -287,9 +288,31 @@
.yanzhengma:hover {
color: #aa863e;
}
/* 添加语言切换按钮样式 */
.language-switcher {
position: absolute;
top: 20px;
right: 20px;
background-color: rgba(255, 255, 255, 0.8);
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
z-index: 1000;
font-size: 14px;
transition: all 0.3s ease;
}
.language-switcher:hover {
background-color: rgba(255, 255, 255, 1);
}
</style>
<body onclick="playAudio()">
<div class="language-switcher" id="language-switcher">
<span id="language-text">切换语言</span>
</div>
<audio id="audio-player" autoplay class="hide-player">
<source
src="https://lovexl-oss.oss-cn-beijing.aliyuncs.com/bed/%E5%8E%9F%E7%A5%9E%E9%A6%96%E9%A1%B5%E8%83%8C%E6%99%AF%E9%9F%B3.mp4"
+372 -372
View File
@@ -1,372 +1,372 @@
import time
import hashlib
from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response
from datetime import datetime, timedelta
import re
from utils.query import query
from utils.errorResponse import errorResponse
from utils.logger import app_logger as logging
from functools import wraps
import secrets
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
import json
import bleach
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import html
# 创建Argon2密码哈希器
ph = PasswordHasher()
# Redis连接
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建限流器
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
ub = Blueprint('user',
__name__,
url_prefix='/user',
template_folder='templates')
def sanitize_input(text):
"""清理用户输入,防止XSS攻击"""
if text is None:
return None
return bleach.clean(str(text), strip=True)
def validate_csrf_token():
"""验证CSRF令牌"""
token = request.form.get('csrf_token')
stored_token = session.get('csrf_token')
if not token or not stored_token or token != stored_token:
return False
return True
def get_client_info():
"""获取客户端信息"""
return {
'ip': request.remote_addr,
'user_agent': str(request.user_agent.string),
'platform': str(request.user_agent.platform),
'browser': str(request.user_agent.browser),
}
def is_suspicious_ip(ip):
"""检查IP是否可疑"""
key = f"login_attempts:{ip}"
attempts = redis_client.get(key)
if attempts and int(attempts) >= 5: # 5次失败尝试
return True
return False
def record_failed_attempt(ip):
"""记录失败的登录尝试"""
key = f"login_attempts:{ip}"
pipe = redis_client.pipeline()
pipe.incr(key)
pipe.expire(key, 1800) # 30分钟后重置
pipe.execute()
def clear_login_attempts(ip):
"""清除登录尝试记录"""
redis_client.delete(f"login_attempts:{ip}")
def set_secure_headers(response):
"""设置安全响应头"""
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return redirect('/user/login')
# 验证会话完整性
if 'client_info' not in session or 'session_id' not in session:
session.clear()
return redirect('/user/login')
# 验证客户端信息
current_client = get_client_info()
stored_client = session['client_info']
if (current_client['ip'] != stored_client['ip'] or
current_client['user_agent'] != stored_client['user_agent']):
session.clear()
return redirect('/user/login')
# 验证会话ID
stored_session_id = redis_client.get(f"session:{session['username']}")
if not stored_session_id or stored_session_id != session['session_id']:
session.clear()
return redirect('/user/login')
return f(*args, **kwargs)
return decorated_function
def hash_password(password: str) -> str:
"""
使用Argon2id算法哈希密码
:param password: 用户输入的密码
:return: 哈希后的密码
"""
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
"""
验证密码
:param stored_hash: 存储的密码哈希
:param password: 用户输入的密码
:return: 是否匹配
"""
try:
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
def validate_password(password: str) -> bool:
"""
验证密码强度
"""
if len(password) < 12: # 增加最小长度要求
return False
if not re.search(r"[A-Z]", password):
return False
if not re.search(r"[a-z]", password):
return False
if not re.search(r"\d", password):
return False
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False
# 检查常见密码模式
common_patterns = ['password', '123456', 'qwerty']
if any(pattern in password.lower() for pattern in common_patterns):
return False
return True
@ub.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
"""处理用户登录请求"""
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
client_ip = request.remote_addr
if is_suspicious_ip(client_ip):
logging.warning(f"可疑IP尝试登录: {client_ip}")
return errorResponse('由于多次失败尝试,请30分钟后再试')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password') # 密码不需要sanitize
if not username or not password:
logging.warning("登录失败:用户名或密码为空")
return errorResponse('用户名和密码不能为空')
# 查询用户信息
sql = "SELECT password, status FROM user WHERE username = %s"
result = query(sql, [username], "select")
if result:
stored_password = result[0]['password']
status = result[0]['status']
if status != 'active':
logging.warning(f"已禁用的账户尝试登录: {username}")
return errorResponse('账户已被禁用')
if verify_password(stored_password, password):
session.clear()
session.regenerate()
# 生成唯一会话ID
session_id = secrets.token_hex(32)
client_info = get_client_info()
# 存储会话信息
session['username'] = username
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session['csrf_token'] = secrets.token_hex(32)
session['client_info'] = client_info
session['session_id'] = session_id
session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=2)
# 在Redis中存储会话ID
redis_client.setex(
f"session:{username}",
int(current_app.permanent_session_lifetime.total_seconds()),
session_id
)
clear_login_attempts(client_ip)
# 记录登录历史
login_history_sql = '''
INSERT INTO login_history
(username, login_time, ip_address, user_agent, success, attempt_count)
VALUES (%s, %s, %s, %s, %s, %s)
'''
query(login_history_sql, [
username,
datetime.now(),
client_info['ip'],
client_info['user_agent'],
True,
redis_client.get(f"login_attempts:{client_ip}") or 0
])
logging.info(f"用户 {username} 登录成功")
response = make_response(redirect('/page/home'))
return set_secure_headers(response)
record_failed_attempt(client_ip)
logging.warning(f"登录失败:用户名或密码错误")
return errorResponse('用户名或密码错误')
except Exception as e:
logging.error(f"登录过程发生错误: {e}")
return errorResponse('登录失败,请稍后重试')
@ub.route('/register', methods=['GET', 'POST'])
@limiter.limit("3 per hour")
def register():
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password')
email = sanitize_input(request.form.get('email'))
if not username or not password or not email:
return errorResponse('用户名、密码和邮箱不能为空')
# 验证用户名格式
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
# 验证邮箱格式
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return errorResponse('邮箱格式不正确')
# 验证密码强度
if not validate_password(password):
return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位')
try:
# 检查用户名和邮箱是否存在
check_sql = """
SELECT
(SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count,
(SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count
"""
result = query(check_sql, [username.lower(), email.lower()], "select")
if result[0]['username_count'] > 0:
return errorResponse('该用户名已被注册')
if result[0]['email_count'] > 0:
return errorResponse('该邮箱已被注册')
# 哈希密码
hashed_password = hash_password(password)
# 插入新用户
insert_sql = '''
INSERT INTO user(username, password, email, status, createTime, last_password_change)
VALUES(%s, %s, %s, %s, %s, %s)
'''
current_time = datetime.now()
query(insert_sql, [
username,
hashed_password,
email,
'active',
current_time,
current_time
])
# 记录注册信息
client_info = get_client_info()
register_history_sql = '''
INSERT INTO register_history
(username, register_time, ip_address, user_agent, email)
VALUES (%s, %s, %s, %s, %s)
'''
query(register_history_sql, [
username,
current_time,
client_info['ip'],
client_info['user_agent'],
email
])
logging.info(f"新用户注册成功: {username}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
@ub.route('/logout')
@login_required
def logout():
"""用户登出"""
try:
username = session.get('username')
client_info = session.get('client_info', {})
# 记录登出历史
logout_history_sql = '''
INSERT INTO logout_history
(username, logout_time, ip_address, user_agent, session_id)
VALUES (%s, %s, %s, %s, %s)
'''
query(logout_history_sql, [
username,
datetime.now(),
client_info.get('ip'),
client_info.get('user_agent'),
session.get('session_id')
])
# 删除Redis中的会话
redis_client.delete(f"session:{username}")
session.clear()
logging.info(f"用户 {username} 成功登出")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"登出过程发生错误: {e}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
import time
import hashlib
from flask import Blueprint, redirect, render_template, request, Flask, session, current_app, make_response
from datetime import datetime, timedelta
import re
from utils.query import query
from utils.errorResponse import errorResponse
from utils.logger import app_logger as logging
from functools import wraps
import secrets
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
import redis
import json
import bleach
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
import html
# 创建Argon2密码哈希器
ph = PasswordHasher()
# Redis连接
redis_client = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# 创建限流器
limiter = Limiter(
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
ub = Blueprint('user',
__name__,
url_prefix='/user',
template_folder='templates')
def sanitize_input(text):
"""清理用户输入,防止XSS攻击"""
if text is None:
return None
return bleach.clean(str(text), strip=True)
def validate_csrf_token():
"""验证CSRF令牌"""
token = request.form.get('csrf_token')
stored_token = session.get('csrf_token')
if not token or not stored_token or token != stored_token:
return False
return True
def get_client_info():
"""获取客户端信息"""
return {
'ip': request.remote_addr,
'user_agent': str(request.user_agent.string),
'platform': str(request.user_agent.platform),
'browser': str(request.user_agent.browser),
}
def is_suspicious_ip(ip):
"""检查IP是否可疑"""
key = f"login_attempts:{ip}"
attempts = redis_client.get(key)
if attempts and int(attempts) >= 5: # 5次失败尝试
return True
return False
def record_failed_attempt(ip):
"""记录失败的登录尝试"""
key = f"login_attempts:{ip}"
pipe = redis_client.pipeline()
pipe.incr(key)
pipe.expire(key, 1800) # 30分钟后重置
pipe.execute()
def clear_login_attempts(ip):
"""清除登录尝试记录"""
redis_client.delete(f"login_attempts:{ip}")
def set_secure_headers(response):
"""设置安全响应头"""
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response.headers['Content-Security-Policy'] = "default-src 'self'"
return response
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'username' not in session:
return redirect('/user/login')
# 验证会话完整性
if 'client_info' not in session or 'session_id' not in session:
session.clear()
return redirect('/user/login')
# 验证客户端信息
current_client = get_client_info()
stored_client = session['client_info']
if (current_client['ip'] != stored_client['ip'] or
current_client['user_agent'] != stored_client['user_agent']):
session.clear()
return redirect('/user/login')
# 验证会话ID
stored_session_id = redis_client.get(f"session:{session['username']}")
if not stored_session_id or stored_session_id != session['session_id']:
session.clear()
return redirect('/user/login')
return f(*args, **kwargs)
return decorated_function
def hash_password(password: str) -> str:
"""
使用Argon2id算法哈希密码
:param password: 用户输入的密码
:return: 哈希后的密码
"""
return ph.hash(password)
def verify_password(stored_hash: str, password: str) -> bool:
"""
验证密码
:param stored_hash: 存储的密码哈希
:param password: 用户输入的密码
:return: 是否匹配
"""
try:
return ph.verify(stored_hash, password)
except VerifyMismatchError:
return False
def validate_password(password: str) -> bool:
"""
验证密码强度
"""
if len(password) < 12: # 增加最小长度要求
return False
if not re.search(r"[A-Z]", password):
return False
if not re.search(r"[a-z]", password):
return False
if not re.search(r"\d", password):
return False
if not re.search(r"[!@#$%^&*(),.?\":{}|<>]", password):
return False
# 检查常见密码模式
common_patterns = ['password', '123456', 'qwerty']
if any(pattern in password.lower() for pattern in common_patterns):
return False
return True
@ub.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
"""处理用户登录请求"""
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
client_ip = request.remote_addr
if is_suspicious_ip(client_ip):
logging.warning(f"可疑IP尝试登录: {client_ip}")
return errorResponse('由于多次失败尝试,请30分钟后再试')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password') # 密码不需要sanitize
if not username or not password:
logging.warning("登录失败:用户名或密码为空")
return errorResponse('用户名和密码不能为空')
# 查询用户信息
sql = "SELECT password, status FROM user WHERE username = %s"
result = query(sql, [username], "select")
if result:
stored_password = result[0]['password']
status = result[0]['status']
if status != 'active':
logging.warning(f"已禁用的账户尝试登录: {username}")
return errorResponse('账户已被禁用')
if verify_password(stored_password, password):
session.clear()
session.regenerate()
# 生成唯一会话ID
session_id = secrets.token_hex(32)
client_info = get_client_info()
# 存储会话信息
session['username'] = username
session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
session['csrf_token'] = secrets.token_hex(32)
session['client_info'] = client_info
session['session_id'] = session_id
session.permanent = True
current_app.permanent_session_lifetime = timedelta(hours=2)
# 在Redis中存储会话ID
redis_client.setex(
f"session:{username}",
int(current_app.permanent_session_lifetime.total_seconds()),
session_id
)
clear_login_attempts(client_ip)
# 记录登录历史
login_history_sql = '''
INSERT INTO login_history
(username, login_time, ip_address, user_agent, success, attempt_count)
VALUES (%s, %s, %s, %s, %s, %s)
'''
query(login_history_sql, [
username,
datetime.now(),
client_info['ip'],
client_info['user_agent'],
True,
redis_client.get(f"login_attempts:{client_ip}") or 0
])
logging.info(f"用户 {username} 登录成功")
response = make_response(redirect('/page/home'))
return set_secure_headers(response)
record_failed_attempt(client_ip)
logging.warning(f"登录失败:用户名或密码错误")
return errorResponse('用户名或密码错误')
except Exception as e:
logging.error(f"登录过程发生错误: {e}")
return errorResponse('登录失败,请稍后重试')
@ub.route('/register', methods=['GET', 'POST'])
@limiter.limit("3 per hour")
def register():
if request.method == 'GET':
response = make_response(render_template('login_and_register.html'))
return set_secure_headers(response)
try:
if request.method == 'POST' and not validate_csrf_token():
logging.warning("CSRF验证失败")
return errorResponse('无效的请求')
username = sanitize_input(request.form.get('username'))
password = request.form.get('password')
email = sanitize_input(request.form.get('email'))
if not username or not password or not email:
return errorResponse('用户名、密码和邮箱不能为空')
# 验证用户名格式
if not re.match(r'^[a-zA-Z0-9_]{4,20}$', username):
return errorResponse('用户名只能包含字母、数字和下划线,长度4-20位')
# 验证邮箱格式
if not re.match(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', email):
return errorResponse('邮箱格式不正确')
# 验证密码强度
if not validate_password(password):
return errorResponse('密码必须包含大小写字母、数字和特殊字符,且长度至少12位')
try:
# 检查用户名和邮箱是否存在
check_sql = """
SELECT
(SELECT COUNT(*) FROM user WHERE LOWER(username) = LOWER(%s)) as username_count,
(SELECT COUNT(*) FROM user WHERE LOWER(email) = LOWER(%s)) as email_count
"""
result = query(check_sql, [username.lower(), email.lower()], "select")
if result[0]['username_count'] > 0:
return errorResponse('该用户名已被注册')
if result[0]['email_count'] > 0:
return errorResponse('该邮箱已被注册')
# 哈希密码
hashed_password = hash_password(password)
# 插入新用户
insert_sql = '''
INSERT INTO user(username, password, email, status, createTime, last_password_change)
VALUES(%s, %s, %s, %s, %s, %s)
'''
current_time = datetime.now()
query(insert_sql, [
username,
hashed_password,
email,
'active',
current_time,
current_time
])
# 记录注册信息
client_info = get_client_info()
register_history_sql = '''
INSERT INTO register_history
(username, register_time, ip_address, user_agent, email)
VALUES (%s, %s, %s, %s, %s)
'''
query(register_history_sql, [
username,
current_time,
client_info['ip'],
client_info['user_agent'],
email
])
logging.info(f"新用户注册成功: {username}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
except Exception as e:
logging.error(f"注册过程发生错误: {e}")
return errorResponse('注册失败,请稍后重试')
@ub.route('/logout')
@login_required
def logout():
"""用户登出"""
try:
username = session.get('username')
client_info = session.get('client_info', {})
# 记录登出历史
logout_history_sql = '''
INSERT INTO logout_history
(username, logout_time, ip_address, user_agent, session_id)
VALUES (%s, %s, %s, %s, %s)
'''
query(logout_history_sql, [
username,
datetime.now(),
client_info.get('ip'),
client_info.get('user_agent'),
session.get('session_id')
])
# 删除Redis中的会话
redis_client.delete(f"session:{username}")
session.clear()
logging.info(f"用户 {username} 成功登出")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)
except Exception as e:
logging.error(f"登出过程发生错误: {e}")
response = make_response(redirect('/user/login'))
return set_secure_headers(response)