电商数据抓取:数据新手新手问答:存储方案做不好会出现哪些采集不稳定
电商数据抓取出现“任务运行正常、数据却越来越少”,很多新手第一反应是目标平台改了页面结构,或者自己的请求被限制了。实际排查中,存储层往往才是被忽略的故障源:磁盘写满、数据库写入变慢、连接池耗尽、队列堆积、重复重试和进度没有落盘,都会把一个原本正常的采集任务,逐步拖成超时、丢数、重复入库甚至整体中断。
我处理这类问题时,不会先问“用了哪种数据库”,而是先画出完整链路:数据从哪里产生,在哪里暂存,什么时候解析,如何写入,怎样去重,任务中断后从哪里恢复。存储方案的稳定性,不取决于数据库名称本身,而取决于数据生产速度、写入速度、失败处理方式和恢复机制是否匹配。
一个电商采集任务通常不是“发请求,然后保存一行数据”这么简单。它至少包含请求、响应、解析、暂存、清洗、去重、写入、状态更新和失败重试等环节。只要后面的存储环节处理不过来,前面的采集线程就会等待、阻塞或不断重试。
例如,采集程序每分钟产生 2,000 条商品记录,但数据库每分钟只能稳定写入 1,200 条。最开始系统可能只是出现一点延迟,运行一段时间后,内存队列会越来越长,消费者线程持续占用连接,最后表现为请求超时、任务失败率升高,甚至让人误以为目标网站不稳定。
我更愿意把这条链路理解为“生产者,缓冲区,消费者”模型。采集程序是生产者,消息队列或本地缓存是缓冲区,数据库和对象存储是消费者。生产速度长期高于消费速度时,任何系统都不可能稳定,区别只在于它会先在哪里暴露问题。

这五类问题并不孤立。磁盘空间不足可能导致数据库写入失败,写入失败又触发重试,重试增加后会产生更多日志和临时文件,最终进一步消耗磁盘空间。这类“故障自我放大”比单次报错更危险,因为系统可能在很长时间内看起来仍然能够运行。
HTTP 请求返回 200,只能说明请求层收到了一个成功响应,不能证明商品字段解析成功,更不能证明数据已经可靠写入数据库。一个完整的成功判断,至少要同时观察请求成功率、有效解析率、入库成功率、业务去重率和任务进度更新率。
在实际监控中,我通常会把“有效入库量”作为比“请求数量”更重要的业务指标。如果某任务发送了 100 万次请求,但最终只生成 60 万条有效商品记录,单看请求成功率很容易得出错误结论。
| 观察指标 | 它回答的问题 | 不能单独证明什么 |
|---|---|---|
| 请求成功率 | 目标地址是否返回了可访问响应 | 不能证明页面内容正确或已入库 |
| 解析成功率 | 关键商品字段是否被正确提取 | 不能证明数据库没有重复数据 |
| 入库成功率 | 解析结果是否被持久化 | 不能证明任务可恢复 |
| 业务去重率 | 重试或重复任务产生了多少重复提交 | 不能代替唯一键约束 |
| 进度确认率 | 已处理位置是否被可靠记录 | 不能证明历史数据已备份 |
假设一个团队每天采集 30 万个商品,保存商品名称、价格、库存、店铺和商品详情。采集程序采用多线程并发请求,数据先放入内存队列,再由几个写入线程批量保存到关系型数据库。
任务刚开始时,数据库磁盘读写正常,写入线程可以及时消费队列。运行数小时后,价格历史表和索引不断增长,单批次写入时间从 300 毫秒增加到 2 秒。采集线程仍然保持原来的并发量,但写入线程逐渐跟不上,内存队列开始增加。
当队列达到预设上限后,采集线程会出现三种表现:一部分线程等待队列空位,一部分线程因等待数据库状态超时,还有一部分任务因为上游超时策略重新发起请求。最终,日志里可能充满“请求超时”和“重试成功”,但真正写入的数据量已经明显下降。
这就是一个容易误判的地方:重试成功不等于数据链路成功。 如果重试请求生成了重复提交,或者数据写入发生在状态更新之前,系统仍然可能同时出现重复和遗漏。
新手估算存储空间时,通常只计算商品表中的结构化字段。例如一条商品记录按 5 KB 计算,每天 100 万条记录就是约 5 GB 原始数据。但电商采集往往还要保存原始 JSON、HTML、图片地址、价格快照、失败响应、解析日志、索引和备份。
如果保存的是商品历史快照,容量还会随采集次数增长。每天 100 万条记录、每条原始结构化数据平均 5 KB,原始数据就是每天约 5 GB。假设索引、日志、临时文件和备份额外占用原始数据的 60%,120%,那么实际规划空间可能达到每天 8,11 GB,且这还没有计算图片和多副本。
这组数字是容量估算示例,不是所有业务的固定比例。真实占用量必须通过一段时间的实测获得,尤其要分别统计数据表、索引、日志、临时文件和备份目录。

