电商数据抓取最容易被低估的,不是请求能不能发出去,而是抓回来的字段能不能在三个月后继续解释。很多团队一开始只有一张商品表:标题、价格、库存、规格、促销全部往里塞;等到新增平台、增加历史价格、支持 SKU 对比时,表里会出现多个含义不清的 price、类型反复变化的 stock,以及没人敢修改的 sku_info。这时真正卡住开发进度的,通常不是 MySQL、文档数据库或数据仓库选错了,而是原始数据、标准字段和业务结果从一开始就没有分层。
电商数据抓取:开发人员问题诊断:字段设计卡在存储混乱怎么办
我在处理电商采集系统时,最常见的误判是把“数据落库困难”归因于数据库不够灵活。开发人员发现不同平台返回的数据结构不一致,就考虑把关系型数据库换成文档数据库;发现字段变化频繁,又把整份接口响应直接存成 JSON。这样做确实能缓解初期建表压力,但并没有解决字段语义冲突。
例如,平台 A 的 price 是当前最低 SKU 价格,平台 B 的 price 是商品展示区间,平台 C 的 price 可能还是未叠加优惠券的售价。如果这些值都直接映射到内部字段 current_price,数据库表面上统一了,业务口径实际上更乱了。
我的核心判断是:字段设计要先解决“它代表什么”,再解决“它存在哪里”。一个字段至少要能回答五个问题:来源是什么、业务含义是什么、数据类型是什么、时间口径是什么、空值意味着什么。答不清这五个问题时,继续增加字段只是在扩大技术债务。
原始层保存平台返回的原始响应、页面解析结果或接口快照,目的是保留证据和回溯能力。标准层将跨平台都需要使用的字段转换成统一语义,例如商品名称、来源商品 ID、标准价格、库存状态和采集时间。业务层则保存面向报表、预警、搜索或推荐的计算结果。
这三层并不是把同一份数据无意义地复制三遍,而是让不同变化速度的数据各自承担职责。平台页面结构变化时,主要影响原始层到标准层的解析逻辑;运营报表口径变化时,主要影响标准层到业务层的计算逻辑;原始数据仍然保留,便于重新解析和核对。
| 数据层 | 主要回答的问题 | 适合保存的内容 | 不适合承担的职责 |
|---|---|---|---|
| 原始层 | 平台当时返回了什么 | 原始 JSON、页面片段、请求结果、解析版本、抓取时间 | 直接作为所有业务报表的唯一数据源 |
| 标准层 | 不同平台的数据如何统一解释 | 商品、SKU、价格、库存、店铺、类目等标准字段 | 承载所有平台特有的临时字段 |
| 业务层 | 当前业务需要如何使用这些数据 | 价格趋势、缺货预警、平台对比、经营指标 | 反向覆盖原始值和标准值 |
字段字典不是文档部门的形式工作,而是数据系统中的“接口协议”。我建议核心字段至少记录字段名、定义、来源、类型、单位、是否可空、更新方式、清洗规则和责任人。
例如,不能只写“价格:商品价格”。更准确的定义应该是:“以人民币分为存储单位、针对单个 SKU、未叠加平台券、在本次抓取时间点可观察到的销售价格”。如果业务还需要券后价,就应新增明确的价格类型,而不是把两种数值都写入同一个字段。

电商页面看起来像是在展示一个商品,数据库却需要表达至少三类对象。商品层回答“这是什么”,例如标题、品牌、类目和商品详情;SKU 层回答“具体卖的是什么规格”,例如黑色、500 克、128GB;动态状态层回答“现在是什么状态”,例如当前价格、库存、促销和配送信息。
把这三类信息全部放在商品表里,最初可能只有十几列,后续很快会出现重复行。一个商品拥有六个 SKU 时,如果每个 SKU 有独立价格和库存,商品标题、品牌和详情就会被重复存储六次。更新商品标题时,开发人员还要面对重复记录、唯一键冲突和部分更新失败。
更严重的是,商品信息通常相对稳定,价格和库存却持续变化。若商品主表只保留一行当前状态,历史价格便会被覆盖;若每次抓取都新增一行,商品基础信息、SKU 信息和状态记录又会重复。这不是简单的“要不要加历史表”,而是静态实体和动态事实没有拆开。
跨平台抓取时,字段名称相同并不等于语义相同。常见的 stock 可能表示真实库存数量,也可能只表示“有货”或“无货”;sales 可能是累计销量、近 30 天销量,也可能是页面展示的模糊区间;brand 可能是品牌名称,也可能是品牌 ID。
如果标准化过程只做字段改名,不做口径确认,就会产生一种危险的“统一假象”。所有平台都有 current_price,但有的含税、有的不含税;有的含促销、有的不含促销;有的针对最低价 SKU、有的针对当前选中 SKU。字段名统一了,数据却不能直接比较。
规格是电商数据中最容易被直接塞进字符串的区域。开发人员常见的处理方式是把规格数组拼成“黑色/M/标准版”,或者把整个规格对象放进 sku_info 字段。这样做便于展示,却不利于去重、查询和跨平台匹配。
规格至少需要区分属性名称、属性值、是否影响 SKU、平台原始顺序和标准化后的顺序。颜色、尺寸、容量通常会影响 SKU;材质、适用人群可能只是商品属性。两者都叫“属性”并不意味着应该以同样方式存储。
页面上的价格和库存并不是脱离上下文的常量。不同地区可能显示不同配送信息,登录状态可能影响优惠价格,抓取时间不同也可能得到不同库存状态。因此,动态字段至少要关联来源平台、来源商品或 SKU、采集时间和抓取任务。
如果只有一个模糊的 update_time,后续很难判断它代表页面更新时间、商品更新时间、数据库更新时间,还是本次抓取完成时间。时间字段名称越模糊,数据出现异常时越难排查。

