电商数据抓取项目最容易出现的一种错觉是:任务日志显示“抓取成功”,数据库记录也在持续增长,研究团队却越来越难回答三个基本问题,这条数据属于哪个商品、它在什么时候有效、它为什么和另一条记录不一样。以我参与排查过的多平台商品监测项目为例,团队最初把问题归因于抓取脚本不稳定,后来抽样检查 500 条记录才发现,真正的故障集中在存储方案:主键没有定义清楚,历史快照被当成重复数据,原始响应和清洗结果混在一起,重试任务又不断追加新记录。
电商数据抓取后的存储混乱,通常不是“数据库不够强”,而是数据对象、版本、来源和写入规则没有被建模清楚。
电商数据抓取:研究团队快速排查:存储方案为何会导致存储混乱
很多团队在电商数据抓取项目启动时,第一反应是选择一个“能扛住大数据量”的数据库。这个顺序往往反了。数据库可以承受数千万行记录,并不意味着研究人员能够准确区分商品、SKU、店铺、抓取批次和历史版本。
如果一张表中同时保存商品标题、规格、价格、库存、店铺、抓取时间和任务状态,但没有明确这些字段分别描述什么对象,数据就会在写入时变成“看起来完整、实际不可解释”的记录。后续即使增加索引、分库分表或更换数据库,也只是让混乱的数据保存得更快。
我通常把“存储混乱”拆成四个层次来判断:身份混乱、时间混乱、来源混乱和状态混乱。身份混乱是同一商品被识别成多个对象;时间混乱是当前值与历史值无法区分;来源混乱是无法追溯到平台、页面和任务;状态混乱则是抓取失败、解析失败和商品下架被混在一起。
| 混乱类型 | 典型表现 | 真正需要检查的对象 | 不能直接采用的处理方式 |
|---|---|---|---|
| 身份混乱 | 同一商品出现多个商品编号 | 平台 ID、店铺 ID、SKU、规格关系 | 仅按商品标题去重 |
| 时间混乱 | 无法判断哪条价格是最新值 | 业务时间、抓取时间、入库时间 | 删除所有旧记录 |
| 来源混乱 | 异常数据无法定位来源 | 平台、页面、任务批次、解析器版本 | 只保留清洗后的字段 |
| 状态混乱 | 失败记录被当成商品缺货 | 请求状态、解析状态、业务状态 | 把空值统一转成 0 |
这张表体现了一个很重要的判断:“一条记录”不是一个完整的数据对象。一条记录至少还需要知道它代表谁、来自哪里、何时采集、是否有效,以及是否属于某个历史版本。

电商数据抓取通常同时产生四类数据:页面或接口的原始响应、标准化后的商品数据、面向分析的汇总数据,以及任务运行日志。它们的访问频率、留存期限和修改方式完全不同,不应该因为都叫“抓取数据”就放入同一张业务表。
原始响应的价值在于可复核和可重算,标准层的价值在于统一口径,应用层的价值在于让研究人员快速查询,任务日志则用于解释数据为什么缺失。若把四类内容混在一起,团队后续会陷入两难:保留原始数据,表越来越宽;删除原始数据,又失去追溯能力。
因此,我在做存储排查时不会先问“使用关系型数据库还是文档型数据库”,而会先问以下四个问题:
在研究团队的语境里,“商品”经常被当成一个自然对象,但在电商系统里,它可能至少包含平台商品、店铺商品、SPU、SKU、销售规格和活动组合。不同平台对这些对象的拆分方式并不一致。
例如,同一款手机在平台甲可能以一个商品 ID 下挂多个颜色和容量 SKU;平台乙可能把不同容量拆成不同商品链接;平台丙还可能把官方套装和单机版放在同一页面。若研究团队把标题或链接当作统一主键,跨平台合并时一定会出现误合并和漏合并。
这也是为什么“同名商品”不等于“同一商品”。标题相同,只能说明展示文本相近;它不能证明品牌、型号、规格、包装数量和销售主体都相同。
“销量”“库存”“原价”“活动价”“上架时间”是多平台项目中最容易造成统计偏差的字段。一个平台的销量可能是累计付款件数,另一个平台可能显示近 30 天销量;一个平台的库存可能是可售库存,另一个平台显示的是库存状态文本。
如果团队只做字段名映射,不做字段口径映射,数据表会看起来非常整齐,分析结果却无法横向比较。尤其是价格字段,必须区分页面展示价、优惠券后价、会员价、满减后的估算价和最终支付价。
我建议在标准层为每个关键字段保留“原始值、标准值、单位和转换规则”。例如,原始价格保存为字符串或原始数值,标准价格保存为统一货币单位,同时记录是否包含促销条件。这样做会增加字段数量,但能避免后续为了追查口径而重新抓取全部数据。

