Add EasyEDA Pro EPRO2 source ingestion (3/5 batch test)
打通 oshwhub origin=pro 现代 Pro 3.x 工程的 EPRO2 源抓取链路。3/5
modern Pro 项目完整解出(共 8423 docs / 542 MB plain):
- X86 主板 7374 docs / 481 MB plain (chain=85, editor=3.2.15)
- 220V 桌面电源 771 docs / 26 MB plain (chain=28, editor=3.2.69)
- ESP-VoCat 278 docs / 7.5 MB plain (chain=12, editor=3.2.91)
剩余 2/5 是 legacy Pro 2.x(立创泰山派 RK3566、梁山派),项目 meta
返回 branch_uuid=null + editorVersion="2.1.40",没有 git-style chain
模型,文档直接挂在 boards[].sch/pcb 字段上,访问端点暂未挖通;元
数据落库 metadata.json,source/ 留空。
实现要点:
- fetch_pro_source(): 4 步流程(project → branch HEAD → structures
→ /branches/<B>/histories/<HEAD> 即返完整 chain,无需 ?limit 批量
端点)+ 逐 history 走 AES-128-GCM 解密(16 字节 IV,pycryptodome
原生支持)+ gunzip + 按 DOCHEAD 切 per-doc EPRO2 流
- EPRO2 解析坑:行末单 `|` 是行终止符不是字段分隔符,必须先
rstrip("|") 再 split("||"),否则 payload JSON 解析失败 silently
swallow 导致 cur_doc 不设 → 第一轮 X86 板 7374 docs 抽出来只剩 2 个
- docType 实测远不止 BOARD/PCB/SCH/SCH_PAGE,还含 SYMBOL /
FOOTPRINT / DEVICE / BLOB / FONT / CONFIG —— Pro 把组件库快照也
随项目存到 history,下游做 EPRO2→KiCad 转换时必须先把这些 lib
doc 加载进 symbol cache
- Pro 2.x vs 3.x 是不同存储模型 —— 3.x 走 branch 模型(已打通),
2.x 走 boards[] 直链(未打通);判别条件:project meta 的
branch_uuid 是否为 null
CLI 新增 --with-pro-source / --backfill-pro-source / --pro-cookie /
--origin(按 origin 字段服务端过滤 listing API),crawl_one() 按
origin=pro 自动 dispatch 到 Pro fetcher。
schema:docType 类型从 integer 放宽到 [integer, string, null]
(兼容 Std 的 1/3 + Pro 的 BOARD/SCH 等),新增 message_count 字段。
License 注意:本批 5 个项目全是 NC-SA / GPL,未达 Pro source doc
§4.2 Forge 白名单(MIT/BSD/Apache/CC0/CC-BY/CERN-OHL-P/Unlicense)。
按 CLAUDE.md "研究用、不再分发" 原则 raw 入库无碍;Forge 投影时
另过白名单。
详细技术细节见 docs/sources/easyeda_pro_source.md rev 3 + log.md。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,21 @@
|
||||
| oshwhub 标记 | 项目 `origin: "std"` | 项目 `origin: "pro"` |
|
||||
| 转 KiCad 成熟度 | `easyeda2kicad.py` 等第三方工具 | **无现成工具**,需自写转换器或用 Pro 编辑器内置"导出 KiCad"功能(其端点待挖) |
|
||||
|
||||
### 1.1 Pro 2.x 旧版与 3.x 新版(重要)
|
||||
|
||||
**Pro 不是单一存储模型**。2026-04 实测发现:
|
||||
|
||||
| 维度 | Pro 2.x 旧版 | Pro 3.x 新版 |
|
||||
|---|---|---|
|
||||
| `editorVersion` | "2.1.40" 等 | "3.2.91" / "3.2.127" |
|
||||
| `branch_uuid` | **null**(无分支模型) | UUID(有 main 分支) |
|
||||
| 文档定位 | 项目 meta 的 **`boards: [{sch, name, pcb}]`** 直接指向 doc UUID | `/structures` 返回多 document 树 |
|
||||
| 历史链 | **不存在**(无 `/branches/<B>/histories/<H>` 端点) | 完整 git-style chain,本节后续描述 |
|
||||
| 状态 | ⏳ **本爬虫暂不支持**;HAR 待录 | ✅ 已打通 |
|
||||
| 实例 | `7360e73d...`(立创·泰山派RK3566)、`0c467598...`(立创·梁山派) | `2507dcb6...`(立创·泰山派RK3576)、`b7784066...`(X86电脑主板)、`ba64bd6f...`(ESP-VoCat) |
|
||||
|
||||
**判别**:先 GET `/api/v4/projects/<P>`,看 `branch_uuid` 是否非空。`null` 即旧版;记录跳过原因到 `data/state/oshwhub_excluded.jsonl`。**旧版工程的 sch/pcb document UUID 在 `boards[]` 字段里**(每个 board = 一对 sch+pcb),但访问端点未知 —— 候选探测:`/api/documents/<doc>`(实测 401,需要不同 cookie scope)、`/api/v4/projects/<P>/snapshots`(200 但返回的是 project meta 而非 doc)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 抓取流程
|
||||
@@ -78,6 +93,18 @@ GET /api/v4/projects/<PROJ_UUID>/branches/<BRANCH_UUID>/structures
|
||||
|
||||
### 2.2 枚举完整 history chain
|
||||
|
||||
**两条等价路径**(任选其一,均返回相同 chain 数组):
|
||||
|
||||
**(a) 编辑器实测路径**(首选 — 2026-04-28 HAR 验证):
|
||||
|
||||
```
|
||||
GET /api/v4/projects/<P>/branches/<B>/histories/<HEAD>
|
||||
```
|
||||
|
||||
URL 看起来像"取单 history 元信息",但 server 实际返回**整条 chain**(HEAD 在数组首位,按 parent 链回溯)。无需分页,crawler 默认走这条。
|
||||
|
||||
**(b) 批量端点**(可选 — 早期挖出的接口,仍可用):
|
||||
|
||||
```
|
||||
GET /api/v4/projects/branches/histories?project=<P>&branch=<B>&history=<H>&limit=<N>&path=
|
||||
```
|
||||
@@ -158,7 +185,7 @@ for ln in all_lines:
|
||||
| `/api/v4/projects/{uuid}/branches` | GET | 分支列表 |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}` | GET | 分支详情 |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/histories` | GET | **单分支 history 列表**(有时 404;用下面的 batch 版) |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/histories/{history}` | GET | 单 history 元信息(含 dataStrUrl/key/iv) |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/histories/{history}` | GET | **以该 history 为 HEAD 的完整 chain 数组**(编辑器加载实测,HAR 2026-04-28;非"单 history") |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/structures` | GET | **项目结构树** |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/thumbs` | GET | 文档列表(含缩略图 URL) |
|
||||
| `/api/v4/projects/{uuid}/branches/{branch}/thumbs/{thumbUuid}` | GET/POST | 单文档缩略图 |
|
||||
@@ -230,7 +257,7 @@ chain 只有 1 条就包含整个 project,因为只有 1 个 document。
|
||||
gunzip 后是 UTF-8 文本流,按 `\n` 分成 N 条消息,每条形如:
|
||||
|
||||
```
|
||||
{"type":"X","ticket":N,"id":"..."}||{payload JSON}||{optional extra fields}
|
||||
{"type":"X","ticket":N,"id":"..."}||{payload JSON}||{optional extra fields}|
|
||||
```
|
||||
|
||||
- `type`:消息类型(见 §3.2)
|
||||
@@ -238,6 +265,7 @@ gunzip 后是 UTF-8 文本流,按 `\n` 分成 N 条消息,每条形如:
|
||||
- `id`:对象的 stable ID(PART id、NET name、document uuid、等)
|
||||
- `payload`:JSON 对象,内容随 type 变化
|
||||
- `||`:字段分隔符;消息内最多见 2-3 段
|
||||
- **行末单个 `|`**:所有消息行都以**单个** `|` 结尾,是行终止符(不是字段分隔符)。解析前应先 `rstrip("|")` 再按 `||` split,否则末尾片段含尾随 `|` 会让 JSON 解析失败 —— 2026-04-28 实测的坑
|
||||
|
||||
本格式是**事件溯源 / 增量操作日志**:一个工程的完整状态 = 所有消息按 level 重放的结果。
|
||||
|
||||
@@ -310,14 +338,29 @@ gunzip 后是 UTF-8 文本流,按 `\n` 分成 N 条消息,每条形如:
|
||||
|
||||
### 3.4 docType 取值
|
||||
|
||||
| docType | 含义 | 样例来源 |
|
||||
|---|---|---|
|
||||
| `BOARD` | PCB 板 | 无界PLUS HEAD history |
|
||||
| `PCB` | PCB 板(另一种叫法) | 泰山派 HEAD history |
|
||||
| `SCHEMATIC` | 原理图父文档 | structures 中 `schematics` 类别,尚未在 DOCHEAD 中见到样例 |
|
||||
| 其它待观察 | `SHEET` / `SYMBOL` / `FOOTPRINT` / `3D_MODEL` 等 | 需覆盖不同类型的 history 事件 |
|
||||
2026-04-28 在 ESP-VoCat(chain=12,editor=3.2.91,278 docs)+ X86 主板(chain=85,editor=3.2.127,待补全)上实测覆盖情况:
|
||||
|
||||
> `BOARD` 与 `PCB` 可能是同义(两个工程的 editVersion 不同:3.2.127 给 BOARD / 3.2.91 给 PCB,疑似 Pro 3.2.x 中期改过命名)。需在更多样本里验证。
|
||||
| docType | 含义 | 计数(ESP-VoCat 12-history 全量) |
|
||||
|---|---|---|
|
||||
| `BOARD` | PCB 板(板物理边框 + 板级元 = "外壳" 容器) | 6 |
|
||||
| `PCB` | PCB primitives(焊盘 / 走线 / 铺铜 / 网络等"内容") | 6 |
|
||||
| `SCH` | 原理图容器 | 6 |
|
||||
| `SCH_PAGE` | 子图(multi-sheet hierarchy 的具体一页,对应 structures 的 `sheets`) | 9 |
|
||||
| `SYMBOL` | 元件符号定义(每个独特符号一份) | 105 |
|
||||
| `FOOTPRINT` | 封装定义 | 55 |
|
||||
| `DEVICE` | 元件库元数据(LCSC 等) | 88 |
|
||||
| `CONFIG` | 项目级配置(layer 表、规则模板、网络颜色等) | 1 |
|
||||
| `BLOB` | 二进制资源(图片 / 3D 模型等) | 1 |
|
||||
| `FONT` | 字体 | 1 |
|
||||
|
||||
**几个重要观察**:
|
||||
- `BOARD` 与 `PCB` 是**两类不同的 doc**(不是同义;之前误以为是命名变化)。一个完整的"PCB 板"由一对 `BOARD` + `PCB` document 组成 —— `BOARD` 持有板物理 / 元属性,`PCB` 持有具体几何元素。
|
||||
- `SCHEMATIC` 在 EPRO2 流里实际叫 **`SCH`**;`structures.schematics` ↔ DOCHEAD `docType: SCH`。
|
||||
- `SHEET` 在 EPRO2 流里实际叫 **`SCH_PAGE`**;`structures.sheets` ↔ DOCHEAD `docType: SCH_PAGE`。
|
||||
- `SYMBOL` / `FOOTPRINT` / `DEVICE` 是项目使用到的**组件库快照**,每个独特组件一份。这意味着抓 EPRO2 源 = 抓项目 + 完整的局部组件库;下游做转换时**必须**先把这些 lib doc 加载进 symbol cache。
|
||||
- ESP-VoCat 共 278 docs,结构树 (`structures`) 只有 15 个用户级 doc。比例 ~18× 是因为组件 / 符号 / 封装定义都是独立 doc。
|
||||
|
||||
> docType doc_uuid 长度并非恒定 16-hex:`SYMBOL` / `FOOTPRINT` / `DEVICE` 等组件类的 uuid 是 32 hex(与 project / branch 同长);`BOARD` / `PCB` / `SCH` / `SCH_PAGE` 等用户文档是 16 hex。`CONFIG` / `FONT` / `BLOB` 用 ASCII 字面量名作为 uuid。
|
||||
|
||||
---
|
||||
|
||||
@@ -446,4 +489,5 @@ PY
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-04-24 (rev 1) | 首版:单 history 4 步链 + AES-128-GCM + gzip + EPRO2 消息流(40 种 types 覆盖 BOARD 全要素) |
|
||||
| 2026-04-24 (rev 2) | 本次:加入 `/structures` 枚举、`/projects/branches/histories?...` 批量 chain 端点、完整重放流水线;大项目(泰山派 36 docs / 35 histories / 66 MB)实测;对他人 public Pro 项目已验证;29 条 `/api/v4/` 端点清单 |
|
||||
| 2026-04-24 (rev 2) | 加入 `/structures` 枚举、`/projects/branches/histories?...` 批量 chain 端点、完整重放流水线;大项目(泰山派 36 docs / 35 histories / 66 MB)实测;对他人 public Pro 项目已验证;29 条 `/api/v4/` 端点清单 |
|
||||
| 2026-04-28 (rev 3) | HAR 实测:`/branches/{branch}/histories/{head}` 即返回整条 chain(无需走 `?limit=N` 批量端点);落地 `crawlers/oshwhub/crawler.py:fetch_pro_source` 端到端打通(5 项目批量抓 EPRO2);schema docType 兼容 string 取值 + 增 message_count 字段 |
|
||||
|
||||
Reference in New Issue
Block a user