一、后台只留一个「爬虫」页(/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 分钟才回收。
68 lines
4.0 KiB
Go
68 lines
4.0 KiB
Go
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"`
|
||
}
|