一条商品价格记录通常至少涉及四个时间:页面显示的业务时间、任务实际抓取时间、数据写入时间,以及数据在系统中被修正的时间。这些时间不能用一个 updated_at 字段替代。
例如,研究人员在 10:00 抓取到价格 99 元,11:00 页面可能显示 89 元,但因为网络重试,11:00 的数据直到 11:08 才写入数据库。如果团队只看入库时间,就可能错误地认为 11:08 才发生价格变化。
更复杂的情况是,页面在 10:00 已经更新,但抓取任务仍读取到了缓存页面。此时页面业务时间、抓取时间和数据可信时间并不相同。研究团队需要知道的是:系统在什么时间看到什么内容,而不是简单地把每次入库都当成事实发生时间。
抓取系统通常会设置超时重试、失败重跑和消息队列补偿。这些机制本身没有问题,但如果写入操作不是幂等的,就会让同一个商品在同一个任务中产生多条完全相同的记录。
更容易被忽视的是“部分成功”:请求已经拿到页面,应用在写入数据库前超时,任务随后重试。第二次请求再次成功时,系统并不知道第一次是否已经完成写入,于是出现一份有效数据和一份重复数据。
应用层先查询、再插入的去重方式,在并发场景下也可能失效。两个进程同时查询到“没有记录”,然后同时插入,最终产生重复行。对于明确的业务唯一关系,应将唯一约束和冲突处理下沉到存储层,同时保留任务批次,方便识别合法的历史数据。
抓取成功通常只代表请求完成、页面返回或解析器没有报错。它不代表商品身份正确、字段口径正确,更不代表数据可以直接用于研究分析。
我会把抓取结果拆成三个状态:请求状态、解析状态和业务数据状态。请求成功但解析失败,不能被标记为“商品缺货”;解析成功但关键字段缺失,也不应该自动进入价格趋势分析。
| 状态层 | 示例 | 研究层面的解释 |
|---|---|---|
| 请求状态 | HTTP 成功、超时、限流、验证码 | 说明是否拿到了响应,不说明内容是否可用 |
| 解析状态 | 结构识别成功、字段定位失败、结构变化 | 说明解析器是否理解页面结构 |
| 业务状态 | 在售、下架、缺货、未知 | 说明商品业务状态,不能由请求结果直接推断 |
一个常见错误是:页面没有抓到库存字段,于是程序把库存写成 0;接口返回空数据,于是程序把商品标记为下架。这些自动填充值会让后续报表看起来完整,却把“未知”伪装成“确定”。
标题会因为促销文案、关键词优化和规格展示变化而变化,链接也可能包含追踪参数、活动参数或动态路径。它们可以作为检索线索,却不适合单独承担业务身份。
更稳妥的方式是同时保留内部主键和外部业务标识。内部主键只负责在数据库中定位一行;外部业务标识则负责描述“平台、店铺、商品或 SKU 的真实身份”。两者不能互相替代。
一个常见的业务唯一键可以由以下信息组合而成:
是否需要把所有字段都放进唯一键,要根据业务含义决定。比如价格变化不应该改变商品主键,但不同 SKU 必须被区分。过度复杂的唯一键会造成维护困难,过于简单的唯一键则会造成错误覆盖。
同一商品出现多条记录,不代表其中多条都应该删除。它可能是不同时间的价格快照,也可能是不同规格、不同店铺或不同活动状态。
真正需要先定义的是“重复的粒度”。如果判断粒度是“同平台、同店铺、同 SKU、同抓取批次、同内容”,那么完全相同的重试记录才属于重复;如果判断粒度只包含“同标题、同链接”,就可能误删合法历史版本。
我建议至少同时计算两种重复率:
两种指标含义不同。任务重复率高,通常要检查重试和幂等写入;对象重复率高,则更可能是主键设计和商品匹配规则有问题。

