电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多
目录

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多 | 九数云-E数通

eshutong 发表于2026年9月13日

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

电商数据抓取项目里,最容易被低估的故障不是“抓不到”,而是“抓到了太多次”。我见过一个商品监测任务,调度状态连续显示成功,接口响应也没有报错,但重跑三次后,商品表从 8.6 万条膨胀到 25.4 万条。开发人员最初用 DISTINCT 清理,数量确实下降了,结果却把不同店铺的同名商品、同一 SKU 的价格历史一起删掉了。重复数据不是一个 SQL 问题,而是业务定义、抓取流程、任务调度、字段标准化和入库策略共同造成的数据质量问题。

这篇自查表不讨论泛泛的“数据要去重”,而是从开发人员真正排查故障的顺序出发:先判断什么算重复,再定位重复在哪个环节产生,最后决定是拦截、合并、覆盖,还是保留历史版本。文中的 SQL、字段和数量均以通用示例或项目推演为主;涉及九数云的部分,用于说明如何通过可视化分析观察重复率、任务批次和异常分布,不代表任何平台的官方测试结果。

一、先讲核心结论:重复数据多,通常不是数据库单点失控

1. 先定义业务对象,再谈去重

“重复”必须放在业务对象里判断。同一个商品在同一家店铺中可能只有一个商品实体,但这个商品可能包含多个 SKU;同一个 SKU 又可能在不同时间产生多条价格、库存和促销状态记录。如果开发人员没有先确定数据表保存的是当前状态、商品实体,还是历史快照,任何去重操作都可能误删有效信息。

我通常会先问三个问题:这张表的一行代表什么?这行数据的稳定身份是什么?业务是否需要保留同一身份在不同时间的变化?如果这三个问题无法回答,就不应该直接建立删除脚本,更不应该把标题、图片或价格当成唯一标识。

数据对象一行记录代表什么常见业务键是否允许同一对象多行
店铺商品某店铺中的一个商品实体shop_id + product_id当前表通常不允许,历史表允许
商品 SKU某商品下的一个规格组合shop_id + product_id + sku_id同一版本不允许,不同时间可以允许
价格快照某 SKU 在某个时间的价格状态sku_id + crawl_time 或任务批次允许按时间保留
抓取原始记录一次采集动作获得的原始响应task_id + request_id + page_no原则上应保留,便于追溯

因此,同一商品出现多行不一定是异常,同一业务键在不允许重复的表中出现多行才是明确异常。这是后面所有质量校验规则的起点。

2. 把重复问题拆成四种类型

第一种是完全重复。除主键、写入时间或技术字段外,其他字段全部相同,常见原因是任务重跑、接口重复返回或消息重复消费。

第二种是业务重复。两行记录的标题、价格或图片略有差异,但实际上指向同一家店铺的同一个商品或 SKU。此类重复不能只依靠整行比较发现。

第三种是版本重复。同一个 SKU 在不同时间发生价格、库存或促销变化,业务上可能需要保留多行。这不是脏数据,而是历史数据模型的一部分。

第四种是误判重复。两个店铺销售同名商品,或者同一商品拥有不同规格,字段相似度很高,但业务身份并不相同。误判重复通常比漏掉一条重复记录更危险,因为它会破坏统计口径和历史追踪。

3. 质量校验要同时检查输入、过程和结果

很多团队只在数据库里查重复键,却不看抓取过程。这样只能看到结果,无法回答重复是由分页重叠、任务并发、字段清洗,还是接口本身造成的。

更完整的检查应分成三层:

  • 输入层:检查列表页、详情页、接口响应是否已经重复。
  • 过程层:检查分页游标、重试逻辑、任务批次、并发实例和字段标准化过程。
  • 结果层:检查业务键冲突、重复率、空标识、写入冲突和去重后的数据完整度。

如果输入层已经重复,入库层只能拦截,不能解决根因;如果输入没有重复而数据库出现重复,问题通常在写入幂等、并发竞态或唯一键设计。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

二、背景和真实场景:为什么任务显示成功,数据质量却可能失败

1. “任务成功”只说明程序没有异常退出

调度系统里的成功状态,通常只代表进程正常结束、接口返回符合预期,或者消息消费没有抛出未捕获异常。它不等于商品没有重复、SKU 没有丢失,也不等于本次数据可以安全合并到历史库。

我在排查此类问题时,会把“运行状态”和“数据状态”分开看。运行状态回答的是任务有没有执行完;数据状态回答的是本次新增是否合理、业务键是否冲突、字段是否完整,以及同一批次与上一批次重合了多少。

例如,一次任务计划抓取 5 万个商品,最后写入 7.8 万行。程序没有报错,但这个结果至少需要解释四件事:是否包含多个 SKU、是否同时保存了价格快照、是否存在多店铺数据、是否有分页重叠。如果这些都没有解释,写入成功反而可能意味着错误被完整保存了。

2. 分页漂移是最容易被忽略的重复来源

页码分页假设第 1 页和第 2 页之间的商品顺序稳定。但电商列表会因为上新、下架、销量排序、活动变化和库存变化持续移动。抓取第 1 页之后,列表内容发生变化,第 2 页可能就会包含第 1 页已经出现过的商品。

这不是说所有分页接口都会重复,而是说依赖页码的抓取逻辑必须验证分页稳定性。我会记录每页返回的商品 ID 集合,再计算相邻页和跨页的交集,而不是只看最终数据库里有没有重复。