现实中需求很少真正稳定。第一个平台只需要商品名称和价格,第二个平台增加规格层级,第三个平台返回促销门槛,运营部门又要求保留历史价格。每增加一次平台,主表就增加一组平台字段,最后出现 platform_a_price、platform_b_price、platform_c_price 这样的结构。
这种设计的问题不是字段多,而是来源逻辑进入了业务实体。未来增加平台时,开发人员不仅要写解析器,还要修改表结构、同步修改查询、更新报表和补充迁移脚本。平台下线后,废弃字段又很少被清理,因为没人能确认历史查询是否依赖它。
更稳妥的做法是保留“来源平台”作为数据维度,而不是把平台名称写进字段名。一个标准价格记录可以通过 source_platform、source_item_id、sku_id 和 price_type 表达来源,不需要为每个平台增加一列。
JSON 适合解决结构不稳定的问题,但它不能代替数据模型。把标题、品牌、当前价格、库存数量和促销对象全部塞进 JSON 后,初期确实不需要频繁改表;但到了报表阶段,查询会变成大量路径表达式和类型转换。
例如,一个平台把库存返回为数字,另一个平台把库存返回为“有货”,第三个平台用布尔值表示是否可售。如果全部保存在 JSON 中,后续统计必须先判断类型,再判断取值含义。数据质量规则也无法简单地通过数据库约束完成。
我的经验是:越是高频查询、需要聚合、需要唯一性约束的字段,越应该结构化;越是平台特有、低频使用、暂时无法解释的字段,越适合保留在 JSON 或原始对象中。
一个商品页面可能同时展示原价、销售价、活动价、会员价、券后价和 SKU 价格。开发人员如果只保存一个 price,必须在代码里隐含一个选择规则。规则一旦没有写入字段字典,后续每个报表、接口和脚本都可能采用不同的解释。
价格记录至少应包括价格类型、金额、币种、适用对象、采集时间和促销上下文。对于 SKU 级价格,还需要说明该价格是否继承商品层价格,还是由 SKU 独立决定。
| 原始字段示例 | 不能直接推断的内容 | 建议的标准字段 | 需要补充的定义 |
|---|---|---|---|
| price | 是最低价、当前选中价还是区间起始价 | amount、price_type | 价格口径、币种、适用层级 |
| original_price | 是否为真实成交前价格 | list_price | 划线价的来源和展示规则 |
| coupon_price | 是否所有用户都能获得 | effective_price | 优惠条件、有效期和计算公式 |
| sku_price | 是否对应当前选中的规格 | sku_id、amount | SKU 关联和采集上下文 |
覆盖写很适合展示“现在是什么”,却不适合回答“什么时候发生了变化”。如果每天抓取一次价格,只更新商品表中的当前价格,那么降价持续了几天、哪次抓取发现变化、价格是否因为解析失败变成零,都无法还原。
我通常会把当前状态和历史快照分开。当前状态表服务于高频读取,快照表服务于趋势分析、异常核对和审计。两者可以通过任务 ID 和采集时间关联起来,不需要让所有业务查询都扫描全量历史。

