feat: 媒体机直传 + 向量回填

- media: spider 下载图片后按内容 sha1 生成 key,经后端预签名直传 S4,上报只带 key
  (spider 不持有 AK/SK;单张失败自动回退为上报 URL)
- embed: 新增 embed 子命令,从后端领取缺向量的图片→下载→本地 CLIP 编码→回传
  (媒体拆分后后端拿不到图片字节,向量只能靠回填补齐)
- serve: 服务化常驻模式(领取 crawl 任务 → 抓取 → 回报)
- config: 新增 media.upload/concurrency、vision.endpoint/concurrency
This commit is contained in:
toom1996
2026-10-25 00:15:22 +08:00
parent 6020426815
commit 3fe0b86e11
11 changed files with 1305 additions and 2 deletions

132
README.md
View File

@ -0,0 +1,132 @@
# spider
Vogue 走秀 / The Impression 街拍采集器。抓到的元数据经 HMAC 签名上报后端 `ingest` 接口,由后端异步下载图片、写库、编码向量——**spider 不直连数据库**。
## 两种运行模式
| 模式 | 命令 | 适用 |
|---|---|---|
| 单次抓取 | `spider vogue -b <品牌id> [-s 场次上限]` | 临时补某个品牌、调试 |
| **常驻服务** | `spider serve -w 4 -i 5 -m 5` | **生产用法**:从后台领取任务 |
服务模式不再由人指定品牌:**后台决定爬谁**(`/admin/crawl`),spider 领取并执行,执行结果回报后台。
## 配置(config.yml)
```yaml
ingest:
enabled: true # serve 模式必须为 true
endpoint: "http://<后端IP>:8092/admin/internal/ingest"
secret: "<与后端 INGEST_SECRET 完全一致>"
hashid_secret: "" # 服务模式不需要(brand_uid 由后端下发)
```
> `endpoint` 末尾是 `/ingest`,客户端会自己推导出 `/crawl/tasks`(领取任务)和 `/crawl/tasks/:id/result`(回报结果)。
## 媒体机直传(把下载/上传从前端搬出来)
```yaml
media:
upload: true # 打开:spider 自己下载图并直传对象存储,后端只写库
concurrency: 6 # 下载/上传并发
```
打开后 spider 会:下载图片 → 按内容 sha1 生成 key(`runway/<sha1>.jpg`)→ 向后端
`/admin/internal/upload-url` 申请一次性 PUT 地址 → 直接上传 → 上报时只带 key。
**关键点**:
- 密钥只在后端:spider 拿的是 15 分钟有效的预签名 URL,不持有存储 AK/SK
- key 是内容寻址,重复上传幂等,重爬不产生孤儿文件
- 单张失败不影响整场:失败的图仍带原始 URL,后端会兜底下载
- 关掉 `upload` 就完全回到旧行为(后端下载),可随时回滚
**什么时候值得打开**:spider 部署在**带宽比后端机器好**的地方时收益明显;
如果两者在同一台机器上,下载带宽是同一份,拆开不会更快(只是把活儿换个进程干)。
## 服务模式怎么跑
```bash
# 编译
go build -o spider .
# 启动:4 路并发、空队列每 5 秒轮询、单品牌内部并发 5
./spider serve -w 4 -i 5 -m 5
```
| 参数 | 含义 | 建议 |
|---|---|---|
| `-w/--workers` | 同时跑几个品牌 | 带宽/CPU 够就 2~4;每个品牌内部还有 `-m` 并发 |
| `-i/--interval` | 队列空时的轮询间隔(秒) | 5~10,别设 1(会把后台接口打满) |
| `-m/--max-co` | 单品牌内部的抓取并发 | 3~8;太大会被 Vogue 限流 |
优雅退出:`Ctrl+C` 会停止领新任务,等手上的品牌跑完再退。
日志里每个任务有两行关键输出:
```
[serve] host-1234-0 领取任务 task=7 品牌=Acne Studios(uid=001AJGov) 场次上限=3
[serve] host-1234-0 任务 7 完成:status=done 提交 3 场,耗时 42s
```
## 单次模式(调试用)
```bash
./spider vogue -b 7 # 只抓 Gucci,不限场次
./spider vogue -b 7 -s 2 # 只抓最近 2 场
./spider vogue -d # debug:只打印,不上报
./spider theimpression -n 10 # 街拍,最多 10 条任务
```
`-b` 是**数据库里的数字 id**(`brands.id`),不是 hashid。
## 后台怎么操作
访问 `http://<后端>:8092/admin/crawl`(需先登录后台)。
### 1. 加任务
| 场景 | 操作 |
|---|---|
| 抓单个品牌 | 去 `/admin/brands` 找到品牌 → 点「加入抓取队列」(默认每品牌 3 场) |
| 批量抓一批 | 在 `/admin/crawl` 顶部「批量加入」:前缀 `A` + 场次上限 `3` + 数量 `50` → 就是把 A 开头的 50 个品牌各抓最近 3 场 |
| 重爬某个品牌 | 在任务列表该行点「重试」(会把状态重置为待领取) |
「场次上限」填 `0` 表示不限场次——**批量时不要填 0**,否则一个老品牌可能有上百场,一次灌进来几万张图。
### 2. 看状态
| 状态 | 含义 |
|---|---|
| 待领取 | 已入队,等 spider 来拿 |
| 执行中 | 某个 spider 已领取(列表里会显示 worker 标识) |
| 已完成 | 跑完了。`已提交` 列是本次提交了几场走秀 |
| 失败 | spider 侧进程级失败,会自动重试最多 3 次;耗尽后需手动「重试」 |
**`已提交 = 0` 不一定是错误**:Vogue 上很多品牌没有走秀页面(实测 A 开头品牌里约 75% 没有),这种情况任务是「已完成、提交 0 场」。
### 3. 取消 / 删除
- **取消**:只是不再被领取,**无法中断正在执行的品牌**(spider 手里还在跑)
- **删除**:只删任务记录,已入库的图集不受影响
### 4. 任务卡在「执行中」怎么办
正常情况会被自动回收:后台对 `执行中` 的任务有 **15 分钟租约**,超过仍未回报就自动放回「待领取」。所以 spider 断电/被杀不会导致任务永久丢失,重启后会被重新领走。
## 抓完之后
任务完成只代表**元数据已提交**。图片下载、写库、编码向量由后端 worker 异步完成,看进度去:
- `/admin/ingest-jobs` — 入库任务(pending / processing / done / failed)
- `/admin/articles` — 已入库的图集(默认 `pending` 状态,前台不可见,需在列表里通过)
## 常见问题
| 现象 | 原因 / 处理 |
|---|---|
| 启动即报「ingest 未启用」 | `config.yml` 的 `ingest.enabled` 不是 true |
| 一直「无任务」 | 后台还没加任务,或全部被其他 spider 领走了 |
| 领取报 401 | `ingest.secret` 与后端 `INGEST_SECRET` 不一致 |
| 任务反复失败 | 看后台列表的「错误」列;常见是网络不通/被 Vogue 限流 |
| 抓到了但前台看不到 | 图集是 `pending`,去 `/admin/articles` 通过审核 |