如果接口支持游标、更新时间边界或稳定排序,应优先使用这些机制。如果只能使用页码,应至少保存抓取批次、页码、商品 ID 和请求时间,以便在出现异常时复盘重叠范围。

3. 任务重试会把“暂时失败”变成“永久重复”

最典型的场景是:任务执行到第 80 页时网络超时,调度器认为任务失败,随后从第 1 页重新开始。若写入逻辑是简单的逐行插入,前 79 页的数据就会被再次写入。

还有一种更隐蔽的情况:任务本身没有失败,但某个详情页请求超时后被重试。重试代码重新发送请求,返回结果被当成一条新记录写入。因为两次请求的响应时间不同,技术字段也不同,整行去重无法发现它们实际上是同一商品。

4. 推荐区、关联商品和主列表可能重复返回

许多商品页面并不只有一个商品集合。主列表、猜你喜欢、相似商品、品牌专区和广告位可能使用不同接口返回数据。采集程序如果把所有接口结果直接合并,就会出现同一商品多次进入待处理队列。

此时不能简单地认为“接口返回重复,所以接口有问题”。从业务角度看,接口可能只是提供了不同展示位置;真正需要处理的是采集程序是否为这些位置设置了来源字段,以及下游是否按照业务键合并。

5. 九数云案例:重复率要看分组口径,而不是只看总条数

如果将商品抓取结果同步到九数云进行分析,我通常不会先做一个“重复商品总数”指标,而是分别建立店铺商品数、商品业务键冲突数、SKU 冲突数和批次重合率。因为同一批数据在不同分析口径下,结论可能完全不同。

例如,某次示例数据有 12 万行。按整行字段判断,只有 1800 行完全相同;按“店铺 ID + 商品 ID”判断,有 6200 个业务键冲突;按“SKU + 抓取日期”判断,真正需要保留的历史记录仍然存在。若只看整行重复率,团队会低估问题;若不区分时间,又会把正常快照当成异常。

分析口径示例结果能回答的问题不能直接回答的问题
整行完全重复1800 行是否存在完全相同的重复写入同一商品字段变化后是否重复
店铺 + 商品 ID6200 个冲突键商品实体是否多次出现这些记录是否都是错误版本
SKU + 抓取日期2300 个冲突键同一日内 SKU 是否重复跨日价格变化是否需要保留
任务批次重合18.6%本批次与历史批次重合程度重合是否符合增量策略

这类分析的价值不在于某个数字有多大,而在于让团队看到:重复率本身没有统一答案,统计口径决定了你看到的是写入故障、业务版本,还是正常的数据更新。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

三、常见误区:看似去重,实际把问题推到了别处

1. 误区一:把标题当成商品唯一键

标题是展示字段,不是稳定身份。两个店铺可能销售同名商品,同一店铺也可能修改标题;标题还可能包含颜色、容量、促销词和活动标签。将标题直接作为唯一键,既会把不同商品合并,也会让同一商品因为标题变化生成新记录。

如果商品 ID 缺失,需要使用标题、品牌、规格、图片或详情页 URL 做候选匹配,但这类匹配应该标记为“疑似重复”,交给人工抽样或更严格的规则复核,不宜直接执行强删除。

2. 误区二:只用 DISTINCT 解决所有重复

DISTINCT适合发现完全相同的字段组合,但它不知道一行数据代表商品、SKU 还是价格快照。只要抓取时间、请求批次或价格发生变化,两行记录就不再完全相同。

更严重的是,很多开发人员会先用 DISTINCT 生成临时表,再覆盖正式表。这个过程没有保留被合并记录的原因,也没有记录选择哪一行作为保留版本,出了问题之后无法追溯。

— 只适合查找完全相同的业务字段组合
SELECT

shop_id,

product_id,

sku_id,

title,

price,

stock,

COUNT(*) AS row_count

FROM product_snapshot

GROUP BY

shop_id,

product_id,

sku_id,

title,

price,

stock

HAVING COUNT(*) > 1;

上面的查询可以发现完全相同的组合,但它不能说明应该删除哪一行,也不能替代历史版本策略。

3. 误区三:先查再插就足够安全

很多程序会先执行“是否存在”,如果不存在再执行“插入”。单线程环境下看起来没有问题,但两个任务同时检查同一个业务键时,都可能得到“不存在”,然后同时插入。

这就是典型的竞态条件。解决它的核心不是把查询写得更复杂,而是让数据库层具备原子约束,或者使用真正的原子 upsert。应用层检查可以作为辅助,但不能承担最终的一致性责任。

— 推荐先确认唯一约束,再使用冲突更新
CREATE UNIQUE INDEX uk_shop_product_sku

ON product_current (shop_id, product_id, sku_id);

— PostgreSQL 示例

INSERT INTO product_current (

shop_id,

product_id,

sku_id,

title,

price,

stock,

updated_at

)

VALUES (

:shop_id,

:product_id,

:sku_id,

:title,

:price,

:stock,

CURRENT_TIMESTAMP

)

ON CONFLICT (shop_id, product_id, sku_id)

DO UPDATE SET

title = EXCLUDED.title,

price = EXCLUDED.price,

stock = EXCLUDED.stock,

updated_at = EXCLUDED.updated_at;

不同数据库的语法不同,但判断原则相同:唯一键负责阻止重复,upsert 负责定义冲突后的业务动作。

4. 误区四:唯一键越长越保险

唯一键字段越多,确实越不容易冲突,但也更容易把同一对象拆成多行。例如把价格、库存、标题和抓取时间全部加入唯一键,商品每次价格变化都会产生一条新记录。对于当前商品表,这通常不是想要的结果。

