ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Pythonr基于selenium如何实现不同商城的商品价格差异分析系统

Pythonr基于selenium如何实现不同商城的商品价格差异分析系统

前言


标题里的「Pythonr」是原文的笔误,正确写法是「Python」,但本文的标题按要求原样保留。比这个笔误更值得先说的,是这套需求本身的前提问题。


「用 Selenium 去各个商城抓商品价格,然后做价格差异分析」——这个设想看起来顺理成章,实际上前半段几乎行不通,而且不合规。原因有三层。第一层是登录墙:多数商城的商品详情、价格往往要求登录后才能完整查看,交易类数据更是绑定账号。第二层是风控与反自动化:平台普遍会识别自动化访问(行为特征、频率、会话校验),把它当成异常流量处理,轻则限流、返回不完整页面,重则封禁账号。第三层是合规:平台的服务条款通常明确禁止未经许可的自动化采集,你即使技术上做得到,也未必被允许做。


所以本文给出的正确路径是:只要平台提供官方开放平台 API,就通过它授权取数——申请开发者账号、按文档授权的接口、遵守它的调用频率与数据使用条款。这条路拿到的是结构化、稳定、合规的数据。如果拿不到授权,那就不做真实站点的采集,而是用你自己构造的本地数据,完整演示「价格差异分析」这后半段——多平台价格对齐、比价表、涨跌与离散度统计。本文全部基于本地构造数据,不含任何绕过登录、验证码、风控的做法。


先明确一件事:分析能力是可以独立练习和交付的,采集权限则取决于平台。把两者分开,需求就成立了。


一、两种取数路径,以及本文的取舍


把取数这件事的选择摊开看:




路径前提合规性本文是否演示



官方开放平台 API取得开发者授权高(按条款用)讲思路,不写具体接口

手工/人工整理的价格表你有权使用该数据取决于数据来源演示分析部分

自己构造的本地数据无无风险完整演示

直接抓商城页面——通常违反条款不做,仅说明为何不可行



本文选第三条:自己构造的本地数据。这不是退而求其次——价格差异分析的核心算法(对齐、比价、统计)与数据来自哪里无关。你在本地把方法跑通,将来真拿到官方数据,把数据源一换即可。反之,如果一开始就把精力花在「怎么把页面抓下来」上,方向就偏了。


那么 Selenium 在这套系统里还有什么正当位置?有的。当你对自己拥有管理权的内部系统做 UI 自动化(比如企业内部采购后台的价格核对),或者当你获得了平台明确许可做 UI 层操作时,Selenium 才派得上用场。它的角色是「驱动一个受你控制的浏览器」,不是「绕过别人的防护」。


二、数据结构设计:先想清楚「可比」


做多平台比价,第一个坎不是算法,而是商品的对齐:同一个商品在不同平台叫法不同、编号不同、规格单位不同。不解决对齐,价格根本没法比。


本文的数据结构设计成「一个商品在一个平台上的一条报价」,三个关键字段:


# 适用于 Python 3.8+
# 本地构造数据,不指向任何真实站点
SAMPLE_ROWS = [
{"sku": "SKU-1001", "platform": "平台A", "title": "无线鼠标 静音版",
"price": 79.0, "date": "2026-03-01"},
{"sku": "SKU-1001", "platform": "平台B", "title": "无线鼠标静音",
"price": 85.0, "date": "2026-03-01"},
{"sku": "SKU-1001", "platform": "平台C", "title": "静音无线鼠标",
"price": 76.5, "date": "2026-03-01"},
{"sku": "SKU-1002", "platform": "平台A", "title": "机械键盘 87键",
"price": 259.0, "date": "2026-03-01"},
{"sku": "SKU-1002", "platform": "平台B", "title": "机械键盘87键",
"price": 249.0, "date": "2026-03-01"},
]

关键是那个sku:它是你自己的对齐主键,把「无线鼠标 静音版」「无线鼠标静音」「静音无线鼠标」这三种叫法归到同一个SKU-1001名下。现实中这个对齐往往需要一张映射表(每个平台的关键词、规格到你的 sku 的对照),这正是比价系统里最费功夫、也最有价值的部分。


对齐的原则是用稳定的标识而不是模糊的标题匹配。标题匹配(去掉空格、统一大小写后比较)只能作为辅助,型号、规格、容量这些确定性字段才是主键。


三、价格对齐与比价表


对齐之后,把同一 sku 在各平台的价格汇总起来,就能算出一张比价表。


# 适用于 Python 3.8+
from collections import defaultdict

def build_price_table(rows):
"""按 sku 汇总各平台价格,返回 {sku: {platform: price}}"""
table = defaultdict(dict)
for r in rows:
# 同一 sku+平台 若有多条,取该日最新一条(示例按日期比较)
table[r["sku"]][r["platform"]] = r["price"]
return {sku: dict(plat) for sku, plat in table.items()}

def spans(table):
"""对每个 sku 计算最高价、最低价与价差"""
result = []
for sku, prices in table.items():
if not prices:
continue
lo = min(prices.values())
hi = max(prices.values())
lo_plat = [p for p, v in prices.items() if v == lo]
hi_plat = [p for p, v in prices.items() if v == hi]
result.append({
"sku": sku,
"最低价": lo,
"最低价平台": "/".join(sorted(lo_plat)),
"最高价": hi,
"最高价平台": "/".join(sorted(hi_plat)),
"价差": round(hi - lo, 2),
})
return result

if __name__ == "__main__":
t = build_price_table(SAMPLE_ROWS)
for row in spans(t):
print(row)