我在排查采集任务时遇到过类似现象:上午任务正常,下午开始大量超时;目标页面在浏览器中可以访问,程序日志却显示请求耗时越来越长。最初大家倾向于调整并发和请求参数,结果并发越调越高,失败越严重。
进一步查看主机指标后,发现数据库所在磁盘接近满载,数据库正在进行大量索引维护,写入延迟已经明显升高。采集程序因为入库阻塞,连接无法及时释放,连接池又把等待时间反馈成了请求超时。目标网站并没有发生变化,真正的瓶颈在本地存储。
这类问题提醒我,排查电商抓取异常时,不能只盯着请求层。只要采集程序采用同步写入,或者请求线程必须等待入库确认,存储端的延迟就会直接表现为请求端的延迟。
商品当前状态、价格历史、库存历史、原始页面和失败日志,生命周期完全不同,却经常被新手放在一张表里。这样做初期开发简单,但随着数据增长,会出现表越来越大、索引越来越多、更新和查询互相影响的问题。
当前状态表通常需要频繁更新,价格历史表更适合追加写入,原始页面可能按文件或对象保存,日志则应该设置过期和归档策略。把这些数据混在一起,会让每一次商品更新都承担不必要的索引和存储成本。
更合理的拆分方式是至少区分三类数据:当前状态、历史快照和原始数据。当前状态服务于快速查询,历史快照服务于趋势分析,原始数据服务于重新解析和问题复盘。
缓存适合放短期状态、任务锁、URL 去重集合和临时队列,但不适合在没有容量规划的情况下长期保存全部商品历史。缓存通常依赖内存,成本和容量边界都更敏感,设置了过期策略后还可能在任务未完成前自动淘汰数据。
如果任务依赖缓存保存进度,却没有持久化或恢复机制,服务重启后可能丢失已处理记录。更严重的是,采集程序会把这些记录当成“未处理”,重新抓取并重复入库。
我的判断标准很简单:丢失后能否接受,决定它是不是可以只放在缓存里。 如果丢失后会造成漏采、重复或业务结论错误,就必须把关键状态写入可靠的持久化存储。
连接池不是越大越好。连接数过少,写入任务会排队;连接数过多,则可能让数据库在 CPU、磁盘和锁资源上发生竞争。尤其是每个连接都执行复杂更新或大批量事务时,盲目增加连接数量只会把瓶颈放大。
一个常见错误是把采集并发数直接设置成数据库最大连接数。采集线程还可能同时需要读取任务状态、更新进度和写入失败日志,这些操作都会争抢连接。真正的连接池设计应该基于数据库实际吞吐、单次事务耗时和并发操作类型,而不是只看机器有多少线程。
批量写入通常能减少网络往返和事务提交次数,但批次也不是越大越好。批次过大时,单次事务占用连接时间更长,失败重试成本更高,内存峰值也更明显。
如果一个批次包含 10,000 条记录,其中一条数据违反约束,处理逻辑不完善时可能导致整批失败。更稳妥的做法是根据数据量和写入延迟测试合理批次,并区分可重试错误和不可重试错误。
在实践中,批量大小应通过压测确定。例如分别测试每批 100、500、1,000 和 5,000 条,记录平均写入耗时、P95 延迟、失败率和内存占用,再选择整体最平衡的区间。
重试只能提高某些暂时性错误的成功机会,不能自动解决重复写入、顺序错乱、进度回退和永久性错误。数据库已写入但客户端没有收到确认时,重试尤其危险,因为客户端无法判断第一次操作是否已经生效。
因此,重试必须和幂等键、状态机及重试上限一起设计。请求失败、解析失败、数据库连接失败、唯一键冲突和字段校验失败,应该有不同的处理路径,不能全部归入“失败后再试一次”。