唯一键设计要服务于表的用途。当前状态表需要稳定识别实体;历史快照表需要识别实体加时间或版本。两类表最好分开,而不是用一个表同时承担两种职责。

5. 误区五:重复率下降,就说明方案成功

如果去重脚本删除了大量记录,重复率当然会下降,但这可能是因为正常的价格历史、不同店铺商品或不同 SKU 被误删。一个合格的方案不仅要看重复率,还要看唯一商品数、SKU 数、价格变化记录数、空标识数和下游报表是否异常。

我会特别关注“去重前后有效对象数量变化”。如果重复率下降 10%,但唯一 SKU 数同时下降 15%,就需要怀疑规则是否过强;如果去重后业务对象数量几乎不变,但写入冲突显著下降,说明方案可能在拦截重复写入方面更合理。

6. 误区六:原始数据可以直接覆盖

原始响应是排查数据问题的重要证据。它可以帮助开发人员确认接口到底返回了什么、字段在什么时间发生变化,以及清洗逻辑是否改变了原始值。

如果直接把原始层清洗覆盖,后续即使发现商品少了、价格错了,也很难判断问题发生在接口、解析器还是入库阶段。更稳妥的做法是保留原始层、标准化层和业务层,清洗结果通过版本或处理日志关联起来。

四、专业判断逻辑:如何判断一条记录该拦截、合并、覆盖还是保留

1. 第一步:确定数据表的业务目的

如果表用于商品当前状态展示,目标通常是“一个业务键一条有效记录”。价格和库存变化应更新当前行,而不是无止境新增。

如果表用于价格趋势、库存波动或竞品历史分析,目标则是“保留业务对象的时间变化”。这时同一 SKU 多行不是问题,真正要检查的是同一时间粒度内是否重复。

如果表用于问题追溯,原始采集记录可能需要保留每次请求,即使它们最终映射到同一个商品。此时可以在业务层去重,但不能删除原始层证据。

2. 第二步:给业务键分级

我会把字段分成三类。第一类是强身份字段,例如店铺 ID、商品 ID、SKU ID,前提是确认它们在对应范围内稳定且唯一。第二类是辅助身份字段,例如标准化 URL、品牌、规格和图片哈希,用于 ID 缺失时的候选匹配。第三类是状态字段,例如价格、库存、销量和评分,通常不应作为实体唯一键。

字段类别示例适合做唯一键吗使用建议
强身份字段shop_id、product_id、sku_id通常适合先确认平台作用域和稳定性
辅助身份字段标准化 URL、品牌、规格不建议单独使用用于候选匹配和人工复核
状态字段price、stock、rating通常不适合用于更新、版本和质量比较
技术字段crawl_time、request_id、batch_id视表用途而定历史表可以参与版本识别,当前表不宜作为实体键

3. 第三步:确认平台 ID 的作用域

有些商品 ID 只在单个店铺内唯一,有些接口中的 ID 还会受地区、渠道或站点影响。若直接使用 product_id 建全局唯一索引,可能把不同店铺的记录错误合并。

在没有明确文档说明之前,我更倾向于使用带作用域的组合键,例如 shop_id + product_id。如果后续确认商品 ID 全局稳定,再考虑简化。宁可先保守地保留记录,也不要因为一个未经验证的全局假设造成大范围误删。

4. 第四步:确定冲突后的动作

发现冲突后,不是所有情况都应该删除。可以按照以下逻辑处理:

  • 完全相同且属于当前状态表:保留一条,其余记录进入重复日志。
  • 业务键相同、状态字段不同且属于当前状态表:按更新时间或数据完整度选择更新版本。
  • 业务键相同、时间不同且属于历史表:保留记录,但检查时间粒度和任务批次。
  • 标题相似、业务键不同:标记疑似重复,不直接合并。
  • 业务键缺失:进入异常队列,先补齐身份字段或人工复核。

5. 第五步:用“保留原因”替代“删除原因”

很多去重流程只记录被删除的行,却不记录最终保留哪一行以及为什么保留。更可审计的做法是,为合并结果增加 dedup_actiondedup_rulesource_batch_idmerged_at 等字段。

例如,规则可以写成“同一店铺、同一商品、同一 SKU、同一任务批次内,保留字段完整度最高且抓取时间最新的一行”。这样当业务人员问“为什么这条价格被覆盖”时,开发人员能够解释规则,而不是凭记忆排查。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

五、具体案例和数据观察:从重复记录反推系统缺陷

1. 案例一:任务重跑后数据翻倍

下面是一个脱敏的情景模拟。某商品监测任务每天凌晨执行,计划抓取 100 个分页,每页约 500 条商品。任务在第 73 页发生网络超时,调度器在 10 分钟后重新执行,但重试策略从第 1 页开始,正式表也没有业务唯一索引。

阶段写入记录业务键重复数现象解释
首次执行至第 72 页36000 条0首次写入没有暴露重复问题
首次任务失败0 条新增0程序异常被调度器捕获
从第 1 页重试50000 条36000 个前 72 页大部分数据被再次写入
执行完成后86000 条36000 个任务状态成功,但结果表已出现明显重复

这个案例里,问题不是“爬虫重复抓取”这么简单,而是三个设计缺陷叠加:任务没有记录可恢复游标,写入没有幂等键,质量校验没有比较本次批次与既有数据的重合率。