实体字段描述相对稳定的对象,例如商品名称和品牌;事实字段描述某个时间点发生的状态,例如某个 SKU 在某次抓取时的价格;计算结果则是根据事实加工出来的指标,例如近 30 天最低价。
三者混在一起会造成数据回写。比如把“近 30 天最低价”写回商品主表后,随着时间窗口变化,它既不像商品属性,也不像某次抓取事实。正确做法是保留价格事实,再由查询或业务层计算最低价。
如果字段只用于展示原始页面,就可以保留平台原始名称和结构;如果字段需要跨平台比较,就必须先统一口径。统一口径不等于统一字段名,而是统一单位、时间范围、适用对象和计算规则。
例如“销量”要先确认是累计销量还是周期销量,“库存”要先确认是数字库存还是可售状态,“价格”要先确认是否包含优惠。无法确认时,不要强行写入标准字段,可以先保留原始值,并将标准字段标记为“不确定”或暂不生成。
高频变化但低频查询的字段,可以采用批量快照和离线处理;低频变化但高频查询的字段,应结构化并建立索引;高频变化且高频查询的字段,通常需要当前状态表和历史事实表并存。
不要只根据字段大小选择存储方式。一个只有几个字节的库存状态,如果每次都被高频查询并参与预警,它的设计优先级可能高于几十 KB 的商品详情。
商品与 SKU 的关联、来源商品 ID 的唯一性、价格记录与抓取任务的关系,都属于结构化约束。若这些信息只放在 JSON 中,重复数据和孤儿记录很难在写入阶段被阻止。
可以把商品、SKU、来源映射、价格快照和库存快照放在关系表中,把平台特有的展示文案、标签数组和未确认字段放在 JSON 中。这样既保留灵活性,又不会牺牲核心数据的一致性。
一条标准价格记录至少应该能追溯到来源平台、来源商品 ID、来源 SKU ID、抓取任务 ID、采集时间和解析器版本。缺少其中任意一项,排错都可能从“查看一条记录”变成“重新抓取整个页面”。
我建议把解析器版本写入标准记录,而不是只写在代码仓库里。字段规则发生变化后,同一商品在不同时间可能由不同版本解析得到,版本号可以帮助判断数据口径是否发生了变化。
这是评估模型韧性的关键问题。理想情况下,平台返回字段变化只影响原始层到标准层的转换;标准字段仍然保持稳定,业务报表不需要逐个修改。如果平台字段直接等于内部字段,平台结构变化就会一路传导到数据库、接口和报表。
| 判断问题 | 如果答案为“是” | 设计倾向 | 典型示例 |
|---|---|---|---|
| 是否需要高频查询或聚合 | 是 | 结构化字段、明确类型、建立索引 | 当前价格、库存状态、商品 ID |
| 是否平台特有且低频使用 | 是 | 原始 JSON 或扩展字段 | 页面标签、平台专属营销文案 |
| 是否随时间持续变化 | 是 | 快照表、事件表或带版本的记录 | 价格、库存、促销状态 |
| 是否需要跨平台对比 | 是 | 标准字段、单位和口径映射 | 统一币种后的销售价 |
| 是否暂时无法解释 | 是 | 先保留原始值,不急于进入标准层 | 未知活动规则、未确认的库存字段 |
下面是一种非常常见的初始结构。它并不意味着开发人员能力不足,而是项目在快速验证阶段优先追求了抓取结果可见。问题通常在数据规模扩大和业务需求增加后才暴露出来。
product_table
id
title
brand
price
old_price
coupon_price
stock
sku_info
promotion
platform_a_price
platform_b_price
update_time
source_url
这张表至少有五个结构问题。第一,商品与 SKU 没有拆分;第二,三种价格没有价格类型和适用条件;第三,库存只有一个模糊字段;第四,平台差异直接进入业务主表;第五,更新时间没有说明是抓取时间还是数据生效时间。
如果把平台 A 和平台 B 的价格都写在同一行,跨平台商品匹配就被迫依赖表结构;如果一个商品有多个 SKU,sku_info 只能通过字符串或 JSON 隐含表达;如果价格发生变化,覆盖写又会丢失历史。
更稳妥的模型可以拆成商品、来源映射、SKU、价格快照、库存快照、原始记录和抓取任务七类对象。它不一定要求一次性建立七张物理表,但逻辑职责应当分开。
product
product_id
product_name
brand_name
category_id
created_at
updated_at
source_product
source_product_id
platform_code
source_item_id
source_url
product_id
sku
sku_id
product_id
source_sku_id
sku_code
attribute_json
price_snapshot
price_snapshot_id
sku_id
price_type
amount
currency
captured_at
crawl_task_id
inventory_snapshot
inventory_snapshot_id
sku_id
availability_status
quantity
captured_at
crawl_task_id
raw_record
raw_record_id
platform_code
source_item_id
parser_version
payload_uri
captured_at
quality_status
crawl_task
crawl_task_id
request_url
started_at
finished_at
response_status
parser_version
这里的关键不是表名,而是职责边界。product 代表内部识别的商品,source_product 代表平台来源映射,sku 代表可售规格,价格和库存则以带时间的快照存在。原始记录保存平台事实,抓取任务保存数据如何产生。
规格是一个适合“结构化核心关系加 JSON 扩展”的场景。SKU 的标准唯一性、来源 SKU ID 和商品关联应当结构化;平台返回的规格顺序、展示文案和未映射属性可以放在扩展对象中。
例如,以下数据可以作为原始或扩展属性保存,但不能直接用整段字符串完成 SKU 去重。
{
"source_sku_id": "A-7788",
"attributes": [
{"name": "颜色", "value": "深灰"},
{"name": "容量", "value": "512GB"}
],
"display_text": "深灰 / 512GB",
"available": true
}跨平台匹配时,应当先把属性名称映射到内部字典,再按照稳定规则排序。例如“颜色=深灰、容量=512GB”与“容量=512GB、颜色=深灰”应被视为同一组规格;但如果一个平台把“套装数量”隐藏在标题中,不能因为文本相似就直接认定为同一 SKU。
价格快照的粒度需要根据业务决定。如果业务只分析商品级最低价,可以保留商品级价格,但必须记录这是最低价口径;如果业务需要比较不同规格,就应下沉到 SKU。库存也一样,数字库存、可售状态和预售状态不能强行压缩成一个字段。
| 数据对象 | 推荐保存方式 | 最低必要字段 | 主要风险 |
|---|---|---|---|
| 商品名称 | 商品主表当前值,必要时保留变更记录 | 商品 ID、名称、来源、更新时间 | 标题变更后无法识别历史版本 |
| SKU 规格 | SKU 关联表加结构化属性 | SKU ID、商品 ID、属性组合、来源 SKU ID | 规格顺序变化造成重复 SKU |
| 价格 | 当前状态加价格快照 | 金额、类型、币种、SKU、采集时间 | 不同价格口径被混为一谈 |
| 库存 | 当前状态加库存快照或状态事件 | 可售状态、数量、区域、采集时间 | 把“未知”误判成“无货” |
| 原始响应 | 对象存储或原始记录表 | 任务 ID、来源、解析版本、存储地址 | 出错后无法重新解析 |
如果团队使用九数云进行经营分析或可视化,可以把它放在标准数据层之后,作为分析与展示环节,而不是让采集程序直接按照报表图表来设计商品表。官网信息可参考:九数云。
在一个典型的电商监测场景中,采集系统负责产生标准商品、SKU、价格和库存数据;数据同步层负责清洗、去重和补充日期维度;分析平台再消费这些结果,形成价格波动、缺货商品、平台差异和类目趋势等指标。
这样做有一个容易被忽略的好处:报表需求变化不会直接推动采集库加字段。比如运营后来需要“近 7 天最低价”“最近一次有效库存”“价格变动次数”,这些属于业务计算指标,应通过数据模型或分析层计算,不应把每个指标都写回商品主表。
需要注意的是,九数云这类分析平台不能替代原始数据存储,也不能自动修复抓取字段口径。若输入数据把活动价和销售价混在一起,图表只会更快地展示错误。分析平台解决的是观察和决策效率,字段治理解决的是数据能否被正确解释。

