Add EasyEDA Pro 2.x legacy source ingestion (5/5 batch closure)

补齐前一批失败的 2 个 legacy Pro 项目(立创·泰山派 RK3566、立创·梁山派),
打通 Pro 2.x 旧版工程的源抓取链路。结合上一 commit 的 modern Pro 3.x
路径,本仓库 5/5 Pro 项目 EPRO2/dataStr 全部端到端打通。

Pro 2.x 与 Pro 3.x 是两个完全不同的存储模型:
- Pro 3.x:git-style branch + linear history chain,AES-128-GCM 加密的
  EPRO2 增量消息流,按 history 重放(已在前一 commit 打通)
- Pro 2.x:无 branch / 无 history。文档以 EasyEDA Std plaintext dataStr
  存储(同 ["DOCTYPE","SCH","1.1"] 格式),按 doc UUID 通过
  /api/v2/documents/lists 批量 GET,主体无加密,只组件库走 AES

Pro 2.x 抓取链由 HAR (tmp/prodownload3.har, 178 请求) 反推:

  GET  /api/v4/projects/<P>                     → boards: [{sch, pcb, name}]
  GET  /api/projects/<P>/ticket?uuid=&g_ticket=-1
                                                → 完整项目 manifest
  POST /api/schematic/lists {uuids:[<sch>]}     → sort: [{uuid:<sheet>}]
  POST /api/v2/documents/lists {uuids,docType:1} → schematic plaintext
  POST /api/v2/documents/lists {uuids,docType:3} → PCB plaintext
  POST /api/coppers/search {paths}              → 铺铜层
  POST /api/textpath/search {paths,project_uuid}→ 字体/文字
  POST /api/v2/resources/search {hash,project_uuid} → BLOB 图片

实现:
- crawlers/oshwhub/crawler.py:
  - fetch_pro_source() refactor 成 dispatcher,先 GET project meta
    检查 branch_uuid,null 即旧版走 _fetch_pro_legacy(),非空走
    _fetch_pro_modern()
  - _fetch_pro_legacy() 新增(按上面 9 步流程拉所有 doc + 辅助层)
  - _pro_post_json() POST helper(与 _pro_get_json 对称)
- schemas/project.schema.json: source_format enum 加 easyeda-pro-legacy
- docs/sources/easyeda_pro_source.md rev 4: §1.1 旧版 vs 新版判别表更新、
  §2.7 新增旧版抓取流程 + 实测数据

落盘约定(旧版):
  source/ticket.json                     完整 manifest
  source/<sheet_uuid>.json               每张原理图(含 dataStr)
  source/pcb_<pcb_uuid>.json             每块 PCB
  source/coppers.json/textpath.json/blobs.json  辅助 PCB 层资源
  source/manifest.json                   索引

实测:
  立创·梁山派      editor=2.1.30, 2 sheets+1 pcb,    1.0 MB,  78 sym/191 fp/128 dev
  立创·泰山派 RK3566 editor=2.1.40, 29 sheets+1 pcb, 0.8 MB, 299 sym/524 fp/295 dev

旧版项目体量比新版小两个数量级(梁山派 1 MB vs RK3576 66 MB)—— 没有
增量 history,组件库走单独端点,本身就是当前快照。

5/5 Pro 项目终极汇总:
  X86 主板          easyeda-pro        3.2.15  7374 docs / 481 MB
  泰山派 RK3566     easyeda-pro-legacy 2.1.40    30 docs / 0.8 MB
  梁山派            easyeda-pro-legacy 2.1.30     3 docs / 1.0 MB
  220V 桌面电源     easyeda-pro        3.2.69   771 docs /  26 MB
  ESP-VoCat         easyeda-pro        3.2.91   278 docs / 7.5 MB

共 8456 docs / ~516 MB plain。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 21:59:25 +08:00
parent 3282a028c4
commit c6279bff08
48 changed files with 2603 additions and 22 deletions

View File

