fix: 抓取前预检已入库的秀,避免重复下载(重爬 62 分钟 0 新增)
问题:图片 key 是内容 sha1,不下载就无法判断是否已存在; 后端又没存 source_url,无法做 URL 级判重。 于是每次重爬都把几百张已下过的图再下一遍 —— 线上实测 Celine 跑了 62 分钟、约 600MB,入库 0 新增。 修复: - 后端新增 GET /admin/internal/shows/covered?brand_uid= 按 (年份, 季节, 类型) 聚合已入库图片张数 - spider 抓取前预检,整场已入库(>=10 张)就跳过,一张图都不下载 - 加 --force 可强制重抓(默认跳过) - 媒体直传并发改为全进程共享信号量:此前秀级 2 × 每场 6 = 12 路 同时打 Vogue,被限流后单张从 9.8s 掉到 ~90s - 批次下载失败时打印第一条错误(此前只统计个数,查不出原因)
This commit is contained in:
@ -31,8 +31,27 @@ import (
|
||||
type Uploader struct {
|
||||
Client *ingest.Client // 复用它来签发预签名 URL(HMAC)
|
||||
Prefix string // 存储前缀:runway / street
|
||||
Concurrency int // 下载与上传的并发数
|
||||
Concurrency int // **全进程共享**的下载/上传并发数
|
||||
HTTP *http.Client
|
||||
|
||||
// sem 全进程共享的信号量,避免「每场秀各开 N 路」叠乘。
|
||||
//
|
||||
// 早先每个 UploadAll 调用各自 make 一个信号量:秀级并发 2 × 每场 6 = 12 路
|
||||
// 同时打 Vogue,被对方限流,单张从 9.8s 掉到 ~90s,还伴随 50% 失败。
|
||||
once sync.Once
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
// semaphore 惰性初始化共享信号量(并发数非法时兜底为 1,否则无缓冲 channel 会死锁)。
|
||||
func (u *Uploader) semaphore() chan struct{} {
|
||||
u.once.Do(func() {
|
||||
c := u.Concurrency
|
||||
if c <= 0 {
|
||||
c = 1
|
||||
}
|
||||
u.sem = make(chan struct{}, c)
|
||||
})
|
||||
return u.sem
|
||||
}
|
||||
|
||||
// Result 单张图的直传结果;Err 非空表示这张失败(调用方应回退为上报 URL)。
|
||||
@ -94,7 +113,19 @@ func (u *Uploader) uploadBatch(ctx context.Context, urls []string, results []Res
|
||||
}
|
||||
data[i] = body
|
||||
})
|
||||
log.Printf("[media] 下载完成 %d 张(失败 %d)", countOK(data), n-countOK(data))
|
||||
// 失败时把**第一条**错误打出来:失败通常是同一个原因(限流 / 连接重置),
|
||||
// 全打会把日志刷爆,而一条就足以定位。此前只统计个数,导致 50% 失败却查不出原因。
|
||||
if ok := countOK(data); ok < n {
|
||||
for i := 0; i < n; i++ {
|
||||
if errs[i] != nil {
|
||||
log.Printf("[media][warn] 下载失败 %s: %v", clipURL(urls[start+i]), errs[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Printf("[media] 下载完成 %d 张(失败 %d)", ok, n-ok)
|
||||
} else {
|
||||
log.Printf("[media] 下载完成 %d 张(失败 0)", ok)
|
||||
}
|
||||
|
||||
// ② 生成本批 key(内容 sha1 + 原扩展名)
|
||||
for i := 0; i < n; i++ {
|
||||
@ -164,11 +195,10 @@ func (u *Uploader) parallel(n int, fn func(i int)) {
|
||||
if n == 0 {
|
||||
return
|
||||
}
|
||||
if u.Concurrency <= 0 {
|
||||
// 兜底:并发为 0 会造成无缓冲 channel 死锁(曾把整条抓取卡住)。
|
||||
u.Concurrency = 1
|
||||
}
|
||||
sem := make(chan struct{}, u.Concurrency)
|
||||
// 共享信号量:并发上限是**整个进程**的,不是每次调用各自一份。
|
||||
// 早先每次 UploadAll 各建一个:秀级并发 2 × 每场 6 = 12 路同时打 Vogue,
|
||||
// 被对方限流后单张从 9.8s 掉到 ~90s,并伴随大量失败。
|
||||
sem := u.semaphore()
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
@ -224,6 +254,14 @@ func (u *Uploader) put(ctx context.Context, putURL, originalURL string, data []b
|
||||
return nil
|
||||
}
|
||||
|
||||
// clipURL 截短 URL 只用于日志(完整 URL 太长,会淹没真正有用的错误信息)。
|
||||
func clipURL(u string) string {
|
||||
if len(u) <= 90 {
|
||||
return u
|
||||
}
|
||||
return u[:90] + "..."
|
||||
}
|
||||
|
||||
// countOK 数一下成功下载的张数(用于日志定位卡在哪一步)。
|
||||
func countOK(data [][]byte) int {
|
||||
n := 0
|
||||
|
||||
Reference in New Issue
Block a user