重构前先把旧表中实际存在的字段和样例抽出来。只看建表语句是不够的,因为同一个字段可能已经被写入多种类型。比如 stock 在表结构中是字符串,实际数据里同时存在“有货”“0”“12”“暂不可售”和空字符串。
字段盘点建议至少包括以下内容:
这一步经常能发现一些“看起来没有问题”的字段其实已经被多个业务部门以不同方式解释。重构时,真正需要优先解决的不是使用次数最多的字段,而是使用范围广、定义冲突大、错误后影响大的字段。
不是每个旧字段都需要原样迁移。可以把旧字段分成四类:保留字段、映射字段、拆分字段和废弃字段。保留字段通常是含义清楚且仍然有业务价值的字段;映射字段是名称不规范但可以转换的字段;拆分字段是一个字段包含多个业务概念;废弃字段则是重复、无来源或已无使用场景的字段。
| 处理类型 | 适用情况 | 处理动作 | 注意事项 |
|---|---|---|---|
| 保留 | 定义明确、来源稳定、仍被使用 | 迁移到标准表并补充字段说明 | 不要因为旧字段名不漂亮就直接重建含义 |
| 映射 | 字段含义基本明确,但名称或类型不统一 | 通过转换规则生成标准字段 | 记录转换失败和异常值 |
| 拆分 | 一个字段包含多个概念或多个层级 | 拆成实体、事实、时间或来源字段 | 先确认旧值能否可靠拆解 |
| 废弃 | 重复、无来源、无查询用途或长期为空 | 停止写入,观察依赖后下线 | 保留迁移记录,避免误删历史证据 |
映射表是迁移的核心控制文件。它不只记录“旧字段对应新字段”,还应记录转换逻辑和无法迁移的情况。下面是一个简化示例:
| 旧字段 | 新字段 | 转换规则 | 异常处理 |
|---|---|---|---|
| title | product_name | 去除 HTML、首尾空格和重复空白 | 为空时进入质量异常队列 |
| price | price_snapshot.amount | 按来源规则判断价格类型和币种 | 区间价格不直接写入单值金额 |
| sku_info | sku.attribute_json | 解析规格名称和值,生成稳定排序 | 无法解析的原文保留在 raw_record |
| stock | inventory_snapshot.status 或 quantity | 数字和状态分别处理 | 未知不等于无货,禁止默认转为 0 |
| update_time | captured_at 或 updated_at | 根据写入流程区分采集和数据库更新时间 | 无法确认时保留旧字段并标记口径未知 |
对于已经服务线上业务的系统,我不建议直接停机重建。更安全的顺序是保留旧表、建立标准表、开发转换程序、回填历史数据、进行一段时间双写,然后对比新旧查询结果,最后再切换接口。
双写期间最容易出现的问题是两套逻辑各自演进。为避免这种情况,建议让解析器只产生标准中间对象,再由统一落库程序分别写入新旧结构。不要让不同业务脚本各自解析一遍页面。
字段重构后,如果没有质量校验,系统仍可能把解析错误写入标准表。至少需要检查必填字段、类型、金额范围、SKU 关联、采集时间、来源 ID 和状态转换。