@@ -45,14 +45,17 @@
| 维度 | Pro 2.x 旧版 | Pro 3.x 新版 |
|---|---|---|
| `editorVersion` | "2.1.40" 等 | "3.2.91" / "3.2.127" |
| `editorVersion` | "2.1.30" / "2.1.40" 等 | "3.2.15" / "3.2.69" / "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 |
| 文档定位 | 项目 meta 的 **`boards: [{sch, name, pcb}]`** + ticket 端点 manifest | `/structures` 返回多 document 树 |
| 历史链 | **不存在** | 完整 git-style linear chain |
| 文档存储 | **plaintext** dataStr同 Std `["DOCTYPE","SCH","1.1"]\n[HEAD,...]`),按 doc UUID 批量 GET | AES-128-GCM 加密的 EPRO2 增量消息流,按 history 重放 |
| 加密 | 仅组件库symbols/devices走 AES文档本身明文 | 全部 history blob 都加密 |
| 实例 | `7360e73d...`RK3566 / 2.1.40 / 30 docs`0c467598...`(梁山派 / 2.1.30 / 3 docs | `2507dcb6...`RK3576`b7784066...`X86 / 7374 docs`ba64bd6f...`ESP-VoCat / 278 docs |
| 状态 | ✅ 已打通HAR `tmp/prodownload3.har` 反推) | ✅ 已打通 |
| schema source_format | `easyeda-pro-legacy` | `easyeda-pro` |
**判别**:先 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
**判别**:先 GET `/api/v4/projects/<P>`,看 `branch_uuid` 是否非空。`null` 即旧版,走 §2.7 的旧版抓取路径;非空走 §2.1-§2.6 的新版路径。`crawlers/oshwhub/crawler.py:fetch_pro_source()` 是 dispatcher根据 `branch_uuid` 自动分流到 `_fetch_pro_modern()``_fetch_pro_legacy()`
---
@@ -233,6 +236,51 @@ Accept: application/json
chain 只有 1 条就包含整个 project因为只有 1 个 document。
### 2.7 Pro 2.x 旧版抓取(无 branch / 无 history chain
适用于 `branch_uuid: null` 的工程(`editorVersion` 形如 `2.1.40`)。文档以 **EasyEDA Std plaintext dataStr** 形式存储,按 UUID 批量 GET不需要解密 / 解压 / 重放。
#### 流程
```
GET /api/v4/projects/<P> → boards: [{sch, pcb, name}]
GET /api/projects/<P>/ticket?uuid=<P>&g_ticket=-1 → manifest: {schematics, schs, pcbs, coppers, textpath, blobs, symbols, footprints, devices, ...}
POST /api/schematic/lists {uuids:[<sch>]} → 父 schematic 容器,含 sort: [{uuid: <sheet>, ticket}, ...]
POST /api/v2/documents/lists {uuids:[<sheets>], docType:1} → 每个 sheet 的 dataStr 明文
POST /api/v2/documents/lists {uuids:[<pcbs>], docType:3} → 每个 PCB 的 dataStr 明文
POST /api/coppers/search {paths:[<copper_paths>]} → 铺铜数据PCB 增量层)
POST /api/textpath/search {paths:[<textpath_paths>], project_uuid, path} → 字体 / 文字路径
POST /api/v2/resources/search {hash:[<sha256>], project_uuid} → BLOB嵌入图片等
POST /api/v2/components/searchByIds {uuids:[<symbol_uuids>]} → 元件符号定义(含 dataStrId/key/ivAES 加密的 lib 数据;可选)
POST /api/devices/searchByIds {uuids:[<device_uuids>]} → 元件库 metadata
```
#### 必需 headers
与 §2.5 相同(`Editor-Version` / `path: <PROJ_UUID>` / `Cookie`)。
#### 实测数据
| 工程 | editorVersion | sheets | pcbs | symbols | footprints | devices | blobs | coppers | size |
|---|---|---|---|---|---|---|---|---|---|
| 立创·梁山派 | 2.1.30 | 2 | 1 | 78 | 191 | 128 | 1 | 29 | 1.0 MB |
| 立创·泰山派 RK3566 | 2.1.40 | 29 | 1 | 299 | 524 | 295 | 32 | 0 | 0.8 MB |
**关键观察**
- 旧版项目体量比新版小**两个数量级**(梁山派 1 MB vs RK3576 66 MB plain—— 因为没有 history 增量、组件库走单独端点、本身就是当前快照。
- `manifest_ticket['schematics']` ↔ schematic CONTAINER板级"原理图"实体);`manifest_ticket['schs']` ↔ 单个 SHEET一页图纸`boards[].sch` 指向前者,需要 schematic/lists 一步把 sort 拆出 sheet UUIDs。
- 抓 plaintext docs 后,**无需** AES 解密(这点跟 Std 一样)。但 symbols/devices 端点返回的 lib 数据**仍然是 AES 加密的 dataStrId blob**,跟 Pro 3.x 同方案;如需还原 lib 内容需照 §2.3 解密流程。
#### 落盘约定
- `source/ticket.json` — 完整 manifest_ticket保留以备后续 lib 重建)
- `source/<sheet_uuid>.json` — 每张原理图(含 `dataStr` 字段)
- `source/pcb_<pcb_uuid>.json` — 每块 PCB
- `source/coppers.json` / `source/textpath.json` / `source/blobs.json` — 辅助层资源
- `source/manifest.json` — 索引 + structure_summary
实现:`crawlers/oshwhub/crawler.py:_fetch_pro_legacy()`
#### B. 大项目 `泰山派3M (RK3576)`36 documents
| 步骤 | 数值 |
@@ -491,3 +539,4 @@ 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-28 (rev 3) | HAR 实测:`/branches/{branch}/histories/{head}` 即返回整条 chain无需走 `?limit=N` 批量端点);落地 `crawlers/oshwhub/crawler.py:fetch_pro_source` 端到端打通5 项目批量抓 EPRO2schema docType 兼容 string 取值 + 增 message_count 字段 |
| 2026-04-28 (rev 4) | HAR `prodownload3.har` 反推 **Pro 2.x 旧版抓取链**`/api/projects/<P>/ticket` + `/api/v2/documents/lists` 批量端点plaintext dataStr+ supplementary coppers/textpath/blobs/components/devices新增 `_fetch_pro_legacy()` + 在 `fetch_pro_source()``branch_uuid` 自动 dispatchschema source_format enum 增 `easyeda-pro-legacy`5/5 Pro 项目3 modern + 2 legacy全部打通 |