Three coupled changes so kicad-cli sch erc runs at the project level (across all sheets of one schematic) instead of single-sheet: 1. (label) → (global_label (shape passive)). EPRO2 nets are project-global by construction (named rails span every page in the SCH and physically wire across PCBs); KiCad's local label is sheet- scoped and triggers `label_dangling` for any name not duplicated on the same page. 2. New root_sch_writer that groups SCH_PAGE docs by their parent SCH (META.schematic), emits one root .kicad_sch per group with one (sheet ...) entry per child, and threads the root-assigned uuid back into each child's (sheet_instances) so KiCad can bind them. --all-sch now defaults to this; --flat falls back to one-file-per-page. 3. EPRO2's "5-Voltage" placeholder COMPONENT (partId pid8a0e77bacb214e, 365 instances on ESP-VoCat) is the editor's power port. The rail name lives in the placement's `Global Net Name` ATTR, not in the PART. We now emit a (global_label "<rail>") at the placement coords whenever that attr is set (101/365 of them on ESP-VoCat — the rest are unconfigured drafts). ESP-VoCat 5 hierarchical roots: 2325 → 2265 violations. Modest because 5 of 6 SCHs are single-page (no cross-sheet nets to resolve), and the one 4-page schematic (CoreBoard) shares only a handful of names across sheets — most net names are de-facto sheet-local. The remaining ~190 pin_not_connected are dominated by 0402-style passives whose pin tip lies on a wire's interior, not at an endpoint; KiCad needs an explicit (junction) at those points and we don't yet emit one. Marked as the next follow-up in log.md. 47 → 52 unit tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
39 KiB
FacereDataset 执行日志
时间倒序,最新在顶部。
2026-04-29 00:30 KiCad 导出 Phase 3 hierarchical:root + global_label + 5-Voltage 电源端口
Claude 会话
接 54f0173。Handoff #3 多 sheet hierarchical:让 kicad-cli sch erc 在 project 视角而不是单 sheet 视角下校验,理论上能把 single-sheet ERC 看不见跨 sheet 的 248+111 残留压下去。
三件事一起做
-
(label)→(global_label)(tools/epro2/kicad/sch_writer.py) EPRO2 的 NET 是项目全局——一个 GND 名字横跨整个 schematic 而且通过 PCB 走线到隔壁板子。KiCad 的(label)是 sheet-scoped,单 sheet ERC 看到一个名字只出现一次就报 dangling;(global_label)才是项目级,hierarchical ERC 在 root 上能跨 sheet 解。 -
tools/epro2/kicad/root_sch_writer.py新模块 按 EPRO2 的SCH_PAGE.META.schematic把页分组,每组 emit 一个 root.kicad_sch,里面 N 个(sheet ...)引子页。子页的(sheet_instances (path "/<assigned_uuid>" ...))必须回引 root 给它分配的 uuid——少了这一步 ERC 把子页当孤岛走。 -
5-Voltage 电源端口识别(
sch_writer.pyCOMPONENT 循环里加判断) 实测 ESP-VoCat 有 365 个 partId=pid8a0e77bacb214e的 COMPONENT——挖了下发现这是 EPRO2 内部的 "Voltage" 占位符号,对应 KiCad 的(power_symbol)。网络名不在 PART 里,而是 placement 自己的Global Net NameATTR(101 个有名字、264 个还是空的草稿态)。每次有名字就在 placement 位置 emit 一个(global_label)。
CLI 改造
uv run python -m tools.epro2.kicad <project> --all-sch 现在默认 hierarchical 输出:每个 SCH 一个目录,里面 root + 子页。--flat 兜回老行为(一张图一个文件)。DELETE_DOC.isDelete=True 的 SCH 直接跳——LCD-BD 那个被删了的没生成。
ESP-VoCat 实测
kicad-cli sch erc <each-root> 跨 5 个 root 累加:
| Type | flat baseline | hier 后 | Δ |
|---|---|---|---|
| wire_dangling | 52 | 52 | 0 |
| pin_not_connected | 196 | 190 | -6 |
| label_dangling → global_label_dangling | 111 | 105 | -6 |
| pin_not_driven | 23 | 21 | -2 |
| endpoint_off_grid | 1372 | 1340 | -32 (LCD-BD 移除带走的) |
| lib_symbol_issues | 571 | 557 | -14 (同上) |
| TOTAL | 2325 | 2265 | -60 |
修得不够多——为什么:EPRO2 的 6 个 SCH 里 5 个只有 1 page,hierarchical 对它们没用;只有 CoreBoard 是真 4-page 多 sheet。CoreBoard 自己里面也只有 GND / MCU_3V3 / VCC_3V3 是真跨 sheet 共享的网,其它(GPIO4, CHIP_PU, AUDIO_I2C_, I2S_)都是 sheet-local——名字虽然 unique 但只在一个 sheet 上有 wire 引用,hierarchical ERC 也救不了,依然 dangling。
剩下 190 PNC 的真原因:抽样发现 C44/C45/R19/R20 这类 0402 元件,wire 从一个 pin 出来直直穿过另一个 pin,但 pin 落在 wire 中段而不是端点。KiCad ERC 要求 pin 落在 wire 端点或 explicit junction 处才认连接,wire 中段穿过的 pin 不自动连。EPRO2 源里这种连接合法但导出时丢了——要修需要做 wire-pin 几何相交,在中段 pin 位置 split wire 或 emit (junction)。下一轮再做。
决策(Why)
- 不走单 root 包全部 9 page:6 块 PCB 是物理独立板子,merge 进一个 root 会把 BaseBoard 的 GND 和 CoreBoard 的 GND 误判成同一个网。EPRO2 已经按 SCH 分好组,按 SCH 拆 root 是结构对齐做法。
(global_label (shape passive)):不知道方向,input/output/bidirectional 都会触发更多 ERC 检查(如 pin_not_driven),passive 最保守。power 网用 power_in 才理想,但需要 driver 元件,超出本轮范围。- 保留占位符号 placement 同时再 emit global_label:5-Voltage 占位符在 KiCad 里画出来虽然冗余但不影响 ERC,删了反而丢视觉信息。
- 不实现自动 junction:geometry 计算成本明显高于本轮收益(<10 PNC 的预期降幅),做成下一轮独立改动更清晰。
测试
47 → 52 单测全过:root_sch_writer 3 个 + power-port label 2 个 + sheet_path/page_num propagate 1 个 - test_named_wire 改 global_label 重写 1 个。
下一步建议
- wire-pin junction emission(中等工作量):算每个 COMPONENT 的 abs pin 位置,对每条 wire 查"非端点 pin 命中",命中就 split wire 或 emit junction。预期把 PNC 从 190 再砍一半左右。
- 或者直接进 Phase 3 真正的 .kicad_pcb 导出——schematic 这边已经够用,PCB 才是 Forge 投影最后一块。
2026-04-28 23:55 KiCad 导出修真实连接错:wire_dangling -88%, pin_not_connected -52%
Claude 会话
接 fb577cc 后续。Handoff 选项 #1:用 ERC violation 数从 850 降到接近 0 作为目标。Bisect 出两个根因,都是结构性的 KiCad 语义错(不是浮点 / 精度问题,所以 grid round 不是答案):
Bug A — sym_writer 漏 Y-flip
KiCad lib symbol 是 Y-up,schematic 是 Y-down,placement 时 KiCad 会再翻一次 Y。我们把 EPRO2 (Y-down) 的 PIN/RECT/POLY/CIRCLE/TEXT 坐标直接当 lib coord 写,KiCad 的翻转就把整个 symbol body 上下镜像了。U8 4-pin 磁吸座实测:pin 1↔4 对调、2↔3 对调,结果 wire 端点跟错位的 pin tip 撞不上 → ERC 报 pin_not_connected + wire_dangling。修法:lib_y = -epro2_y,pin/text rotation 也镜像 lib_rot = (360 - rot) % 360。
Bug B — 没发 net label,KiCad 看不出 EPRO2 的命名网络
EPRO2 的 WIRE op 带 NET attr(TXD / GND / VBUS ……),多段 LINE 通过同名 NET 连成一个网,不需要几何相邻。KiCad 不知道这套,只看几何。修法:在 sch_writer 里查每条 LINE 的 lineGroup → WIRE → NET attr,命中就在 LINE 起点 emit 一个 (label "<NET>")。同名 label 在多条物理 wire 上 → KiCad ERC 才认这是同一个网。per-LINE 不是 per-WIRE:单个 WIRE id 下面的 LINE 段不一定共端点,每段都得有 label 才不被判 dangling。
ESP-VoCat 9 sheets ERC 对比
| Type | baseline | after | Δ |
|---|---|---|---|
| wire_dangling | 444 | 52 | −88% |
| pin_not_connected | 406 | 196 | −52% |
| real connectivity 合计 | 850 | 248 | −71% |
| label_dangling | 0 | 111 | new (warn) |
| pin_not_driven | 0 | 23 | new (connector pin 类型问题) |
| endpoint_off_grid | 1372 | 1372 | unchanged (cosmetic, EPRO2 用 30/20/10 mil pitch,不在 KiCad 50 mil 网格上;不能 round——会把 < 50 mil 的 pin 间距压到一起) |
| lib_symbol_issues | 571 | 571 | unchanged (没注册 facere lib,cosmetic) |
剩余 248 real-connectivity 错主要是 single-sheet ERC 的天然限制:很多网只在一个 sheet 上有这一个 pin,对端在别的 sheet。kicad-cli 一次只看一个 .kicad_sch,看不见跨 sheet。彻底修要 Phase 3(hierarchical 顶层 + sheet links)。
关键决策(记 Why)
- 不 round to grid:50 mil grid 会塌缩 < 50 mil pin pitch(实测 4-pin 磁吸座是 10 mil pitch),破坏几何。EPRO2 源已经在整数 mil,浮点不是 root cause。
- per-LINE label 不是 per-WIRE:同 WIRE id 下两条 LINE 段不共端点是常态(不同地方各连一段),都得 label 才不孤立。
- 用
(label)不是(global_label):实验过两种语法,single-sheet ERC 都判 dangling(因为这个 sheet 上只出现一次);语义上 EPRO2 net 全局,但 single-sheet 校验视角看不见跨 sheet,换 global_label 帮不上。Phase 3 hierarchical 重构时再切。 - 不做 ERC config tweak:KiCad 8 的 connection grid 是硬编码 50 mil,schematic 文件里没法配;想消 endpoint_off_grid 必须破坏 EPRO2 几何或者升级到 KiCad 9 + custom severity。
测试
41 → 46 单测全过:新增 test_pin_y_negated_for_kicad_lib_y_up_convention / test_pin_rotation_mirrored_to_compensate_y_flip / test_rect_y_negated / test_named_wire_emits_label_at_line_start / test_unnamed_wire_emits_no_label。
下一步建议
- Phase 3 hierarchical:写 root .kicad_sch 引用所有子 sheet + 把跨 sheet 的 NET 升级成
(global_label),single-sheet ERC 残留的 248 + 111 大概率随之降到 < 100。 - (并行) 消 lib_symbol_issues 571:emit
sym-lib-table+ 独立 .kicad_sym。
2026-04-28 23:30 oshwhub 全量 listing 索引落本地:33,695 项 / 28.4 MB
Claude 会话
为了在"扩量到 top-30 / top-50 / 全量"前先量化候选池规模 + 质量分布,把 oshwhub listing API 全量扫一遍落地。
关键收获(之前以为是黑箱)
- listing API 直接返回
total字段:Pro 21,202 / Std 12,493,合计 33,695。 pageSize无上限,实测 1000 工作良好;全量索引 = 35 次请求 / 71 秒 / 35 MB 流量。sort参数被服务端静默忽略——传啥都返回相同顺序(grade desc → 隐式质量分 desc)。"按时间排序"必须先拉全集再本地排。origin默认 std——不带参数永远看不到 Pro 池。license不在 listing 响应,必须挨个抓详情页(QPS=0.5 → ~19 小时全量)。
数据画像(写到 docs/sources/oshwhub_listing_full.md)
- Pro 长尾极重:grade=1 占 82%,真正 A 档(grade≥3 & like≥10)只有 1,356 (6.4%)
- Std 高质量比例反而高:A 档 1,450 (11.6%),因为平台老 7 年(2016 起 vs Pro 2021 起),项目有时间累积点赞
- Std 已停滞:2021-2022 见顶(3.4k/年),之后断崖(1.5k → 0.9k → 0.4k → 0.05k 2026Q1)
- Pro 还在快速膨胀:2023 起线性增长,2025 全年 7.4k,2026Q1 已 1.1k
- 作者长尾健康:Pro 10,536 个 / Std 5,531 个唯一作者;top-1 占比 0.4% / 1.5%
- 立创官方账号占据头部(course-examples / li-chuang-kai-fa-ban / li-chuang-zhi-neng-ying-jian-bu)
实操含义
放量决策有了量化锚点:S 档 583 项 / A 档 2,806 项 / B 档 6,243 项 / 全量 33,695。Pro 工程源体积外推(基于 5 项实测均值),全 Pro 约 1 TB——超出 Gitea LFS 舒适区,必须配 size cap + license 白名单。
下一步
- 在本地 jsonl 上按 A 档过滤,做 license 详情页扫描(一次性 ~7 小时)
- license 白名单 ∩ A 档 → 真候选清单
- 然后再决定批量下载源
文件
scripts/dump_listing_index.py—— 一次性全量扫描脚本,可重抓data/state/oshwhub_listing_full.jsonl—— 28.4 MB,gitignore(可重建,不入库)docs/sources/oshwhub_listing_full.md—— 给人看的简报
2026-04-28 22:00 Pro 2.x 旧版工程源抓取链路打通,5/5 Pro 项目全部 ✅
Claude 会话
承接刚做的 3/5 modern Pro 批量。用户录了第三份 HAR(tmp/prodownload3.har 103 MB / 178 请求,目标是登录态打开梁山派 legacy Pro 2.x 项目),让我把 RK3566 / 梁山派两个旧版项目也补上。
HAR 反推:Pro 2.x 用一套完全不同的端点
旧版(editorVersion: 2.1.x)没有 git-style branch + history 模型。HAR 里看到的关键端点:
| 端点 | 作用 |
|---|---|
GET /api/projects/<P>/ticket?uuid=<P>&g_ticket=-1 |
完整项目 manifest(schematics / schs / pcbs / coppers / textpath / blobs / symbols / footprints / devices / block_symbol) |
POST /api/schematic/lists {uuids:[<sch>]} |
父 schematic 容器,含 sort: [{uuid: <sheet>, ticket}] 即子图 UUIDs |
POST /api/v2/documents/lists {uuids:[...], docType:1} |
抓 schematic 子图(plaintext dataStr,跟 Std 一样的 ["DOCTYPE","SCH","1.1"]\n[HEAD,...] 格式) |
POST /api/v2/documents/lists {uuids:[...], docType:3} |
抓 PCB(同样 plaintext dataStr) |
POST /api/coppers/search {paths:[...]} |
铺铜层数据(PCB 上独立分发的增量层) |
POST /api/textpath/search {paths, project_uuid, path} |
字体 / 文字路径(同上) |
POST /api/v2/resources/search {hash, project_uuid} |
BLOB(嵌入图片) |
POST /api/v2/components/searchByIds / /api/devices/searchByIds |
元件库 metadata(其 dataStrId 仍走 modules.lceda.cn AES 加密 blob) |
关键差异:旧版的工程主体 plaintext(无加密 / 无 history 重放),只有元件库走 Pro 3.x 的 AES 方案。这反而比 3.x 简单很多。
判别规则:/api/v4/projects/<P> 返回的 branch_uuid 是不是 null。null 即旧版。
实现
crawlers/oshwhub/crawler.py refactor:
fetch_pro_source()拆成 dispatcher(先 GET project meta → 检查branch_uuid)_fetch_pro_modern()—— 原 EPRO2 chain 流程,去掉重复的 project meta 调用_fetch_pro_legacy()—— 新增,按上面 9 步流程拉所有 doc + 辅助层_pro_post_json()—— POST helper(与 GET helper 对称)
落盘约定(旧版):
source/ticket.json—— 完整 manifest(保留备 lib 重建)source/<sheet_uuid>.json—— 每张原理图source/pcb_<pcb_uuid>.json—— 每块 PCBsource/coppers.json/textpath.json/blobs.json—— 辅助 PCB 层资源source/manifest.json—— 索引 + structure_summary
schema:source_format enum 加 easyeda-pro-legacy。
实测:2/2 legacy 项目打通
| 项目 | editor | sheets | pcbs | sym | fp | dev | coppers | textpath | blobs | size |
|---|---|---|---|---|---|---|---|---|---|---|
| 立创·梁山派 | 2.1.30 | 2 | 1 | 78 | 191 | 128 | 29 | 3 | 1 | 1.0 MB |
| 立创·泰山派 RK3566 | 2.1.40 | 29 | 1 | 299 | 524 | 295 | 0 | 0 | 32 | 0.8 MB |
旧版项目比新版小两个数量级(梁山派 1 MB vs RK3576 66 MB)—— 没有增量 history、组件库走单独端点、本身就是当前快照。
5/5 Pro 项目终极汇总
| # | 项目 | source_format | editor | docs | size |
|---|---|---|---|---|---|
| 1 | X86 主板 | easyeda-pro | 3.2.15 | 7374 | 481 MB |
| 2 | 立创·泰山派 RK3566 | easyeda-pro-legacy | 2.1.40 | 30 | 0.8 MB |
| 3 | 立创·梁山派 | easyeda-pro-legacy | 2.1.30 | 3 | 1.0 MB |
| 4 | 220V 桌面电源 | easyeda-pro | 3.2.69 | 771 | 26 MB |
| 5 | ESP-VoCat 喵伴 | easyeda-pro | 3.2.91 | 278 | 7.5 MB |
合计 8456 docs / ~516 MB plain 源数据,5/5 端到端打通。
下一步
- EPRO2 → KiCad 转换器(仍是 Forge 投影硬门槛,ESP-VoCat 7.5 MB 是不错的小样本起手)
- 旧版 dataStr → KiCad 复用 Std 转换链(同格式,已有
easyeda2kicad.py支持) - 阶梯放量到 50 / 500 项目时做风控压测
2026-04-28 21:35 Pro 工程源(EPRO2)批量抓取打通:3/5 modern Pro 项目 ✅,2/5 legacy 2.x ❌
Claude 会话
承接刚做完 Std 链路;用户给了浏览器 HAR (tmp/prodownload.har,174 请求,目标 = 立创·泰山派 RK3576 现代 Pro 项目) 验证 endpoint 形态,要求"找 5 个专业版项目,看能不能批量抓 EPRO2"。
5 个候选
按 oshwhub /api/project?origin=pro&sort=hot 取前 5(详情 API 拿 license):
| # | UUID | License | 项目 | 结果 |
|---|---|---|---|---|
| 1 | b77840665e2e... |
GPL 3.0 | 【全网首发】X86电脑主板 | ✅ |
| 2 | 7360e73de5dd... |
GPL 3.0 | 立创·泰山派 RK3566 开发板 | ❌ legacy 2.x |
| 3 | 0c4675983733... |
GPL 3.0 | 立创·梁山派开发板 | ❌ legacy 2.x |
| 4 | dc91a91e6693... |
CC BY-NC-SA 4.0 | (高颜值)220V 300W 桌面电源 | ✅ |
| 5 | ba64bd6f1c9c... |
GPL 3.0 | ESP-VoCat 喵伴 AI 萌宠 | ✅ |
License 全是限制性(CC-NC-SA / GPL)— Pro 用户群是立创团队/教育机构,默认上 NC-SA。和用户对齐:本仓库为研究用、不再分发,license 字段忠实落库;下游 Forge 投影时再用白名单过滤。
关键发现:Pro 2.x ≠ Pro 3.x(重要!)
立创开发板的旗舰板(RK3566 / 梁山派)抓 /api/v4/projects/<uuid> 返回 branch_uuid: null + editorVersion: "2.1.40"。
这是 Pro 编辑器旧版(2.x):没有 git-style branch/history 模型,文档直接通过 boards: [{sch, name, pcb}] 字段定位。我们之前调研的全是 3.x(泰山派 RK3576 / 无界 PLUS),3.x 才有 /branches/<B>/structures + /histories/<H> 全套。
旧版的访问端点暂未挖通:/api/v4/documents/<doc> 404;/api/documents/<doc> 401;/api/v4/projects/<P>/snapshots 200 但响应体是 project meta 不是 doc。需要录一份"在 pro.lceda.cn 编辑器里打开 RK3566"的 HAR 才能继续。已记入 docs/sources/easyeda_pro_source.md §1.1。
EPRO2 解析坑:行末单 |(找了 2 轮才看到)
第一轮跑 5 个项目结果:X86 board 7374 docs 抽出来只剩 2 个,220V 电源和 ESP-VoCat 都是 0 docs。
定位过程:
- 直接 dump 一条 history 的 lines,看到 DOCHEAD payload 行末有单个
|,例如{"docType":"BOARD",...,"version":"..."}|。 - 我的解析
json.loads(ln.split(b"||")[1])拿到带尾随|的字符串 →Extra data: line 1 column 127。 - silently swallow exception →
cur_doc没设 → 全 chain 的 message 被丢弃。 - 修复:解析前先
ln.rstrip(b"|").split(b"||")。已在docs/sources/easyeda_pro_source.md §3.1记录"行末单|是行终止符不是字段分隔符"。
修复后批量结果
3 个 modern Pro 项目完整解出来:
| 项目 | chain | docs | plain | blob | editor |
|---|---|---|---|---|---|
| ESP-VoCat | 12 | 278 | 7.5 MB | 1.1 MB | 3.2.91 |
| 220V 电源 | 28 | 771 | 26.3 MB | 7.4 MB | 3.2.69 |
| X86 主板 | 85 | 7374 | 481 MB | 61 MB | 3.2.15 |
X86 主板(5123 FOOTPRINT + 1243 DEVICE + 837 SYMBOL = 7203 个组件库 doc)数据量惊人,是个超复杂工程。
docType 取值表(实测扩展)
之前 doc 只列了 BOARD/PCB/SCHEMATIC/SHEET。实测 EPRO2 流里 docType 实际取值更细:
- 用户级文档:
BOARD(板物理边框)+PCB(板内容)+SCH(原理图)+SCH_PAGE(子图)。一个完整 PCB 板 = 一对BOARD+PCB,不是命名变化。 - 组件库 / 资源:
SYMBOL/FOOTPRINT/DEVICE/BLOB/FONT/CONFIG。每个独特组件 / 字体 / 项目配置都是独立 doc。 - 抓 EPRO2 = 抓项目 + 完整的局部组件库快照。下游做 EPRO2 → KiCad 转换时必须先把 lib doc 加载进 symbol cache。
已更新到 docs/sources/easyeda_pro_source.md §3.4。
代码改动
crawlers/oshwhub/crawler.py:- 新增
make_pro_source_client()—— 加载~/.secrets/pro-lceda-cookie-header.txt,自动配Editor-Version/Referer/ per-requestpathheader - 新增
fetch_pro_source(client, project_uuid, proj_dir, sleep)—— 4 步流程:project meta → branch HEAD → structures → history chain,然后逐 history 解密(AES-128-GCM, 16 字节 IV)+ gunzip + 按 DOCHEAD 切 per-doc - 新增
_order_history_chain()—— 沿 parent 链从 root 到 HEAD 排序 - 新增
_pro_get_json()—— 包装/api/v4GET 调用,自动加pathheader + 校验 success - 扩展
crawl_one()接pro_source_client参数,按list_item.origin自动 dispatch - 新增 CLI flag
--with-pro-source/--backfill-pro-source/--pro-cookie/--origin - 新增
_run_backfill_pro_source()(filter onraw_fields.origin == "pro")
- 新增
schemas/project.schema.json:docType类型从integer放宽到["integer","string","null"](兼容 Std 的 1/3 + Pro 的 BOARD/PCB/SCH 等字符串),新增message_count字段docs/sources/easyeda_pro_source.mdrev 3:加 §1.1 Pro 2.x vs 3.x、§3.1 行末|警告、§3.4 docType 实测表、§2.2 单 history endpoint 即返完整 chain
数据落地
data/raw/oshwhub/
b77840665e2e48148c1b04ce84b5f7e7/ # X86 主板(modern Pro 3.2.15)
source/
manifest.json # 7374 docs index
structure.json # 项目树(boards/schematics/sheets/pcbs)
<doc_uuid>.epro2 # 7374 个 EPRO2 文件
dc91a91e669349898d709a5ba02f5b5f/ # 220V 电源(modern Pro 3.2.69)
ba64bd6f1c9c467ba3b674a54943557d/ # ESP-VoCat(modern Pro 3.2.91)
7360e73de5dd428e9f29e10573f2d8ac/ # legacy Pro 2.x,无 source/
0c46759837334318aa4882d6d37f96fa/ # legacy Pro 2.x,无 source/
下一步
- (重要)legacy Pro 2.x 抓取链:录 HAR 看 RK3566 / 梁山派 在 pro.lceda.cn 编辑器打开时走什么 endpoint
- 想跑量到 50 / 500 项目时,先做风控测试:阶梯放量,监控 403 / 429 / 1111111
- EPRO2 → KiCad 转换器是 Forge 投影前置硬门槛
- 可考虑 cookie 轮换 / 多账号 pool(Pro 风控相对 Std 严)
2026-04-28 19:50 Std 工程源链路打通 + 10 板子 schematic/PCB 全部回填
Claude 会话
承接计划:把已抓 10 个板子的"需登录才能下载的原理图 + PCB"补齐。
关键发现:根本不需要登录
10 个板子全是 origin: "std"(EasyEDA 标准版)。原 plan.md §1.6 假设源数据要登录 — 实际公开项目的 dataStr 匿名可访。
调研路径(4 轮探测,留痕在 data/state/std_probe[1-5]/):
/api/user、/api/projects401(cookie 已过期,但和源抓取无关)oshwhub.com/api/project/<uuid>浏览器 UA 匿名 200,返回version_documents[](含 doc uuid + master + history chain)modules.lceda.cn/histories/<hash>.json仍 403(与 Pro 同结构,但 Std 不走这条路)- 翻 Std 编辑器
/editor/6.5.51/js/main.min.js(5 MB)grep/api/,找到 76 个端点。关键的ajaxDetail = '/api/documents/{uuid}' - 命中:
https://lceda.cn/api/documents/<doc>?uuid=<doc>&path=<doc>匿名 200,body 是完整 EasyEDA JSON(dataStr.{head,canvas,shape,BBox,colors[layers,objects,DRCRULE,...]})
两种响应 shape(按 docType 区分):
- docType=1 (Schematic):返"项目视图",
result.schematics[0].dataStr - docType=3 (PCB):返"文档视图",
result.dataStr直接在顶层
与 Pro 的差异
| 维度 | Std (本轮) | Pro (docs/sources/easyeda_pro_source.md) |
|---|---|---|
| 鉴权 | 无需 | lceda_pro_session 必须 |
| 加密 | 无 | AES-128-GCM + gzip |
| 源格式 | 扁平 EasyEDA JSON(shape[]) |
EPRO2 消息流(事件溯源) |
| 多 doc | version_documents[] 逐个 GET |
/structures + history chain 重放 |
实施
docs/sources/easyeda_std_source.md:完整调研(含 dataStr 字段、抓取伪代码、附录重跑脚本)crawlers/oshwhub/crawler.py:- 新增
make_source_client()—— 浏览器 UA + Referer,规避 oshwhub/api/project/<uuid>端点对FacereDataset/0.1UA 的 reject(在 commit message 注明 UA 例外原因) - 新增
fetch_std_source():项目 → version_documents → 逐文档 dataStr → 落source/<doc>.json+source/manifest.json - 新增
--with-source标志(爬新项目时一并抓源)和--backfill-source(仅扫已有项目补源) - QPS ≤ 0.2(
SLEEP_SOURCE = 5.0s)
- 新增
schemas/project.schema.json:加source_format/source_path/source_documents/editor_version字段(前 3 个进 enum 锁定,后续新源好对齐)
跑批结果(dev1,QPS 0.2)
10/10 全成功;schema 验证 10/10 pass:
| 项目 | docs | docTypes | 大小 | editor |
|---|---|---|---|---|
| ST-LINK V2-1 | 2 | [1,3] | 682 KB | 6.5.39 |
| USB 电压电流表 | 4 | [3] | 1.2 MB | 6.5.15 |
| 红外热成像 | 2 | [1,3] | 1.6 MB | 6.5.22 |
| t12-858d 焊台 | 11 | [1,3] | 6.1 MB | 6.5.15 |
| 加热台量产计划 | 6 | [1,3] | 12.0 MB | 6.5.43 |
| ESP32-S3 智能手表 | 4 | [1,3] | 1.4 MB | 6.5.41 |
| RT300-MKV 可调电源 | 3 | [1,3] | 3.3 MB | 6.5.23 |
| YuzuMaix V831 | 4 | [1,3] | 5.4 MB | 6.5.37 |
| 盖革计数器 | 6 | [1,3] | 1.2 MB | 6.5.47 |
| ZVS 感应加热 | 3 | [1,3] | 990 KB | 6.5.40 |
合计:45 个文档 / 33.2 MB(中位 ~1.5 MB / 项目,附件主体约为附件主流量的 6%)
观察:
- USB 电压电流表只有 PCB 文档(4 个:主板 + 盖板 + 底板 + 面板,作者未上传原理图源)
- t12 焊台 11 个文档(拆得碎,估计含多个独立模块)
- editor 版本散布在 6.5.15 - 6.5.47(取决于作者上一次保存项目时的客户端版本)
落盘结构(per project)
data/raw/oshwhub/<uuid>/
├── metadata.json # ★ 新增字段:source_format/source_path/source_documents/editor_version
├── description.md
├── cover.*
├── _urls.json
├── files/ # 用户附件(已存在,LFS)
└── source/ # ★ 新:EasyEDA Std 工程源
├── manifest.json # 文档清单 + 抓取时间 + upstream version_documents 留档
└── <doc_uuid>.json # 完整 dataStr 响应(普通 git,文件 1-5 MB 量级)
source/*.json 走普通 git 而不是 LFS(10 项目共 33 MB 完全够用;放量时再考虑加 LFS 规则)。
安全 / 合规
- 无登录态、无凭据使用 → 无账号封禁风险,无 cookie 泄漏顾虑
- UA 例外:源抓取使用浏览器 UA 而非
FacereDataset/0.1,原因写入docs/sources/easyeda_std_source.md §3与本 commit message - License:与原项目附件相同的 license 字段已在 metadata,下游 whitelist 过滤逻辑不变
未决 / 下一步
- dev1 上的
~/.secrets/lceda.jsoncookie 已过期(XSRF 4-22 失效,今天 4-28),但本任务已不依赖它。是否保留待定 —— Pro 流程可能仍要 easyeda2kicad.py转换:现有 45 个 dataStr 是关键测试样本,可立刻跑(plan.md §1.7)- 放量决策:从 10 → 50 → 全量 12,493 时,按 33 MB / 10 ≈ 3.3 MB/proj 估算,全量源 ~40 GB(不含附件本体)
- 多账号轮询、Pro 链路打通仍是
plan.md §1.6的开放项(仅当遇到 Pro 项目时才用得上)
改动清单
- 新增:
docs/sources/easyeda_std_source.md、scripts/probe_std_api[1-5].py、data/raw/oshwhub/<uuid>/source/(10 项目) - 修改:
crawlers/oshwhub/crawler.py、schemas/project.schema.json、10 项目的metadata.json - 待人工:
projects.md重生成(脚本未跑);plan.md §1.6状态从 ⏳ → ✅,§1.7 unblocked
2026-04-24 00:25 打通 pro.lceda.cn 工程源完整链 + EPRO2 格式解析
Claude 会话
核心成果:立创 EDA Pro 工程源的 API + 加密 + 格式三层全打通。
完整链路
1. GET /api/v4/projects/<PROJ> → branch_uuid
2. GET /api/v4/projects/<PROJ>/branches/<BRANCH> → history_uuid
3. GET /api/v4/projects/<PROJ>/branches/<BRANCH>/histories/<HIST> → {key, iv, dataStrUrl}
4. GET <dataStrUrl> (modules.lceda.cn) → 417 KB 加密 blob
5. AES-128-GCM decrypt (tag=blob[-16:]) → 417 KB gzip
6. gunzip → 2.7 MB EPRO2 源流
关键 headers:Editor-Version: 3.2.127 / path: <PROJ_UUID> / Referer: https://pro.lceda.cn/editor /
Cookie: lceda_pro_session=...(与 u.lceda.cn 的 session 不共享)
加密细节
- 算法: AES-128-GCM(从
modules.lceda.cn/pro-mgr/.../project-worker.js里this.tool.decrypt({name:"AES-GCM",iv:this.iv,tagLength:128},...)反查确认) - key / iv 都是 32 hex = 16 byte
- WebCrypto 约定:ciphertext || 16-byte-authTag(末尾附)
- 解密后 gzip magic
1f 8b 08,gunzip 得最终源流
EPRO2 格式
立创 EDA Pro 2 的事件溯源格式:消息流按 \n 分行,每行 {"type":...,"ticket":N,"id":...}||{payload}||[extra]。
示例样本(无界PLUS BOARD 文档)8 357 条消息,40 种 type:
- PART / COMPONENT / ATTR / PIN(零件与属性)
- PAD / VIA / WIRE / NET / PAD_NET(PCB 电气)
- LINE / POLY / RECT / ARC / CIRCLE / ELLIPSE / TEXT(几何)
- LAYER (1572) / LAYER_PHYS / ACTIVE_LAYER(层堆叠)
- FILL / POUR / POURED(铺铜)
- RULE / RULE_SELECTOR / RULE_TEMPLATE(设计规则)
与 Std 版对比
| u.lceda.cn (Std) | pro.lceda.cn (Pro) | |
|---|---|---|
| Cookie | lceda_session |
lceda_pro_session |
| 源 API | 单一 /api/projects/<uuid> |
4 步 /api/v4/... 链 |
| 版本控制 | 无 | branches + histories |
| 加密 | 待验证 | AES-128-GCM |
| 源格式 | EasyEDA JSON(扁平) | EPRO2 消息流 |
| 工具 | easyeda2kicad.py 第三方 |
无现成 KiCad 转换器 |
落地
- 新建
docs/sources/easyeda_pro_source.md(完整调研,见该文档附录 A 一键重跑) pyproject.toml加pycryptodome>=3.23.0- 清理:dev1 上
/tmp/source.blob与/tmp/source.json(后者含 Charles 私人工程源 2.7 MB)
待验证 / 下一步
- 他人公开 Pro 工程能否同样 4 步通 —— 需 HAR
- SCHEMATIC docType 的 API 入口(本次只解出 BOARD)
- 多 document 枚举(project → documents 列表端点)
- Pro 编辑器"导出 KiCad"功能的 API 端点(若存在,能省自写转换器的工作)
- 对齐
OSHWHUB_INGEST_SPEC.md(Forge 消费侧要求.kicad_sch+ 更严 license whitelist)
⚠️ 安全
Charles 在聊天里粘过两次 cookie(u.lceda.cn 一次 + pro.lceda.cn 一次),已写入 dev1 ~/.secrets/。
当前会话 transcript 含明文 —— 本轮验证完 Charles 应登出再重登一次,让测试期间暴露过的 session invalidate。
2026-04-23 20:10 策略大调:登录内容入场 + 云服务器 + EDA→KiCad 转换
Claude 会话
四项变更落实到文档(暂不写代码,等云服务器到位):
1. 登录态内容纳入范围
原则(CLAUDE.md):
- 合法账号登录后抓,禁止盗号 / 共享号
- 凭据集中云服务器
~/.secrets/(mode 700),不入 git / 日志 / metadata - 仍不绕付费墙、不破 DRM、不抓站点明确禁抓的内容
- 换号 / 重登事件记
docs/secrets.md(只事件、不含值)
2. 云服务器(广州,待交付)
新增 plan.md §0.5 基础设施段:
- 0.5.1 机器初始化:git / git-lfs / uv / python 3.11+,非 root SSH,
~/.secrets/ - 0.5.2 调度:tmux/nohup 长跑 + systemd timer 增量
- 0.5.3 登录态获取:cookie 导出流程
3. 存储分级演进
plan.md §1.4 改写:
- 前期 < 50 GB:云服务器磁盘 + Gitea LFS
- 中期 50–200 GB:评估 Gitea 容量压力;扩容 or 分仓
- 后期 > 200 GB:迁对象存储(OSS / COS / MinIO),Gitea 只存元数据 + 指针
- 50 GB 是决策评估点,不过早迁移
4. 立创 EDA → KiCad 转换
新增 plan.md §1.6(登录态工程源抓取)+ §1.7(EDA→KiCad 转换):
- §1.6 用登录账号抓
u.lceda.cn/api/project/<uuid>工程源 JSON,存source.json - §1.7 写
scripts/convert_to_kicad.py,候选工具easyeda2kicad.py(pypi,活跃维护) - 批处理扫
data/raw/oshwhub/→ 输出data/processed/oshwhub/<uuid>/kicad/ - 跑
kicad-cli sch erc / pcb drc做语法校验,失败样本记data/state/convert_failed.jsonl - 目的:打通 oshwhub (EasyEDA) 与 bshada/open-schematics (KiCad) 两个生态的训练语料
同步改动
docs/sources/oshwhub.md§3.5 从"未开放"改为"需登录,纳入范围";R4 风险更新README.md数据源表加「登录态」列,加运行环境说明
等待
- 广州云服务器到位 → 启动 Phase 0.5
- 账号登录凭据由 Charles 提供
2026-04-23 19:55 oshwhub.md 重写成完整调研文档
Claude 会话
Charles 要求把 12 493 总数验证、90 项目采样结果合进主调研文档。
docs/sources/oshwhub.md 重写为 9 节 + 2 附录的完整调研:
- 一页纸 TL;DR 表
- 站点架构
- robots.txt 与合规
- API 与抓取入口(列表 / SSR 详情 / 附件 CDN / 排除项 / 未开放端点)
- 项目总数验证(新):三路 sort 一致 + 分页二分搜索(250 × 50 = 12 500 吻合)+ grade 覆盖抽样
- 抽样语料特征(从 oshwhub_corpus_estimate.md 并入):体积 / 文件类型 / license 分布
- Schema 映射
- 速率与礼貌
- 目录输出约定
- 风险与未解决(7 条)
- 附录:重跑命令、变更历史
删除重复文件 oshwhub_corpus_estimate.md(内容已并入 §5)。
2026-04-23 19:50 加入 HF bshada/open-schematics 计划
Claude 会话
Charles 点名把 https://huggingface.co/datasets/bshada/open-schematics 纳入第一批。
调研结论:
- 这是已预处理的 HF 数据集,非待爬网站
- 78 parquet shards 6.4 GB;CC-BY-4.0(商用友好)
- 10K+ 条记录,每条含
.kicad_sch源 / PNG / 组件列表 / JSON / YAML / name / description - 与 oshwhub (EasyEDA) 互补,补 KiCad 生态
决定:
- 整包镜像到
data/external/huggingface/bshada--open-schematics/,不拆 per-project 目录 - 用
huggingface-cli download ... --repo-type dataset拉;parquet 走 LFS - 维护单独的
datasets.md,不与 per-project 的projects.md混
改动:
- 新增
docs/sources/hf_bshada_open_schematics.md完整调研 plan.md加 Phase 1.5README.md数据源表加一行
未下载,等拍板 6.4 GB LFS 预算。
2026-04-23 19:30 Phase 1 MVP:10 个高质量 oshwhub 项目入库
Claude 会话:承接仓库初始化
API 调研结论
- 列表 API:
GET https://oshwhub.com/api/project?page=N&pageSize=M&sort=hot,无鉴权,返回 12493 个项目元数据(含 grade / likes / stars / views / forks) - 详情:
GET https://oshwhub.com/<path>是 SSR HTML,嵌入 escaped JSON,含license+attachments[](每个带 name / src / size / md5 / ext / mime / download_count) - 附件 CDN:
https://image.lceda.cn{src}— 已验证无鉴权直接下载 - EasyEDA 工程源 JSON(
u.lceda.cn)需登录,v0.1 不抓 - 详细调研见
docs/sources/oshwhub.md
选 10 个高质量项目
判据:grade == 4(平台精品徽章) + likes ≥ 100 + 应用领域多样(避免同类堆叠)+ 排除 _copy 派生仓。
10 个项目覆盖:调试器、加热台、盖革计数器、可调电源、焊台、智能手表、USB 测电流、ZVS 感应加热、AI 开发板、红外热成像。
MVP 爬虫
位置:crawlers/oshwhub/crawler.py
list_projects()— 列表 API 分页pick_top()— 按 like×3 + star + fork×2 + views/100 + comments×2 + grade×50 排序parse_detail_html()— 从 SSR HTML 提取 title / license / description / attachmentscrawl_one()— 每项目产出:metadata.json/description.md/cover.*/files/*/_urls.json- QPS ≤ 0.5(
SLEEP_BETWEEN = 2.0),UA 显式声明FacereDataset/0.1
抓取与入库
- 10/10 成功,52 个附件,524 MB
- Gitea LFS(v25.4.3 原生支持)+ 本地
git-lfs 3.5.1(用户态二进制装在~/.local/bin/) .gitattributes规则:data/raw/**/files/**一律走 LFS;元数据(metadata.json / description.md / _urls.json / cover.*)走普通 git- 每项目目录结构:
data/raw/oshwhub/<uuid>/ ├── metadata.json # 按 schemas/project.schema.json ├── description.md ├── cover.{jpg,png,jpeg} ├── _urls.json # 所有原始 URL 清单 └── files/* # 原始附件(LFS)
改动汇总
- 新增:
crawlers/oshwhub/{__init__,__main__,crawler}.py、schemas/project.schema.json、docs/sources/oshwhub.md、pyproject.toml - 修改:
.gitattributes(缩窄到data/raw/**/files/**)、.gitignore(移除data/raw/*排除)
下一步建议
- 验收 10 个项目元数据质量(随机抽 2-3 条对照原站)
- 决定 Phase 1.4 放量目标(50?500?全量 12493?)
- Phase 2 准备:GitHub KiCad repo 调研
2026-04-23 19:40 fs-web-stream 排查 + schema 自动校验
Claude 会话(自主推进)
fs-web-stream.jlc.com 定性
重新抓 /CYIIOT/ST_LINK-V2_1 并看 13 个 fs-web-stream.jlc.com 链接的上下文:全部是嘉立创服务侧栏/推广图标(3D 打印、发热片、Ican、EDA 扩展广场、开源硬件平台 badge 等),与项目本身无关。image.lceda.cn/attachments/ 就是项目附件的唯一入口,已确认闭环。docs/sources/oshwhub.md 对应章节已更新。
scripts/validate.py
jsonschema 做两层校验:
- 默认:所有
data/raw/**/metadata.json对schemas/project.schema.json的结构校验 --check-files:另外验证每条 file 的本地 path 存在且 sha256 匹配
结果:10/10 项目两项全通过。
新增
scripts/validate.pypyproject.toml加jsonschema>=4.26
待决策
- 放量规模 —— 已提供实测数据:median ≈ 110 GB,p90 上界 ≈ 660 GB,建议预算 150–180 GB(见
docs/sources/oshwhub_corpus_estimate.md) - 是否需要抓
u.lceda.cn的 EasyEDA 源 JSON(需登录,v0.1 跳过)
2026-04-23 19:45 全量规模实测 + License 分布
Claude 会话(自主推进)
写 scripts/estimate_size.py,只抓详情 HTML 解析 attachments[].size,不下载;采样 90 个 hot 项目(3 页 × 30)。
关键发现:
- 单项目 median 9 MB / mean 22 MB / p90 54 MB / max 204 MB;12493 全量 median 估算 110 GB,p90 上界 660 GB
- 视频 (.mp4 + .qt) 占 54% 存储!如果训练只要 PCB/原理图/BOM,加
--skip-ext mp4,qt存储直接砍半 - License 分布健康:GPL 3.0 占 49%,Public Domain 21%,CC 系列 ~20%,CERN/TAPR OHL 6%;样本内无闭源
- NC (Non-Commercial) 占 ~11%,商用场景必须过滤
结果固化到 docs/sources/oshwhub_corpus_estimate.md,可随时重跑验证。
建议
- 存储预算定 180 GB(median + 15% buffer)
- Phase 1.4 前给 crawler 加
--skip-ext开关滤视频 - 下游建立 license whitelist 过滤 NC / 未知
2026-04-23 18:50 仓库初始化 & 数据源调研
Claude 会话:初始化
完成:
- 从
git.deepknow.site/Facere/FacereDataset克隆空仓到~/repo/FacereDataset - 调研立创开源平台(oshwhub.com)初步数据:
robots.txt仅 Disallow/posts,其他路径允许- 存在
sitemap.xml(首页 + explore + activities + market 等入口已列出) - 项目详情页路径为
/detail/<uuid>(示例f0652fd2ae3e40b8a0ecc8dc773e3512) - 图片 CDN:
image.lceda.cn/oshwhub/pullImage/... - 文件下载:
fs-web-stream.jlc.com/fs-web-stream/file-operation/download/<snowflake-id> - 页面是 Next.js SPA,首屏 HTML 800KB,但数据加载具体 API 入口需要浏览器 trace(留给 Phase 1.1)
- 创建项目骨架:
README.md— 项目简介与数据源表CLAUDE.md— 项目级 Claude 指令(爬虫规约、合规红线、schema 要求)plan.md— 6 阶段建设计划(Phase 0 骨架 → Phase 5 数据清洗 → Phase 6 持续运营)log.md— 本文件.gitignore— 排除data/rawdata/processeddata/statePython 缓存等- 目录骨架
crawlers/ schemas/ scripts/ data/{raw,processed,state} docs/{sources,} - 每个空目录放
.gitkeep
- 首次提交 & 推送到
origin main
下一步建议:
- 拍板存储方案(本地盘 / Gitea LFS / 外部 OSS)—— 影响 Phase 1.4 放量时机
- 目标规模(1 万 / 10 万 / 全量)
- 决定是否保留二进制附件或只存 URL
- 完成上述 3 项后启动 Phase 1.1(用
chrome-devtoolsMCP 录 oshwhub 的 network trace 定位真实 API)