Why: - 策略调整:登录后才能访问的内容从"禁止"改为"纳入本项目范围", 同时明确凭据管理红线(合法账号、不入 git、云服务器隔离)。 解锁 u.lceda.cn 工程源 JSON,这是训练数据质量的关键升级。 - 计划中"存储"和"运行环境"一直模糊,现在按 Charles 提供的广州云服务器 + 存储分级演进(Gitea LFS → 对象存储)给出清晰路径。 - 打通 oshwhub (EasyEDA) 与 bshada/open-schematics (KiCad) 两个生态, 需要一个 EDA→KiCad 批转换脚本。先把它纳入 plan,等拿到工程源再实现。 What: - CLAUDE.md: 登录态条款从"不抓"改为"合法账号可抓",凭据管理写死在 ~/.secrets/,事件记 docs/secrets.md;合规红线同步更新 - plan.md §0.5: 新增 基础设施段(机器初始化 / 调度 / 登录态获取) - plan.md §1.4: 存储分级演进(< 50 GB 云盘,50-200 GB 评估,> 200 GB 迁对象存储) - plan.md §1.6: 登录态抓 u.lceda.cn 工程源 - plan.md §1.7: scripts/convert_to_kicad.py 批处理,候选 easyeda2kicad.py - plan.md 风险表: 加账号封禁 / 转换失败 / 云服务器单点故障三条 - docs/sources/oshwhub.md: u.lceda.cn 从"未开放"移到"需登录,已纳入范围" - README.md 数据源表: 加"登录态"列 + 运行环境说明 - log.md: 本次策略变更记录 未改:未新增 docs/infra.md(等机器到位 + 真实细节后再写),scripts/convert_to_kicad.py 尚未实现(等拿到工程源样本再实现)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 KiB
立创开源硬件平台 (oshwhub.com) — 数据源调研
平台:立创开源硬件平台(嘉立创 EDA / 深圳创电优选科技有限公司) URL:https://oshwhub.com 首版调研:2026-04-23 维护:每次爬虫新发现或数据结构变更须更新本文件
TL;DR
| 指标 | 值 | 备注 |
|---|---|---|
| 公开项目总数 | 12 493 | 已发布到开源广场的项目(见 §4 验证) |
| 抓取复杂度 | 低 — 中 | 列表 API 开放,详情走 SSR HTML 解析 |
| License 主流 | GPL 3.0 (49%) / Public Domain (21%) / CC 系列 (20%) / CERN·TAPR OHL (6%) | 采样 90 项目,见 §5.3 |
| 单项目体积中位数 | 9 MB(mean 22 MB, p90 54 MB, max 204 MB) | §5.1 |
| 全量存储估算 | 110 GB (median) / 660 GB (p90 上界) | §5.1 推算 |
| 反爬风险 | 低 | 列表 API 无 rate-limit 实测,建议 QPS ≤ 0.5 自律 |
| 登录态要求 | 匿名够用但登录更全 | 公开元数据 + 附件走匿名端点(见 §3);工程源 JSON 需登录(见 §3.5 & plan.md §1.6) |
| 当前已入库 | 10 项目 / 52 附件 / 535 MB | 见仓库 projects.md |
爬虫实现:crawlers/oshwhub/crawler.py
1. 站点架构
- Next.js App Router SPA。首页、列表页由客户端 fetch 渲染(首屏 HTML 无卡片 DOM);详情页
/<user>/<slug>与/detail/<uuid>是 SSR,首屏 HTML 已包含完整元数据与附件列表 - axios baseURL =
https://oshwhub.com(见/_next/static/chunks/8661-*.js) - CDN:
- 封面 / 附件:
https://image.lceda.cn - 嘉立创服务侧栏图标:
https://fs-web-stream.jlc.com(与项目内容无关,见 §3.4)
- 封面 / 附件:
2. robots.txt 与合规
User-agent: *
Disallow: /posts
Sitemap: https://oshwhub.com/sitemap.xml
/detail/、/explore、/api/project 均允许抓取。本项目所有抓取严格按 robots 执行。
3. API 与抓取入口
3.1 项目列表 — 主入口
GET https://oshwhub.com/api/project?page=N&pageSize=M&sort=hot
| 参数 | 含义 | 备注 |
|---|---|---|
page |
页码(从 1 起) | |
pageSize |
页大小 | 最大 50;超过返回空 |
sort |
排序 | hot(默认)/ like / new |
返回:
{
"success": true,
"code": 0,
"result": {
"page": 1,
"pageSize": 50,
"total": 12493,
"totalPage": 250,
"lists": [ /* project items */ ]
}
}
每条 list item 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
uuid |
string (32 hex) | 项目全局 ID |
name |
string | 项目名 |
introduction |
string | 简介 (≤ 200 字) |
path |
string | <username>/<slug>,用于拼详情 URL |
owner |
object | {uuid, username, nickname, avatar, team} |
count |
object | {fork, star, like, views, watch} |
grade |
int | 质量档位(观察到 0–4;4 = 精品徽章) |
thumb |
string | 封面(以 //image.lceda.cn/ 或 /images/ 开头) |
tags |
string[] | 可能为空 |
created_at |
ISO 8601 | 项目创建时间(EDA 编辑器创建) |
updated_at |
ISO 8601 | 项目上次更新时间 |
oshwhub_publish_at |
ISO 8601 | 发布到开源广场的时间(sort=new 按此排) |
public, publish |
bool | 本 API 返回的记录里恒为 true |
comments_count |
int |
陷阱:
sort=new实际按oshwhub_publish_at排,不是created_at。page=1下最早可能看到created_at: 2021-01-17的项目(因为它刚发布到广场)。
3.2 项目详情 — SSR HTML 解析
GET https://oshwhub.com/<path> # 例:https://oshwhub.com/CYIIOT/ST_LINK-V2_1
GET https://oshwhub.com/detail/<uuid> # 等价
HTML 内嵌 escaped JSON(Next.js RSC payload),关键字段位置:
<title>自制ST-LINK V2-1(开源版本) - 立创开源硬件平台 - 深圳创电优选科技有限公司</title>
<meta name="description" content="最近迷上了攻城狮工具的制作..." />
...
<!-- 正文 HTML 中某处(在 RSC 字符串里,用 \" 转义):-->
\"license\":\"GPL 3.0\"
\"creator\":{\"uuid\":\"367b6ee2c2114a459898e14b1268a641\"}
\"attachments\":[
{\"id\":1,\"uuid\":\"83ade303...\",\"name\":\"ST-Link V2.1官方图纸.pdf\",
\"src\":\"/attachments/2020/7/mRn5hQ....pdf\",
\"mime\":\"application/pdf\",\"ext\":\"pdf\",
\"size\":183323,\"md5\":\"bdb976690426a0e3216ad3aacd9878cc\",
\"download_count\":3720},
...
]
提取流程见 crawlers/oshwhub/crawler.py::parse_detail_html:
<title>→ 项目标题(去后缀)<meta name="description">→ 简介re.search(r'\\"license\\":\\"([^\\"]+)\\"')→ license 字段- 定位
\\"attachments\\":[,括号平衡法截出数组,反转义\"→"后json.loads
3.3 附件下载 CDN
https://image.lceda.cn{attachments[].src}
# 例:https://image.lceda.cn/attachments/2020/7/mRn5hQ....pdf
- HTTP 200,无鉴权(直接 curl 即可)
- 覆盖类型:PDF / ZIP / RAR / 7z / MP4 / CSV / BIN / JSON / JPG / PNG 等用户自传全类型
- 这是项目用户文件的唯一入口,已穷举排查确认
3.4 已排查的「非项目源」路径
-
https://fs-web-stream.jlc.com/fs-web-stream/file-operation/download/<snowflake-id>— 详情页 HTML 里会出现这类 URL(十几个每页),但 context 都是嘉立创服务侧栏/推广图标:- "嘉立创 3D 打印" icon
- "开源硬件平台" badge
- "EDA 扩展广场" banner
- "Ican" / "发热片" 等产品线图标
与项目本身无关,爬虫应跳过。
3.5 需登录的端点(纳入本项目范围,待 Phase 1.6 实现)
| 端点 | 现象(匿名) | 登录后预期 |
|---|---|---|
/api/project/<uuid> |
{"code":104001,"success":false} |
返回项目完整元数据(待验证) |
u.lceda.cn/api/projects |
HTTP 401 "尚未登录" | EasyEDA 编辑器的工程列表 |
u.lceda.cn/api/project/<uuid> |
302 重定向 | 工程源 JSON(schematic + PCB + 组件)— 训练数据最关键的一层 |
/api/search/* |
空响应 | 可能可用(待验证) |
策略(按 CLAUDE.md "登录态"条款):使用合法账号登录后抓,账号放云服务器 ~/.secrets/,QPS ≤ 0.2 自律防封号,凭据不入 git。
实施见 plan.md §1.6;抓到的源 JSON 会走 scripts/convert_to_kicad.py(§1.7)统一到 KiCad 格式。
4. 项目总数验证
API 报告 total: 12493(三种 sort 下一致),采用两步交叉验证:
4.1 三路 sort 一致性
for sort in hot new like; do
curl -s "https://oshwhub.com/api/project?page=1&pageSize=1&sort=$sort" \
| python3 -c "import sys,json;print(json.load(sys.stdin)['result']['total'])"
done
# 三次输出均为 12493
4.2 分页边界二分搜索
以 pageSize=50 二分查找最后一页有数据的页号:
page 2000 -> 0 items page 253 -> 0
page 687 -> 0 page 251 -> 0
page 468 -> 0 last real ≈ 250
250 × 50 = 12 500,与 total=12493(最后一页 43 条不满)完全吻合。
4.3 覆盖范围抽样
按 sort=new 翻到中间几页,grade 档位分布:
| 页 | 观察到的 grades |
|---|---|
| 1 | 全 grade=4(精品) |
| 100 | grade=3 |
| 200 | grade=2 |
| 300 | grade=2 |
| 1000 | grade=0 |
结论:12 493 包含所有档位(0–4),不是仅精品子集。这就是"立创开源广场"的公开可下载全量。
注:该数字不等于立创 EDA 编辑器里所有用户工程的总数——私有草稿、未发布工程不在 API 范围内,也不应被本项目爬取。12 493 是合规可抓的上限。
5. 抽样语料特征(90 项目实测)
重跑命令:uv run python scripts/estimate_size.py --pages 3 --page-size 30 --sort hot
采样方法:只解析详情 HTML 的 attachments[].size 求和,不下载附件
采样日期:2026-04-23
5.1 单项目体积分布
| 指标 | 附件数 | 体积 |
|---|---|---|
| mean | 3.1 | 22.2 MB |
| median | 2 | 9.0 MB |
| p90 | — | 54.2 MB |
| max | 15 | 204.5 MB |
90 个样本合计 2001 MB。偏差说明:样本来自 sort=hot top 90,偏向有人气、附件齐的项目;长尾(低人气、无附件)项目的中位数会更低。
全量 12 493 项目存储推算:
| 基数 | 估算 |
|---|---|
| mean × total | 271 GB |
| median × total | 110 GB ← 推荐规划值 |
| p90 × total | 662 GB ← 容量上界 |
建议预算:150–180 GB(median + 15% buffer,+ 考虑 hot 偏差修正后向下取)。
5.2 文件类型分布(按字节)
| 后缀 | 样本总量 | 占比 |
|---|---|---|
.mp4 |
1 029 MB | 51% |
.zip |
676 MB | 34% |
.rar |
72 MB | 4% |
.qt |
66 MB | 3% |
.pdf |
32 MB | 2% |
.bin |
27 MB | 1% |
.jpeg |
26 MB | 1% |
.7z |
10 MB | <1% |
关键洞察:视频(
.mp4 + .qt≈ 54%)占存储一半。训练语料如果主要用 PCB / 原理图 / BOM / 叙事,加--skip-ext mp4,qt,mov,avi可直接省一半存储。
5.3 License 分布(90 项目样本)
| License | 计数 | 占比 |
|---|---|---|
| GPL 3.0 | 44 | 49% |
| Public Domain | 19 | 21% |
| CC BY-NC-SA 4.0 | 5 | 6% |
| CERN Open Hardware License | 4 | 4% |
| CC BY-NC-SA 3.0 | 3 | 3% |
| CC BY-SA 4.0 | 2 | 2% |
| TAPR Open Hardware License | 2 | 2% |
| CC-BY-NC-SA 3.0 | 2 | 2% |
| 其他 CC 变种 | 2 | 2% |
全部开源 / 公共领域,样本中无闭源。但注意:
-
49% GPL 3.0:训练模型无直接违反,但下游若发布模型权重 + 数据集作为 derivative work,保守应沿用 GPL 或评估 scope
-
~11% NC(Non-Commercial):商用场景必须过滤剔除
-
全量里
license: "unknown"(作者未选)比例可能更高,下游按 whitelist 过滤;默认建议 whitelist:{Public Domain, CC0, CC BY 4.0, CC BY-SA 4.0, MIT, Apache-2.0, BSD*, CERN-OHL*}
6. Schema 映射
对齐 schemas/project.schema.json(跨源统一 schema):
| 本项目 schema 字段 | oshwhub 来源 |
|---|---|
source |
常量 "oshwhub" |
source_url |
https://oshwhub.com/<path> |
project_id |
uuid(32 hex) |
title |
详情页 <title> 去后缀,或列表 name |
description_short |
列表 introduction |
description_path |
"description.md"(本地文件) |
author.username |
owner.username |
author.display_name |
owner.nickname |
author.user_id |
owner.uuid |
license |
SSR HTML 嵌入 JSON 的 license 字段;缺省 "unknown" |
tags |
列表 tags |
created_at, updated_at |
列表同名字段 |
published_at |
oshwhub_publish_at |
crawled_at |
运行时 datetime.now(timezone.utc) |
metrics.{likes,stars,forks,views,watch,comments} |
count.{like,star,fork,views,watch} + comments_count |
cover.url / cover.path |
thumb / 本地 cover.* |
files[].{name,url,size,md5,ext,mime,original_id} |
attachments[] 逐字段映射 |
files[].{path,sha256} |
下载后写入 |
raw_fields.{path,grade,origin,public,publish} |
保留源站原始字段 |
7. 速率与礼貌
当前配置(crawlers/oshwhub/crawler.py):
| 动作 | 频率 |
|---|---|
| 列表 API | 页间隔 2 s(按 SLEEP_BETWEEN),QPS 0.5 |
| 详情 HTML | 同上,每次 ~500 KB |
附件下载 (image.lceda.cn) |
每个文件后 2 s sleep |
UA 声明为 FacereDataset/0.1 (+https://git.deepknow.site/Facere/FacereDataset),不伪装浏览器。
实测无 rate-limit 触发。放量时建议并发 ≤ 3,遇 429/5xx 指数退避,连续失败 ≥ 5 次停爬人工介入。
8. 目录输出约定
每个项目落盘到:
data/raw/oshwhub/<uuid>/
├── metadata.json # 符合 schemas/project.schema.json
├── description.md # 标题 + 简介 + 作者/许可证/发布时间
├── cover.{jpg,png,jpeg} # 封面(普通 git)
├── _urls.json # 所有原始 URL 清单(重下用)
└── files/ # 原始附件(Git LFS,见 .gitattributes)
├── xxx.pdf
├── xxx.zip
└── ...
9. 已知风险与未解决
| # | 风险 / 问题 | 影响 | 当前策略 / 缓解 |
|---|---|---|---|
| R1 | 站点加反爬(UA 指纹 / 验证码 / IP 限) | 放量中断 | 自律 QPS ≤ 0.5;若触发切换到 lightpanda 或真 Chrome + 降速 |
| R2 | license: "unknown" 或非标准字符串 |
下游合规风险 | 严格 whitelist 过滤;oshwhub_license_normalize 待建 |
| R3 | 单项目附件 > 100 MB | LFS 存储激增 | 目前 p90=54 MB 可接受;大于阈值记录 URL 不下载的开关待加 (--max-file-size) |
| R4 | EasyEDA 源 JSON 需登录 | 账号被封 → 工程源获取中断 | Phase 1.6 登录爬,QPS ≤ 0.2,账号隔离在云服务器 ~/.secrets/,必要时多号轮询 |
| R5 | 长描述 HTML 格式保真 | 文本语料噪声 | 当前只存简介;长文本提取(含图片/表格)留待 Phase 5 清洗 |
| R6 | grade 字段含义未官方公开 |
质量筛选判据模糊 | 经验:grade=4 是精品徽章;其它档位抽样判据后续补 |
| R7 | 增量更新策略 | 重复抓取浪费 | updated_at 对比 + LFS prune 策略未实现 |
附录 A — 一次性重跑
# 规模估算(无下载)
uv run python scripts/estimate_size.py --pages 3 --page-size 30 --sort hot
# 列表 API 探测
curl -s "https://oshwhub.com/api/project?page=1&pageSize=1" | python3 -m json.tool
# 某项目详情
curl -s "https://oshwhub.com/CYIIOT/ST_LINK-V2_1" -o /tmp/detail.html
# MVP 爬取 10 个高质量
uv run python -m crawlers.oshwhub --top 10 --min-likes 50 --min-grade 4
# 指定 UUID 列表爬
uv run python -m crawlers.oshwhub --uuids aaa,bbb,ccc --out data/raw/oshwhub
附录 B — 变更历史
| 日期 | 变更 |
|---|---|
| 2026-04-23 | 首版:API 探索、SSR 解析方案、fs-web-stream 排查、12 493 总数验证、90 项目采样(体积 / 类型 / license 分布)合并成本文 |