如果项目只是一次性导出一个简单名单,保留原始响应的必要性可能不高。但只要项目需要长期运行、持续监控或支撑研究报告,原始数据就是纠错和复核的依据。
平台页面结构发生变化时,标准层突然出现大量空值。没有原始响应,团队无法判断是页面真的没有该字段,还是解析器已经失效。两种情况的业务结论完全不同。
原始数据也不意味着把所有内容无限期保存。团队可以按照字段敏感性、文件大小、研究价值和留存周期制定策略:关键字段长期保留,完整响应按周期归档,涉及敏感信息的内容进行脱敏或限制访问。
数据库选型确实会影响查询性能、扩展成本和运维复杂度,但它无法替团队定义“什么是同一个商品”,也无法自动判断“这条空值是缺货还是解析失败”。
我见过一些项目在半年内先后引入关系型数据库、文档型数据库和搜索引擎,但数据混乱没有消失,反而出现了三套商品 ID、两套更新时间和多个同步延迟。问题不是组件能力不足,而是没有统一数据契约。
在没有完成主键、时间、状态和分层设计之前,不建议通过增加存储组件解决混乱。系统越多,复制、同步、回填和权限管理的链路越长,错误也更难定位。
面对一张已经混乱的大表,最有效的第一步通常不是迁移数据,而是抽取一个具有代表性的样本。建议同时覆盖多个平台、多个店铺、不同商品类型和不同抓取时间。
样本量不必一开始就很大。对一个已经运行的项目,我通常会先抽查 100 到 500 个商品身份,再根据异常比例决定是否扩大范围。抽查的目标不是得出行业统计,而是识别错误模式。
每条样本至少检查以下信息:
数据量大不等于数据质量差。为了避免团队把所有问题归因于存储容量,我建议至少计算重复率、字段缺失率、可追溯率和最新值命中率。
| 指标 | 计算方式 | 适合判断的问题 |
|---|---|---|
| 任务重复率 | 同批次重复记录数 ÷ 同批次总记录数 | 重试、并发和幂等写入是否失效 |
| 字段缺失率 | 关键字段为空的记录数 ÷ 总记录数 | 解析器、页面变化或字段映射是否异常 |
| 可追溯率 | 能关联原始来源的标准记录数 ÷ 标准记录总数 | 是否能够复核清洗结果 |
| 最新值命中率 | 正确返回最新版本的商品数 ÷ 商品总数 | 当前状态表和版本查询是否可靠 |
这四个指标最好按平台、任务、商品类目和日期分组统计。总平均值经常掩盖问题。例如整体字段缺失率只有 4%,但某个平台在页面改版后缺失率达到 38%,这就不是一般性数据波动,而是解析链路的局部故障。

