Files
backend_v2/internal/model/crawl_task.go
toom1996 e930ac41c8 feat: 爬虫页取代抓取任务/入库任务两页,抓取改全量+覆盖率跳过
一、后台只留一个「爬虫」页(/admin/spider)
抓取与入库本就是同一条流水线的上下游,分两页看谁都看不全。
新页三块:状态条(在线/卡住 + 队列数 + 待入库数)、正在抓(含进度与心跳)、
最近入库结果(品牌/场次/结果/说明)。只读,取消与重试一并下线。

二、抓取一律全量
删除品牌页的场次上限下拉与 hidden maxShows=3,入队固定 0(全量)。
crawl_tasks.max_shows 列保留,历史任务照常显示。

三、跳过判定改覆盖率
- brand_runways 增加 expected_images(spider 上报的应有张数)
- /shows/covered 返回 images + expected
- 实际张数 >= 应有 - 5 才跳过;expected=0 时回落 10 张阈值
- 实体已存在时不再直接跳过:按 key 比对把缺的图补进库(repaired)
  否则「判定不完整→重爬→后端跳过」会形成死循环,带宽白花、缺图永远补不上

四、租约 900→300 秒,且心跳算存活
只看 locked_at 会把跑了几小时的全量抓取误判成卡死;
加上 progress_at 后,真卡住 5 分钟才回收。
2026-10-25 23:59:54 +08:00

68 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package model
// CrawlTask 后台派发给爬虫服务的「品牌抓取任务」。
//
// 与 ingest_jobs 的分工:
// - ingest_jobs:一场走秀的入库任务,由后端 worker 消费(下载图 / 写库 / 编码);
// - crawl_tasks:一个品牌的抓取任务,由 spider 服务领取执行(可能在另一台机器)。
//
// 两层解耦的好处:后台只需表达「我要爬哪些品牌」,不必关心 spider 在哪、跑几个。
// spider 拉不到任务就空转,任务积压也不会影响已有数据的读写。
const (
CrawlQueued = "queued" // 待领取
CrawlRunning = "running" // 已被某个 worker 领取
CrawlDone = "done" // 执行完成(不代表每场都入库成功,见 last_error)
CrawlFailed = "failed" // 执行失败(spider 进程报错 / 品牌在 Vogue 无页面等)
CrawlCanceled = "canceled" // 后台手动取消
)
// CrawlStuckTimeoutSec 是 running 任务的「静默时长」阈值:领取后这么久既没回报、
// 也没有进度心跳,才视为 worker 已崩溃(断电 / 被杀),任务可被重新领取。
//
// 300 秒而不是更长:卡住后 5 分钟内自动回收,不必人工干预。
// 判活同时看 progress_at(spider 每 60 秒打一次心跳)——只看 locked_at 会把
// 跑了十几分钟的正常任务误判成卡死(一次全量抓取可能要几个小时)。
const CrawlStuckTimeoutSec = 300
// CrawlMaxAttempts 自动重试上限;达到后置 failed,需后台手动「重试」。
const CrawlMaxAttempts = 3
// CrawlTask 品牌抓取任务。
// 时间字段与项目其它表一致,统一用 Unix 秒(uint32)。
type CrawlTask struct {
ID uint32 `gorm:"primaryKey;column:id" json:"id"`
BrandID uint32 `gorm:"column:brand_id;not null" json:"brand_id"`
// Status 见上方常量;默认 queued。
Status string `gorm:"column:status;size:16;not null;default:'queued'" json:"status"`
// Priority 越大越先被领取;用于「手动加的」优先于「批量加的」。
Priority int16 `gorm:"column:priority;not null;default:0" json:"priority"`
// MaxShows 单品牌最多抓几场(0=不限)。批量重爬历史品牌时用它控制入库量。
MaxShows int16 `gorm:"column:max_shows;not null;default:0" json:"max_shows"`
// Worker 领取者标识(主机名 + 进程号),便于排查「谁跑的」。
Worker string `gorm:"column:worker;size:64;not null;default:''" json:"worker"`
// LockedAt 领取时间,配合 CrawlStuckTimeoutSec 做租约回收。
LockedAt uint32 `gorm:"column:locked_at;not null;default:0" json:"locked_at"`
// Attempts 已被领取次数;用于自动重试与放弃判定。
Attempts int16 `gorm:"column:attempts;not null;default:0" json:"attempts"`
// ShowsSubmitted 本任务向 ingest 提交了多少场走秀(spider 回报)。
ShowsSubmitted int32 `gorm:"column:shows_submitted;not null;default:0" json:"shows_submitted"`
LastError string `gorm:"column:last_error;type:text" json:"last_error"`
// 进度:spider 每处理完一场秀上报一次。
// 一次抓取可能跑几十分钟,没有这四项后台只能看到「执行中」,分不清慢和卡死。
ProgressDone int16 `gorm:"column:progress_done;not null;default:0" json:"progress_done"`
ProgressTotal int16 `gorm:"column:progress_total;not null;default:0" json:"progress_total"`
ProgressText string `gorm:"column:progress_text;size:160;not null;default:''" json:"progress_text"`
ProgressAt uint32 `gorm:"column:progress_at;not null;default:0" json:"progress_at"`
CreatedAt uint32 `gorm:"column:created_at;not null;default:0" json:"created_at"`
UpdatedAt uint32 `gorm:"column:updated_at;not null;default:0" json:"updated_at"`
}
// TableName 指定表名。
func (CrawlTask) TableName() string { return "crawl_tasks" }
// CrawlTaskRow 是列表/领取接口用的投影行,附带品牌名(列表展示与拼 Vogue URL 都用得上)。
type CrawlTaskRow struct {
CrawlTask
BrandName string `gorm:"column:brand_name"`
}