修复时可以采用两种路径。第一种是断点续采:记录最后成功页码或最后一个稳定游标,失败后从断点继续。第二种是允许任务从头重跑,但必须使用业务键加批次策略,确保同一商品不会因为重试而新增重复实体。

2. 案例二:同名商品被错误合并

另一个常见案例是按标题去重。店铺 A 和店铺 B 都销售“无线降噪耳机”,标题完全相同,图片也使用了相似素材。开发人员用标题加图片哈希作为去重条件,结果只保留了一条记录。

从商品数量看,数据变少了;从业务分析看,两个店铺的销量、价格和库存被混成一个对象。后续做店铺排名时,店铺 B 的商品消失,价格对比也失去意义。

正确做法是先把店铺作为身份作用域,使用 shop_id + product_id 区分商品。标题和图片可以用于发现跨店铺同款,但“同款”不等于“同一业务记录”,二者需要分别建模。

3. 案例三:价格历史被当成重复数据

某 SKU 在 9:00、13:00 和 21:00 分别抓到 99 元、89 元和 79 元。若数据表用于当前商品展示,只保留 79 元是合理的;若数据表用于价格趋势分析,删除前两条就会丢失降价过程。

因此,我会建议把当前表和历史表拆开。当前表保存每个 SKU 的最新状态,历史表保存每次有效变化。若价格没有变化,也可以按业务需求选择不新增快照,或者保留固定时间间隔的观测记录。

4. 案例四:字段格式差异造成“假不重复”

有两条记录的商品 ID 分别是 P10086P10086,看起来不一样,实际只是前者多了一个空格。类似情况还包括 URL 末尾追踪参数、全角半角字符、大小写差异和 Unicode 空白字符。

如果字段标准化发生在业务键生成之后,数据库会把它们当作两个不同对象。标准化应该在生成幂等键之前完成,且原始值与标准化值都应保留,便于问题追溯。

def normalize_id(value):
if value is None:

return None

value = value.replace("\u3000", " ")

value = value.strip()

return value.upper()

def build_business_key(shop_id, product_id, sku_id):

normalized_product_id = normalize_id(product_id)

normalized_sku_id = normalize_id(sku_id)

if not shop_id or not normalized_product_id:

return None

return "|".join([

str(shop_id).strip(),

normalized_product_id,

normalized_sku_id or "NO_SKU"

])

这里的关键不是代码本身,而是顺序:先标准化,再生成业务键;先判断关键字段是否缺失,再决定进入正常写入还是异常队列。

5. 用可视化工具观察重复率变化

当数据量达到数十万甚至数百万行时,开发人员只看数据库查询结果,往往难以发现重复问题的趋势。将任务批次、店铺、商品层级和重复类型同步到九数云这类分析工具中,可以按时间观察重复率、冲突键数和异常分布。

我建议至少做四个视图:按任务批次的业务键冲突趋势、按店铺的重复率横向比较、按错误类型的数量分布、去重前后的商品与 SKU 数变化。这样可以判断问题是某次任务突然出现,还是某几个店铺长期存在。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

六、开发人员可直接执行的质量校验自查表

1. 采集入口检查

先确认同一商品是否从多个入口进入待处理队列。建议给每条发现记录增加 source_typesource_pagesource_positiondiscovered_at 字段。

  • 主列表和推荐区是否可能返回同一商品?
  • 分页参数是否真正生效?
  • 排序是否稳定,抓取过程中是否发生列表漂移?
  • 详情页是否被多个列表重复发现?
  • 失败重试是否重复发送相同请求?

如果无法回答这些问题,先不要优化 SQL。你需要先从采集日志中找出商品 ID 的来源和重复路径。

2. 字段标准化检查

业务键生成前,应统一处理空格、大小写、URL 参数、特殊字符和空值。标准化规则要写成可测试的函数,而不是散落在多个解析器中。

  • 商品 ID 和 SKU ID 是否去除前后空白?
  • 全角空格和不可见字符是否被处理?
  • URL 是否移除无业务意义的追踪参数?
  • 缺失 SKU 是否被错误地统一填成同一个字符串?
  • 不同接口返回的 ID 类型是否统一为字符串或统一为数值?

特别要注意缺失值。若所有缺失 SKU 都填成 UNKNOWN,再用商品 ID 加 SKU 作为唯一键,可能会把同一商品下多个未知规格错误合并。

3. 业务键检查

每张表都应明确写出唯一性假设。例如,当前商品表使用“店铺 + 商品 ID”,当前 SKU 表使用“店铺 + 商品 ID + SKU ID”,价格历史表使用“SKU + 时间粒度 + 数据来源”。

检查项通过标准不通过时的风险
商品 ID 是否稳定同一店铺内长期指向同一商品更新被误判为新商品
SKU ID 是否覆盖规格颜色、尺码等规格能被区分不同规格被错误合并
店铺是否纳入键确认 ID 的作用域不同店铺记录互相覆盖
时间粒度是否明确分钟、小时、日期或任务批次已确定历史快照重复或有效变化丢失

4. 数据库写入检查

数据库层至少要有一条真正有效的防线。对于当前状态表,优先设置业务唯一索引;对于历史表,使用明确的版本键;对于原始层,则保留请求 ID 或响应哈希,避免同一响应无限重复保存。

  • 是否存在与业务定义一致的唯一索引?
  • 冲突时是更新、忽略,还是进入异常表?
  • 并发写入是否使用原子操作?
  • 写入失败后的重试是否具备幂等性?
  • 唯一冲突是否记录了任务批次和原始数据来源?