在选择数据库之前,我会先确认四件事。第一,数据是保存商品当前状态,还是保存每一次抓取的历史快照;第二,数据是结构稳定的表格,还是字段变化明显的原始文档;第三,写入是持续小批量,还是集中批量导入;第四,任务中断后是否必须从上次位置继续。
这四个问题比“哪个数据库性能最好”更有决策价值。因为当前状态、历史快照、原始文档和任务进度,需要的读写方式并不一样。一个适合快速更新当前库存的设计,不一定适合保存数月价格历史。
| 数据类型 | 典型内容 | 主要操作 | 优先关注点 |
|---|---|---|---|
| 当前状态 | 商品名称、当前价格、库存 | 按商品唯一键更新 | 查询速度、幂等更新、唯一约束 |
| 历史快照 | 价格变化、库存变化、抓取批次 | 持续追加写入 | 写入吞吐、分区、归档周期 |
| 原始数据 | HTML、JSON、图片、截图 | 按任务或商品读取 | 容量、成本、关联索引、备份 |
| 任务状态 | 页码、游标、重试次数、最后成功时间 | 频繁读取和更新 | 持久化、原子更新、恢复速度 |
| 运行日志 | 请求结果、解析错误、数据库错误 | 追加写入和按时间查询 | 保留期限、压缩、检索效率 |
第一个维度是数据一致性。商品当前价格和库存如果用于下游决策,就不能因为重复执行而产生无法解释的状态。需要强唯一键、事务或明确的版本规则。相反,部分原始响应可以允许异步归档,不必阻塞主数据写入。
第二个维度是写入吞吐。价格历史和库存快照通常是追加型数据,重点是持续写入能力和后续查询成本。此时不应把大量复杂索引都放在写入路径上,否则每新增一条记录,都要维护多个索引。
第三个维度是恢复要求。只要任务需要长时间运行,就必须回答:数据库暂时不可用时数据放在哪里,进程重启后从哪里继续,部分批次成功后如何判断,原始数据能否重新解析。

关系型数据库适合商品、SKU、店铺、价格和库存等结构化数据。它的优势不在于“抓取专用”,而在于唯一约束、事务、条件查询和更新逻辑比较清晰。对于刚开始做电商数据采集的小团队,它通常是保存核心业务结果的稳妥起点。
文档型数据库适合字段变化较大的详情数据,尤其是不同来源的商品属性不完全一致时。但字段灵活不代表可以不做规范。商品唯一标识、抓取时间、来源、版本和解析状态仍然应该保持统一,否则后续分析会被大量不一致字段拖累。
缓存适合任务锁、短期去重、临时队列和热点数据,不适合没有保留策略地保存全量历史。对象存储或文件系统适合原始 HTML、JSON、图片和截图,但必须在主数据库中保存文件与商品、任务、批次之间的关联关系。
| 存储方式 | 适合保存 | 不适合承担 | 新手注意事项 |
|---|---|---|---|
| 关系型数据库 | 结构化商品和交易相关字段 | 无限增长的原始大文件 | 控制索引数量,设计业务唯一键 |
| 文档型数据库 | 嵌套详情、字段变化较大的内容 | 没有统一规范的长期分析数据 | 统一来源、时间、版本和商品标识 |
| 缓存系统 | 短期队列、去重集合、任务锁 | 唯一的永久数据源 | 设置容量上限、过期策略和持久化方案 |
| 对象或文件存储 | HTML、JSON、图片、截图 | 复杂条件查询和实时状态更新 | 保存路径、文件摘要和业务关联键 |
磁盘满并不只是“新数据写不进去”。数据库可能需要额外空间完成排序、索引维护、事务日志和临时表操作,因此即使表面上还剩一部分空间,也可能出现写入延迟突然升高。
排查时不要只查看数据库数据目录,还要检查日志目录、备份目录、临时目录和容器存储层。很多任务是数据库没有写满,但采集程序保存失败响应和原始页面的本地目录先被占满。
建议设置分级告警。例如磁盘使用率达到 70% 时提醒规划,达到 80% 时检查增长最快的目录,达到 90% 时暂停非必要原始数据写入并执行归档。具体阈值要结合备份速度和磁盘增长速度调整。
写入吞吐不足时,最先出现的通常不是明确报错,而是入库延迟逐步升高。采集端看到的是队列变长、内存增加和任务完成时间延后,数据库端看到的则是锁等待、磁盘写入增加或连接长期占用。
常见原因包括逐条提交事务、每条记录都触发多个索引更新、批次过大、字段类型不合理以及同时进行大量查询。优化时应先测量,不要直接把所有参数调大。
连接池耗尽的典型日志是“获取连接超时”,但很多程序会把它统一记录成“请求失败”。如果请求线程在拿不到数据库连接时仍然持有网络资源,就会造成网络连接和数据库连接互相等待。
排查时应同时观察数据库当前连接数、应用连接池使用数、空闲连接数、连接等待时间和长事务数量。只看数据库最大连接数,无法判断应用是否存在连接未释放。
连接使用应遵循几个原则:短事务优先、异常时释放连接、断线后能够重建、批量任务设置超时时间。对于长时间运行的采集进程,还要避免使用永不过期的失效连接。
电商数据的去重不能只依赖商品名称、价格或整行内容。商品名称可能变化,价格本来就会变化,整行内容又可能因为抓取时间不同而始终不同。真正可靠的去重依据,应来自业务唯一标识。
如果保存商品当前状态,可以使用来源、店铺标识、商品 ID 和 SKU ID 等字段组成唯一键。如果保存历史快照,则需要把抓取批次或抓取时间纳入版本设计,不能简单地把所有相同 SKU 都覆盖掉。
当前状态和历史数据要分开处理。当前状态表适合“同一商品重复提交时更新”,历史快照表适合“每次有效抓取都追加”,原始数据则可以按照任务批次和文件摘要进行归档。
分页页码不是所有场景下都足够可靠。目标数据可能在采集过程中新增、下架或排序变化,简单记录“已完成第 20 页”可能导致下一次从错误位置继续。更稳妥的进度标识可以是更新时间游标、商品 ID、分页游标、任务批次和最后确认写入位置的组合。
断点记录必须和数据写入状态配合。不能先把进度更新到“已完成”,再慢慢写数据,否则程序在中途崩溃后会出现进度已前进、数据却未落库的漏采。比较稳妥的方式是:数据成功写入并通过幂等校验后,再确认对应进度。
只保存成功数据会让采集任务看起来很干净,却失去排错能力。出现数据缺口时,你无法判断是请求失败、字段解析失败、业务校验失败,还是数据库写入失败。
建议至少保存任务编号、数据来源、对象标识、请求时间、响应状态、解析状态、入库状态、失败原因、重试次数和最后处理时间。失败记录也要有保留期限,过期后归档或删除,避免日志本身成为容量故障来源。

