# oshwhub 扩抓批 200:执行计划 **创建**:2026-04-29 **承接**:`docs/plans/oshwhub_batch50.md`(已 100% 完结) **目标**:从 65 项扩到 265 项 — 验证流水线在中等规模的稳定性,开始覆盖 A 档第二梯队 **候选清单**:`data/state/oshwhub_batch200_candidates.jsonl`(200 行) **主执行机**:dev1(广州,~30× 网络优势已实测) --- ## 1. batch-50 经验回放(影响这一批的设计) | 教训 | 这一批的对策 | |---|---| | dev1 Guangzhou 网络是关键加速器 | 全部步骤都在 dev1 跑,SG 这边只是同步消费 | | Pro 2.x 占高质量 Pro 池 80% | Pro 2.x parser 必须稳;deprecated-board 已修 | | `git push` 单次 70 MB 在 dev1 → gitea 链路 6.5% 丢包 → 360 KB/s | dev1 origin 已切 SSH 协议(commit `8220c99`),后续 push 流畅 | | `--max-source-mb 200` 没触发(25 Pro 最大才 9 MB)| 保留 cap 防 X86-class outlier,但预期触发率极低 | | `git push 2>&1 \| tail -5` 永远 exit 0 吞掉错误 | 启用 `set -o pipefail` 或检查 PIPESTATUS | | Pro 2.x boards[] 可能含废弃 sch/pcb 返 401 | 已修:`ticket.schematics`/`pcbs` 求交集(commit `c3cac97`)| | 详情 HTML 抓取是 server-render bound(~50ms / 项)| 不优化;本批 200 项 ~10s 即可 | --- ## 2. 候选池筛选标准(与 batch-50 一致) 数据源:`data/state/oshwhub_listing_full.jsonl`(33,695 项全量索引) **硬过滤**: - `grade >= 3 AND like >= 10`(A 档) - `uuid` 不在已抓 65 项内(剩余 A 档 = 2,741 项 Pro 1326 + Std 1415) **排序**:`like*3 + star + fork*2 + views/100 + comments*2 + grade*50` **多样性约束**: - 单作者全局上限 2 - Pro / Std 100:100 均衡(同 batch-50 思路) --- ## 3. 选出的 200 项概况 | 维度 | 值 | |---|---| | 总数 | 200(100 Pro + 100 Std) | | 唯一作者 | 173(max 单作者 2 项) | | like 区间 | min=22 / median=258 / max=624 | | grade 分布 | 4: 118 项, 3: 82 项 | > like 中位数 258 比 batch-50 的 554 低——因为 batch-50 已经吃掉了 A 档头部。本批进入 A 档第二梯队,整体仍合格。 完整名单见 `data/state/oshwhub_batch200_candidates.jsonl`。 --- ## 4. 执行步骤 > 全部在 dev1 跑。SG 端只 `git pull` 消费成果。 ### Step 0 — 候选池冻结 ✅ `oshwhub_batch200_candidates.jsonl` 已生成。 ### Step 1 — 详情页扫 license(~3 min @ concurrency=5) ```bash ssh dev1 'export PATH=$HOME/.local/bin:$PATH && \ cd ~/repo/FacereDataset && \ PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \ --from-jsonl data/state/oshwhub_batch200_candidates.jsonl \ --no-files --no-cover --concurrency 5 \ --out data/raw/oshwhub' ``` 预期:200 项 / ~3-5 分钟(dev1 详情页 p90=73ms × 200/5 并发 ≈ 3s 网络 + server render 时间 ≈ 几分钟)。 ### Step 2 — license 分布盘点(无过滤入库) ```bash ssh dev1 'cd ~/repo/FacereDataset && uv run python - <"] += 1 for lic, n in licenses.most_common(): print(f" {n:>4} {lic}") EOF' ``` 按 batch-50 数据外推:~64% GPL 3.0、~16% Forge-friendly 自由、~16% NC variants。 ### Step 3 — Pro 100 项 backfill source(~10-15 min) ```bash ssh dev1 'export PATH=$HOME/.local/bin:$PATH && \ cd ~/repo/FacereDataset && \ PRO_UUIDS=$(jq -r "select(.origin==\"pro\") | .uuid" \ data/state/oshwhub_batch200_candidates.jsonl | paste -sd,) && \ PYTHONUNBUFFERED=1 uv run python -u -m crawlers.oshwhub \ --backfill-pro-source \ --uuids "$PRO_UUIDS" \ --max-source-mb 200' ``` 预期分布(按 batch-50 的 Pro 比例外推): - Pro 2.x legacy ~80 项(chain 平均 < 30),每项 ~30s → ~40 分钟…… > 等等,得拆估算:dev1 端 Pro chain 是 0.5s sleep × N + CDN 0.2s × M。粗算单项 average = 0.5×5 + 0.2×30 = 8.5s。100 项串行 = ~14 min。 > 如果想再快可以加 backfill 路径的 concurrency(目前没加,是个小改动)。先不优化,看实测。 ### Step 4 — Std 100 项 backfill source(~5-10 min) ```bash STD_UUIDS=$(jq -r 'select(.origin=="std") | .uuid' \ data/state/oshwhub_batch200_candidates.jsonl | paste -sd,) ssh dev1 ... --backfill-source --uuids "$STD_UUIDS" ``` 每项 ~5-10s(Std 比 Pro 快,doc 数少),100 项 ~10 min。 ### Step 5 — 附件下载(**可选 / 推迟**) batch-50 的 50 项声明附件 2.36 GB;外推 batch-200 ~9-10 GB。Gitea LFS 当前用量 < 5 GB,加 10 GB 还在舒适区。 但下载时间是关键因素: - 200 项 × 平均 50 MB 附件 / 5 MB/s(dev1 → image.lceda.cn 估算)= ~30 分钟 - 加 `--skip-ext mp4,qt,mov` 可砍 30-50% 体积 **建议**:先做 Step 1-4 拿到 license + source;附件单独评估后再决定。如果只为 EPRO2 → KiCad 训练,附件不需要。 ### Step 6 — 验收 + push ```bash ssh dev1 'cd ~/repo/FacereDataset && \ uv run python scripts/build_index.py && \ git add data/raw/oshwhub/ projects.md && \ git commit -m "batch-200: ..." && \ git push origin main' ``` --- ## 5. 资源预算 | 项 | 估算 | 备注 | |---|---:|---| | API 调用 | ~2,500 次 | 200 详情 + 100 Pro × 5 calls + 100 Std × 5 calls + chain blob 几百次 | | dev1 walltime(Step 1-4)| **~25-35 分钟** | 单 IP 串行;加 backfill concurrency 可压到 10-15 min | | 源工程体积 | **~2.5 GB** | batch-50 实测 12 MB/项 × 200 = 2.4 GB | | Gitea push(dev1 SSH 链路)| ~3-5 min | 同 batch-50 量级,SSH transport 健康 | | 附件(如做 Step 5)| +10 GB / +30 min | 可砍 mp4 节流 | | **LFS 总增量(不含附件)** | **~2.5 GB** | 当前 < 5 GB → batch-200 后 ~7-8 GB | | **LFS 总增量(含附件)** | **~12 GB** | → 批后 ~17 GB;离 200 GB 迁移线还远 | ## 6. 风险与预案 | 风险 | 概率 | 影响 | 预案 | |---|---|---|---| | Pro cookie 过期半路 | 低 | 100 项 Pro 可能多个失败 | 已在 dev1 + SG 同 cookie;过期表现是连续 401,监控前 5 个失败即停 | | 单 Pro 项目超 200 MB | 低 | size cap 触发跳过 | `--max-source-mb 200` 拦截,记 oversize state | | Pro 2.x 又出现新边缘 case(非 deprecated-board)| 中 | 单项 fail | 当前 fix 处理 deprecated 类;新 case 用 try/except 单点收敛后 retry | | 附件 mp4 视频拉爆磁盘 | 中(如做 Step 5)| 多消耗 5+ GB | `--skip-ext mp4,qt,mov` | | dev1 → gitea 链路波动期间 push | 中 | 需手动重试 | 已切 SSH,BBR 已开;最差 retry | | 触发 oshwhub 限流 | 低 | 整批中断 | 现有 sleep 已在实测水位;监控 200/non-200 比例,连续 3 个 5xx 即停 | ## 7. 验收标准 - [ ] `data/raw/oshwhub/` 新增 ≥ 195 个项目目录(允许 ≤ 5 项 fail) - [ ] 每个新增项目的 `metadata.json` 含 `license` + `source_format` + `source_path` - [ ] license 分布盘点写入 log - [ ] `projects.md` 自动重建后包含全 265 项 - [ ] LFS 增量 < 5 GB(不含附件)/ < 15 GB(含附件) - [ ] log.md 顶部一条总结性日志 - [ ] dev1 push 整批 ≤ 10 min wall ## 8. 后续候选 - **batch-500** —— 把 A 档(2,741 项)一次性吃完(剩 ~2,540) - **B 档(grade≥2 & like≥5)扩量** —— 总池子 ~6,243 项 - **附件细分策略** —— 按 ext 分类 LFS(Gerber/STEP 留、视频走外链) - **Pro 2.x → KiCad 解析链路** —— 复用 `easyeda2kicad.py`(plan.md §1.7 提过但未做) - **license 详情扫全 A 档** —— 全 2,806 项 license 落本地,给所有未来批选作过滤依据