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>
7.0 KiB
为什么爬取产出 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/ 二进制图(我们暂未爬),其它都对得上