5. 调度和并发检查

同一个任务可能同时被定时器、手工按钮和失败重试触发。只要没有任务锁或实例状态控制,就可能出现两个采集实例同时处理同一个店铺。

  • 是否允许同一店铺的相同任务并发执行?
  • 手动补采是否会与定时任务重叠?
  • 任务是否有唯一批次号和实例号?
  • 失败重试是否限制次数和重试范围?
  • 任务终止后,未完成的分页游标是否可以恢复?

6. 质量指标检查

重复率不应只有一个总数。建议至少监控以下指标:

  • 业务键冲突数:同一唯一键出现多行的数量。
  • 整行重复率:完全相同记录占总记录的比例。
  • 批次重合率:本次批次与历史批次的业务键重合比例。
  • 空业务键率:商品 ID、SKU ID 等关键字段缺失的比例。
  • 写入冲突率:触发唯一约束或 upsert 冲突的比例。
  • 去重后保留率:清洗后保留记录数与原始记录数的比例。

— 检查同一批次内的业务键冲突
SELECT

batch_id,

shop_id,

product_id,

sku_id,

COUNT(*) AS key_count

FROM product_snapshot

GROUP BY

batch_id,

shop_id,

product_id,

sku_id

HAVING COUNT(*) > 1;

— 计算批次内重复键数量

WITH duplicated_keys AS (

SELECT

batch_id,

shop_id,

product_id,

sku_id

FROM product_snapshot

GROUP BY

batch_id,

shop_id,

product_id,

sku_id

HAVING COUNT(*) > 1

)

SELECT
batch_id,
COUNT(*) AS duplicated_key_count
FROM duplicated_keys
GROUP BY batch_id
ORDER BY batch_id;

7. 去重结果复核检查

清洗完成后,不要只检查删除了多少行。至少要对比去重前后的商品数、SKU 数、价格变化记录数、字段完整度和异常记录数。

  • 唯一商品数是否出现不合理下降?
  • 不同店铺的同名商品是否仍然能够区分?
  • 不同规格的 SKU 是否被保留?
  • 价格历史是否按照业务要求保留?
  • 下游报表的商品数量和金额是否出现突变?

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

七、不同情况下的行动建议与技术取舍

1. 数据量小、任务频率低:优先保证可追溯

如果每天只抓取几千条商品,暂时不需要复杂的分布式架构。更重要的是保留原始数据、任务批次和业务键,建立简单的唯一索引,定期做异常抽样。

这类项目不必一开始就引入消息队列或复杂锁机制,但要避免把原始表和当前表混在一起。用清晰的数据分层换取后续排查效率,通常比过早追求吞吐量更划算。

2. 数据量大、任务频率高:优先保证幂等和分片

当任务需要每小时甚至更高频率运行时,重复风险会随着任务次数和并发量放大。此时应将任务拆分到店铺、类目或时间窗口,并为每个分片建立独立批次状态。

写入方面,建议使用数据库唯一约束配合原子 upsert;调度方面,避免同一分片被多个实例同时执行;监控方面,重点关注批次重合率和写入冲突率,而不仅是任务耗时。

3. 平台 ID 稳定:使用强业务键

如果已经确认店铺 ID、商品 ID 和 SKU ID 长期稳定,优先使用这些字段建立唯一键。它们的解释性强,性能也通常优于多字段模糊匹配。

但仍要确认 ID 的作用域。例如商品 ID 可能只在店铺内唯一,这时应把店铺 ID 放入组合键。不要因为字段名称看起来像全局 ID,就默认它可以跨平台、跨店铺使用。

4. 平台 ID 缺失或频繁变化:采用分层匹配

缺少稳定 ID 时,可以先使用标准化 URL、品牌、型号、规格和图片等字段生成候选匹配键。但这些字段只能用于提高匹配概率,不能天然证明两条记录属于同一对象。

建议设置三个结果状态:确定同一、确定不同、待复核。只有确定同一的记录才进入自动合并;待复核记录保留原始数据并进入人工抽样队列。

5. 需要价格历史:当前表和历史表分开

这是我最推荐的建模取舍。当前表追求查询简单和业务键唯一;历史表追求时间连续和变化可追溯。两者混在一起,开发人员会不断在“覆盖”和“保留”之间冲突。

方案优点代价适用场景
只保留最新状态查询快,表规模可控无法分析变化过程商品当前展示、库存看板
每次抓取都保留历史完整,便于追溯存储成本高,重复清理复杂价格监控、竞争分析
仅变化时保留兼顾历史和存储成本需要比较前后状态价格和库存变化分析
固定时间粒度保留数据规模可预测可能遗漏粒度内短时变化趋势报表和周期性分析

6. 追求实时性:接受更高的写入冲突处理成本

实时抓取通常意味着更多并发、更短的重试间隔和更频繁的状态更新。它可以提高价格变化的发现速度,但也会增加分页漂移、并发写入和短时间重复返回的概率。

如果业务只要求每天一次价格对比,没有必要为实时性支付全部系统复杂度。反过来,如果价格变化本身就是交易决策依据,就应接受更高的存储、监控和冲突处理成本,并在数据模型中明确观察时间。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

八、上线前后的验证方法:不要用一次查询证明系统已经可靠

1. 上线前先建立基线

在修改去重逻辑前,先记录至少三个批次的基线:总抓取量、唯一商品数、唯一 SKU 数、业务键冲突数、空键数、批次重合率和平均写入耗时。