表结构只能告诉你有哪些列,数据契约还要说明每一列的含义、来源、类型、更新规则、允许为空的条件和异常处理方式。缺少数据契约时,同一个字段可能被不同开发人员按不同方式写入。
例如,stock 这个字段到底是整数、字符串还是状态枚举?“缺货”是 0、空值、字符串,还是单独的业务状态?如果这些问题没有在契约中写清楚,数据分析人员只能靠猜,开发人员则会不断增加兼容逻辑。
一份可执行的数据契约至少应包含:
当业务对象和数据契约明确之后,才进入数据库层排查。重点不是先看数据库品牌,而是看是否存在与业务规则一致的唯一约束、索引、事务和冲突处理。
例如,当前状态表可能需要以“平台 + 店铺 + SKU”作为唯一键;历史快照表则不能把抓取时间简单排除在外,否则不同时间的合法记录会被错误拦截。两张表的唯一性规则不应完全相同。
查询层同样容易产生“看似重复”的结果。研究人员查询商品历史时,如果没有按抓取时间排序、没有筛选有效状态或没有关联 SKU,报表中就可能同时出现多个价格。此时问题不一定在写入,也可能在查询语义。
假设一个研究团队长期监测多个电商平台的商品价格、库存和促销变化。团队希望回答的问题包括:同类商品的价格区间如何变化、促销活动持续多久、不同店铺的库存状态是否同步,以及某个品牌的商品在不同平台上是否存在明显价差。
这类问题决定了系统不能只保存“当前价格”。如果只保留最新值,团队无法计算价格变化幅度;如果只保存未经整理的历史页面,又很难稳定地按 SKU、店铺和平台做比较。
项目初期常见的简化方案是:每次抓到一个商品,就把标题、链接、价格、库存和更新时间写入一张表。这个方案上线快,但它把商品主体、一次观测和当前状态混成了同一行。
运行几周后,团队可能看到如下数据:
| 商品标题 | 平台 | 链接 | 价格 | 库存 | 更新时间 |
|---|---|---|---|---|---|
| 某型号手机 12GB+256GB | 平台甲 | 链接 A?source=1 | 2999 | 有货 | 10:00 |
| 某型号手机 12GB+256GB | 平台甲 | 链接 A?source=2 | 2899 | 有货 | 12:00 |
| 某型号手机 12GB+256GB | 平台甲 | 链接 A | 2899 | 0 | 12:05 |
这三条记录可能分别代表一次价格变化、追踪参数不同的同一页面,以及一次库存字段解析失败。若不结合任务日志和原始响应,单看业务表,团队无法判断它们是历史版本、重复记录还是错误记录。
在这类项目中,我不会先让开发人员批量删除重复行,而会先建立异常分类。删除是不可逆的,分类却能帮助团队找到错误的生产机制。
下面是一组用于演示排查方法的样本推演。假设团队抽查 500 个商品身份、共 2,400 条抓取记录,发现 318 条需要进一步解释。注意,这不是某个企业的公开统计,而是便于理解排查过程的模拟样本。
| 异常类别 | 记录数 | 占异常记录比例 | 初步判断 |
|---|---|---|---|
| 同批次完全重复 | 86 | 27.0% | 重试或并发写入缺少幂等控制 |
| 同商品多 SKU 混合 | 72 | 22.6% | 商品主体和销售规格没有拆分 |
| 价格变化但无历史版本标识 | 61 | 19.2% | 当前状态和历史快照使用同一写入逻辑 |
| 来源无法回溯 | 54 | 17.0% | 没有保存任务批次或原始响应地址 |
| 库存未知被写成缺货 | 45 | 14.2% | 解析异常与业务状态混用 |
从这个模拟样本可以看到,前两类异常已经占到接近一半。团队如果一开始就讨论“是否迁移到更适合大数据的数据库”,很可能绕开了最直接的两个问题:业务标识不完整,以及写入没有幂等。

对于这类研究项目,一个实用的分层方式是:原始层保存页面或接口响应和任务上下文;标准层保存统一字段、平台映射和业务标识;当前状态层服务于“现在是什么”;历史快照层服务于“过去发生了什么”;应用层则根据价格监测、竞品研究或库存分析建立专门数据集。
如果团队使用九数云这类数据分析平台来制作研究看板,关键不是把未经治理的抓取宽表直接接入,而是先明确数据集的粒度。例如,价格趋势图应使用“SKU,抓取时间”的快照数据,商品总览表则可以使用“SKU 当前状态”的最新数据。
分析平台适合帮助团队观察重复率、缺失率、价格变动和平台差异,但它不能替代上游的数据建模。若源数据中同一商品有多个不一致的标识,分析工具只能把这种不一致可视化,不能自动证明哪一个身份才是正确的。
存储重构完成后,不要只看数据库中的行数是否减少。更重要的是观察数据质量指标是否改善,以及研究人员完成一次分析所需的人工解释是否减少。
我会建议至少建立四类监控视图:数据接入视图、身份匹配视图、质量异常视图和研究结果视图。数据接入视图关注任务成功率和延迟;身份匹配视图关注商品与 SKU 的关联;质量异常视图关注缺失、重复和未知状态;研究结果视图则用来验证清洗规则有没有改变核心结论。