如果项目只有一个来源平台,每天抓取量不大,业务只关心当前商品列表,可以采用相对简单的关系表加原始 JSON。商品、SKU、当前价格和库存仍建议分开,但不必一开始就建立复杂的数据仓库。
这类场景的重点不是追求完整历史,而是保证来源 ID、采集时间和解析状态清楚。即使暂时不保留所有价格快照,也应该保留最近若干次原始记录,至少能判断一次解析异常是否来自页面变化。
此时必须建立来源映射和标准字段。商品匹配不能只依赖标题,因为同一商品可能存在不同标题、包装规格或店铺信息。应结合来源商品 ID、品牌、规格、条码或人工确认结果,形成内部商品与平台商品的映射关系。
价格比较时,要明确比较的是商品级最低价、同规格 SKU 价格,还是满足相同促销条件后的有效价格。没有统一比较口径时,图表中的“平台价格差”可能只是字段定义不同造成的假差异。
这类场景不能只保存当前状态,至少需要价格和库存快照。快照表可以按天、小时或事件变化记录,频率取决于业务对变化的敏感度。高频采集不等于必须每次都永久保存完整页面,可以将高频状态保存为结构化快照,将原始页面按策略归档。
如果需要计算历史最低价,最好由价格快照聚合得到,并明确时间窗口。不要在抓取代码中直接计算并写入一个永久字段,因为窗口、币种和价格类型变化后,旧结果很难重新解释。
可以采用原始对象存储、标准关系表和少量扩展字段的组合。原始层适合保留完整响应,标准层只提取当前业务真正需要的核心字段,扩展字段用于承接尚未稳定的平台属性。
这种方案的取舍是存储成本更高、数据链路更长,但解析规则变化时回溯能力更强。应设定原始数据保存周期、压缩策略、敏感字段脱敏规则和访问权限,不能把“全部保留”理解成无限期无差别保存。
这时通常需要分工:关系型数据库服务当前商品、SKU和状态查询;消息队列或任务系统承接采集和解析流程;对象存储保留原始数据;分析数据库或数据仓库承接历史聚合。
不要让实时接口直接扫描全量价格历史,也不要让报表系统直接读取正在写入的原始 JSON。当前查询、历史分析和原始回溯的访问模式不同,分层存储的价值就在于让不同系统服务不同负载。
| 业务情况 | 最低可行方案 | 优先解决的问题 | 不必过早投入的部分 |
|---|---|---|---|
| 单平台当前展示 | 关系表加原始 JSON | 字段含义、来源 ID、当前状态 | 复杂数仓和全量历史 |
| 多平台价格比较 | 标准层加来源映射 | 价格口径、SKU 对齐、币种单位 | 没有明确需求的高频采集 |
| 价格趋势分析 | 当前表加快照表 | 时间维度、价格类型、异常隔离 | 把所有指标预计算进主表 |
| 全字段长期留存 | 对象存储加标准关系表 | 原始回溯、保存周期、权限控制 | 所有原始字段都建索引 |
| 实时与历史并重 | 在线库、原始层、分析层分工 | 读写隔离、任务状态、数据延迟 | 让一个数据库承担所有访问模式 |

数据库设计问题往往隐藏在数据分布里。建议统计每个核心字段的非空率、类型分布、取值数量、异常值比例和来源平台差异。一个在建表语句中只有一个 price 的字段,可能在实际数据中表现为数字、区间文本、空字符串和带货币符号文本四种形态。
如果一个字段的非空率很高,但有效值比例很低,它可能只是被大量写入了默认值。库存字段尤其要关注“0”和“未知”的混淆,因为解析失败后写零,会直接制造假缺货。
商品标题重复并不代表商品重复,来源商品 ID 重复也不一定代表同一内部商品。应分别统计来源层重复、内部商品重复和 SKU 规格重复。商品与 SKU 的关联缺失、价格记录找不到 SKU、库存记录没有对应任务,都是模型边界不清的信号。
数据质量不能只靠格式校验。价格在短时间内从几百元变成零、单个商品的 SKU 数从十几个突然变成一个、全平台库存同时变为无货,都可能是解析或访问异常,而不是业务真实变化。
可以建立基础变化规则,例如价格变化阈值、商品数量波动阈值、单次任务异常率和字段分布漂移。规则不应过于绝对,因为促销确实可能造成大幅变化;更好的方式是把异常标记为待确认,不要直接覆盖当前有效值。
如果每次向业务人员解释“这个价格不包括券”“这个库存其实只是有货状态”“这个时间是写入时间不是采集时间”,说明字段治理已经影响到决策。报表看起来有数据,不代表数据可以被稳定使用。
在分析层使用九数云或其他可视化工具时,可以通过字段说明、指标口径和数据更新时间降低误读,但根本仍在标准层。分析图表应能展示数据来源、更新时间、过滤条件和异常记录数量,而不是只给出一个漂亮的数字。