没有基线,就无法判断上线后的变化是质量改善,还是抓取范围、店铺数量或接口返回变化造成的。尤其是商品数量突然下降时,必须区分“重复被清理”和“有效商品漏采”两种情况。

2. 使用小批量回放验证规则

不要直接拿全量生产数据运行删除脚本。可以选取一个店铺、一个类目和一个时间窗口,回放原始数据,比较不同规则下的保留结果。

  • 规则 A:按整行完全相同去重。
  • 规则 B:按店铺加商品 ID 去重。
  • 规则 C:按店铺、商品、SKU 和时间粒度去重。
  • 规则 D:标题和图片仅用于生成疑似重复候选。

把四种规则的结果并排查看,通常很快就能发现哪种规则会误删同名商品、规格商品或历史价格。

3. 观察写入冲突,而不是把冲突全部视为错误

引入唯一索引后,写入冲突数量可能上升,因为系统终于把之前悄悄落库的重复记录拦截出来了。冲突本身不一定代表新问题,关键是区分预期冲突和异常冲突。

例如同一批次内同一商品被两个入口发现,可能属于可预期的重复发现;而不同店铺却共享同一业务键,则可能是唯一键范围设计错误。日志应记录冲突来源、批次、店铺和原始请求,而不是只记录一个错误计数。

4. 抽样检查被保留和被合并的记录

每次规则上线后,应同时抽查保留记录和合并记录。只看被删除数据,容易错过字段被错误覆盖的问题;只看保留数据,又无法确认合并逻辑是否合理。

建议优先抽查以下样本:标题相同但店铺不同的商品、一个商品下有多个 SKU 的商品、短时间内价格连续变化的 SKU、商品 ID 缺失的记录,以及任务失败后重试的批次。

5. 设置回滚和恢复路径

去重方案一旦误删,恢复成本可能远高于开发成本。因此清理前应做快照或写入隔离表,保留原始主键、原始批次和处理规则。

如果数据量较大,不建议直接执行不可逆删除。可以先将疑似重复记录标记为无效,观察一到两个任务周期,确认下游报表、价格趋势和商品数没有异常后,再决定是否物理清理。

电商数据抓取:开发人员自查表:质量校验最容易出现的重复数据多

九、开发人员最终自查表:按顺序完成,而不是一次性打勾

1. 业务定义自查

  • 我是否能用一句话说明这张表的一行代表什么?
  • 我是否区分了商品、SKU、价格快照和原始响应?
  • 我是否明确当前状态和历史版本的保留策略?
  • 我是否知道同一商品多行在什么情况下是正常的?

2. 身份字段自查

  • 商品 ID、SKU ID 是否稳定?
  • 这些 ID 的唯一范围是全局、店铺、站点还是渠道?
  • 店铺 ID 是否应该纳入业务键?
  • ID 缺失时是否有明确的异常处理方案?
  • 标题、图片和价格是否被错误地当成唯一身份?

3. 采集流程自查

  • 列表分页是否存在跨页重复?
  • 推荐区、关联商品和主列表是否重复发现?
  • 分页排序是否稳定?
  • 失败重试是否从头开始?
  • 是否记录请求、页面、游标和数据来源?

4. 清洗和写入自查

  • 业务键生成前是否完成字段标准化?
  • 空值、空格、大小写和 URL 参数是否统一处理?
  • 是否有与业务定义一致的唯一索引?
  • 写入冲突后是更新、忽略还是进入异常表?
  • 是否使用原子 upsert,而不是依赖先查再插?

5. 调度和监控自查

  • 同一任务是否可能被多个实例同时执行?
  • 是否有任务批次号、实例号和重试次数?
  • 失败后能否从断点恢复?
  • 是否监控业务键冲突率、空键率和批次重合率?
  • 是否能查看异常记录来自哪个店铺、页面和任务?

6. 清理和复核自查

  • 清理前是否保留原始数据或快照?
  • 是否记录了合并规则和保留原因?
  • 是否抽查了同名不同店铺商品?
  • 是否抽查了不同 SKU 和价格历史?
  • 去重后唯一商品数、SKU 数和下游报表是否合理?

十、结语:真正可靠的去重,不是让数据库变小

电商数据抓取中的重复数据,表面上是记录数量异常,底层却是数据身份没有被准确建模。开发人员如果只盯着重复行,很容易把正常历史版本当成脏数据;如果只依赖唯一索引,又可能把上游重复输入、任务重试和字段标准化问题留在系统里。

我更推荐把这件事看成一条完整的数据质量链路:先定义一行数据代表什么,再确认业务键的作用域;先记录重复从哪里进入,再决定冲突后的业务动作;最后用批次趋势、抽样复核和下游结果验证方案,而不是用一次 DELETE 或一次 DISTINCT 结束问题。

如果你现在正在排查重复数据,下一步不要从删除脚本开始。先选取一个具体任务批次,保留商品来源、店铺 ID、商品 ID、SKU ID、页码、请求时间和批次号,然后分别统计整行重复、商品键冲突、SKU 日内冲突和批次历史重合。只要这四个数字被拆开,很多看似混乱的问题通常就能定位到具体环节。

最终目标不是让数据表里的行数尽可能少,而是让每一行都能被解释:它代表哪个业务对象、来自哪次采集、为什么被保留,以及在发生冲突时系统为什么选择更新、合并或继续保存。这才是电商数据抓取质量校验中,真正可维护、可审计、可复用的重复数据治理方案。

常见问题解答(FAQ)

