Colleague-facing explainer at docs/sources/pro_crawl_vs_export.md. Addresses the "I see 278 .epro2 files but my browser only downloaded one" confusion: web download is a ZIP container (extension is a UX choice, not a format), our crawl produces per-doc message streams. Both carry equivalent EPRO2 data; only real gap is IMAGE/ binary previews which we don't fetch yet. Why per-doc and not ZIP: the ZIP path has no public endpoint — three HARs confirm the export button fires zero HTTP requests, it's pure client-side JSZip on data already loaded by the editor. Our crawler hits the same chain endpoints the editor uses internally, which delivers per-doc streams. Log entry references the 278 vs 266 doc-count delta for ESP-VoCat (we walk full history chain, web export is a current snapshot). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
167 lines
7.0 KiB
Markdown
167 lines
7.0 KiB
Markdown
# 为什么爬取产出 N 个 .epro2 文件,而网页端导出只有一个 .epro2
|
||
|
||
> 给同事的科普向解释。如果你看到我们仓库里某个 Pro 项目目录下躺着几十到上千个 `.epro2`,
|
||
> 而你自己从网页端"下载工程包"只拿到一个 `.epro2`,你不是抓错了,它们也不是同一种文件,
|
||
> 只是名字撞了。
|
||
|
||
---
|
||
|
||
## 一句话结论
|
||
|
||
**网页端下载的那个 `.epro2` 是 ZIP 压缩包**(扩展名误导),里面其实有三样东西:
|
||
工程消息流 + 工程元数据 + 嵌入图片。
|
||
**我们爬取产出的 N 个 `.epro2` 是工程内每个"文档"各自的消息流**,
|
||
一个组件一文件、一张原理图一文件、一块 PCB 一文件,没打包。
|
||
|
||
两者**承载的数据是等价的**,只是打包方式相反——一个是"全压一坨",
|
||
一个是"每文档一个文件"。
|
||
|
||
---
|
||
|
||
## 拿 ESP-VoCat 做实例
|
||
|
||
同一个项目(`ba64bd6f...`),两种获取方式得到的东西:
|
||
|
||
### 网页端"下载工程包" → `ProPrj_ESP-VoCat.epro2`(1 个文件,1.4 MB)
|
||
|
||
文件名虽然是 `.epro2`,**实际是 ZIP**。`unzip -l` 看里面:
|
||
|
||
```
|
||
IMAGE/ (目录)
|
||
IMAGE/38cc57d9...webp 60 KB ─┐
|
||
IMAGE/450927588f1f...webp 260 KB │
|
||
IMAGE/78404fce3de8...webp 134 KB │ 6 张嵌入图片
|
||
IMAGE/9904a7de7784...webp 48 KB │ (组件预览/3D 缩略图)
|
||
IMAGE/a20de4ce4ca4...webp 141 KB │
|
||
IMAGE/b3294b3234d6...webp 286 KB ─┘
|
||
project2.json 25 KB 工程元数据
|
||
ESP-VoCat 喵伴...epru 6.25 MB 全部 doc 拼成一坨的消息流
|
||
```
|
||
|
||
### 我们爬取产出 → `data/raw/oshwhub/ba64bd6f.../source/`(278 个文件,7.5 MB)
|
||
|
||
每个 doc 一个独立的 `.epro2`:
|
||
|
||
```
|
||
00184cbbad5a8d33.epro2 每个 SYMBOL 一个文件 × 105
|
||
0a29a31811039d37.epro2 每个 FOOTPRINT 一个 × 55
|
||
037c... .epro2 每个 DEVICE 一个 × 88
|
||
... 总共 278 个 .epro2 文件
|
||
manifest.json 索引(哪个文件对应哪个 docType)
|
||
structure.json 工程结构树(哪些 sheet / pcb / board 属于哪些 board)
|
||
```
|
||
|
||
按 docType 分布看:
|
||
|
||
| docType | 我们爬取 | 网页端导出 |
|
||
|---|---:|---:|
|
||
| SYMBOL(原理图符号) | 105 | 105 |
|
||
| DEVICE(元件 BOM 元数据) | 88 | 88 |
|
||
| FOOTPRINT(PCB 封装) | 55 | 55 |
|
||
| SCH_PAGE(原理图分页) | 9 | 6 |
|
||
| SCH(原理图容器) | 6 | 3 |
|
||
| PCB(PCB 容器) | 6 | 3 |
|
||
| BOARD(板子顶层) | 6 | 3 |
|
||
| BLOB / FONT / CONFIG | 1 / 1 / 1 | 1 / 1 / 1 |
|
||
| **合计** | **278** | **266** |
|
||
|
||
---
|
||
|
||
## 那 N 个文件到底是啥?为什么这么多
|
||
|
||
EasyEDA Pro 是**文档式工程**——跟 KiCad「整个项目两个大文件」的思路不一样:
|
||
|
||
- KiCad:1 个 `.kicad_sch` + 1 个 `.kicad_pcb`,组件库走外部引用
|
||
- EasyEDA Pro:**每用到一种组件,就把它的 symbol / footprint / device 各自快照成独立文档存进工程里**,
|
||
外加多页原理图、容器层、全局资源……
|
||
|
||
ESP-VoCat 用了大约 100 种不同的元件(ESP32-WROOM、各种规格的电阻、按键、LED、晶振、屏幕模组……),
|
||
所以**自动产生 248 条都是元件库快照**:105 SYMBOL + 55 FOOTPRINT + 88 DEVICE。
|
||
剩下 30 条才是工程"主体"(9 张原理图 + 6 套 PCB/BOARD/SCH 容器 + FONT/BLOB/CONFIG 全局资源)。
|
||
|
||
> **直觉对照**:把 EPRO2 想象成**每个文档的 git history**,不是项目压缩包。
|
||
> 一个 SYMBOL 文件 ≈ 立创元件库里"这颗 0603 电阻"的快照 + 你在工程里对它的每次微调。
|
||
|
||
每个 EPRO2 文件内部是**事件流**(event-sourced),按行排:
|
||
|
||
```
|
||
{"type":"DOCHEAD","ticket":4456}||{"docType":"FOOTPRINT","uuid":"a20de4ce..."}|
|
||
{"type":"OP","op":"ADD","id":"e123",...}||{...payload...}|
|
||
{"type":"OP","op":"UPDATE","id":"e123",...}||{...payload...}|
|
||
...
|
||
```
|
||
|
||
回放这些 event 就能拿到文档的当前状态。
|
||
|
||
---
|
||
|
||
## 为什么数量对不上(278 vs 266)
|
||
|
||
我们多 12 个,全部集中在**容器层文档**:SCH_PAGE 多 3、SCH 多 3、PCB 多 3、BOARD 多 3。
|
||
|
||
原因很简单:
|
||
|
||
- **网页端导出**是当前快照——只导"这个项目此刻包含哪些 doc"
|
||
- **我们的爬虫**走完整 history chain——把工程历史上**演化路径**上出现过的 doc 都拉下来了
|
||
(比如项目历史里"加过一张图、又删了",那张图的 SCH_PAGE 在我们这里有,在网页导出里没有)
|
||
|
||
元件库部分(248 条)完全一致,没有差异。
|
||
|
||
---
|
||
|
||
## 体积对比
|
||
|
||
| 形态 | 大小 | 说明 |
|
||
|---|---:|---|
|
||
| 网页 ZIP 压缩态 | **1.41 MB** | 适合用户下载 |
|
||
| 网页 ZIP 解压(.epru + json + 6 图) | 7.20 MB | |
|
||
| 我们爬取(278 个独立 .epro2) | **7.85 MB** | 多 12 容器 + 缺 IMAGE/ |
|
||
|
||
> 我们多 0.65 MB 是那 12 个历史容器层;他们多的部分是 6 张 IMAGE/ 预览图(~0.93 MB),
|
||
> 这是我们当前**确实缺的一块数据**——blob 引用爬到了,二进制图片本体没爬。
|
||
> 不影响 EPRO2 → KiCad 转换语料生成(KiCad 端没对应字段),但如果要做"原貌可视化还原"会需要补。
|
||
|
||
---
|
||
|
||
## 为什么我们走 N 文件而不是 1 ZIP
|
||
|
||
简单说:**ZIP 这条路根本没有公开端点可爬**。
|
||
|
||
- 三份 HAR 反复抓过编辑器流量,**网页端"导出"按钮压根不发任何 HTTP 请求**
|
||
- 它是纯前端 JS 操作:编辑器把已经加载到内存的数据用 `JSZip` 在浏览器里现拼现压
|
||
- 所以服务端没有 `/export` 之类的 endpoint 给爬虫调用
|
||
|
||
我们走的是**官方编辑器加载工程时用的同一套 API**(`/api/v4/projects/<P>/branches/<B>/histories/<H>` 拿 chain,
|
||
逐段 AES-GCM 解密 + gunzip + 按 DOCHEAD 切分),所以拿到的是"原汁原味"的 per-doc EPRO2 流。
|
||
|
||
附带好处:
|
||
|
||
| | per-doc 爬取 | 网页 ZIP |
|
||
|---|---|---|
|
||
| 单文档 diff | ✅ 容易(每文件独立) | ❌ 全在一坨流里 |
|
||
| 增量更新 | ✅ 只重抓变动 doc | ❌ 整包重下 |
|
||
| LFS 友好 | ✅ 单文件可独立寻址 | ❌ 一个大 zip 改一字节 LFS 也得重传 |
|
||
| 历史完整性 | ✅ 全 chain | ❌ 只有当前快照 |
|
||
| 体积 | 7.5 MB 平铺 | 1.4 MB 压缩态 |
|
||
|
||
---
|
||
|
||
## 你应该用哪种
|
||
|
||
| 用途 | 选哪种 |
|
||
|---|---|
|
||
| 训练 LLM 做 EDA 任务、做 EPRO2 → KiCad 转换、做 BOM 抽取 | 我们爬的 per-doc |
|
||
| 给立创的人复刻你的工程(直接拿去用编辑器打开) | 网页端 ZIP |
|
||
| 想看项目历史演化、做版本 diff | 我们爬的 per-doc(含 history) |
|
||
| 想拿到组件预览图(原始 webp) | 网页端 ZIP(IMAGE/ 目录) |
|
||
|
||
---
|
||
|
||
## TL;DR
|
||
|
||
- "网页端 .epro2" = ZIP 容器(叫 .epro2 是 UX 决定,不是格式)
|
||
- "爬取的 .epro2" = 工程内每个文档自己的 EPRO2 消息流
|
||
- 两者**信息量基本等价**,包装方式相反
|
||
- 我们选 per-doc 是因为 ZIP 这条路根本没有服务端端点可爬,并且 per-doc 对下游处理更友好
|
||
- 唯一**真实差异**是 IMAGE/ 二进制图(我们暂未爬),其它都对得上
|