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

292
templates/index.html Normal file
View File

@@ -0,0 +1,292 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文章导出 - 百家号管理系统</title>
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="/static/css/icons-local.css">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<!-- 主布局容器 -->
<div class="app-container">
<!-- 左侧菜单栏 -->
<aside class="sidebar">
<!-- Logo区域 -->
<div class="sidebar-logo">
<div class="sidebar-logo-icon">
<i class="bi bi-cloud-download"></i>
</div>
<div class="sidebar-logo-text">
<div class="sidebar-logo-title">百家号工具</div>
<div class="sidebar-logo-subtitle">文章导出系统</div>
</div>
</div>
<!-- 菜单导航 -->
<nav class="sidebar-nav">
<ul class="nav-menu">
<li class="nav-item">
<a href="/" class="nav-link active">
<i class="bi bi-download"></i>
<span>文章导出</span>
</a>
</li>
<li class="nav-item">
<a href="/queue" class="nav-link">
<i class="bi bi-list-task"></i>
<span>任务队列</span>
<span class="nav-badge" id="queueBadge" style="display: none;">0</span>
</a>
</li>
</ul>
</nav>
<!-- 用户信息区域 -->
<div class="sidebar-user">
<div class="user-info-card">
<div class="user-avatar">
<i class="bi bi-person-fill"></i>
</div>
<div class="user-details">
<div class="user-name">{{ username }}</div>
<div class="user-role">管理员</div>
</div>
<button class="logout-btn" id="logoutBtn" title="登出">
<i class="bi bi-box-arrow-right"></i>
</button>
</div>
</div>
</aside>
<!-- 主内容区域 -->
<main class="main-content">
<!-- 顶部导航栏 -->
<header class="top-navbar">
<h1 class="navbar-title">
<i class="bi bi-file-earmark-arrow-down"></i>
文章导出
</h1>
</header>
<!-- 内容区域 -->
<div class="content-area">
<!-- 页面头部 -->
<div class="page-header">
<h2 class="page-title">
<i class="bi bi-newspaper"></i>
百家号文章导出
</h2>
<p class="page-description">输入百家号作者主页链接,导出指定时间范围内的文章信息</p>
</div>
<!-- 输入表单卡片 -->
<div class="card">
<div class="card-body">
<div class="form-group">
<label class="form-label">
<i class="bi bi-link-45deg label-icon"></i>
百家号作者主页地址
</label>
<input
type="text"
id="authorUrl"
class="form-input"
value="https://baijiahao.baidu.com/u?app_id=1700253559210167"
placeholder="例如https://baijiahao.baidu.com/u?app_id=1700253559210167"
>
<div class="input-hint">
<i class="bi bi-info-circle"></i>
请输入完整的百家号作者主页URL地址
</div>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-cookie label-icon"></i>
Cookie (可选,如果出现登录页面请填写)
</label>
<textarea
id="cookieInput"
class="form-input form-textarea"
rows="3"
placeholder="如果需要登录请粘贴浏览器中的Cookie"
></textarea>
<div class="input-hint">
<i class="bi bi-info-circle"></i>
获取方法:打开百家号 → F12开发者工具 → Network → 刷新页面 → 点击任意请求 → 复制Request Headers中的Cookie
</div>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-calendar-range label-icon"></i>
时间范围
</label>
<select id="monthsSelect" class="form-select">
<option value="0.15" selected>近5天</option>
<option value="0.33">近10天</option>
<option value="1">近1个月</option>
<option value="6">近6个月</option>
<option value="12">近12个月</option>
</select>
</div>
<div class="form-group">
<label class="form-label">
<i class="bi bi-filter label-icon"></i>
内容过滤
</label>
<div class="checkbox-group">
<label class="checkbox-label">
<input type="checkbox" id="articlesOnlyCheckbox" checked>
<span class="checkbox-text">仅爬取文章(跳过视频内容)</span>
</label>
</div>
<div class="input-hint">
<i class="bi bi-info-circle"></i>
勾选后将过滤掉所有视频类型的内容,只保留文章
</div>
</div>
</div>
</div>
<!-- 信息卡片 -->
<div class="card info-card">
<div class="card-body">
<div class="info-grid">
<div class="info-item">
<i class="bi bi-file-earmark-spreadsheet info-icon"></i>
<div class="info-content">
<div class="info-label">导出格式</div>
<div class="info-value">Excel (.xlsx)</div>
</div>
</div>
<div class="info-item">
<i class="bi bi-card-list info-icon"></i>
<div class="info-content">
<div class="info-label">导出内容</div>
<div class="info-value">文章标题、发布时间</div>
</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div style="display: flex; gap: 10px;">
<button id="exportBtn" class="btn btn-primary" style="flex: 1;">
<i class="bi bi-download"></i>
即时导出
</button>
<button id="addToQueueBtn" class="btn btn-secondary" style="flex: 1;">
<i class="bi bi-plus-circle"></i>
添加到队列
</button>
</div>
<!-- 加载状态 -->
<div id="loadingBox" class="loading-box" style="display: none;">
<div class="loading-spinner"></div>
<p class="loading-text">正在获取文章数据,请稍候...</p>
<!-- 进度详情 -->
<div id="progressDetails" class="progress-details" style="display: none;">
<div class="progress-bar-container">
<div id="progressBar" class="progress-bar"></div>
</div>
<div class="progress-info">
<span id="progressMessage" class="progress-message">初始化...</span>
<span id="progressPercent" class="progress-percent">0%</span>
</div>
<div id="progressSteps" class="progress-steps">
<div class="step-item">
<i class="bi bi-1-circle"></i>
<span>解析URL</span>
</div>
<div class="step-item">
<i class="bi bi-2-circle"></i>
<span>启动浏览器</span>
</div>
<div class="step-item">
<i class="bi bi-3-circle"></i>
<span>加载页面</span>
</div>
<div class="step-item">
<i class="bi bi-4-circle"></i>
<span>滚动获取</span>
</div>
<div class="step-item">
<i class="bi bi-5-circle"></i>
<span>提取数据</span>
</div>
<div class="step-item">
<i class="bi bi-6-circle"></i>
<span>生成Excel</span>
</div>
</div>
</div>
</div>
<!-- 结果显示 -->
<div id="resultBox" class="result-box" style="display: none;">
<div id="resultMessage" class="result-message"></div>
<button id="downloadBtn" class="btn btn-success" style="display: none;">
<i class="bi bi-file-arrow-down"></i>
下载Excel文件
</button>
</div>
<!-- 文章预览列表 -->
<div id="articlePreview" class="card article-preview" style="display: none;">
<div class="card-header">
<i class="bi bi-list-ul"></i>
已提取文章列表
<span id="articleCount" class="article-count">0篇</span>
</div>
<div class="card-body">
<div id="articleList" class="article-list"></div>
</div>
</div>
<!-- 使用说明卡片 -->
<div class="card">
<div class="card-header">
<i class="bi bi-info-circle"></i>
使用说明
</div>
<div class="card-body">
<div class="info-steps">
<div class="info-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>复制URL</h4>
<p>在浏览器中打开百家号作者主页复制完整的URL地址</p>
</div>
</div>
<div class="info-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>配置参数</h4>
<p>选择时间范围和代理设置如需要可填写Cookie</p>
</div>
</div>
<div class="info-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>导出文章</h4>
<p>点击“即时导出”或“添加到队列”,等待处理完成</p>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- jQuery - 使用国内CDN -->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/main.js"></script>
</body>
</html>