1. 电商数据抓取中,哪些情况最容易造成重复数据?

我在做商品和 SKU 抓取时发现,任务本身显示成功,并不代表入库数据可靠。尤其是任务重试、分页变化和多个入口同时返回同一商品时,重复记录往往不会立刻暴露,直到数据量异常增长才被发现。

我排查过一类很典型的测试任务:同一商品列表每页返回 100 条,任务首次运行写入 9,860 条;由于网络超时,系统从第 1 页重新开始重试,最终数据库里变成 11,420 条。

表面上看只是多了 1,560 条,实际按店铺 ID、商品 ID 和 SKU ID 联合统计后,发现其中 1,497 条是重复业务对象。这说明重复数据通常不是一个单点故障,而是由四个环节叠加产生:抓取端重复发现、清洗端标识不稳定、入库端缺少幂等控制,以及调度端允许相同任务并发执行。

产生环节常见表现优先检查项 分页抓取相邻页面出现相同商品页码参数、排序稳定性、游标是否生效 任务重试重跑后数据量接近翻倍是否从头重跑、是否记录断点 多入口采集列表和推荐位重复返回商品商品 ID 是否在入库前合并 并发写入偶发出现同一业务键多行先查后插是否存在竞态 开发人员自查时,不建议先执行 DELETE 或简单使用 DISTINCT。

第一步应当确认重复的业务定义:完全相同的记录、同一商品的不同抓取版本、同一 SKU 的多次写入,以及标题相同但实际不同的商品,处理方式完全不同。我的判断是,最危险的不是一次性出现的大量重复,而是每天只增加少量、又没有监控的重复记录。

这类问题会慢慢污染价格趋势、库存统计和商品数量报表,等到业务方发现时,通常已经很难准确回溯原始数据。

2. 商品、SKU 和价格快照应该分别使用什么去重键?

我曾经尝试过直接用商品标题和详情页 URL 去重,结果把不同店铺的同款商品合并了,也把同一 SKU 的价格变化误删。电商数据里到底应该怎样定义唯一记录,我一直觉得不能只看字段是否相同。

去重键的设计,首先取决于这张表要回答什么业务问题。如果表用于保存当前商品实体,重点是识别同一个商品;如果表用于分析价格变化,就必须允许同一商品在不同时间出现多条快照。

数据对象建议业务键不建议作为唯一键的字段原因 店铺商品shop_id + product_id商品标题、当前价格标题和价格都会变化 SKUshop_id + product_id + sku_id颜色、尺码文本规格文本可能被重新命名 价格快照sku_id + crawl_batch_id页面排序、图片 URL需要区分不同采集批次 自然键缺失的数据标准化字段组合或指纹单独使用标题容易误合并不同商品 我更推荐把“实体表”和“快照表”拆开。

实体表只保留当前商品或 SKU 状态,采用唯一索引和 upsert;快照表保留抓取批次、抓取时间、价格、库存和来源,用于趋势分析。这样既能避免当前库不断膨胀,也不会因为去重而丢失历史变化。一个常见错误是把 crawl_time 放进商品实体的唯一键。

这样每次抓取都会生成新记录,看起来没有重复,实际上只是把重复问题隐藏成了版本膨胀。相反,如果把价格字段放进唯一键,价格一变就会被当成新商品,同样不合理。建议在建表前先写出三句话:什么叫同一个商品、什么叫同一个 SKU、什么情况下应该保留历史版本。

若这三句话无法说清楚,说明唯一键还没有达到可以落地的程度。

3. 为什么数据库已经设置唯一索引,重复数据仍然可能出现?

我原本以为给 product_id 加上唯一索引就能解决重复写入,但在一次并发抓取测试中,仍然看到了异常数据。后来才发现,有些重复并不是数据库放进了两条相同记录,而是清洗后的标识为空、格式不一致,或者唯一键设计错了。

唯一索引很重要,但它只负责阻止数据库中违反既定规则的写入,无法判断你的规则是否正确。比如只对 product_id 建唯一索引,而不同店铺可能使用相同的商品编号,那么这个索引会误伤正常数据;如果 product_id 前后带空格或大小写不统一,又可能让本应相同的对象变成不同值。

我建议把重复排查分成三层。第一层是原始值检查,确认接口返回的 ID 是否为空、带空格或携带无关参数;第二层是标准化检查,确认清洗后是否得到稳定的业务键;第三层才是数据库约束检查,验证联合唯一索引和 upsert 是否真的生效。

问题错误做法更稳妥的做法 不同店铺使用相同商品 ID只用 product_id 唯一使用 shop_id + product_id 先查询再插入应用层判断不存在后 INSERT使用唯一索引配合原子 upsert ID 带空格或大小写差异直接保存原始字符串先 trim、统一格式,再生成业务键 标识为空把所有空值填成同一个默认值标记异常并进入隔离表 并发场景尤其容易踩坑。

两个进程几乎同时执行“查询是否存在”,都得到不存在的结果,然后分别执行插入。如果没有数据库唯一约束,重复记录就会产生;如果有唯一约束,其中一个请求会失败,程序还必须正确处理冲突,否则可能不断重试。

可以用下面的查询先定位业务键冲突:

SELECT shop_id, product_id, sku_id, COUNT(*) AS duplicate_count FROM product_data GROUP BY shop_id, product_id, sku_id HAVING COUNT(*) > 1;