原始层的职责是保存采集时看到的内容和上下文。它可以是对象存储中的 JSON、HTML、接口响应文件,也可以是带有原始字段的追加表。关键是不要在进入原始层之前就丢掉所有无法识别的字段。
一条原始数据最好能关联以下信息:来源平台、页面地址或接口标识、抓取时间、任务批次、请求结果、响应摘要、解析器版本和数据留存策略。对于大体量响应,可以将正文放在对象存储中,数据库只保存索引和元数据。
原始层不需要追求最方便的查询体验。它更像“证据仓库”,主要服务于审计、重新解析和问题回溯。把原始层直接提供给研究人员使用,反而容易造成字段口径混乱。
标准层负责把不同平台的数据转换成可以比较的结构。这里应完成类型转换、单位统一、枚举映射、商品身份关联和异常标记,但应同时保留原始字段或原始字段引用。
例如,平台甲的库存字段是整数,平台乙的库存字段是“仅剩少量”,标准层可以增加一个统一的库存状态字段,但不能把“仅剩少量”武断转换成具体数量。标准值应该允许“未知”“不可见”和“估算”等状态存在。
标准层还应记录规则版本。规则改变后,同一个原始值可能被转换成不同的标准值。如果没有版本号,团队无法解释历史报表为什么发生变化。
当前状态层只回答“目前系统认可的最新状态是什么”。它适合给研究看板、接口和日常检索使用,因此应该尽量保持结构稳定、查询路径清晰。
当前状态表不需要保存每一次抓取,但必须保留最后一次有效更新时间、最后一次抓取时间、数据来源和状态。若最近一次任务失败,不能直接用失败结果覆盖上一次有效状态。
一个更稳妥的更新逻辑是:只有当本次数据通过身份、关键字段和状态校验时,才更新当前状态;失败数据进入异常表或任务日志,由后续流程决定是否补偿。
历史快照层的粒度应提前定义。例如,“SKU,抓取时间”可以作为价格和库存变化的基本粒度。如果某个字段没有变化,是否仍然保存快照,则要根据研究需求、存储成本和查询方式决定。
对于变化监测,可以采用全量快照或变更事件两种方式。全量快照易于还原任意时间点状态,但存储成本较高;变更事件节省空间,却要求团队准确处理初始状态、事件顺序和缺失期间。
| 方案 | 主要优点 | 主要代价 | 更适合的场景 |
|---|---|---|---|
| 全量历史快照 | 还原任意时间点相对直接 | 存储量和处理量较大 | 价格趋势、长期研究、审计 |
| 变更事件记录 | 节省存储,便于识别变化 | 重建当前状态更复杂 | 变价提醒、状态变更监控 |
| 当前表加定期归档 | 日常查询简单,成本可控 | 无法保留所有瞬时变化 | 对历史精度要求不高的看板 |
没有一种方案适用于所有项目。研究团队要先确认自己需要的是“变化发生过的证据”,还是“每天某个时间点的横截面”。这两个需求看似接近,实际会决定完全不同的存储成本。

应用层不应该只是标准层的复制品。它应围绕具体问题组织数据。例如,价格监测应用数据集可以提供商品、SKU、平台、店铺、有效价格和观测时间;库存研究数据集则应重点保留库存状态、变化事件和可信度。
使用数据分析平台制作报表时,最好将“研究口径”固化在数据集或模型中,而不是让每个分析人员在图表配置里各自筛选。否则同一个商品的价格趋势可能因为筛选最新值的规则不同,出现多个版本。
新项目不需要一开始就建设复杂的数据湖或多套数据库,但必须把最小业务边界定义好。至少要区分来源平台、店铺、商品、SKU、抓取任务和观测时间。
建议第一版就保留原始字段、标准字段和任务批次。即使暂时不保存完整页面响应,也要保存能够定位原始来源的地址、摘要或文件索引。
新项目还应优先实现幂等写入。抓取脚本可以先简单,但同一个任务批次重复执行时,不能无限追加相同记录。尽早设置唯一约束,比运行半年后再清理重复数据成本低得多。
已经出现大量重复记录时,第一步不是直接迁移全部数据,而是暂停会继续制造重复的写入路径。检查重试、并发、批量提交和失败补偿,先避免异常数量继续增长。
第二步是建立“可信数据”和“待解释数据”两个区域。能够确认身份、时间和来源的记录进入可信区域;无法解释的记录保留原始内容并进入待处理队列。不要为了让报表变干净而大批量删除未知记录。
第三步才是按异常类型进行清洗。主键问题、SKU 混合、历史版本和状态误判应分别处理,不能用一条全局去重 SQL 解决所有问题。
价格研究最怕“当前价覆盖历史价”。此类项目应优先设计价格快照或价格变更事件,并明确价格的有效条件,例如是否包含优惠券、是否为会员价、是否需要满减。
如果平台只展示区间价或活动条件不完整,数据应标记为估算或不可直接比较。研究报告中宁可减少样本,也不要把口径不一致的价格强行合并。
库存字段的空值不等于 0。页面没有展示库存、接口被限流、解析器找不到字段、商品确实缺货,这四种情况必须分别记录。
库存看板还应显示数据可信时间。如果最近一次有效库存是昨天,而今天任务全部失败,系统应提示“数据未更新”,而不是继续把昨天的库存当作今天的实时状态。
数据量增长到一定阶段后,可以考虑将原始响应、标准数据和分析数据放到不同的存储组件中。但这一步应建立在数据契约稳定、主键清晰和同步边界明确的基础上。
原始大文件可以进入对象存储,当前状态和结构化关联数据可以进入关系型数据库,历史分析数据可以进入分析型存储,全文检索需求再考虑搜索组件。每增加一个组件,都应同时写清楚数据从哪里来、多久同步一次、谁负责校验。

