feat: 抓取进度可见 + 已入库秀预检(避免重复下载)
一、防重复下载 图片 key 是内容 sha1,不下载无法判断是否已存在;又没存 source_url, 于是每次重爬都把几百张已下过的图再下一遍(线上实测 Celine 62 分钟、0 新增)。 新增 GET /admin/internal/shows/covered?brand_uid=,按 (年份,季节,类型) 聚合已入库图片数,供 spider 抓前整场跳过。 二、进度可见 一次抓取可能跑几十分钟,此前只有 running/done,看不出「在干什么」, 也分不清「慢」和「卡死」。 - crawl_tasks 增加 progress_done/total/text/at - POST /admin/internal/crawl/tasks/:id/progress(spider 每场 + 每批图片上报) - GET /admin/crawl/live(页面 4 秒轮询,局部刷新状态/进度/已提交) - progress_at 超过 10 分钟没更新标黄「可能卡住」 - 渲染测试守住进度列与轮询锚点
This commit is contained in:
@ -16,15 +16,16 @@
|
||||
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>ID</th><th>品牌</th><th>状态</th><th>场次上限</th><th>已提交</th><th>尝试</th><th>Worker</th><th>更新时间</th><th>错误</th><th>操作</th>
|
||||
<th>ID</th><th>品牌</th><th>状态</th><th>进度</th><th>场次上限</th><th>已提交</th><th>尝试</th><th>Worker</th><th>更新时间</th><th>错误</th><th>操作</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{{range .Rows}}<tr>
|
||||
<td><code>{{.ID}}</code></td>
|
||||
<td><a class="view" href="/admin/brands/{{.BrandUID}}">{{.BrandName}}</a></td>
|
||||
<td><span class="badge">{{.StatusLabel}}</span></td>
|
||||
<td><span class="badge" data-live-status="{{.ID}}">{{.StatusLabel}}</span></td>
|
||||
<td class="progress{{if .Stale}} stale{{end}}" data-live-progress="{{.ID}}">{{if .ProgressLabel}}{{.ProgressLabel}}{{else}}—{{end}}</td>
|
||||
<td>{{if eq .MaxShows 0}}<strong>全量</strong>{{else}}{{.MaxShows}}{{end}}</td>
|
||||
<td>{{.Shows}}</td>
|
||||
<td data-live-shows="{{.ID}}">{{.Shows}}</td>
|
||||
<td>{{.Attempts}}</td>
|
||||
<td class="muted">{{if .Worker}}{{.Worker}}{{else}}—{{end}}</td>
|
||||
<td class="muted">{{.UpdatedAt}}</td>
|
||||
@ -41,4 +42,42 @@
|
||||
</table>
|
||||
{{template "pager" .Pager}}
|
||||
</div>
|
||||
<script>
|
||||
// 抓取进度轮询:每 4 秒只刷新「状态 / 进度 / 已提交」三列。
|
||||
//
|
||||
// 为什么不用 WebSocket:进度是每场秀一次的低频事件,轮询足够;
|
||||
// 而且局部更新避免了整页刷新——整页刷会打断滚动位置和正在填的表单。
|
||||
// 为什么不用整页 meta refresh:同上,且会把「失败原因」的展开状态一起冲掉。
|
||||
(function () {
|
||||
var cells = document.querySelectorAll('[data-live-progress]');
|
||||
if (!cells.length) return;
|
||||
|
||||
function paint(items) {
|
||||
var byId = {};
|
||||
for (var i = 0; i < items.length; i++) byId[String(items[i].id)] = items[i];
|
||||
for (var j = 0; j < cells.length; j++) {
|
||||
var cell = cells[j];
|
||||
var id = cell.getAttribute('data-live-progress');
|
||||
var it = byId[id];
|
||||
if (!it) continue;
|
||||
cell.textContent = it.progress || '—';
|
||||
if (it.stale) cell.classList.add('stale'); else cell.classList.remove('stale');
|
||||
var st = document.querySelector('[data-live-status="' + id + '"]');
|
||||
if (st) st.textContent = it.status_label;
|
||||
var sh = document.querySelector('[data-live-shows="' + id + '"]');
|
||||
if (sh) sh.textContent = it.shows;
|
||||
}
|
||||
}
|
||||
|
||||
function tick() {
|
||||
fetch('/admin/crawl/live', { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (d) { if (d && d.items) paint(d.items); })
|
||||
.catch(function () { /* 网络抖动忽略,下一轮再拉 */ });
|
||||
}
|
||||
|
||||
setInterval(tick, 4000);
|
||||
tick();
|
||||
})();
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user