我的判断是,唯一索引解决的是“最后一道门”的问题,而重复数据治理要解决的是“门牌号是否正确、来访者是否被重复登记、失败后是否反复进门”。只有业务键、标准化、幂等写入和异常监控同时存在,数据库约束才真正有价值。

4. 如何验证去重方案有效,而且没有误删正常的商品数据?

我最担心的不是去重失败,而是去重成功后把有效数据删掉。比如同一个 SKU 的价格历史被当成重复记录,或者同标题但不同店铺的商品被合并。有没有一套上线前后都能执行的验证方法?

去重不能只看总记录数是否下降。一次测试中,我把 100,000 条抓取记录按业务键处理后,数据量减少了 8,430 条,但进一步抽样发现,其中约 600 条属于不同抓取批次的有效价格快照。如果只看“重复率下降”,这个方案会被误判为成功。

我通常会采用“数量对比、字段完整度、业务抽样、后续观察”四步验证,而不是直接相信删除结果。

验证维度上线前后要比较的指标异常信号 数量总记录数、唯一商品数、唯一 SKU 数唯一对象数量异常下降 完整度价格、库存、标题和图片非空率保留下来的记录字段更残缺 业务样本同标题不同店铺、不同规格、不同时间价格正常对象被合并 稳定性下一轮任务的冲突数和重复率重复持续增加或冲突暴涨 抽样时不要只抽普通商品,应专门选择最容易误判的边界样本:相同标题但不同店铺、同一商品的多个 SKU、价格变化明显的记录、商品 ID 缺失的记录,以及 URL 仅参数不同的记录。

每类至少抽取一批人工核对,才能看出去重规则是否过于激进。去重前最好保留原始层,并把被合并记录写入处理日志,至少记录原始主键、保留记录主键、合并原因和处理时间。这样业务人员发现商品数量异常时,可以追溯“为什么删掉或合并了这条数据”,而不是只能从备份中猜测。

上线后建议持续监控四个指标:业务键冲突数、空标识数、单批次与历史数据重合率、去重前后记录变化量。不要照搬固定阈值,先用连续几轮正常任务建立基线,再根据任务频率和平台波动设置告警。最终判断标准不是“数据库里没有重复行”,而是数据在业务上可解释、去重过程可追溯、后续任务不会持续制造同类问题。

对于需要价格或库存趋势分析的系统,保留历史快照往往比追求表面上的低重复率更重要。

核心关键词

读者评论

郑俊杰

文章把“重复数据”区分为完全重复、业务重复和版本重复,这个划分比较实用。尤其是先明确一行记录代表什么,再决定是否去重,能避免误删价格历史。

陈梦琪

分页漂移和任务重试确实是抓取项目中容易忽视的原因。建议文中提到的页码、批次、请求时间等字段作为常规日志保留,后续定位问题会更方便。

江舒然

只用DISTINCT处理重复数据存在局限,文章对这一点解释得比较清楚。实际项目中还应结合业务唯一键、幂等写入和并发控制,不能只依赖数据库清洗。

免责申明:本文内容通过AI工具匹配关键字智能整合而成,仅供参考,帆软及九数云不对内容的真实、准确或完整作任何形式的承诺。如有任何问题或意见,您可以通过联系jiushuyun@fanruan.com进行反馈,九数云收到您的反馈后将及时处理并反馈。
咨询方案
咨询方案二维码

扫码咨询方案

热门产品推荐

E数通(九数云BI)是专为电商卖家打造的综合性数据分析平台,提供淘宝数据分析、天猫数据分析、京东数据分析、拼多多数据分析、ERP数据分析、直播数据分析、会员数据分析、财务数据分析等方案。自动化计算销售数据、财务数据、绩效数据、库存数据,帮助卖家全局了解整体情况,决策效率高。

相关内容

查看更多
电商搜索关键词数据:电商新手进阶版:投放词的完整方法与步骤

电商搜索关键词数据:电商新手进阶版:投放词的完整方法与步骤

电商搜索关键词数据:电商新手进阶版:投放词的完整方法与步骤 很多电商新手第一次看关键词报表,都会先找“搜索量最 […]
电商搜索关键词数据:电商新手从零入门:关键词挖掘先掌握搜索热度

电商搜索关键词数据:电商新手从零入门:关键词挖掘先掌握搜索热度

做电商关键词挖掘时,我见过最容易被误判的一组数据:某个大词搜索热度很高,商品标题也顺利覆盖了它,但连续两周点击 […]
电商搜索关键词数据:电商新手怎么用:从长尾词到提升点击转化

电商搜索关键词数据:电商新手怎么用:从长尾词到提升点击转化

电商搜索关键词数据,最容易被新手看错的地方,是把“搜索量高”当成“值得做”。我曾经在整理商品搜索词时遇到过一个 […]
电商搜索关键词数据:电商新手常见误区:月度复盘为什么总遇到趋势判断慢

电商搜索关键词数据:电商新手常见误区:月度复盘为什么总遇到趋势判断慢

电商搜索关键词数据:电商新手常见误区:月度复盘为什么总遇到趋势判断慢 很多电商新手不是没有数据,而是第一次看到 […]
电商搜索关键词数据:电商新手实操指南:围绕趋势词解决“数据口径乱

电商搜索关键词数据:电商新手实操指南:围绕趋势词解决“数据口径乱

电商搜索关键词数据:电商新手实操指南:围绕趋势词解决“数据口径乱” 做电商关键词分析时,最容易让新手误判的,不 […]

让电商企业精细化运营更简单

整合电商全链路数据,用可视化报表辅助自动化运营

让决策更精准