一张宽表适合快速验证字段是否能抓到、报表能否跑通。对于数据量小、平台少、生命周期短的项目,它仍然有价值。
但宽表会把不同粒度的数据放在一起。商品主体字段、SKU 字段、价格快照和任务字段不断增加后,空值、重复和字段歧义会同步上升。宽表不是绝对错误,问题在于团队是否清楚它只是早期过渡方案。
关系型结构适合保存平台、店铺、商品、SKU、任务和当前状态之间的关系,也适合利用唯一约束和事务控制写入一致性。
它的代价是建模需要更早做决定。平台字段变化较快时,团队需要设计扩展字段、映射表或独立原始层,否则每次平台改版都可能触发表结构调整。
文档型存储适合保存结构变化多、平台差异大的原始数据。它能减少早期建模压力,但也容易让同一个字段在不同文档中出现不同类型和不同含义。
如果后续要做跨平台统计,仍然需要标准层。文档型结构可以缓解原始数据保存问题,却不能替代商品身份、字段口径和版本管理。
组合式架构可以同时满足原始文件保存、结构化查询和大规模分析,但它要求团队管理数据复制、延迟、失败重试和权限。一个数据在多个系统中不一致时,排查成本可能高于单库方案。
| 方案 | 适合解决的问题 | 不适合解决的问题 | 团队需要承担的成本 |
|---|---|---|---|
| 单一宽表 | 快速验证、短期导出 | 复杂历史、跨平台统一 | 后期清洗和字段治理 |
| 结构化关系模型 | 业务关系、约束、当前状态 | 完全不稳定的原始结构 | 前期建模和变更管理 |
| 原始文档存储 | 保留页面和接口响应 | 直接支撑复杂统计 | 标准化和查询转换 |
| 分析型存储 | 历史聚合和大规模研究 | 高频事务写入 | 数据同步和模型维护 |
| 多存储组合 | 同时满足多类数据需求 | 缺少治理能力的团队 | 同步、运维、权限和监控 |
我的判断标准很简单:如果团队还无法用一句话解释每张表的粒度,就不应该继续增加存储组件。先把“这一行代表什么”说清楚,再讨论性能和扩展。

上线前可以做一次“故障注入”测试:人为制造重复任务、字段缺失、接口超时、页面结构变化和部分写入失败,然后观察系统能否保留正确状态、记录异常原因并支持重试。比起只测试正常流程,这种测试更接近电商抓取项目的真实运行环境。