业务唯一键是幂等写入的基础。对于商品数据,常见组成包括来源、店铺、商品 ID 和 SKU ID。对于没有 SKU 概念的商品,可以使用来源、店铺和商品 ID。不要把商品名称、当前价格和抓取时间作为唯一键,因为这些字段会变化。
唯一键设计要先明确业务目标。如果只关心当前价格,就让同一商品重复提交时更新同一条记录;如果要分析价格变化,就需要另建历史表,每一次有效变化或每一次采集快照都形成可追溯记录。
一个简单的状态字段可以显著提高排查效率。例如,把任务对象区分为待处理、请求成功、解析成功、待入库、入库成功、可重试失败和永久失败。这样,任务重启后就能从未完成状态继续,而不是把全部对象重新执行。
状态更新要避免“跳跃”。对象没有完成入库时,不能直接标记为最终成功。可重试失败和永久失败也不能混在一起,否则调度器会对参数错误、字段缺失等问题无限重试。
| 状态 | 含义 | 下一步动作 | 是否允许自动重试 |
|---|---|---|---|
| 待处理 | 尚未发起请求 | 进入采集队列 | 允许 |
| 请求成功 | 收到可处理响应 | 进入解析阶段 | 不需要 |
| 解析失败 | 页面缺少关键字段或结构异常 | 保留原始响应,人工或规则复核 | 按原因决定 |
| 待入库 | 数据已完成清洗 | 执行幂等写入 | 允许 |
| 入库成功 | 结果已持久化且进度已确认 | 进入下一个对象 | 不需要 |
| 永久失败 | 参数、字段或业务规则无法通过 | 进入失败队列或人工处理 | 不允许无限重试 |
可重试错误通常包括临时网络中断、数据库连接短暂失败、服务暂时过载等。不可重试错误通常包括字段缺失、唯一键格式错误、数据类型不合法和业务规则不满足。
重试间隔不应固定为立即重试。连续立即重试可能让已经过载的数据库承受更多压力。可以采用逐步延长等待时间,并设置最大次数和最大等待时间。达到上限后,将对象转入失败队列,等待后续处理。
def should_retry(error_type, retry_count):
retryable_errors = {
"network_timeout",
"connection_reset",
"temporary_storage_error"
}
max_retry_count = 4
if error_type not in retryable_errors:
return False
return retry_count < max_retry_count示例代码只表达处理思路,实际项目还需要补充日志、退避时间、幂等写入和失败队列。最重要的不是“重试几次”,而是每次重试都不会因为重复执行而破坏数据。
一个常见的错误顺序是:先更新分页游标,再写数据库。程序在两步之间崩溃时,任务恢复会跳过已经标记过的范围,但数据库里并没有对应数据。
更安全的顺序是先处理和持久化当前批次,确认批次写入成功后,再更新进度记录。即使程序在确认前崩溃,恢复时重复处理该批次,也可以依赖唯一键和幂等写入避免重复污染。