不要为了快速交付,直接从原始 JSON 中临时拼报表后永久固化。可以先创建一个明确标注口径的临时标准数据集,例如“未含券销售价”“商品级最低 SKU 价”或“最近一次有效价格”。
同时记录样本范围、平台、时间区间、异常数量和未匹配 SKU 数量。这样即使报表先上线,后续也能沿着口径继续完善,而不是让临时 SQL 变成无人维护的事实标准。

宽表最明显的优点是开发快、查询直观,适合字段稳定、平台单一、业务简单的早期项目。很多团队并不是不能使用宽表,而是没有设置退出条件,最终让早期临时结构承载了多平台、历史分析和复杂 SKU 业务。
它的代价包括重复存储、字段含义冲突、平台扩展困难和历史状态缺失。只要出现两个以上平台、一个商品多个 SKU 或价格历史需求,就应该重新评估宽表是否仍然合适。
高度规范化可以清晰表达实体关系、保证唯一性和减少重复,但表数量多、关联查询复杂,对开发和运维能力要求更高。对于只需要展示当前商品列表的小项目,过度规范化可能带来不必要的开发成本。
如果选择规范化方案,建议为常用查询建立视图或业务宽表,而不是让每个前端接口都手写复杂关联。规范化负责保证数据底座,查询模型负责服务使用场景,两者不必互相替代。
全 JSON 方案对平台差异非常包容,适合早期探索、原始数据归档和字段尚未确认的场景。但它会把类型、唯一性和口径问题推迟到查询阶段,后续统计、索引和数据质量治理的成本通常会上升。
如果团队决定使用文档模型,也建议在文档内部保留稳定的元数据,例如来源平台、来源 ID、采集时间、解析版本和质量状态。不要让业务数据与请求日志、调试信息和页面展示碎片无限混在同一个文档里。
混合分层通常是电商抓取最均衡的选择:核心实体和动态事实结构化,平台特有字段保留在原始对象中,分析数据进入独立的业务层。它既支持跨平台比较,又保留重新解析的可能。
代价是系统链路更长,需要处理数据延迟、失败重试、版本兼容、存储成本和权限管理。团队如果没有数据运维能力,应先从少量核心字段开始,不要一开始就建立过于复杂的实时数仓架构。
| 方案 | 开发速度 | 跨平台能力 | 历史追溯 | 适用边界 |
|---|---|---|---|---|
| 宽表 | 高 | 低至中 | 低 | 单平台、低规模、当前展示 |
| 规范化关系模型 | 中 | 高 | 高 | 商品、SKU、价格和库存关系清晰的业务 |
| 全 JSON 文档 | 中至高 | 高 | 中至高 | 原始留存、结构变化频繁、查询要求较低 |
| 混合分层 | 中 | 高 | 高 | 多平台、动态字段、分析和回溯并重 |