电商数据抓取需要结合平台使用规则、接口授权、访问限制、账号权限和数据使用目的进行判断。公开可访问并不自动等于可以任意采集、长期保存或对外分发。
研究团队在设计原始层时,应明确哪些字段属于必要数据,哪些字段只是在页面上顺带出现。与个人信息、联系方式、用户评论或商家敏感经营信息相关的内容,需要单独评估收集必要性、访问权限和留存期限。
如果所有人都可以直接修改标准数据,数据异常很难追溯;如果分析人员只能看到最终指标,又无法核查数据来源,研究结论也缺少复核能力。
更合理的权限通常包括:原始层只读、标准层由数据工程角色维护、应用层面向研究团队开放查询,规则和口径变更需要留下审批或版本记录。权限不只是安全问题,也是数据可信度问题。
完整原始响应可能占用大量空间,并且未必需要永久保留。团队可以根据研究周期、复核需求和合规要求设置分层留存:近期完整保留,长期保留关键字段和摘要,过期数据按规则归档或删除。
但在删除之前,要确认标准层和应用层是否还能解释历史数据。否则原始层一旦被清理,团队可能只能看到一个结论,却无法回答这个结论是由什么页面、什么规则和什么时间点产生的。
选择三个最重要的研究场景,例如价格趋势、库存监测和跨平台商品比较。分别写清楚每个场景需要的对象粒度、时间粒度和可信条件。
从不同平台和不同日期抽取 100 到 500 个商品身份,记录重复、缺失、来源不明、SKU 混合和状态误判的数量。不要急于修改数据,先确认异常是否具有规律。
对照任务日志、重试记录和数据库写入结果,确认是否存在重复投递、部分提交和并发竞态。优先修复会持续制造新异常的环节。
把原始层、标准层、当前状态层和历史层的职责写成文档,同时为关键字段补充定义、单位、空值规则、来源和更新方式。
至少上线任务重复率、字段缺失率、来源可追溯率和最新值命中率。指标要支持按平台、任务和日期下钻,否则只能看到平均数,无法定位问题。
用整改前后的数据分别计算同一个价格趋势或平台比较结果。如果核心结论发生明显变化,不要急着判断哪一个版本正确,而应回到原始响应和口径定义,确认变化来自数据修复还是统计规则改变。
只有在数据模型、写入规则和查询模式明确后,才评估是否需要对象存储、分析型数据库或搜索组件。若当前瓶颈只是查询没有索引,增加系统数量通常不是最优解。
电商数据抓取项目的核心竞争力,不是抓到了多少页面,也不是数据库里累计了多少行,而是研究团队能否在数周或数月之后,仍然回答每条数据的身份、时间、来源和状态。
如果同一商品在系统里出现三条记录,团队应该能够解释它们分别代表不同 SKU、不同时间快照,还是一次失败重试;如果价格从 99 元变成 89 元,团队应该能够判断这是页面真实变化、促销条件变化,还是解析逻辑变化。
存储设计的第一原则不是“尽量少存”,而是“保留足够的证据,让数据可以被复核”;第二原则不是“组件越多越先进”,而是“每一层都有明确职责”;第三原则不是“自动去重”,而是“先定义什么才算重复”。
下一步可以从 100 条商品记录开始:检查平台和 SKU 标识,补齐抓取批次,区分当前状态与历史快照,统计重复率和可追溯率,再决定是否需要重构表结构或更换存储组件。这个顺序看起来不快,却比直接迁移数千万条无法解释的数据更稳妥。
当原始数据可保留、标准数据可统一、历史数据可追溯、应用数据可高效查询时,存储才真正服务于研究。否则,数据库只是在替团队保存尚未解决的问题。
我负责评估电商数据采集方案时,最初也会怀疑是不是数据库容量、查询性能或存储类型选错了。但实际排查几次后发现,同一套数据库换成另一种,重复记录、错误覆盖和历史数据丢失仍然会发生,我想知道应该先查什么。
多数情况下,数据库类型不是第一根因,业务主键、数据版本和写入幂等才是。一次排查中,我会先用一批可控样本复现:同一平台、同一店铺、同一商品连续抓取 3 次,并人为触发 1 次失败重试。如果最终出现 4 条记录,优先检查写入规则,而不是立即更换数据库。
可以按下面的顺序区分问题层级: 现象优先检查项常见根因 同一商品重复出现业务主键、唯一约束只使用标题或自增 ID 价格历史被覆盖版本与时间字段把当前状态表当历史表使用 重试后数据膨胀幂等写入、任务批次应用层查重存在并发漏洞 字段越来越难维护数据模型和分层所有平台共用一张宽表 我的判断标准是:如果问题可以通过定义唯一键、增加抓取批次号、拆分当前状态与历史快照解决,就不应先归因于数据库选型。
只有当数据模型已经稳定,仍然出现明确的查询、写入或扩展瓶颈时,才有必要重新评估存储组件。
我在检查价格和库存数据时,经常看到同一个商品在一天内出现十几条记录。团队成员通常会直接按商品链接去重,但这样可能把真实的价格变化也删掉,我不确定什么才算真正重复。
“同一商品多条记录”不等于“重复数据”。判断前必须同时看平台、店铺、SKU、抓取批次和抓取时间;只比较商品标题或链接,通常会把合法历史误删。
我实际排查时会把记录分成三类: 记录类型判断特征处理方式 完全重复业务标识、字段值、抓取批次均相同通过幂等键合并或拒绝写入 历史快照商品标识相同,但价格、库存或状态不同保留,并记录抓取时间 不同业务对象SKU、店铺或规格不同拆分为独立对象,不做简单去重 例如,同一 SKU 在 10:00 售价 99 元、12:00 售价 89 元,这两条不是重复,而是价格变化。
如果 12:00 因任务重试产生两条完全相同的记录,才属于可清理的重复。比较稳妥的设计是同时保留“当前状态表”和“历史快照表”。当前状态表只服务于快速查询最新值,历史表按照商品、SKU、抓取时间和任务批次保存变化;这样既不会让日常查询被历史数据拖慢,也不会为了去重丢失研究所需的时间序列。
我曾经参与过多平台商品数据方案评审,最初把公共字段和平台专属字段放进一张表,看起来开发很快,查询也直观。但平台一多,空字段、同名不同义和字段改动同时出现,研究人员反而更难确认数据口径。
宽表的问题不只是字段太多,而是它把不同层次的对象和不同口径的数据强行放在一起。商品主体、SKU 规格、店铺关系、价格快照和平台原始字段,本来就有不同的生命周期,却被迫共享同一套更新规则。以三个平台为例,字段名都叫“销量”,实际可能分别表示累计支付件数、近 30 天成交量和页面展示值。
若直接汇总,表面上字段统一了,研究结论却可能已经失去可比性。
存储区域适合保存的内容主要价值 原始层原始响应、页面字段、请求上下文可审计、可重新解析 标准层统一类型、单位、枚举和标识跨平台比较 当前状态层最新价格、库存、上架状态快速查询和监控 历史快照层每次抓取的变化记录趋势分析和回溯 我的建议不是完全禁止宽表,而是限定它的用途:报表展示或固定主题的应用层可以使用宽表,原始数据和标准化数据不要依赖它承载全部职责。
公共字段应先统一口径,平台特有字段放入扩展结构,并保留来源平台、解析器版本和抓取时间。
我不希望一开始就投入数周重构,也不想听到“换数据库就好了”这种结论。假设手上已有一批抓取数据,我想用半天到一天做一次低成本体检,判断问题究竟来自采集、建模、写入,还是查询逻辑。
最快的方法不是先看总数据量,而是抽取一小批可追溯样本。建议随机选 100 个商品,覆盖多个平台、店铺和 SKU,再沿着“原始记录,标准记录,应用结果”逐条回放。我通常会记录四个指标:重复率、字段缺失率、最新数据命中率和可追溯率。它们不代表行业统一标准,但能帮助团队把“感觉很乱”转化为可讨论的问题。
重复率 = 被判定为非法重复的记录数 ÷ 总记录数;字段缺失率 = 关键字段为空的记录数 ÷ 总记录数;最新数据命中率 = 查询结果正确返回最新版本的商品数 ÷ 商品总数;可追溯率 = 能定位到来源任务和抓取时间的记录数 ÷ 抽样记录数。
检查动作若发现异常下一步 按平台、店铺、商品 ID 聚合同一标识对应多种对象重新定义业务主键 对比连续两次抓取变化记录被覆盖或重复拆分当前表与历史表 查看失败重试日志同一批次重复写入增加幂等键和唯一约束 回放原始数据标准字段无法解释保留来源和解析器版本 如果半天检查后发现主要问题是重复写入,就先修复幂等和唯一约束;
如果是字段口径冲突,就先做标准层;如果是历史无法还原,就补建快照机制。只有当这些基础问题解决后,性能仍不达标,才值得讨论更换数据库或引入新的存储系统。


读者评论
文章把存储混乱归因于对象、时间、来源和状态定义不清,而不是简单归因于数据库容量,这个判断比较准确。尤其是商品身份和SKU关系,确实容易被忽略。
对原始响应、标准化数据、分析汇总和任务日志进行分层保存的建议很实用。这样既能支持日常查询,也方便异常时回溯和重新解析。
文中对请求状态、解析状态和业务状态的区分值得参考。抓取失败不应直接等同于缺货,空值也不能随意转成零,否则会明显影响后续统计。
时间字段部分分析得比较细,业务时间、抓取时间和入库时间确实不能混为一谈。不过实际落地时还需要结合数据更新频率设计保留策略。
文章中的图表数据明确标注为情景模拟而非行业统计,这一点比较客观。关于唯一约束、幂等写入和重试重复的说明,对排查并发写入问题有帮助。