Initial commit: 百家号文章采集系统
This commit is contained in:
2078
static/css/bootstrap-icons.css
vendored
Normal file
2078
static/css/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
static/css/bootstrap-icons.min.css
vendored
Normal file
5
static/css/bootstrap-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
81
static/css/icons-local.css
Normal file
81
static/css/icons-local.css
Normal file
@@ -0,0 +1,81 @@
|
||||
/* Bootstrap Icons - 本地精简版 */
|
||||
/* 使用多CDN备份策略确保字体文件加载 */
|
||||
|
||||
@font-face {
|
||||
font-display: block;
|
||||
font-family: "bootstrap-icons";
|
||||
src:
|
||||
url("https://unpkg.com/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2") format("woff2"),
|
||||
url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2") format("woff2"),
|
||||
url("https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.woff2") format("woff2"),
|
||||
url("https://unpkg.com/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff") format("woff"),
|
||||
url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff") format("woff");
|
||||
}
|
||||
|
||||
.bi::before,
|
||||
[class^="bi-"]::before,
|
||||
[class*=" bi-"]::before {
|
||||
display: inline-block;
|
||||
font-family: bootstrap-icons !important;
|
||||
font-style: normal;
|
||||
font-weight: normal !important;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
vertical-align: -.125em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* 项目中使用的图标 */
|
||||
.bi-shield-lock-fill::before { content: "\f621"; }
|
||||
.bi-person-fill::before { content: "\f4da"; }
|
||||
.bi-key-fill::before { content: "\f494"; }
|
||||
.bi-box-arrow-in-right::before { content: "\f1cb"; }
|
||||
.bi-hourglass-split::before { content: "\f47e"; }
|
||||
.bi-file-earmark-text::before { content: "\f32a"; }
|
||||
.bi-person-circle::before { content: "\f4d6"; }
|
||||
.bi-box-arrow-right::before { content: "\f1cd"; }
|
||||
.bi-link-45deg::before { content: "\f4b3"; }
|
||||
.bi-info-circle::before { content: "\f489"; }
|
||||
.bi-cookie::before { content: "\f2a0"; }
|
||||
.bi-calendar-range::before { content: "\f1e9"; }
|
||||
.bi-shield-check::before { content: "\f61c"; }
|
||||
.bi-file-earmark-spreadsheet::before { content: "\f324"; }
|
||||
.bi-card-list::before { content: "\f1ed"; }
|
||||
.bi-download::before { content: "\f30b"; }
|
||||
.bi-plus-circle::before { content: "\f512"; }
|
||||
.bi-1-circle::before { content: "\f657"; }
|
||||
.bi-2-circle::before { content: "\f658"; }
|
||||
.bi-3-circle::before { content: "\f659"; }
|
||||
.bi-4-circle::before { content: "\f65a"; }
|
||||
.bi-5-circle::before { content: "\f65b"; }
|
||||
.bi-6-circle::before { content: "\f65c"; }
|
||||
.bi-file-arrow-down::before { content: "\f310"; }
|
||||
.bi-list-ul::before { content: "\f4bc"; }
|
||||
.bi-clock::before { content: "\f279"; }
|
||||
.bi-check-circle-fill::before { content: "\f26b"; }
|
||||
.bi-three-dots::before { content: "\f62d"; }
|
||||
.bi-book::before { content: "\f194"; }
|
||||
.bi-exclamation-triangle::before { content: "\f33c"; }
|
||||
.bi-inbox::before { content: "\f486"; }
|
||||
.bi-list-task::before { content: "\f4ba"; }
|
||||
.bi-cloud-download::before { content: "\f265"; }
|
||||
.bi-file-earmark-arrow-down::before { content: "\f30e"; }
|
||||
.bi-newspaper::before { content: "\f4ca"; }
|
||||
.bi-kanban::before { content: "\f48d"; }
|
||||
.bi-list-check::before { content: "\f4b6"; }
|
||||
.bi-collection::before { content: "\f285"; }
|
||||
.bi-arrow-repeat::before { content: "\f130"; }
|
||||
.bi-check-circle::before { content: "\f26a"; }
|
||||
.bi-x-circle::before { content: "\f623"; }
|
||||
.bi-pause-circle::before { content: "\f4c2"; }
|
||||
.bi-list::before { content: "\f4b4"; }
|
||||
.bi-eye::before { content: "\f341"; }
|
||||
.bi-trash::before { content: "\f5de"; }
|
||||
.bi-file-text::before { content: "\f32d"; }
|
||||
.bi-chevron-bar-left::before { content: "\f276"; }
|
||||
.bi-chevron-left::before { content: "\f284"; }
|
||||
.bi-chevron-right::before { content: "\f285"; }
|
||||
.bi-chevron-bar-right::before { content: "\f277"; }
|
||||
.bi-x::before { content: "\f62a"; }
|
||||
1050
static/css/style.css
Normal file
1050
static/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
2
static/js/jquery.min.js
vendored
Normal file
2
static/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
381
static/js/main.js
Normal file
381
static/js/main.js
Normal file
@@ -0,0 +1,381 @@
|
||||
// 检查jQuery是否加载
|
||||
if (typeof jQuery === 'undefined') {
|
||||
console.error('jQuery未加载,请检查网络连接');
|
||||
alert('jQuery加载失败,请刷新页面或检查网络连接');
|
||||
} else {
|
||||
$(document).ready(function() {
|
||||
let currentFilename = null;
|
||||
let progressInterval = null;
|
||||
let currentStep = 0;
|
||||
const steps = [
|
||||
{ name: '解析URL', percent: 5 },
|
||||
{ name: '启动浏览器', percent: 15 },
|
||||
{ name: '加载页面', percent: 30 },
|
||||
{ name: '滚动获取文章', percent: 70 },
|
||||
{ name: '提取数据', percent: 85 },
|
||||
{ name: '生成Excel文件', percent: 95 }
|
||||
];
|
||||
|
||||
// 登出按钮点击事件
|
||||
$('#logoutBtn').click(function() {
|
||||
if (confirm('确定要登出吗?')) {
|
||||
$.ajax({
|
||||
url: '/api/logout',
|
||||
type: 'POST',
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 加载队列统计信息并更新徽章
|
||||
function updateQueueBadge() {
|
||||
$.ajax({
|
||||
url: '/api/queue/stats',
|
||||
type: 'GET',
|
||||
success: function(response) {
|
||||
if (response.success && response.stats) {
|
||||
const pending = response.stats.pending || 0;
|
||||
const processing = response.stats.processing || 0;
|
||||
const total = pending + processing;
|
||||
|
||||
if (total > 0) {
|
||||
$('#queueBadge').text(total).show();
|
||||
} else {
|
||||
$('#queueBadge').hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
// 忽略错误,不显示徽章
|
||||
$('#queueBadge').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 初始加载徽章
|
||||
updateQueueBadge();
|
||||
|
||||
// 每10秒更新一次徽章
|
||||
setInterval(updateQueueBadge, 10000);
|
||||
|
||||
// 导出按钮点击事件
|
||||
$('#exportBtn').click(function() {
|
||||
const url = $('#authorUrl').val().trim();
|
||||
const cookies = $('#cookieInput').val().trim();
|
||||
const months = parseFloat($('#monthsSelect').val()); // 改为parseFloat支持小数
|
||||
const articlesOnly = $('#articlesOnlyCheckbox').is(':checked'); // 获取是否只爬取文章
|
||||
|
||||
// 验证URL
|
||||
if (!url) {
|
||||
showError('请输入百家号作者主页地址');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.includes('baijiahao.baidu.com') || !url.includes('app_id=')) {
|
||||
showError('URL格式不正确,请输入完整的百家号作者主页地址');
|
||||
return;
|
||||
}
|
||||
|
||||
// 开始导出(始终使用默认代理)
|
||||
startExport(url, cookies, months, articlesOnly, true, '');
|
||||
});
|
||||
|
||||
// 下载按钮点击事件
|
||||
$('#downloadBtn').click(function() {
|
||||
if (currentFilename) {
|
||||
window.location.href = `/api/download/${currentFilename}`;
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框回车事件
|
||||
$('#authorUrl').keypress(function(e) {
|
||||
if (e.which === 13) {
|
||||
$('#exportBtn').click();
|
||||
}
|
||||
});
|
||||
|
||||
// 开始导出
|
||||
function startExport(url, cookies, months, articlesOnly, useProxy, proxyApiUrl) {
|
||||
// 隐藏结果框和文章列表
|
||||
$('#resultBox').hide();
|
||||
$('#downloadBtn').hide();
|
||||
$('#articlePreview').hide();
|
||||
|
||||
// 显示加载框
|
||||
$('#loadingBox').show();
|
||||
$('#progressDetails').show();
|
||||
updateProgress('开始初始化...', 0);
|
||||
|
||||
// 启动进度模拟
|
||||
startProgressSimulation();
|
||||
|
||||
// 禁用按钮
|
||||
$('#exportBtn').prop('disabled', true);
|
||||
|
||||
// 构建请求数据(始终启用代理)
|
||||
const requestData = {
|
||||
url: url,
|
||||
cookies: cookies || '',
|
||||
months: months, // 直接使用months,不要用 || 6 因为0.33是有效值
|
||||
use_proxy: useProxy, // 始终启用代理
|
||||
proxy_api_url: proxyApiUrl || '',
|
||||
articles_only: articlesOnly // 仅爬取文章
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
$.ajax({
|
||||
url: '/api/export',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestData),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
currentFilename = response.filename;
|
||||
completeAllSteps();
|
||||
|
||||
// 显示文章列表
|
||||
if (response.articles && response.articles.length > 0) {
|
||||
displayArticles(response.articles, response.count);
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
showSuccess(`导出成功!共获取到 ${response.count} 篇文章`);
|
||||
$('#downloadBtn').show();
|
||||
}, 500);
|
||||
} else {
|
||||
stopProgressSimulation();
|
||||
showError(response.message || '导出失败');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
stopProgressSimulation();
|
||||
|
||||
// 检查是否需要登录
|
||||
if (xhr.status === 401 || (xhr.responseJSON && xhr.responseJSON.need_login)) {
|
||||
alert('登录已过期,请重新登录');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMessage = '导出失败,请检查网络连接或稍后重试';
|
||||
|
||||
if (xhr.responseJSON && xhr.responseJSON.message) {
|
||||
errorMessage = xhr.responseJSON.message;
|
||||
} else if (xhr.status === 0) {
|
||||
errorMessage = '无法连接到服务器,请确保后端服务已启动';
|
||||
} else if (xhr.status === 500) {
|
||||
errorMessage = '服务器内部错误,请稍后重试';
|
||||
}
|
||||
|
||||
showError(errorMessage);
|
||||
},
|
||||
complete: function() {
|
||||
// 隐藏加载框
|
||||
$('#loadingBox').hide();
|
||||
$('#progressDetails').hide();
|
||||
|
||||
// 启用按钮
|
||||
$('#exportBtn').prop('disabled', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新进度显示
|
||||
function updateProgress(message, percent) {
|
||||
$('#progressMessage').text(message);
|
||||
$('#progressBar').css('width', percent + '%');
|
||||
$('#progressPercent').text(Math.round(percent) + '%');
|
||||
}
|
||||
|
||||
// 启动进度模拟
|
||||
function startProgressSimulation() {
|
||||
currentStep = 0;
|
||||
$('.step-item').removeClass('active completed');
|
||||
|
||||
progressInterval = setInterval(function() {
|
||||
if (currentStep < steps.length) {
|
||||
// 标记当前步骤为活跃
|
||||
$('.step-item').eq(currentStep).addClass('active');
|
||||
|
||||
// 更新进度
|
||||
updateProgress(steps[currentStep].name + '...', steps[currentStep].percent);
|
||||
|
||||
// 模拟步骤完成
|
||||
setTimeout(function() {
|
||||
let step = currentStep;
|
||||
$('.step-item').eq(step).removeClass('active').addClass('completed');
|
||||
}, 1000);
|
||||
|
||||
currentStep++;
|
||||
}
|
||||
}, 2000); // 每2秒一个步骤
|
||||
}
|
||||
|
||||
// 停止进度模拟
|
||||
function stopProgressSimulation() {
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval);
|
||||
progressInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 完成所有步骤
|
||||
function completeAllSteps() {
|
||||
stopProgressSimulation();
|
||||
$('.step-item').removeClass('active').addClass('completed');
|
||||
updateProgress('导出完成!', 100);
|
||||
}
|
||||
|
||||
// 显示文章列表
|
||||
function displayArticles(articles, totalCount) {
|
||||
$('#articleCount').text(totalCount + '篇');
|
||||
$('#articleList').empty();
|
||||
|
||||
if (articles.length === 0) {
|
||||
$('#articleList').html(`
|
||||
<div class="article-empty">
|
||||
<i class="bi bi-inbox"></i>
|
||||
<p>暂无文章数据</p>
|
||||
</div>
|
||||
`);
|
||||
} else {
|
||||
articles.forEach(function(article, index) {
|
||||
const articleHtml = `
|
||||
<div class="article-item">
|
||||
<div class="article-item-header">
|
||||
<div class="article-number">${index + 1}</div>
|
||||
<div class="article-title">${escapeHtml(article['标题'] || '未知标题')}</div>
|
||||
</div>
|
||||
<div class="article-meta">
|
||||
<div class="article-time">
|
||||
<i class="bi bi-clock"></i>
|
||||
${escapeHtml(article['发布时间'] || '未知')}
|
||||
</div>
|
||||
<div class="article-badge">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
已提取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('#articleList').append(articleHtml);
|
||||
});
|
||||
|
||||
// 如果总数大于显示数,显示提示
|
||||
if (totalCount > articles.length) {
|
||||
$('#articleList').append(`
|
||||
<div class="article-item" style="text-align: center; color: var(--text-secondary);">
|
||||
<i class="bi bi-three-dots"></i>
|
||||
还有 ${totalCount - articles.length} 篇文章未显示,请下载Excel查看完整列表
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
$('#articlePreview').fadeIn();
|
||||
|
||||
// 滚动到文章列表
|
||||
setTimeout(function() {
|
||||
$('html, body').animate({
|
||||
scrollTop: $('#articlePreview').offset().top - 20
|
||||
}, 500);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// HTML转义
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccess(message) {
|
||||
$('#resultMessage')
|
||||
.removeClass('error')
|
||||
.addClass('success')
|
||||
.html(`✅ ${message}`);
|
||||
$('#resultBox').fadeIn();
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showError(message) {
|
||||
$('#resultMessage')
|
||||
.removeClass('success')
|
||||
.addClass('error')
|
||||
.html(`❌ ${message}`);
|
||||
$('#resultBox').fadeIn();
|
||||
$('#downloadBtn').hide();
|
||||
}
|
||||
|
||||
// 添加输入框焦点效果
|
||||
$('#authorUrl').focus(function() {
|
||||
$(this).parent().addClass('focused');
|
||||
}).blur(function() {
|
||||
$(this).parent().removeClass('focused');
|
||||
});
|
||||
|
||||
// 添加到队列按钮点击事件
|
||||
$('#addToQueueBtn').click(function() {
|
||||
const url = $('#authorUrl').val().trim();
|
||||
const months = parseFloat($('#monthsSelect').val());
|
||||
const articlesOnly = $('#articlesOnlyCheckbox').is(':checked'); // 获取是否只爬取文章
|
||||
|
||||
// 验证URL
|
||||
if (!url) {
|
||||
showError('请输入百家号作者主页地址');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url.includes('baijiahao.baidu.com') || !url.includes('app_id=')) {
|
||||
showError('URL格式不正确,请输入完整的百家号作者主页地址');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到队列(始终启用默认代理)
|
||||
$.ajax({
|
||||
url: '/api/queue/add',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
url: url,
|
||||
months: months,
|
||||
use_proxy: true, // 始终启用代理
|
||||
proxy_api_url: '', // 使用默认代理API
|
||||
articles_only: articlesOnly // 仅爬取文章
|
||||
}),
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
showSuccess('任务已添加到队列,系统将后台处理');
|
||||
// 3秒后跳转到队列页面
|
||||
setTimeout(function() {
|
||||
window.location.href = '/queue';
|
||||
}, 3000);
|
||||
} else {
|
||||
showError(response.message || '添加任务失败');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
if (xhr.status === 401) {
|
||||
alert('登录已过期,请重新登录');
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
showError('添加任务失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user