电商抓取不仅是技术问题,还涉及访问频率、接口授权、账户权限、数据保存和再使用边界。不同平台的规则不能一概而论,开发人员应根据具体平台的公开协议、接口文档和授权范围进行核实。
不要把登录凭证、个人信息或不必要的页面数据无差别写入原始层。原始保留应服务于解析回溯和业务审计,而不是成为没有访问控制的“数据垃圾场”。
一个字段如果定义清楚、来源稳定、类型固定,即使存在几十个也未必难维护。相反,一个名为 info 或 data 的 JSON 字段,如果同时承载商品属性、促销条件、SKU 规格和页面状态,维护复杂度会远高于多个清晰的结构化字段。
所以不要用“少建表、少加字段”作为唯一目标。更重要的是让每个字段有稳定的职责,让每种变化有合适的记录位置。
电商数据的难点不在于今天抓到一条商品记录,而在于明天平台改了字段、后天价格发生变化、下周业务新增一个分析口径时,系统仍然能解释过去和现在的数据。
原始记录提供变化证据,标准层提供统一语义,历史快照提供时间脉络,解析器版本提供规则上下文,质量校验提供异常隔离。五者结合起来,才构成可维护的抓取数据系统。
如果当前系统已经出现字段混乱,不必立刻推倒重来。可以先选择商品、价格和库存三个最关键对象,做一次字段盘点;再建立来源映射、标准字段和快照表;随后选取一小段历史数据进行回填和对账。
如果团队还处于项目初期,建议从三层模型的最小版本开始:原始记录、标准商品/SKU 表、价格和库存快照。平台特有字段先保留,不急于全部标准化;业务指标放在分析层计算,避免报表需求不断反向改造采集库。
电商数据抓取的存储混乱,最终不是靠某一种数据库解决的,而是靠清晰的数据分层、可解释的字段字典、可追溯的变化记录和有边界的标准化规则解决的。当一个字段能够说明“来自哪里、代表什么、何时有效、如何转换、出错后如何回溯”,它才真正具备进入业务系统的资格。
我一开始也以为是数据库容量和表结构的问题:平台一增加,商品表就继续加字段,最后甚至把价格、规格、库存和促销都塞进 JSON。后来我发现,真正难排查的不是字段多,而是同一个字段没人能说清楚含义、来源和更新时间。应该先从哪些现象入手诊断?
我在一个脱敏的多平台商品采集项目中遇到过类似情况。最初的商品表只有十几个字段,后来为了兼容不同平台,陆续增加了 price、sale_price、coupon_price、stock、sku_info 等字段。三个月后,开发人员发现同一商品有时能查到三个价格,报表却不知道应该使用哪一个。
排查后发现,问题并不是数据库性能,而是字段语义没有定义清楚。例如,有的平台 price 表示原价,有的平台表示当前最低 SKU 价格,还有的平台返回的是一个价格区间。字段名称虽然相同,业务含义却不同,直接合并后必然产生混乱。我通常用下面四个问题判断根因: 第一,这个字段是否只有一个明确含义;
第二,它是商品静态属性,还是价格、库存这类动态状态;第三,它是否需要保留来源平台和采集时间;第四,字段变化后能否从原始数据重新解释。如果四个问题中有两个以上答不上来,优先修正数据模型,而不是更换数据库。
症状更可能的根因优先处理方式 字段数量不断增加平台字段直接暴露给业务表增加来源映射层 同一字段类型不一致缺少标准字段和类型规则建立字段字典 价格无法追溯只保存当前值增加价格快照表 JSON 无法查询核心字段没有结构化拆出高频查询字段 我的判断原则是:存储选型解决的是“数据放在哪里”,数据建模解决的是“数据代表什么”。
如果字段含义没有统一,换成文档数据库、搜索引擎或数据仓库,只会把混乱迁移到另一个地方。正确顺序应当是先定义实体、字段语义、时间维度和来源,再决定哪些字段进入关系表,哪些字段保留在 JSON 或原始存储中。
我现在的表里有商品名称、规格文本、价格、库存和活动信息,但一个商品有多个规格时,字段只能靠字符串拼接保存。结果是同一商品的颜色、容量和价格都无法准确关联,后续做比价和库存分析时经常出现重复数据。商品表、SKU 表和动态数据表到底应该怎么划分?
最容易踩的坑,是把“商品是什么”和“商品当前卖多少钱、有没有库存”放进同一张表。商品名称、品牌、类目通常变化较慢;SKU 规格、价格和库存则可能随平台、区域、活动和采集时间变化。它们的变化频率不同,放在一起就会产生大量重复和覆盖更新。我更建议先按业务问题拆层。
商品层回答“这是什么”,保存内部商品 ID、来源平台、平台商品 ID、商品名称、品牌、类目和店铺;SKU 层回答“具体卖的哪一种”,保存平台 SKU ID、颜色、尺寸、容量等销售属性;价格和库存层回答“在什么时间是什么状态”。一个简化的逻辑结构可以是: product 保存商品主体;
sku 通过 product_id 关联商品;price_snapshot 关联 SKU 和采集任务,记录价格类型、金额、币种和采集时间;inventory_snapshot 记录库存数量、可售状态和状态时间;crawl_task 保存来源 URL、请求时间、解析器版本和响应状态。
数据类型典型字段是否建议放商品主表原因 商品静态信息名称、品牌、类目是变化较慢且查询频繁 销售规格颜色、尺寸、容量否一个商品可能对应多个 SKU 价格活动价、会员价、券后价否需要区分价格口径并保留历史 库存数量、是否有货、预售状态否受时间和区域影响 还有一个经常被忽略的判断:规格属性不一定等于 SKU 属性。
材质、产地可能只是商品展示属性,而颜色、尺寸、容量可能直接决定 SKU、价格和库存。只有会影响购买组合的属性,才应该进入 SKU 维度。如果平台没有稳定的 SKU ID,我会先对规格名称和值做标准化,再按商品 ID 加排序后的规格组合生成内部指纹。
但这个指纹只能作为辅助去重键,不能取代原始平台 ID,因为平台改名、规格顺序变化或拆分合并都可能导致指纹变化。
为了兼容不同平台,我把接口返回结果整体存成 JSON,开发初期确实很快,但后来查询价格区间、筛选有货 SKU 和统计平台差异时,SQL 越写越复杂。有人建议全部改成关系表,也有人说 JSON 足够灵活,我应该怎么划分两者的边界?
我测试过“全部 JSON”和“全部关系表”两种极端方案,结论是两者都不适合长期承担全部职责。全部 JSON 的优点是接入快、平台特有字段不容易丢失;但当业务开始按价格、库存、品牌和规格组合查询时,字段类型不稳定、索引难设计、空值含义不统一的问题会集中暴露。全部关系表也不是答案。
不同平台的促销标签、配送描述和扩展属性差异很大,如果每个新字段都修改表结构,采集系统会被平台页面变化牵着走。因此,比较稳妥的方式是“三层存储”:原始层保留 JSON,标准层保存跨平台统一字段,业务层保存报表和应用需要的派生结果。
字段特征推荐存储方式判断理由 商品 ID、SKU ID、当前价格结构化字段需要关联、去重和高频查询 库存状态和采集时间结构化字段需要排序、聚合和异常检查 平台特有促销文案JSON 或扩展表语义可能不稳定,查询频率较低 原始接口响应原始 JSON 或对象存储用于回溯解析和排错 历史最低价、价格差业务计算表属于派生结果,不是原始事实 我会把“是否需要作为筛选条件、关联条件、排序条件或质量校验条件”作为拆字段标准。
只要某个字段经常出现在查询条件中,或者需要设置唯一性、非空、金额范围等规则,就不应该只放在 JSON 里。例如,平台返回的 coupon_text 可以保留在原始 JSON 中;
但经过解析后得到的 coupon_amount、currency 和 valid_until,如果要参与价格比较,就应该结构化保存。这样既保留了平台原文,也避免每次报表查询都重新解析字符串。需要特别注意的是,JSON 不是“无需设计”的存储方式。
即使保留 JSON,也要记录原始数据版本、采集时间、来源平台、解析器版本和数据哈希。否则 JSON 虽然保存下来了,团队仍然无法判断它是哪个规则解析出来的。
我们现在有一张上线多年的商品表,里面混着平台字段、SKU 文本、当前价格、库存和促销字段,直接重构又担心影响旧接口。我想知道有没有一种风险更低的迁移流程,既能逐步拆分,又能验证新旧数据没有明显偏差?
这类系统不适合一次性重写。我见过最危险的做法,是开发人员先设计一套“理想模型”,然后直接把旧表停写、批量迁移、切换接口。真正上线后才发现旧接口依赖了某些没有文档记录的字段,或者旧表里的空字符串其实代表“平台未返回”,并不等于“没有库存”。更稳妥的迁移方式是先盘点,再兼容,最后切换。
第一步不要急着建表,而是抽取一段时间的真实数据样本,记录每个字段的实际类型、空值比例、来源平台、调用接口和下游使用方式。字段名看起来相同,实际值可能完全不同,必须以样本而不是字段名称为准。
迁移阶段主要动作验收重点 字段盘点统计样本、类型、空值和下游依赖找出重复字段和隐含语义 建立标准层新增商品、SKU、价格和库存结构定义类型、单位、来源和时间 回填历史通过转换程序导入旧数据记录无法映射的字段和原因 双写验证新旧模型并行写入比较数量、金额和状态差异 流量切换先切换低风险查询,再切核心接口观察错误率和查询延迟 旧表下线冻结写入并保留只读周期确认没有遗漏依赖 在一次类似迁移中,我们重点对比了四类指标:商品去重数量、SKU 数量、当前价格差异和有货状态差异。
价格不能只比较字符串,因为旧表中的“199-299”可能需要拆成最低价和最高价;库存也不能简单比较布尔值,要把“无货、预售、区域不可售、未返回”分开。迁移期间建议保留一个 unmapped_field 或异常记录表,把无法确定含义的旧字段暂存起来,而不是强行映射。
强行映射会制造看似完整、实际错误的数据;显式保留不确定性,反而便于后续补充规则。最后,双写并不等于迁移完成。还要检查解析器版本、采集任务 ID、原始数据地址和更新时间是否贯通。只有新模型能回答“这条价格来自哪个平台、哪次任务、哪个解析规则”,系统才真正具备排错和回溯能力。


读者评论
文章把“字段混乱”归因于语义和层级没有分开,这个判断比较准确。原始层、标准层、业务层的划分适合跨平台采集,但实施时还需要明确数据同步和版本管理规则。
商品、SKU和动态状态拆分得很清楚,尤其是价格库存不能和商品基础信息混存这一点,对需要保留历史快照的系统很有参考价值。
字段字典部分比较实用,价格类型、单位、空值含义和更新时间这些细节,确实比单纯选择关系型数据库还是文档数据库更重要。
文章没有简单否定JSON,而是按查询频率、聚合需求和字段稳定性判断存储方式,这种取舍更符合实际项目,避免了过度结构化。
文中的示意数据已经注明是情景模拟,这一点比较客观。不过如果能补充表结构示例、索引设计和迁移步骤,开发人员会更容易落地。