如果每天只采集几千到几万条商品,优先选择维护成本低的方案。关系型数据库保存商品当前状态、价格历史和任务状态,文件或对象存储保存少量原始页面,采集程序内部使用有限长度队列即可。
这个阶段不必急着引入复杂的分布式架构。更重要的是做好唯一键、失败记录、磁盘告警和备份恢复。很多小项目并不是因为吞吐不够失败,而是因为任务重启后无法判断哪些数据已经成功保存。
当采集速度明显高于数据库写入速度,或者多个采集任务同时运行时,可以增加消息队列或持久化缓冲。采集程序只负责产生标准化消息,写入消费者负责控制批量、连接和重试。
异步写入的好处是把请求耗时和存储耗时解耦,但它也增加了监控要求。必须知道队列长度、最老消息年龄、消费速率和失败消息数量,否则异步系统可能只是把问题藏起来。
当队列堆积时,不要只增加消费者数量。应先确认数据库是否还有余量。如果数据库已经是瓶颈,增加消费者只会加剧锁竞争和连接消耗。
当每天持续保存数十万到数百万条历史快照,表的增长速度和查询范围会成为主要问题。此时需要考虑按时间或业务维度分区、冷热数据分层、历史数据归档和备份窗口。
历史快照不一定要永久保存在在线数据库中。近期数据可以用于实时分析,较早数据可以压缩后放入低成本存储。归档前要确保数据可检索,并保留批次、来源和校验信息,否则归档只是把问题从数据库搬到了文件目录。
不同平台的商品详情字段经常不一致。把所有字段都强行设计成固定表结构,会导致字段频繁变更;完全不做结构化,又会让后续分析只能处理大段原始 JSON。
较好的折中方式是:把来源、店铺、商品 ID、SKU ID、抓取时间、价格、库存和解析状态等核心字段标准化;把变化较大的商品属性、营销标签和详情模块保存为文档或原始数据,并记录版本和来源。

第一步是确认增长最快的目录和表,而不是直接删除文件。先区分数据库数据、日志、备份、临时文件和原始页面,再判断哪些数据可以压缩、归档或删除。
第二步是暂停非必要的原始内容保存,保留结构化结果和失败摘要,避免在处理容量故障时继续增加大文件。第三步是为未来增长建立日级监控,计算每天增长量和剩余可用天数。
不要在没有备份确认的情况下直接删除数据库日志或手工文件。错误删除可能导致数据库无法恢复,处理结果比磁盘满更加严重。
先比较生产速率和消费速率,确认是采集端过快、数据库写入变慢,还是消费者出现错误。查看最老消息等待时间比只看队列总数更有价值,因为总数可能短暂波动,最老消息持续变老则说明系统已经无法及时处理。
如果数据库还有余量,可以适度调整批量大小和消费者数量;如果数据库已达到 CPU、磁盘或连接瓶颈,应降低采集并发并优先恢复消费能力。队列的作用是吸收短时波动,不是长期掩盖吞吐不足。
先暂停会继续扩大重复的重试任务,保留当前数据库快照。然后统计重复记录的来源:是同一任务重复执行、多个任务同时采集,还是重试机制没有判断第一次写入是否成功。
修复时要先建立业务唯一键,再处理历史重复数据。不要简单地按整行内容去重,因为价格、抓取时间和库存字段变化会让同一商品看起来像不同记录。
先确认是否有可用的任务批次、分页游标或更新时间游标。如果完全没有进度记录,短期内可以通过业务唯一键和采集时间范围重新执行,但必须把这次补采标记为独立批次,避免无法区分新旧数据。
长期修复需要把进度作为正式业务数据保存,而不是只放在程序变量、临时文件或缓存中。每个任务都应有明确的开始时间、结束时间、最后成功位置和失败对象数量。
检查数据库磁盘、连接数、锁等待、慢查询、索引变化和最近是否执行了大批量更新。不要在没有找到瓶颈前同时修改批量大小、连接数和采集并发,否则很难判断哪个调整产生了影响。
可以先做一个小规模对照测试:固定采集数据,分别测试不同批次和不同并发下的写入耗时,记录平均值和 P95 延迟。稳定的方案不应只看平均速度,还要看尾部延迟和失败时的恢复成本。