这张表就是「价格差异分析」最直接的产出:同款商品在哪个平台最便宜、差多少。注意处理「多个平台并列最低」的情况——用列表再拼接,而不是只取第一个,否则报表会漏掉并列的情况,这在选品决策里可能是要紧的信息。


四、涨跌与离散度统计


比价表只看「当前」,但价格是随时间变的。真正有分析价值的是趋势(涨跌)和离散度(各平台价格有多分散)。标准库statistics就能算均值和标准差。


# 适用于 Python 3.8+
import statistics
from collections import defaultdict

def price_volatility(rows):
"""按 sku 计算各平台价格的均值、标准差与极差"""
by_sku = defaultdict(list)
for r in rows:
by_sku[r["sku"]].append(r["price"])
out = []
for sku, prices in by_sku.items():
if len(prices) < 2:
# 样本不足,如实标注,别硬算标准差
out.append({"sku": sku, "样本数": len(prices),
"均值": prices[0] if prices else None,
"标准差": None, "极差": 0})
continue
out.append({
"sku": sku,
"样本数": len(prices),
"均值": round(statistics.mean(prices), 2),
"标准差": round(statistics.stdev(prices), 2), # 样本标准差
"极差": round(max(prices) - min(prices), 2),
})
return out

两个细节要点。第一,statistics.stdev()算的是样本标准差(除以 n-1),pstdev()才是总体标准差(除以 n);只有一条数据时无法计算标准差,应当返回None并如实标注样本不足,不要拿 0 冒充「分散度为 0」。第二,离散度大不等于有问题——它可能反映各平台促销节奏不同,也可能反映规格没对齐好。分析结论必须结合业务判断,不能只看一个数字下结论。


涨跌则要把同一 sku 在不同日期的价格排好序再比较:


# 适用于 Python 3.8+
def price_change(rows):
"""按 sku+平台 找出最早与最晚报价,计算涨跌"""
series = {}
for r in rows:
key = (r["sku"], r["platform"])
series.setdefault(key, []).append((r["date"], r["price"]))
changes = []
for (sku, plat), items in series.items():
items.sort(key=lambda x: x[0]) # 按日期升序
first_price = items[0][1]
last_price = items[-1][1]
changes.append({
"sku": sku, "平台": plat,
"首次": first_price, "最新": last_price,
"涨跌额": round(last_price - first_price, 2),
})
return changes

排序依据是日期字符串——只有当日期是YYYY-MM-DD这种字典序等于时间序的格式时才成立。如果用3/1/2026这种格式,字符串排序会出错,必须先转成datetime.date再比。


五、什么时候才轮到 Selenium


回到标题。如果你确实拿到了官方授权,或者操作的是你自己有管理权的内部系统,那么 Selenium 可以作为取数的一环:驱动一个受控浏览器,在你被允许的页面上取数据。此时的写法要守住几条:用显式等待而非硬睡、控制访问频率、只在你被授权的范围内操作。


但要注意:官方的开放平台 API 通常比 UI 自动化更适合取数——它返回结构化数据、有稳定的字段契约、有明确的频率说明,不像页面那样说改就改。所以即便有授权,优先级也应是「API 优先,UI 自动化兜底」。


至于「绕过登录墙、破解验证码、伪装正常用户来采集」,本文不做,也不建议做:技术上的短暂成功换来的是账号封禁与法律风险,性价比极低。


常见坑点



  • ❌ 以为「能打开页面就能抓到价格」,忽略了登录墙与风控的存在。


✅ 先确认平台是否提供官方 API;没有授权就不采,改用自有数据。



  • ❌ 用商品标题模糊匹配来对齐不同平台的同款商品。


✅ 用型号、规格等确定性字段做对齐主键,标题匹配只作辅助。



  • ❌ 只取第一个最低价平台,漏掉多个平台并列最低的情况。


✅ 收集所有并列项再展示,避免报表丢信息。



  • ❌ 只有一条价格就调statistics.stdev,或算不出就填 0。


✅ 样本不足时返回None并如实标注,不拿 0 冒充「无波动」。



  • ❌ 用非YYYY-MM-DD的日期字符串排序,涨跌算反。


✅ 先把日期转成datetime.date再排序比较。



  • ❌ 离散度一大就断定「平台在乱定价」,忽略规格未对齐或促销差异。


✅ 把统计量当线索,结合业务与数据质量一起判断。



  • ❌ 觉得「反正只是看看」,就去抓真实商城页面,触犯条款。


✅ 分析能力用本地数据练;真实数据只从官方授权渠道取。



  • ❌ 拿到官方数据接口后仍坚持用 Selenium 抓页面,舍近求远。


✅ API 优先,UI 自动化只在无 API 且有授权时兜底。


总结




环节推荐做法红线



取数官方 API 授权取数不绕登录/验证码/风控

无授权时用自己构造的本地数据不碰真实站点

对齐型号/规格做主键不靠模糊标题匹配

比价均价、极差、并列最低不臆造缺失值

趋势按日期排序算涨跌日期格式要可排序

Selenium自有系统或已授权场景只做受控、合规的操作



这个标题的两种前提——「合规取数」和「价格差异分析」——前者的可行性取决于平台授权,后者的能力可以立刻开始练。正确的做法是把它们拆开:分析部分今天就能用本地数据完整做出来(对齐、比价、涨跌、离散度),取数部分则老老实实走官方开放平台,拿不到授权就不做。守住这条线,系统才既站得住技术,也站得住合规。




RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进