Two CLI gates needed before scaling Pro batch beyond top-5:
--skip-ext mp4,qt,mov (attachment filter)
Skips video extensions in attachment download. Phase 1 measurements
showed mp4+qt occupy ~54% of attachment storage. Entry still recorded
in metadata.json with skipped:ext:<token> so we can re-fetch later if
the policy changes. Honors both server-declared `ext` and filename
suffix, case-insensitively.
--max-source-mb N (Pro source size cap)
Trips inside the chain replay loop on encrypted-blob total. On trip:
raise ProjectOversizeError, wipe partial source/, append a row to
data/state/oshwhub_pro_oversize.jsonl. Lets us shortlist 50+ Pro
projects without one X86-board-class outlier (~500 MB) blowing the
LFS budget. Std and Pro 2.x legacy are not capped (both <2 MB in
sample).
Verified:
- cap=0 trips on first blob (1.2 MB), source/ wiped, state recorded
- cap=100 runs full ESP-VoCat (7.5 MB plain, 278 docs)
- skip-ext microtest: 8/8 cases (case-insensitive, declared/suffix
fallback, empty-token edge cases)
Plan + frozen candidate list for the next 50 projects:
- docs/plans/oshwhub_batch50.md
- data/state/oshwhub_batch50_candidates.jsonl (gitignore exception added)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
197 lines
7.6 KiB
Markdown
197 lines
7.6 KiB
Markdown
# oshwhub 扩抓批 50:执行计划
|
||
|
||
**创建**:2026-04-29
|
||
**目标**:在已落库的 15 项(5 Pro + 10 Std)基础上,再抓 50 个高质量项目
|
||
**候选清单**:`data/state/oshwhub_batch50_candidates.jsonl`(50 行)
|
||
**承接**:`docs/sources/oshwhub_listing_full.md`(全量索引盘点)
|
||
|
||
---
|
||
|
||
## 1. 目标
|
||
|
||
- 把 corpus 从 15 项扩到 65 项,覆盖 Pro + Std + Pro 3.x + Pro 2.x 各形态
|
||
- 在 LFS 1 TB 红线内,先做 ~3-4 GB 量级的中等规模验证
|
||
- 结束后用 license 白名单评估这 50 项里有多少能进 Forge 投影
|
||
- 暴露 ≥ top-5 才会出现的 corner case(例如:超大 Pro 项目、私有附件、跨账号 fork)
|
||
|
||
**显式不做**:详情页全量扫描(19h 工作量,留待后续按 A 档全池一次性做);下游 KiCad 转换(Phase 3 还在做)。
|
||
|
||
---
|
||
|
||
## 2. 候选池筛选标准
|
||
|
||
数据源:`data/state/oshwhub_listing_full.jsonl`(33,695 项全量 listing 索引)
|
||
|
||
**硬过滤**:
|
||
- `grade >= 3 AND like >= 10`(A 档)
|
||
- `uuid` 不在已抓 15 项内
|
||
|
||
**排序**:复合质量打分
|
||
```
|
||
score = like*3 + star + fork*2 + views/100 + comments_count*2 + grade*50
|
||
```
|
||
|
||
**多样性约束**:
|
||
- 单作者上限 2 个项目(避免 `course-examples` / `45coll` 等高产账号占满)
|
||
- Pro / Std 25:25 平衡(Pro 是新平台增长焦点,Std 是 7 年质量积累)
|
||
|
||
---
|
||
|
||
## 3. 选出的 50 项概况
|
||
|
||
| 维度 | 值 |
|
||
|---|---|
|
||
| 总数 | 50(25 Pro + 25 Std)|
|
||
| 唯一作者 | 43 |
|
||
| Max 单作者 | 2 |
|
||
| like 区间 | min=246 / median=554 / max=2137 |
|
||
| view 区间 | min=54k / median=117k / max=362k |
|
||
| grade 分布 | 4: 33 项, 3: 17 项 |
|
||
|
||
**Pro 头部**(前 5):HelloWord-Keyboard 2137 likes / STM32 桌面宠物 / OV-Watch 智能手表 / mini 加热台 / ESP-SparkBot
|
||
|
||
**Std 头部**(前 5):触摸调光雪花灯 1036 likes / 桌面可调电源 / 立创 EDA PCB 直尺 / 自平衡莱洛三角 / PN532 NFC
|
||
|
||
完整名单见 `data/state/oshwhub_batch50_candidates.jsonl`。
|
||
|
||
---
|
||
|
||
## 4. 执行步骤
|
||
|
||
### Step 0 — 候选池冻结 ✅
|
||
|
||
`data/state/oshwhub_batch50_candidates.jsonl` 已生成,作为本次批的"事实档案"。后续就算重排序也以这份为准。
|
||
|
||
### Step 1 — 详情页抓 license(~5 min,QPS=0.5)
|
||
|
||
只对这 50 项扫详情页,提取 license / 完整 attachments 列表 / 原始描述。
|
||
不在这一步下载附件、不下源工程,**只是获取每项的 metadata 全集**。
|
||
|
||
```bash
|
||
PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \
|
||
--uuids "$(jq -r .uuid data/state/oshwhub_batch50_candidates.jsonl | paste -sd,)" \
|
||
--no-files \
|
||
--out data/raw/oshwhub
|
||
```
|
||
|
||
> 目前 `crawl_one` 已经会落 metadata.json + description.md + cover;`--no-files` 跳过附件。
|
||
|
||
**预期产出**:50 个 `data/raw/oshwhub/<uuid>/metadata.json`,含 license 字段。
|
||
|
||
### Step 2 — license 分布盘点
|
||
|
||
写个一次性脚本读 50 份 metadata.json,统计 license 分布。**这一步只做盘点不做过滤**——按 CLAUDE.md "研究用、不分发" 原则,所有 license 都入库。下游 Forge 投影时再用白名单过滤。
|
||
|
||
```bash
|
||
uv run python - <<'EOF'
|
||
import json, glob, collections
|
||
licenses = collections.Counter()
|
||
for p in glob.glob("data/raw/oshwhub/*/metadata.json"):
|
||
m = json.load(open(p))
|
||
if m["uuid"] in batch_50_uuids:
|
||
licenses[m.get("license") or "<none>"] += 1
|
||
print(licenses)
|
||
EOF
|
||
```
|
||
|
||
预期会有大量 NC-SA / GPL(参考已抓 5 Pro:5/5 都是 GPL/NC-SA)。
|
||
|
||
### Step 3 — Pro 子集预检 + 抓源(~30-60 min)
|
||
|
||
25 个 Pro 项目里要先判 3.x vs 2.x(`branch_uuid` 是否 null),然后对应走 modern / legacy 路径:
|
||
|
||
```bash
|
||
PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \
|
||
--backfill-pro-source \
|
||
--uuids "$(jq -r 'select(.origin=="pro") | .uuid' \
|
||
data/state/oshwhub_batch50_candidates.jsonl | paste -sd,)" \
|
||
--pro-cookie ~/.secrets/pro-lceda-cookie-header.txt
|
||
```
|
||
|
||
**时间预算**(已应用 chain replay sleep 优化,commit `1e06ba6`):
|
||
- 小型项目(chain ~10):~30s 各
|
||
- 中型(chain ~30-50):~1-2 min 各
|
||
- 大型(chain ~100+):~3-5 min 各
|
||
- 超大(X86 量级 chain ~700):~3-5 min 各(CDN 部分降到 0.2s/req)
|
||
|
||
按已抓 5 项观察,预期 25 项里:
|
||
- ~22 个 Pro 3.x(chain replay)
|
||
- ~3 个 Pro 2.x(plaintext dataStr)
|
||
|
||
### Step 4 — Std 子集抓源(~30 min)
|
||
|
||
```bash
|
||
PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \
|
||
--backfill-source \
|
||
--uuids "$(jq -r 'select(.origin=="std") | .uuid' \
|
||
data/state/oshwhub_batch50_candidates.jsonl | paste -sd,)"
|
||
```
|
||
|
||
每项 ~30s(QPS=0.2 / 5s sleep × 几个 API 调用),25 项 ≈ 12-15 min。
|
||
|
||
### Step 5 — 附件下载(按需,可选)
|
||
|
||
附件(Gerber / STEP / PDF / 视频)默认存 LFS,但视频占大头(之前 Phase 1 实测 mp4+qt 占 54%)。
|
||
**建议加 `--skip-ext mp4,qt,mov`** 节省 30-50% 体积。
|
||
|
||
```bash
|
||
PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \
|
||
--uuids "$(jq -r .uuid data/state/oshwhub_batch50_candidates.jsonl | paste -sd,)" \
|
||
--out data/raw/oshwhub
|
||
# 注:当前 crawler 没有 --skip-ext 选项;要加得改 crawl_one;如果偷懒就先全抓再人工删
|
||
```
|
||
|
||
> 这一步没那么紧迫,可以挪到 Step 3/4 完成后单独跑。
|
||
|
||
### Step 6 — 验收 + 记录
|
||
|
||
跑 `scripts/build_index.py` 重建 `projects.md`,更新 `log.md`,把统计信息写到 `docs/plans/oshwhub_batch50.md` 末尾"实施结果"段。
|
||
|
||
---
|
||
|
||
## 5. 资源预算
|
||
|
||
| 项 | 估算 | 备注 |
|
||
|---|---:|---|
|
||
| API 调用 | ~600 次 | 50 详情 + 25×3 Pro meta + 25 Std API + chain blobs |
|
||
| 时间 | **~1.5-2 小时** | 远低于之前估算的 19h(因为只 50 项 + sleep 优化)|
|
||
| 存储(源工程)| ~1.5 GB | Pro 平均 30 MB、Std 平均 15 MB |
|
||
| 存储(附件,估)| 2-3 GB | 含视频;不含视频估 1 GB |
|
||
| **总 LFS 增量** | **~3-4 GB** | 远低于现有 LFS 余量 |
|
||
| 网络下行 | ~4 GB | 大部分走 LFS |
|
||
|
||
距离 200 GB"对象存储迁移决策点"还很远。
|
||
|
||
---
|
||
|
||
## 6. 风险与预案
|
||
|
||
| 风险 | 概率 | 影响 | 预案 |
|
||
|---|---|---|---|
|
||
| Pro cookie 过期半路 | 中 | 卡住 Pro 抓取 | 重抓 cookie;爬虫已有 `oshwhub_pro_failed.jsonl` 重试机制(**未实现**,现在失败只 stderr)|
|
||
| 单个 Pro 项目超大(X86 量级 ~500 MB) | 中 | LFS 单项膨胀 | 设置 size cap,超过 200 MB 跳过 + 记 state |
|
||
| license 全是 NC-SA → 下游 Forge 投影门槛低 | 高 | Forge 阶段才发现 | 提前在 Step 2 盘点;**本批不做过滤**,只入库 |
|
||
| 某个项目附件含 mp4 视频 → 附件总量爆炸 | 中 | 多消耗 1-2 GB LFS | 加 `--skip-ext mp4,qt,mov` 选项(需小改 crawler)|
|
||
| 抓 50 项过程中触发反爬 / 限流 | 低 | 中途中断 | 已有 5s/req QPS;如果触发,加 jitter;从中断点续 |
|
||
|
||
---
|
||
|
||
## 7. 验收标准
|
||
|
||
- [ ] `data/raw/oshwhub/` 下新增 ≥ 45 个项目目录(允许 ≤ 5 项失败)
|
||
- [ ] 每个新增项目的 `metadata.json` 含 `license`、`source_format`、`source_path`
|
||
- [ ] Pro 25 项里 3.x / 2.x 分类正确(看 `source_format` 字段)
|
||
- [ ] LFS 增量 ≤ 5 GB
|
||
- [ ] `projects.md` 自动重建后包含新 50 项
|
||
- [ ] log.md 顶部一条总结性日志
|
||
|
||
---
|
||
|
||
## 8. 后续
|
||
|
||
成功结束后的下一步候选:
|
||
1. **license 详情扫描全 A 档** —— 把 license 信息覆盖到 2,806 项 A 档候选池,为下一次扩抓提供过滤条件
|
||
2. **扩抓批 200**(按 license 白名单 + 同样多样性策略)
|
||
3. **Pro 工程二进制图(IMAGE/)补抓** —— 当前的真实数据 gap,要不要补取决于下游需求
|
||
4. **风控压测** —— 在某一批做并发提速试验,找 oshwhub / pro.lceda.cn 的限流真上限
|