ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

用Python爬取DigiKey元器件价格库存:从BOM清单到CSV的自动化方案

2026/9/2 1:34:51 拓冰建站 浏览量
用Python爬取DigiKey元器件价格库存:从BOM清单到CSV的自动化方案 简介digikey_webscraper 是一个基于 Python 的 Web Scraping 工具面向电子工程师与采购人员用于从 Digi-Key Electronics 自动提取元件价格、库存与属性等元数据能够替代人工逐页查询提升批量数据采集效率。压缩包共 2 个文件包含 1 个 Python 脚本和 1 个 HTML 文件整体仅 2KB。Python 脚本负责发送请求、解析 HTML 并提取目标字段HTML 文件则是抓取下来的页面结果或属性样本方便对照结构进行调试。该资源虽小却完整示范了 HTML 解析、requests 请求、异常处理与数据存储等爬虫核心流程尤其适合希望快速了解静态网页抓取思路、掌握 BeautifulSoup 选择器写法的入门开发者。目前已有 158 人学习下载对需要小体积参考案例或 Digi-Key 数据采集方案的读者具有直接借鉴价值。 刚把 BOM 表里 40 多颗料号核对完已经是晚上十一点。我盯着 CSV 里那几列价格和库存忽然意识到如果早点把这套 DigiKey 爬虫写出来这些活儿本来应该在一小时内收工。很多做硬件、做采购的朋友可能都有类似经历DigiKey 页面数据很全但靠人眼一页一页翻效率低到让人怀疑人生。所以我把这个项目取名 digikey_webscraper目的很纯粹——输入一份制造商料号清单自动从 DigiKey 拿到对应的产品链接、制造商信息、描述、价格区间和库存状态最后落地成 csv。这篇文章会把我的完整思路、页面分析过程、核心代码和踩过的几个坑都记录下来希望给想做同类数据采集的朋友省点时间。1. 为什么放着官方 API 不用非要自己写爬虫1.1 需求源头BOM 对比和成本估算最初触发这个项目的是供应链比价。我手头有一份十几页的元器件清单里面不只有 DigiKey 海外库存的料号还有一部分是 Mouser、Arrow 等渠道的备选。DigiKey 在这些渠道里往往是第一参考因为它页面信息最规范参数表、datasheet 链接、ROHS 状态都齐全。但问题是清单里 40 颗料逐个人工打开浏览器搜索光复制粘贴就够折腾更不用说还要记录价格和库存最后再填回表格。人一旦做这种机械重复的事肯定会有错漏不是看错价格区间就是把某个型号的库存数量抄串了。于是我想能不能写一个脚本输入料号输出一行结构化数据。这是 digikey_webscraper 最初的原型一个简单的“料号进、字段出”的查询工具。后来使用中发现光靠搜索不一定准同一个制造商料号在 DigiKey 上可能对应多个 DigiKey 零件编号还要处理淘汰料、停产料和各种描述差异。这些边界问题让爬虫的代码量从最初的五十行膨胀到三百多行但也正因为处理过这些细节现在跑起来反而更踏实。1.2 官方 API 的获取门槛和小团队的现实约束DigiKey 是有官方 API 的这个必须承认。但实际去申请过就会知道要做正式的 API 集成需要注册企业信息、走审批流程、配置 OAuth并且调用频率、数据范围也有严格限制。对于我这种只需要偶尔拉一批库存和价格、想快速验证原型的小项目来说这个前置成本确实有点高。相比之下页面抓取的核心优势不是“绕过限制”而是零门槛快速启动。网站公开的产品页本身就包含了用户能看到的全部字段我只需要按普通用户的访问节奏去读取不涉及任何超出公众浏览范畴的操作。这种轻量级方案尤其适合一次性 BOM 核对、产品选型小样本分析、教学实验、内部工具的数据源验证。当然如果后续需求升级成每天的定时全量同步我还是会建议老老实实去对接官方接口这个取舍后面还会再展开。2. 动手前先把 DigiKey 站点的“脾气”摸清楚2.1 列表页和详情页的结构差异DigiKey 的搜索和产品详情是两套完全不同的页面逻辑。搜索列表页 URL 形如https://www.digikey.com/en/products/filter/...后面跟很长一串筛选条件用户点击的每个筛选器都会反映在 URL 参数里。列表页的核心价值是“从关键词到结果候选”每个条目包含 DigiKey 零件编号、制造商料号、简短描述、最小起订量和价格区间但这里的价格只是一个大概值而且通常不是整页的全部数据。详情页才是真正的高价值页面URL 形如https://www.digikey.com/en/products/detail/制造商名/制造商标识/零件编号数字串。这里能看到完整的参数表格、分类属性、环境与出口分类甚至还有一些 JSON-LD 结构化数据。对于爬虫来说详情页的 JSON-LD 是天然的解析入口里面塞满了name、manufacturer、description、sku、url这些字段比硬抠 HTML 的 CSS 类名稳定得多。我第一次解析时把重点放在表格的tr上结果因为某些产品自定义属性存在差异经常拿不到想要的参数后来改成优先读 JSON-LD再用表格补漏成功率才提上来。2.2 库存和价格的真实加载方式难点不在列表页而在详情页的动态数据。DigiKey 的商品库存数量并不是老老实实写在初始 HTML 里的页面加载时会向后端独立的接口发请求拿回一段 JSON再把“实时库存”和“单价区间”渲染到对应区域。这意味着直接用 requests 拿到的 HTML 源码里库存栏往往只有空壳或者一个“登录后可查看实际库存”的提示。我当时用浏览器开发者工具里的网络面板抓了一番很快确认了这个机制。这时候有两个选择一是上 Playwright 这类浏览器自动化工具真实渲染页面等网络请求完成再快照页面二是找到页面内部使用的那个 JSON 接口自己拼参数去请求。考虑到项目只做小批量查询我最后选择了“requests 拿静态内容 少量 JSON 接口补动态数据”的组合既拿到了价格和库存又不用为了几百条数据去维护一套浏览器进程。这个方案更轻但要求必须看清每个请求的参数结构实践下来对调试能力有一定考验。2.3 robots.txt 与请求频率的底线写任何爬虫第一件该看的永远是robots.txt。DigiKey 的 robots 规则对公共产品页是允许常规抓取的但明确限制了某些路径和频率。我给自己定的规矩很简单单线程、顺序请求、每两个请求之间至少间隔 1 秒。听起来保守但对几十颗料号来说整个流程也就几十秒完全够用。实在有大批量需求应该去申请 API而不是把抓取频率拉满。这里我想多说一句。爬虫最容易踩的线不是技术而是“贪”。有人觉得把并发拉到 10、20 个线程几分钟就能刷完上万条数据结果换来的是 IP 被临时限制、验证码弹窗甚至账号封禁。其实 DigiKey 对请求频率很敏感频繁访问之后即使不加任何代价光验证码就会把你的流程彻底打乱。正确做法是在脚本里内置延迟并把请求失败后的重试间隔设置成指数退避。这个设计不是对网站“示弱”而是保证你的爬虫能长时间稳定运行。3. 写一个能稳定跑完一轮的抓取主循环3.1 技术选型requests BeautifulSoup 就够别过度设计项目初期我评估过 Scrapy 和 Playwright。Scrapy 确实功能全但对于只需要处理一个站点的轻量任务它的事件框架和中间件配置反而显得笨重。Playwright 能解决动态渲染可每次跑任务都要拉起一个浏览器内存和稳定性都是额外负担。最后我选了requestsBeautifulSoup的组合理由很简单DigiKey 的关键静态数据结构还算规整动态部分可以用接口补齐没必要上重型工具。代码如下先做一个带重试机制的会话封装import requests import time from bs4 import BeautifulSoup from urllib.parse import urljoin HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } class DigiKeySession: def __init__(self, delay1.0, retries3): self.session requests.Session() self.session.headers.update(HEADERS) self.delay delay self.retries retries def get(self, url): for attempt in range(self.retries): try: resp self.session.get(url, timeout15) resp.raise_for_status() return resp except requests.RequestException as e: print(f[retry {attempt1}] {url} - {e}) time.sleep(self.delay * (2 ** attempt)) return None3.2 搜索入口用 URL 直达搜索结果输入制造商料号后最简单的入口是直接请求 DigiKey 的搜索页URL 形如base_search_url https://www.digikey.com/en/products/result?keywords{mpn}这里有个小技巧如果搜索词精确匹配且结果唯一DigiKey 有时会直接 302 跳转到详情页如果匹配不唯一则停留在列表页。所以函数里必须同时处理“重定向后的详情页”和“包含多个候选的列表页”两种情况。def search_mpn(session, mpn): url fhttps://www.digikey.com/en/products/result?keywords{mpn} resp session.get(url) if not resp: return None # 重定向后 URL 包含 /detail/ 说明直接到了详情页 if /products/detail/ in resp.url: return resp.url # 否则解析列表页找第一个产品链接 soup BeautifulSoup(resp.text, html.parser) link soup.select_one(a[href*/products/detail/]) if link: return urljoin(url, link.get(href)) return None3.3 详情页解析从 JSON-LD 拿主字段表格只作补充详情页的解析我强烈建议优先取script typeapplication/ldjson里的数据。这个脚本块里的字段名相对稳定且经过 JSON 规范化比解析 HTML 表格更抗页面改版。拿到 JSON-LD 之后再解析厂商、料号、描述、URL 等基础信息。import json def parse_detail_html(html, product_url): soup BeautifulSoup(html, html.parser) data {} # 优先从 JSON-LD 提取 for script in soup.find_all(script, typeapplication/ldjson): try: obj json.loads(script.string) if not isinstance(obj, dict): continue if sku in obj or name in obj: data[mpn] obj.get(sku) or obj.get(mpn) data[name] obj.get(name) data[description] obj.get(description) data[manufacturer] (obj.get(brand) or {}).get(name) break except (json.JSONDecodeError, AttributeError): continue # 表格补充字段分类、包装、工厂库存等 for row in soup.select(table tr): cells row.find_all([td, th]) if len(cells) 2: key cells[0].get_text(stripTrue) value cells[1].get_text(stripTrue) if key and value: data[key] value data[url] product_url return data这样得到的data字典里会有大量字段后续可以直接转成 pandas DataFrame 或者写入 CSV。需要注意的是某些参数表格里同一行可能包含多个值比如封装编码会有“Digi-Key 零件编号”和“制造商标准封装”两列所以我统一用“键-值”模式存储宁可拍到多列也不要错过任何信息。3.4 断点续跑与增量更新一次性抓取几百条数据还好但如果中途超时中断从头再来就很浪费时间。我给脚本加了非常轻量的断点机制用一个done.txt文件记录已经成功的料号下次启动时自动跳过同时把失败料号记录到failed.txt。def load_done_set(pathdone.txt): try: with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) except FileNotFoundError: return set() def mark_done(path, mpn): with open(path, a, encodingutf-8) as f: f.write(mpn \n)主流程循环里每次请求之间主动调用time.sleep(session.delay)这样即使单线程跑完整个清单也不会触发服务器的频率限制。抓取完成后再统一写 CSV用 DataFrame 的to_csv指定utf-8-sig编码方便 Excel 直接打开不乱码。4. 实测一轮完整抓取那些文档里不会写的坑4.1 库存数据真的不在初始 HTML 里我第一版爬虫跑完后发现导出的 CSV 里库存列基本是空的。排查了很久才意识到详情页里那个“库存数量”虽然肉眼可见但它不是请求 HTML 时一并返回的而是浏览器异步向内部接口请求后填充的。定位到具体的 JSON 接口后问题又来了接口返回的字段名很精简不像 JSON-LD 那么直观而且有些字段直接从 URL 参数里带过来的认证信息经过编码自己拼请求很容易拼错。最后我选择了一个折中方案把库存数量当作二级字段能获取到就填获取不到就留空并打日志同时用页面上的“在售/停产/过时”这样的状态字段兜底。这个状态字段对采购决策同样有参考价值比强求一个精确数字更有意义。4.2 价格要抓“数量阶梯价”而不是单一低价首版脚本抓到的最小起订量价格是没错的但做成本估算时发现批量采购价格和单颗价格差很多如果只取最小价格BOM 估算会偏乐观。DigiKey 的价格表是按“1-99、100-499、500-999”这样的数量区间展示的每个区间对应不同单价。解析这种阶梯价要做一点小处理把表格里文本形式的区间拆成最小数量和对应价格。我的做法是正则匹配数字区间存成一个列表写入 CSV 时按“json 字符串”序列化到单列里方便后续读取判断。import re def parse_price_tiers(rows): tiers [] pattern re.compile(r([\d,])\s*-\s*([\d,])|\\s*([\d,])) for row in rows: cells [c.get_text(stripTrue) for c in row.find_all([td, th])] if len(cells) 2: m pattern.search(cells[0]) if m: low int((m.group(1) or m.group(3)).replace(,, )) tier {min_qty: low, price: cells[1]} tiers.append(tier) return json.dumps(tiers, ensure_asciiFalse)4.3 重定向和失效页面的处理DigiKey 搜索结果里有点特殊的情况某些料号搜索后并不能得到稳定的详情页而是被重定向到“替代产品”或“类似产品”。如果你直接使用重定向后的 URL很可能会抓到一颗和你原始意图完全不同的料。这时必须做一层校验把返回页面的sku字段与输入料号做模糊匹配如果不一致宁可标记为mismatch也不要把它当成正确结果写进 CSV。这种校验逻辑看起来简单却能在实际项目中救你一命。我曾经跑过一批很老的停产料DigiKey 列表页前三位都是替代料如果不做检查整张表都会变成错误的“替代对照表”。加了校验后我能准确区分“找到原料”和“找到替代料”再交给下游用户判断数据的可信度立刻不一样。5. 不把站点搞挂的自觉合规和边界5.1 robots.txt 与频率的底线DigiKey 的 robots.txt 本身不是写着“Disallow: /”那么一刀切它允许公共产品路径的抓取但也明确限制了部分内部路径。我的原则是只访问公开可见的列表页、详情页和必要的数据接口绝不触碰任何需要登录、需要额外鉴权的资源。对于请求频率我坚持单线程 1 秒以上的间隔。这个参数虽然慢但是可持续。真实世界里很多爬虫跑崩不是功能 bug而是访问量太大触发了防护所以这个“慢”是很划算的。5.2 官方 API 能解决的需求就不要再重复造轮子如果项目的核心需求不是“一次性比对”而是要长期、系统性地获取 DigiKey 数据那么老老实实申请官方 API 是正确的选择。官方 API 提供更完整的数据字段、更规范的请求接口和有保障的服务质量。自己写爬虫的隐性成本不只是开发和调试还有网站改版后你随时要跟进维护。DigiKey 几乎每次前端发布都会调整部分 DOM 结构哪怕只是 CSS 类的改变也会让你的选择器失效。所以我的建议是先评估数据量和使用频率如果只是个人小项目爬虫方案足够如果是团队基础设施API 才是稳定解。5.3 我后来对爬虫架构做的调整项目跑通之后我还做过一轮优化。首先是把所有请求函数从“查询即返回”改成“先缓存在内存里最终统一落盘”避免 CSV 反复打开关闭。其次是增加随机延迟的偏移量比如基础延迟 1 秒再随机加 0.2 到 0.5 秒让请求节奏更贴近自然浏览。最后是增加了请求结果的摘要打印每处理一个料号都输出状态和耗时这样长时间跑批时能肉眼看到进度而不是对着空白终端干等。这些都是小细节但叠加起来效果显著。最终这套脚本耗时从最初的每次抓取都有人盯着变成现在后台挂机就能跑完一份 BOM 清单输出结果也稳定可靠。把机械劳动交给代码之后省下来的时间拿去核对参数、联系渠道、和供应商沟通才是这个爬虫真正值回票价的地方。本文还有配套的精品资源点击获取