逐条实时写入通常更容易确认单条结果,但网络往返和事务提交成本较高;批量写入吞吐更好,却需要处理部分失败和批次重试。对价格监控这类业务,可以让核心当前状态快速更新,历史快照异步批量落库。
如果业务更关心“最新价格尽快可见”,可以优先保证当前状态表的写入;如果业务更关心“每一次采集都可追溯”,则需要保留批次、原始数据和失败记录,并接受更高的存储成本。
只保存结构化结果成本较低,但页面结构变化后很难复盘。保存全部原始响应会增加容量和备份成本,却能在解析规则修复后重新处理历史数据。
我的建议是分层保留:近期失败响应和关键批次保留较长时间,普通成功原始响应按业务价值设置较短周期,历史结构化数据则按分析需求归档。不要采用“全部永久保存”或“全部立即删除”这两种极端策略。
强一致写入通常需要更多事务控制、约束和等待时间。对商品当前状态、任务进度和业务唯一键,应该优先保证一致性;对原始页面、非关键日志和部分统计缓存,则可以采用异步处理。
关键是不要让低价值的大文件阻塞高价值的业务状态。商品价格当前值无法写入时,任务需要报警;一张非关键页面截图暂时无法归档时,不应让整个商品采集任务失败。
单数据库方案容易理解,适合验证业务和小规模任务;队列加数据库能吸收波动,但增加了部署和监控成本;分层存储和分布式架构扩展能力更强,却要求团队具备容量治理、故障演练和数据恢复能力。
新手最容易犯的错误是为了预想中的百万级数据,提前搭建复杂系统,结果连唯一键、失败记录和备份恢复都没有做好。架构复杂度应当由已经出现的瓶颈推动,而不是由想象中的规模推动。
| 目标 | 优先选择 | 可以接受的代价 | 不建议牺牲的部分 |
|---|---|---|---|
| 尽快看到最新商品状态 | 结构化当前状态表、异步历史写入 | 历史数据稍后可见 | 唯一键和当前状态正确性 |
| 保存完整历史轨迹 | 追加型历史表、批次记录、归档策略 | 更多存储成本和查询设计 | 时间、来源和批次可追溯 |
| 降低短期开发成本 | 单库加简单任务表 | 横向扩展能力有限 | 备份、幂等和失败记录 |
| 承受采集波动 | 有限长度队列、批量消费者 | 需要额外监控和重试逻辑 | 队列不能无限堆积 |
| 降低长期容量成本 | 冷热分层、压缩和定期归档 | 查询历史数据需要额外步骤 | 归档数据仍应可恢复和校验 |
新手不需要一开始搭建复杂监控平台,但至少要有一张能回答“现在是否还在有效工作”的面板。面板上应同时展示采集、解析、存储和恢复四个层面的指标。
| 监控类别 | 建议指标 | 异常信号 |
|---|---|---|
| 采集层 | 请求量、超时量、响应状态分布 | 请求量正常但有效数据减少 |
| 解析层 | 关键字段为空率、解析失败率 | 大量响应成功但解析失败 |
| 存储层 | 写入延迟、失败率、连接等待数 | 延迟升高或连接池持续满载 |
| 队列层 | 队列长度、最老消息年龄、消费速率 | 积压持续增长且无法回落 |
| 恢复层 | 最后成功进度、失败对象数、备份状态 | 任务完成但进度未更新或备份不可恢复 |