382
templates/login.html Normal file
View File

@@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 百家号文章导出工具</title>
<!-- Bootstrap Icons - 使用本地CSS + CDN字体 -->
<link rel="stylesheet" href="/static/css/icons-local.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #0052D9;
--primary-hover: #003DA6;
--bg-gradient-start: #f5f7fa;
--bg-gradient-end: #e8eef5;
--card-bg: #ffffff;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
--success-color: #10b981;
--error-color: #ef4444;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
width: 100%;
max-width: 420px;
}
.login-card {
background: var(--card-bg);
border-radius: 16px;
box-shadow: var(--shadow);
padding: 40px;
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-icon {
width: 64px;
height: 64px;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 16px;
font-size: 32px;
color: white;
}
.login-title {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
.login-subtitle {
font-size: 14px;
color: var(--text-secondary);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 8px;
}
.input-wrapper {
position: relative;
}
.input-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
font-size: 18px;
}
.form-input {
width: 100%;
padding: 12px 12px 12px 40px;
font-size: 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
outline: none;
transition: all 0.3s;
}
.form-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 82, 217, 0.1);
}
.btn-login {
width: 100%;
padding: 14px;
font-size: 16px;
font-weight: 500;
color: white;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
margin-top: 24px;
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 82, 217, 0.3);
}
.btn-login:active {
transform: translateY(0);
}
.btn-login:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.alert {
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
margin-bottom: 20px;
display: none;
animation: slideDown 0.3s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #6ee7b7;
}
.alert-error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fca5a5;
}
.divider {
text-align: center;
margin: 24px 0;
position: relative;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: var(--border-color);
}
.divider-text {
display: inline-block;
background: var(--card-bg);
padding: 0 16px;
color: var(--text-secondary);
font-size: 14px;
position: relative;
}
.info-box {
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: 8px;
padding: 12px 16px;
margin-top: 20px;
}
.info-box p {
margin: 4px 0;
font-size: 13px;
color: #1e40af;
}
.info-box strong {
font-weight: 600;
}
.footer {
text-align: center;
margin-top: 24px;
color: var(--text-secondary);
font-size: 14px;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-card">
<div class="login-header">
<div class="login-icon">
<i class="bi bi-shield-lock-fill"></i>
</div>
<h1 class="login-title">欢迎登录</h1>
<p class="login-subtitle">百家号文章导出工具</p>
</div>
<div id="alertBox" class="alert"></div>
<form id="loginForm">
<div class="form-group">
<label class="form-label">用户名</label>
<div class="input-wrapper">
<i class="bi bi-person-fill input-icon"></i>
<input
type="text"
id="username"
class="form-input"
placeholder="请输入用户名"
autocomplete="username"
required
>
</div>
</div>
<div class="form-group">
<label class="form-label">密码</label>
<div class="input-wrapper">
<i class="bi bi-key-fill input-icon"></i>
<input
type="password"
id="password"
class="form-input"
placeholder="请输入密码"
autocomplete="current-password"
required
>
</div>
</div>
<button type="submit" id="loginBtn" class="btn-login">
<i class="bi bi-box-arrow-in-right"></i>
登录
</button>
</form>
</div>
<div class="footer">
<p>© 2025 百家号文章导出工具 | 仅供学习交流使用</p>
</div>
</div>
<script src="/static/js/jquery.min.js"></script>
<script>
// 检查jQuery是否加载
if (typeof jQuery === 'undefined') {
console.error('jQuery未加载请检查网络连接');
alert('jQuery加载失败请刷新页面或检查网络连接');
} else {
$(document).ready(function() {
// 登录表单提交
$('#loginForm').submit(function(e) {
e.preventDefault();
const username = $('#username').val().trim();
const password = $('#password').val().trim();
if (!username || !password) {
showAlert('请输入用户名和密码', 'error');
return;
}
// 禁用按钮
$('#loginBtn').prop('disabled', true).html('<i class="bi bi-hourglass-split"></i> 登录中...');
// 发送登录请求
$.ajax({
url: '/api/login',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
username: username,
password: password
}),
success: function(response) {
if (response.success) {
showAlert('登录成功,正在跳转...', 'success');
setTimeout(function() {
window.location.href = '/';
}, 1000);
} else {
showAlert(response.message || '登录失败', 'error');
$('#loginBtn').prop('disabled', false).html('<i class="bi bi-box-arrow-in-right"></i> 登录');
}
},
error: function(xhr, status, error) {
let errorMessage = '登录失败,请稍后重试';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
}
showAlert(errorMessage, 'error');
$('#loginBtn').prop('disabled', false).html('<i class="bi bi-box-arrow-in-right"></i> 登录');
}
});
});
// 显示提示信息
function showAlert(message, type) {
const alertBox = $('#alertBox');
alertBox.removeClass('alert-success alert-error');
alertBox.addClass('alert-' + type);
alertBox.text(message);
alertBox.show();
// 3秒后自动隐藏
if (type === 'error') {
setTimeout(function() {
alertBox.fadeOut();
}, 3000);
}
}
// 回车登录
$('#username, #password').keypress(function(e) {
if (e.which === 13) {
$('#loginForm').submit();
}
});
});
}
</script>
</body>
</html>

1508
templates/queue.html Normal file

File diff suppressed because it is too large Load Diff