给库存系统接上立创商城的物料参数
🤖 AI 辅助说明:本文由 AI 辅助整理,代码与步骤均已实际跑通;完整的免责声明与 AI 说明放在文末。
起因:想自动填参数,结果卡在了密钥上
上一篇《本地 AI 电子元器件库存系统》里,我把库存系统做出来了,但录入依然靠手填。于是想加个功能:粘一个型号进去,参数、封装、数据手册自动填好。
第一反应是用立创开放平台的商城 API。申请很快,真要跑起来却卡在三件事上:签名很讲究(每个请求都要把方法、地址、时间戳、随机串、请求体拼成 5 行字符串,再用密钥算一个「防伪码」,少一个换行都会失败);失败提示不友好(凭证不对时,网关统一返回 404,看起来像地址写错,其实是在说「我不认识你」);文档只有签名规则、没有接口清单(每个接口要传什么、返回什么,得登录控制台才看得到)。
不过这条路我最后还是走通了(上一篇里的旧系统就是靠它入库的),只是过程比想象中绕得多。把当时踩到的坑列在这儿,免得你重走一遍:
- 接口只认「商品 ID」(itemId),不认立创编号(C 开头),也不能用型号或关键字查——编号和关键字我都试过,接口会返回 200,但业务结果是「找不到这个商品」;
- 当时对方接口 / 文档的地址似乎调整过,我这边状态一直不对,排查起来更费劲;
- 于是一颗料要走完整的一串:打开立创商城网页 → 搜到同款商品 → 复制商品页链接 → 回到程序里从链接提取商品 ID → 再调 API 拿参数。
能用,但每次入库都要多绕这几步——这也是我后来去找「不靠密钥、直接按型号或编号就能拿参数」的办法的直接原因。
正犯愁时突然想到:这些参数,立创自己的网页上本来就是公开的。网页能显示,数据就一定拿得到,只是走的不是那条「官方大门」。
一、资料从哪来:立创商城的公开页面
简单说,我用到的都是立创商城公开的物料资料,不需要申请开发者密钥:
| 想要什么 | 从哪儿来 | 入口 |
|---|---|---|
| 型号 → 立创编号 | 立创EDA 的公开搜索 | easyeda.com/api/eda/product/search |
| 编号 → 商品信息 | 立创EDA 的公开详情 | easyeda.com/api/products/<编号>/components |
| 参数、封装、规格书 | 立创商城商品页 | 页面里自带的 __NEXT_DATA__ 数据 |
补充一句:立创商城的商品页是提前在服务器上拼好的(为了让搜索引擎能收录),所以名称、品牌、参数表、图片、规格书地址这些内容,在页面打开之前就已经整块写进了 HTML——属于页面自带的公开内容,不需要登录,也不需要任何模拟操作。
二、那官方 API 为什么反而难用?
不是官方 API 不好,而是它面向「下单、查库存、拿批号」这类正式业务,安全要求高,门槛自然就高:
- 而且它认的是商品 ID 这类内部标识:拿立创编号或关键字去查,接口会返回 200,但业务上告诉你「找不到这个商品」(我当时就卡在这里)。
对个人做工具来说,这笔账很清楚:先用手边就能拿到的公开数据把功能跑起来;等真需要下单、需要权威库存了,再回头啃签名也不迟。
三、整理过程中踩到的两个小坑
第一个坑:商品 ID 不是立创编号。 一开始想当然地用编号去拼商品页地址,结果被跳到了 404 页面。正确顺序是:先用编号问立创EDA,从返回里拿到真正的商品 ID,再打开商品页。
第二个坑:数据手册的地址是「半截」的。 页面里存的是 /upload/public/pdf/... 这样的半截地址,直接拼主站域名,返回的却是一个网页而不是 PDF。试了几个域名,真正能下到文件的是 atta.szlcsc.com。
四、最后拿到手的数据长什么样
拿一颗常见的无线模块举例子(立创编号 C42411897):
| 项目 | 结果 |
|---|---|
| 型号 | E103-RTL8189FTV |
| 品名 | 2.4GHz 无线模块 |
| 品牌 | EBYTE(亿佰特) |
| 封装 | SMD,12×12mm |
| 分类 | WiFi 模块 |
| 最小包装 | 1000 圆盘 |
| 数据手册 | atta.szlcsc.com 上的 PDF |
参数表也能整段拿到:核心芯片 RTL8189FTV-VC-CG、支持协议 IEEE802.11b/g/n、接口类型 SDIO、工作电压、发射功率、接收灵敏度、工作温度……一共十几项,恰好是入库时最不想手打的东西。
五、自己动手:最小可运行实现
下面两份脚本都是能直接跑的完整代码:一个文件、一条命令,不装任何第三方库。Node 18 以上用 demo.mjs,Python 3.8 以上用 demo.py,输出一致——输入编号或型号都行。
Node 版(demo.mjs)
// demo.mjs —— 用法:node demo.mjs <立创编号 或 型号>
const UA = "my-bom-tool/1.0"; // 写上自己工具的名字即可,不需要伪装浏览器
const input = process.argv[2];
if (!input) {
console.error("用法:node demo.mjs <立创编号 或 型号>");
process.exit(1);
}
const getJson = async (url) => {
const res = await fetch(url, { headers: { "user-agent": UA } });
if (!res.ok) throw new Error(`${url} → HTTP ${res.status}`);
return res.json();
};
// ① 型号 → 立创编号(如果输入的就是 C 开头的编号,这一步可以跳过)
async function findCode(keyword) {
const data = await getJson(
`https://easyeda.com/api/eda/product/search?keyword=${encodeURIComponent(keyword)}&page=1&pageSize=10`,
);
const hit = data?.result?.productList?.[0];
if (!hit) throw new Error("没有找到这个型号");
return hit.number; // 形如 C42411897
}
// ② 编号 → 商品 ID(商品页地址用的是 ID,不是编号)
async function findProductId(code) {
const url = `https://easyeda.com/api/products/${code}/components?version=6.4.19.5`;
const info = (await getJson(url))?.result;
if (!info?.lcsc?.id) throw new Error("没有拿到商品 ID");
return info.lcsc.id; // 形如 44398166
}
// ③ 商品 ID → 参数(参数就在商品页的 HTML 里)
async function readItem(id) {
const res = await fetch(`https://item.szlcsc.com/${id}.html`, {
headers: { "user-agent": UA },
});
const html = await res.text();
const raw = html.match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/)?.[1];
if (!raw) throw new Error("页面结构可能变了");
const web = JSON.parse(raw).props.pageProps.webData;
const rec = web.productRecord ?? {};
const files = (rec.fileTypeVOList ?? []).flatMap((group) => group.detailVOList ?? []);
const pdf =
rec.pdfFileDetailVO?.fileUrl ??
files.find((file) => String(file.fileUrl ?? "").toLowerCase().endsWith(".pdf"))?.fileUrl;
return {
品名: rec.productName,
型号: rec.productModel,
品牌: web.brandVO?.brandName,
封装: rec.encapsulationModel,
分类: web.currentCatalog?.catalogName,
最小包装: `${rec.productMinEncapsulationNumber ?? ""}${rec.productMinEncapsulationUnit ?? ""}`,
数据手册: pdf ? `https://atta.szlcsc.com${pdf}` : "", // 注意这个域名
参数: (web.paramList ?? []).map((p) => `${p.parameterName}=${p.parameterDetailValue}`),
};
}
// 串起来
const code = /^c\d+/i.test(input) ? input.toUpperCase() : await findCode(input);
const id = await findProductId(code);
console.log({ 编号: code, ...(await readItem(id)) });node demo.mjs C42411897
node demo.mjs RTL8189FTVPython 版(demo.py)
#!/usr/bin/env python3
"""立创物料参数查询:输入型号或立创编号,打印品名/品牌/封装/参数/数据手册。
用法:
python demo.py C42411897
python demo.py RTL8189FTV
只用 Python 标准库,不需要安装任何依赖(Python 3.8+)。
"""
import json
import re
import sys
import urllib.parse
import urllib.request
UA = "my-bom-tool/1.0"
TIMEOUT = 15
SEARCH = "https://easyeda.com/api/eda/product/search"
DETAIL = "https://easyeda.com/api/products/{code}/components?version=6.4.19.5"
ITEM = "https://item.szlcsc.com/{id}.html"
PDF_BASE = "https://atta.szlcsc.com"
def get(url, accept="application/json, text/plain, */*"):
request = urllib.request.Request(url, headers={"User-Agent": UA, "Accept": accept})
with urllib.request.urlopen(request, timeout=TIMEOUT) as response:
return response.read().decode("utf-8", "replace")
def get_json(url):
return json.loads(get(url))
def find_code(keyword):
"""型号 -> 立创编号(C 开头)"""
payload = get_json(SEARCH + "?keyword=" + urllib.parse.quote(keyword) + "&page=1&pageSize=10")
hits = (payload.get("result") or {}).get("productList") or []
if not hits:
raise SystemExit("没有找到匹配「%s」的型号" % keyword)
first = hits[0]
return first.get("number") or first.get("mpn")
def load_item(code):
"""立创编号 -> 商品 ID + 型号(立创EDA 的公开详情)"""
payload = get_json(DETAIL.format(code=code))
result = payload.get("result") or {}
lcsc = result.get("lcsc") or {}
title = result.get("title") or ""
model = title.rsplit("_", 1)[0] if "_" in title else title
return str(lcsc.get("id") or ""), model
def load_detail(product_id):
"""商品 ID -> 商城商品页里自带的 __NEXT_DATA__"""
html = get(ITEM.format(id=product_id), accept="text/html")
matched = re.search(r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>', html, re.S)
if not matched:
raise SystemExit("商品页结构变了,没找到 __NEXT_DATA__")
web = json.loads(matched.group(1))["props"]["pageProps"]["webData"]
return web.get("productRecord") or {}, web
def main():
if len(sys.argv) < 2:
raise SystemExit("用法:python demo.py <型号或立创编号>")
keyword = sys.argv[1].strip()
code = keyword.upper() if re.fullmatch(r"C\d{3,}", keyword, re.I) else find_code(keyword)
product_id, model = load_item(code)
if not product_id:
raise SystemExit("立创EDA 没收录 %s,拿不到商品 ID" % code)
record, web = load_detail(product_id)
images = [x for x in str(record.get("luceneBreviaryImageUrls") or "").split("<$>") if x]
pdf = str((record.get("pdfFileDetailVO") or {}).get("fileUrl") or "")
if not pdf:
for group in record.get("fileTypeVOList") or []:
for file in group.get("detailVOList") or []:
if str(file.get("fileUrl") or "").lower().endswith(".pdf"):
pdf = str(file["fileUrl"])
break
rows = [
("立创编号", record.get("productCode") or code),
("品名", record.get("productName") or ""),
("型号", record.get("productModel") or model),
("品牌", (web.get("brandVO") or {}).get("brandName") or ""),
("封装", record.get("encapsulationModel") or ""),
("分类", (web.get("currentCatalog") or {}).get("catalogName") or ""),
("最小包装", "%s%s" % (record.get("productMinEncapsulationNumber") or "", record.get("productMinEncapsulationUnit") or "")),
("数据手册", PDF_BASE + pdf if pdf else "(无)"),
]
for name, value in rows:
print("%s: %s" % (name, value))
params = (web.get("paramList") or [])[:12]
if params:
print("")
print("参数:")
for param in params:
value = param.get("parameterDetailValue") or param.get("parameterValue") or ""
print(" - %s: %s" % (param.get("parameterName"), value))
print("")
print("主图: %s" % (record.get("breviaryImageUrl") or (images[0] if images else "(无)")))
if __name__ == "__main__":
main()python demo.py C42411897
python demo.py RTL8189FTV以 C42411897 为例,两份脚本打印出来的都是这些内容:
立创编号: C42411897
品名: 2.4GHz无线模块
型号: E103-RTL8189FTV
品牌: EBYTE(亿佰特)
封装: SMD,12x12mm
分类: WiFi模块
最小包装: 1000圆盘
数据手册: https://atta.szlcsc.com/upload/public/pdf/source/20250228/1570F1779EEBB0E83D0A90BA7B153B26.pdf
参数:
- 核心芯片: RTL8189FTV-VC-CG
- 支持协议: IEEE802.11b/g/n
- 接口类型: SDIO
- 频率: 2.484GHz
- 天线形式: 邮票孔
- 发射功率: 16dBm
- 工作电压: 3V~3.5V
- 接收电流: 79mA
- 发送电流: 319mA
- 接收灵敏度: -90dBm;-92dBm;-86dBm
- 工作温度: -20℃~+70℃三点提醒:请求头里写个自己的工具名(示例里的 my-bom-tool/1.0)就够了,不需要伪装成浏览器;要长期稳定地跑,把下一节那三件事补上;这是给个人工具用的思路,要做成对外服务或高频使用,请走官方开放平台的正式接口。
六、怎么让它长期稳定地跑?
「能拿到」和「能一直拿到」是两回事。我做了三件事:
- 尽量少请求:识别结果在本地缓存 90 天。参数、封装、手册几乎不会变(价格和库存会变,所以不拿它们当入库依据);同一颗料第二次识别直接命中缓存,一个请求都不发。
- 对上游客气一点:请求排队,间隔不小于 350 毫秒(相当于主动把节奏放慢);一次识别最多请求三次。
- 万一被限速,就等一等:遇到 403 / 429 / 503 自动冷却,5 秒 → 30 秒 → 5 分钟阶梯往后退,冷却期间旧的缓存照常可用。
顺便回答一个很多人关心的问题:会不会要求登录或者人机验证? 整条链路没有账号,对方的安全策略最多只能按 IP 限速——最坏的结果是「等一会儿再来」,不会封号。前提是别把它当批量刷数据的工具,正常个人使用完全没问题(「型号 → 编号」我用的是立创EDA 的搜索,和商城是同一套物料资料)。
七、最后的效果
现在我的库存系统里,入库弹窗顶部多了一个「识别」框:输入型号、立创编号或商品链接,都能自动获取并一键填入表单——料号、名称、规格(品牌 / 封装 / 用途)、备注(编号、商品 ID、最小包装、参数表、数据手册)全都自动带出来。要整理一批料的时候还有「批量识别」:把一列型号粘进去,它会排队一条条抓,带进度和结果列表,最后可以一键全部入库。
如果你也在做自己的元器件管理工具,希望这篇能帮你省下卡在密钥上的那几个小时。核心思路就一句话:优先用平台公开的物料资料,往往比走开发者接口更快拿到——但要记得对它友好一点,别高频请求。
⚠️ 免责声明:本文为个人学习笔记,按「现状」提供,不保证准确性、完整性与时效性,也不构成任何专业建议;文中代码与接口调用仅供个人学习与技术研究,禁止用于高频抓取、批量传播、商业用途或任何违法用途。您须自行遵守《中华人民共和国网络安全法》《数据安全法》《个人信息保护法》《著作权法》等法律法规,以及立创商城 / 立创EDA(嘉立创)的用户协议、robots 规则与使用条款;本文提及的平台、名称与商标归各自权利人所有,与本博客无任何隶属或合作关系。因使用本文内容产生的任何直接或间接后果(含设备损坏、数据丢失、账号风险与法律责任),均由使用者自行承担;此外,本站为个人非商业博客,全部内容免费分享、截至目前作者未因此取得任何收益或对价,作者与读者之间不构成任何服务或合同关系,使用风险与后果请自行承担。详见《免责声明》。
🤖 AI 生成说明:本文由 AI 辅助撰写与整理,虽已尽力人工核对、两份脚本也在本地实际运行验证过(Node 18+ / Python 3.14),但仍可能存在错误、遗漏或过时之处;示例数据取自立创商城的公开页面。采纳关键结论前请自行复核,如有疏漏欢迎指正。
本文配套的功能在自己的博客系统里实现(Next.js),入口是私有的;上面两份脚本都能独立运行,不需要我这边的东西。涉及的三方接口均为公开只读接口,请遵守对方的使用条款,不要滥用。
相关文章
评论
评论区由 giscus 提供(基于 GitHub Discussions),内容会保存在 GitHub 上;详见《隐私政策》。

微光录