feat: 提交热榜评论分析工具 MVP 基线

This commit is contained in:
meijiali
2026-07-03 11:12:12 +08:00
parent 0231be6338
commit 9bad7b6e63
60 changed files with 4058 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
+35
View File
@@ -0,0 +1,35 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}热榜评论分析工具{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/app.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/app.js" defer></script>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary border-bottom">
<div class="container">
<a class="navbar-brand" href="/">热榜评论分析工具</a>
<div class="navbar-nav">
<a class="nav-link" href="/">任务列表</a>
</div>
</div>
</nav>
<main class="container py-4">
{% block breadcrumbs %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page">首页</li>
</ol>
</nav>
{% endblock %}
{% block content %}{% endblock %}
</main>
<footer class="border-top py-3">
<div class="container text-muted small">内部演示工具 | 仅供学习参考</div>
</footer>
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block title %}{{ hotspot.title }} 汇总报告 - 热榜评论分析工具{% endblock %}
{% block breadcrumbs %}
<nav aria-label="breadcrumb"><ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item"><a href="/tasks/{{ task.id }}">任务 #{{ task.id }}</a></li>
<li class="breadcrumb-item active">汇总报告</li>
</ol></nav>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">{{ hotspot.title }} 汇总报告</h1>
<div class="d-flex gap-2">
<a class="btn btn-outline-primary" href="/api/export/hotspots/{{ hotspot.id }}.md">导出 Markdown 报告</a>
<a class="btn btn-outline-secondary" href="/api/export/hotspots/{{ hotspot.id }}/comments.csv">导出热点评论 CSV</a>
</div>
</div>
{% if report %}
<section class="card"><div class="card-body">
<p class="lead">{{ report.summary }}</p>
<pre class="bg-light p-3">{{ report.markdown_content }}</pre>
</div></section>
{% else %}
<div class="alert alert-secondary">报告生成中,请稍候...</div>
{% endif %}
{% endblock %}
+66
View File
@@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block title %}任务列表 - 热榜评论分析工具{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">任务列表</h1>
<button class="btn btn-outline-secondary btn-sm" onclick="window.location.href='/'">手动刷新</button>
</div>
<section class="card mb-4">
<div class="card-header">创建抓取任务</div>
<div class="card-body">
<div id="form-error" class="alert alert-danger d-none" role="alert"></div>
<form id="task-form" onsubmit="submitTask(event)">
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label" for="platform">平台</label>
<select class="form-select" id="platform" name="platform">
<option value="xiaohongshu">小红书</option>
<option value="douyin">抖音</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="hotspot_limit">热点数</label>
<input class="form-control scale-input" id="hotspot_limit" name="hotspot_limit" type="number" min="1" max="10" value="5">
</div>
<div class="col-md-2">
<label class="form-label" for="item_limit_per_hotspot">每热点内容</label>
<input class="form-control scale-input" id="item_limit_per_hotspot" name="item_limit_per_hotspot" type="number" min="1" max="10" value="5">
</div>
<div class="col-md-2">
<label class="form-label" for="comment_limit_per_item">每内容评论</label>
<input class="form-control scale-input" id="comment_limit_per_item" name="comment_limit_per_item" type="number" min="10" max="100" value="50">
</div>
<div class="col-md-3">
<button class="btn btn-primary w-100" id="submit-btn" type="submit">开始抓取</button>
</div>
</div>
<div class="form-text text-muted mt-2" id="scale-hint">
预计最多抓取:<strong id="scale-calc">1250</strong> 条评论(实际数量可能受平台返回数量、去重、失败、限流影响)
</div>
</form>
</div>
</section>
<section class="card">
<div class="card-header">历史任务</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th>任务 ID</th>
<th>平台</th>
<th>创建时间</th>
<th>配置规模</th>
<th>进度</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="task-table-body">
{% include "partials/task_rows.html" %}
</tbody>
</table>
</div>
</section>
{% endblock %}
+34
View File
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}{{ item.title or item.source_item_id }} 详情 - 热榜评论分析工具{% endblock %}
{% block breadcrumbs %}
<nav aria-label="breadcrumb"><ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item"><a href="/tasks/{{ task.id }}">任务 #{{ task.id }}</a></li>
<li class="breadcrumb-item"><a href="/hotspots/{{ hotspot.id }}/report">热点 #{{ hotspot.rank }}{{ hotspot.title }}</a></li>
<li class="breadcrumb-item active">{{ item.title or item.source_item_id }}</li>
</ol></nav>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">{{ item.title or item.source_item_id }}</h1>
<div class="d-flex gap-2">
<a class="btn btn-outline-primary" href="/api/export/items/{{ item.id }}.md">导出 Markdown 报告</a>
<a class="btn btn-outline-secondary" href="/api/export/items/{{ item.id }}/comments.csv">导出评论 CSV</a>
</div>
</div>
{% if report %}
<section class="card mb-4"><div class="card-body">
<p class="lead">{{ report.summary }}</p>
<pre class="bg-light p-3">{{ report.markdown_content }}</pre>
</div></section>
{% endif %}
<section class="card"><div class="card-header">评论明细</div><div class="table-responsive">
<table class="table mb-0"><thead><tr><th>评论内容</th><th>情绪</th><th>标签</th><th>点赞</th></tr></thead><tbody>
{% for comment in comments %}
<tr><td>{{ comment.content }}</td><td>{{ comment.sentiment }}</td><td>{% for label in comment.labels | from_json %}<span class="badge bg-secondary me-1">{{ label }}</span>{% else %}-{% endfor %}</td><td>{{ comment.like_count or 0 }}</td></tr>
{% else %}
<tr><td colspan="4" class="text-center text-muted">暂无评论数据(该内容无评论或评论抓取为空)</td></tr>
{% endfor %}
</tbody></table>
</div></section>
{% endblock %}
+7
View File
@@ -0,0 +1,7 @@
{% macro label_tags(labels_json) %}
{% for label in labels_json | from_json %}
<span class="badge bg-secondary me-1">{{ label }}</span>
{% else %}
<span class="text-muted">-</span>
{% endfor %}
{% endmacro %}
+10
View File
@@ -0,0 +1,10 @@
{% macro sentiment_badge(sentiment) %}
{% set config = {
"positive": ("正向", "bg-success"),
"neutral": ("中性", "bg-warning text-dark"),
"negative": ("负向", "bg-danger"),
"unknown": ("未知", "bg-secondary"),
} %}
{% set label, style = config.get(sentiment, ("未知", "bg-secondary")) %}
<span class="badge {{ style }}">{{ label }}</span>
{% endmacro %}
+4
View File
@@ -0,0 +1,4 @@
{% macro status_badge(status) %}
{% set label, style = status_badge_config(status) %}
<span class="badge {{ style }}">{{ label }}</span>
{% endmacro %}
+21
View File
@@ -0,0 +1,21 @@
{% for task in tasks %}
{% set status_label, status_class = status_badge_config(task.status) %}
<tr>
<td><code>{{ task.id }}</code></td>
<td>{{ task.platform | platform_label }}</td>
<td>{{ task.created_at }}</td>
<td><small class="text-muted">热点 {{ task.hotspot_limit }} / 内容 {{ task.item_limit_per_hotspot }} / 评论 {{ task.comment_limit_per_item }}</small></td>
<td>{{ task.successful_items_count }} / {{ task.total_items_count }}</td>
<td>
<span class="badge {{ status_class }}">{{ status_label }}</span>
{% if task.error_stage or task.error_type %}
<br><small class="text-muted">{{ task.error_stage }} / {{ task.error_type }}</small>
{% endif %}
</td>
<td><a class="btn btn-sm btn-outline-primary" href="/tasks/{{ task.id }}">查看</a></td>
</tr>
{% else %}
<tr>
<td colspan="7" class="text-center text-muted py-4">还没有任何任务,请在上方创建第一个任务</td>
</tr>
{% endfor %}
+47
View File
@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}任务 #{{ task.id }} - 热榜评论分析工具{% endblock %}
{% block breadcrumbs %}
<nav aria-label="breadcrumb"><ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item active">任务 #{{ task.id }}</li>
</ol></nav>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h3 mb-0">任务 #{{ task.id }}</h1>
<button class="btn btn-outline-secondary btn-sm" onclick="window.location.href='/'">手动刷新</button>
</div>
<section class="card mb-4"><div class="card-body">
{% set label, cls = status_badge_config(task.status) %}
<p>平台:{{ task.platform | platform_label }} <span class="badge {{ cls }}">{{ label }}</span></p>
<p>成功 {{ task.successful_items_count }} / 共 {{ task.total_items_count }} 条内容</p>
{% if task.error_message %}<div class="alert alert-danger">{{ task.error_stage }} / {{ task.error_type }}{{ task.error_message }}</div>{% endif %}
</div></section>
{% if task.status == "running" and not hotspots %}
<div class="text-center text-muted py-5"><div class="spinner-border text-warning mb-3"></div><p>正在抓取热点数据,请稍候...</p></div>
{% endif %}
<div class="accordion" id="hotspot-list">
{% for hotspot in hotspots %}
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button {% if not loop.first %}collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#hotspot-{{ hotspot.id }}">
热点 #{{ hotspot.rank }}{{ hotspot.title }}
</button>
</h2>
<div id="hotspot-{{ hotspot.id }}" class="accordion-collapse collapse {% if loop.first %}show{% endif %}" data-bs-parent="#hotspot-list">
<div class="accordion-body">
<a class="btn btn-sm btn-outline-primary mb-2" href="/hotspots/{{ hotspot.id }}/report">查看热点级汇总报告</a>
<ul class="list-group">
{% for item in hotspot.content_items %}
<li class="list-group-item d-flex justify-content-between">
<span>{{ item.title or item.summary or item.source_item_id }} <small class="text-muted">{{ item.status }}</small></span>
<a class="btn btn-sm btn-outline-secondary" href="/items/{{ item.id }}">查看详情</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}