电商数据抓取不仅是技术问题,还涉及平台规则、访问授权、个人信息保护和数据使用范围。采集前应确认数据来源、访问权限、使用目的、保留期限和是否包含个人信息,不应为了提高覆盖率而扩大采集范围。
商品公开信息、店铺公开信息与订单、联系方式、收货信息等敏感数据的风险等级不同。后者需要更严格的授权、访问控制、脱敏和留存管理,不能因为“程序可以抓到”就默认“业务可以长期保存”。
失败日志经常会记录完整请求参数、响应内容和接口地址,其中可能包含身份凭证、用户标识或不应长期保留的字段。日志应进行脱敏,限制访问权限,并设置保留周期。
原始响应和截图也应建立访问控制。能够重新解析数据的文件,通常具有比普通运行日志更高的业务价值和风险,不能放在公开目录或没有权限控制的本地路径中。
备份文件存在,不代表恢复一定成功。备份可能不完整、版本不兼容、缺少关联文件,或者恢复后无法与任务进度对应。建议至少定期抽取一个任务批次,验证数据库、原始文件和进度记录能否一起恢复。
如果恢复后无法回答“哪些数据已入库、哪些数据需要补采”,那么备份方案仍然不完整。恢复目标应包含数据本身、任务状态和业务关联关系。
电商数据抓取中,存储方案做不好,最先出现的可能是写入变慢,接着是队列堆积、连接耗尽、重试增加,最后才表现为任务超时、数据丢失和重复入库。很多团队在最后一步才开始排查,已经很难还原故障的真实起点。
我对新手的建议不是一开始就选择最复杂的数据库,而是先把五件事做扎实:容量可计算、写入有边界、数据可幂等、任务可续采、失败可追踪。只要这五件事没有完成,增加并发、增加重试或更换数据库,往往只能暂时改变故障表现。
下一步可以从一个真实任务开始,记录一小时内的请求量、解析量、入库量、失败量、队列长度、写入延迟和磁盘增长量。用这组数据重新估算存储需求,再决定是否需要批量写入、异步队列、分层存储或历史归档。
真正稳定的采集系统,不是永远不报错,而是报错时能知道错在哪一层,恢复后不会重复污染数据,也不会因为一次重启就丢失整个任务的进度。这才是存储方案对电商数据抓取最重要的价值。
我刚开始抓商品数据时,只统计了商品名称、价格和库存这些字段,觉得每天新增几百万条也占不了多少空间。后来任务运行几天后突然出现写入失败和频繁超时,我想知道明明数据量没有暴增,为什么磁盘占用却增长得这么快?
磁盘空间不足通常不是“最后一条数据写不进去”这么简单,而是会提前影响数据库的索引、临时文件、事务日志和排序操作。当磁盘使用率接近上限时,写入延迟会先升高,采集程序随后出现排队、超时和重试,最终才表现为入库失败。
我在一次模拟压测中按每条商品记录 5 KB、每天新增 100 万条计算,原始数据约为 5 GB。但实际规划空间时,数据库索引、日志、临时文件、备份和历史价格快照又额外占用了约 2 至 3 倍空间。也就是说,不能只按“字段大小×数据条数”准备磁盘。
占用来源常见内容容易被忽略的原因 业务数据商品、SKU、价格、库存历史快照会持续累积 索引商品 ID、店铺 ID、时间字段索引通常会增加额外空间 运行文件日志、临时表、失败响应异常时增长速度更快 原始内容HTML、JSON、图片文件体积远大于结构化字段 我的判断是,电商抓取至少要把“当前状态”和“历史记录”分开规划。
商品当前价格可以放在结构化表中,价格变动记录和原始页面则应设置保留周期、归档策略或独立存储,不能全部堆在同一张业务表里。新手可以设置磁盘使用率、数据库日志大小和每日新增数据量三个告警指标。
不要等到磁盘 100% 才处理,最好在达到预设阈值时暂停低优先级任务、清理临时文件或扩容,并提前验证备份是否能够恢复。
我观察到请求端一直有返回,程序日志也显示任务没有停止,但数据库里的有效记录越来越少。重启任务后,部分商品又被写了一遍,我不确定这是目标网站响应慢,还是存储端处理不过来导致的。
这类问题的核心不是“程序有没有继续运行”,而是采集端的生产速度是否超过了存储端的消费速度。假设采集程序每分钟产生 1000 条记录,但数据库每分钟只能稳定写入 600 条,剩余 400 条就会进入内存、队列或临时文件,积压一段时间后必然引发超时和重试。
在一次小规模测试中,我分别比较了逐条写入和批量写入。逐条提交每次都要经历连接、事务和磁盘刷写,写入耗时明显更高;改成每批 200 至 500 条,并减少不必要的索引维护后,单位时间内的有效入库量提升了约 2 倍。这个数字只适用于该测试环境,不能直接当作所有数据库的性能标准。
现象更可能的原因优先检查项 队列长度持续增加生产速度高于写入速度批量大小、写入耗时 内存逐渐上涨失败数据没有及时释放队列上限、异常处理 重试后重复记录没有幂等写入业务唯一键、重试逻辑 采集线程频繁超时写入阻塞反向拖慢请求连接池、锁等待、事务时长 解决时不要只给采集线程增加并发。
并发越高,可能只是把更多数据推向本来就慢的数据库,结果变成连接数耗尽、锁竞争加剧和重复重试。更稳妥的做法是设置有上限的缓冲队列,让采集速度根据存储端实际消费能力动态调整。同时要给商品或 SKU 设计业务唯一键。例如保存商品当前状态时,可以使用平台标识、店铺标识和商品 ID 的组合;
保存价格历史时,则应额外加入抓取时间或版本号。这样即使任务重跑,系统也能区分更新、跳过和新增历史记录。
我一开始把去重集合、商品数据、失败页面和任务进度都放进同一个存储系统,短期看起来很方便,运行一段时间后却发现内存增长很快,历史数据也越来越难查询。我想知道这些存储方式到底应该怎样分工,而不是单纯比较谁的性能更高。
存储选型不应该从“哪种数据库最好”开始,而应该先看数据的生命周期。需要高频判断、短期存在的数据和需要长期查询、审计、恢复的数据,稳定性要求完全不同,把它们混在一起才是新手最容易踩的坑。
存储方式更适合保存不建议承担的职责 关系型数据库商品、SKU、价格、库存、任务状态无限增长的原始页面和图片 文档型数据库字段变化大、层级复杂的商品详情完全不设计唯一键和数据版本 Redis 等内存存储短期去重、任务锁、临时队列、缓存无期限保存全量历史数据 文件或对象存储HTML、JSON 原文、图片、截图直接替代结构化查询数据库 我的经验判断是,小规模项目不需要一开始就搭建复杂的分布式系统。
一个关系型数据库保存结构化结果,文件存储保存原始响应,内存存储处理短期去重和任务锁,再配合失败日志,通常已经能覆盖大多数商品、价格和库存采集场景。真正需要重点设计的是关联关系。原始文件不能只按随机文件名保存,而应记录数据来源、任务批次、商品 ID、抓取时间和文件路径。
否则文件虽然还在,但无法知道它对应哪条商品记录,出了问题也无法重新解析。还要给临时存储设置容量上限和过期策略。内存存储一旦没有过期时间,去重集合和历史任务状态会悄悄占满内存;本地文件如果没有定期归档,也可能因为日志和失败页面堆积拖垮整台机器。
我以前只在任务开始和任务结束时记录状态,觉得只要程序最终显示成功就够了。后来服务器重启后,任务只能从第一页重新抓,结果既产生了大量重复商品,也无法确认中间哪些页面其实没有成功保存。
采集任务的“请求成功”不等于“数据已经安全保存”。一个商品至少要经过请求、解析、校验、写入和确认几个阶段,如果系统只记录任务总状态,就无法知道任务究竟停在哪一步,重启后只能从头执行。断点续采至少需要保存一种可恢复的进度标识,例如分页游标、最后成功处理的商品 ID、更新时间游标或任务批次号。
对于分页接口,页码本身不一定可靠,因为商品上下架和排序变化可能导致同一页内容发生移动,按更新时间或稳定 ID 记录进度通常更安全。
记录内容作用缺失后的风险 任务批次号区分不同轮次采集无法判断数据属于哪次任务 进度游标支持中断后继续重启后只能从头抓取 解析状态区分请求成功和字段完整空数据可能被误认为成功 入库状态确认数据是否真正写入出现漏采或重复入库 重试次数识别异常任务失败记录被无限重试 我更推荐把“当前状态表”和“采集事件表”分开。
当前状态表只保留商品最新价格、库存等结果,采集事件表则记录每次请求、解析、入库和失败情况。这样既方便业务查询,也能在故障后追溯到底是请求失败、解析失败,还是数据库拒绝写入。幂等写入是断点续采的另一半。重启或网络重试不可避免,关键是重复提交不能重复创造业务记录。
系统应根据商品或 SKU 的业务唯一键执行更新、跳过或生成新版本,而不是简单使用“每次插入一行”的方式。上线前可以做一次故障演练:任务运行到中途时主动停止采集进程、断开数据库连接,再恢复服务,检查是否能从最近成功位置继续、是否出现重复记录、是否能找到失败原因。
能通过这三个检查,才算具备基本的可恢复能力。


读者评论
这篇文章把采集不稳定和存储层联系起来讲得比较清楚,尤其是生产速度高于写入速度时,队列积压最终会反向拖慢请求,适合新手建立完整链路意识。
文中对“请求返回200不等于数据成功入库”的提醒很实用。实际项目中确实需要同时关注解析率、入库率、去重率和进度确认率,不能只看接口响应。
容量估算部分有参考价值,但示例中的索引、日志和备份占用比例会因数据结构、压缩策略和保留周期而变化,最好结合线上实测再做规划。
关于连接池、批量写入和重试机制的分析比较客观。批次大小和并发数不能照搬固定值,应该通过压测观察延迟、失败率和资源占用后调整。
文章提出拆分当前状态、历史快照和原始数据,这对长期运行的商品采集任务很有帮助。若再补充断点续采和故障演练的实施示例,操作性会更强。