Initial commit: 百家号文章采集系统

This commit is contained in:
sjk
2025-12-19 22:48:58 +08:00
commit 0d5bbb1864
37 changed files with 11774 additions and 0 deletions

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

File diff suppressed because one or more lines are too long

View 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

File diff suppressed because it is too large Load Diff

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
View 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
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('添加任务失败,请稍后重试');
}
});
});
});
}