news 2026/10/15 16:19:14

搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案

一、接口基础说明
点击获取key和secret

唯品会关键词商品列表接口(搜索接口)

  • 请求方式:通常 POST/GET,关键词、页码、每页数量、排序、筛选参数
  • 天然限制:
    1. 最大分页上限:很多唯品会搜索接口不能无限翻页,页码到一定值后直接返回空列表,不是报错,是无数据;
    2. 动态字段:促销、预售、组合商品、盲盒类商品返回字段不一样;部分字段时而返回时而缺失;
    3. 分页不稳定:同一 page 多次请求,商品条目数量可能不一致,存在商品重复、商品丢失;
    4. 限流风控:短时间连续翻页会返回 429、验证码、空 data,无明确错误码。

注意:接口权限、签名、cookie/access_token 鉴权不在本文方案内,本文聚焦分页容错逻辑 + 字段归一化。

二、分页容错方案(重点)

2.1 常规分页逻辑(不推荐直接使用)

page = 1 while True: resp = request(keyword, page) items = resp['data']['itemList'] if len(items)==0: break save(items) page +=1

问题:

  • 遇到临时返回空数组直接终止,误判为采集完成;
  • 没有去重,同一商品跨页重复入库;
  • 不处理限流、超时、接口抖动;
  • 到达平台最大页码阈值,直接退出无法区分 “真无数据” 和 “接口截断”。

2.2 增强分页容错设计

1)三重重试策略(分级重试)

表格

异常类型重试次数间隔策略
网络超时、连接失败3 次1s,2s,4s指数退避
200 但 data 为空 / 无 itemList2 次3s重试后仍空,标记为疑似结束,不直接退出
429 限流3 次5s,10s,20s长间隔,达到上限暂停当前关键词任务

规则:重试只针对临时异常;业务空数据(商品真的没有)不重试。

2)分页结束判定(核心改进)

不再依靠itemList.length == 0作为唯一终止条件,采用双条件判定:

  1. 连续 N 页(推荐 2 页)返回商品数量 = 0 → 判定搜索结果结束;
  2. 或者 page 到达预设最大页码阈值(如 max_page=50,根据唯品会搜索限制配置)强制停止,防止死循环。

原因:唯品会接口偶尔单页抖动返回空,下一页又恢复数据;单页空不能代表全部商品抓取完毕。

3)商品去重,解决跨页重复
  • 唯一主键:itemId(唯品会商品 ID)作为唯一标识;
  • 本地内存 /redis 临时集合,保存本轮关键词已采集 itemId;
  • 新拉取到商品:如果 itemId 已存在,跳过入库,日志标记【重复商品】;
  • 任务结束清空该关键词的去重集合。
4)页码跳变保护

增加校验:返回列表里的商品数量,远小于 pageSize 时,记录告警; 例如每页请求 30 条,返回 < 5 条,记录日志warning: page{page} item count low,便于后续排查是否触发平台结果截断。

5)任务断点续采

持久化当前任务状态:keyword、current_page、已采集itemId集合、状态,存入数据库 /redis; 任务中断(程序崩溃、网络断),重启时读取状态,从 current_page 继续采集,不用从头跑。

2.3 分页伪代码示例(PHP)

$keyword = "xxx"; $page = $task['current_page']; $maxPage = 50; $emptyPageCount = 0; $pageSize = 30; $redisKey = "vip:search:dedup:".$keyword; while($page <= $maxPage){ $ret = vipApiRequest($keyword, $page, $pageSize); if($ret['code'] !== 0){ // 异常重试逻辑 $retryOk = retryApi(function() use ($keyword,$page,$pageSize){ return vipApiRequest($keyword, $page, $pageSize); },3); if(!$retryOk){ log_error("page {$page} 请求失败"); break; } $ret = $retryOk; } $itemList = $ret['data']['itemList'] ?? []; if(empty($itemList)){ $emptyPageCount++; if($emptyPageCount >=2){ log_info("连续2页空,搜索任务结束"); break; } $page++; continue; } $emptyPageCount = 0; foreach($itemList as $rawItem){ $itemId = $rawItem['itemId']; if(redis()->sIsMember($redisKey,$itemId)){ log_info("重复商品 {$itemId},跳过"); continue; } redis()->sAdd($redisKey,$itemId); // 字段归一化处理 $stdItem = normalizeItem($rawItem); saveToDB($stdItem); } $task['current_page'] = $page + 1; saveTaskStatus($task); $page++; usleep(800*1000); // 节流,控制请求频率 } redis()->del($redisKey);

三、字段归一化方案

3.1 痛点

  1. 字段名不统一:有的叫salePrice,有的promotionPrice;原价字段有的marketPrice、origPrice;
  2. 部分商品无 SKU 数组,直接单商品价格;多规格商品 SKU 列表嵌套层级不一致;
  3. 图片字段:有的返回主图 url,有的返回图片数组,缩略图 / 原图混合;
  4. 空值:不存在字段返回null/""/ 直接不返回 key;
  5. 促销标签、库存字段:普通商品、预售、清仓结构差异大。

3.2 归一化设计思路

输入:接口原始异构 JSON 对象 rawItem输出:统一结构体 stdItem,上层业务固定字段读取,不需要判断原始字段策略:多层级取值 + 默认兜底值 + 类型强制转换 + SKU 归一化

3.3 标准输出结构体设计

{ "item_id": "", "title": "", "sub_title": "", "brand_name": "", "price": 0, "original_price": 0, "discount": "", "stock_status": "", "sales": 0, "main_image": "", "image_list": [], "category_name": "", "sku_list": [ { "sku_id": "", "spec_name": "", "price": 0, "stock_num": 0 } ], "promotion_tags": [], "source_raw": {}, "crawl_time": "" }

3.4 字段映射 & 安全取值函数

核心:封装安全取值函数getSafeVal($arr, $path, $default),支持多层 key 读取,key 不存在返回默认值,避免Undefined index报错。

示例映射规则:

表格

标准字段原始候选字段顺序兜底默认值处理逻辑
item_iditemId""字符串
titletitle""去除首尾空格
pricesalePrice / promotionPrice / currentPrice0float,空转为 0
original_pricemarketPrice / origPrice0float
main_imageimgUrl / mainPic""优先取第一张图
image_listpicList / imageArray[]数组,过滤空 url
salessaleCount / salesVolume0转为数字
SKU 归一化(难点)

两种情况:

  1. 商品返回skuInfo数组:遍历每个 SKU 映射到标准 sku 结构;
  2. 商品无 SKU 列表(单品):自动构造一个虚拟 SKU,spec_name="默认规格",sku_id=itemId,价格取商品主价。

这样上层业务统一读取 sku_list,不用区分单品 / 多规格商品。

3.5 PHP 归一化函数示例

function normalizeItem($raw){ $std = []; $std['item_id'] = getSafeVal($raw,'itemId',''); $std['title'] = trim(getSafeVal($raw,'title','')); $std['brand_name'] = getSafeVal($raw,'brandName',''); $std['price'] = floatval(getSafeVal($raw,'salePrice',0)); $std['original_price'] = floatval(getSafeVal($raw,'marketPrice',0)); $std['main_image'] = getSafeVal($raw,'imgUrl',''); $std['image_list'] = getSafeVal($raw,'picList',[]); $std['sales'] = intval(getSafeVal($raw,'saleCount',0)); $std['promotion_tags'] = getSafeVal($raw,'tagList',[]); $std['crawl_time'] = date('Y-m-d H:i:s'); // SKU归一化 $rawSkuList = getSafeVal($raw,'skuInfo',[]); $std['sku_list'] = []; if(!empty($rawSkuList)){ foreach($rawSkuList as $sku){ $std['sku_list'][] = [ 'sku_id' => getSafeVal($sku,'skuId',''), 'spec_name' => getSafeVal($sku,'spec',''), 'price' => floatval(getSafeVal($sku,'skuPrice',0)), 'stock_num' => intval(getSafeVal($sku,'stock',0)), ]; } }else{ // 单品构造虚拟SKU $std['sku_list'][] = [ 'sku_id' => $std['item_id'], 'spec_name' => '默认规格', 'price' => $std['price'], 'stock_num' => 0, ]; } $std['source_raw'] = $raw; // 保留原始数据用于排查 return $std; } // 安全多层取值 function getSafeVal($data, $key, $default=null){ if(!is_array($data)) return $default; return isset($data[$key]) ? $data[$key] : $default; }

四、监控与告警(保障长期稳定)

  1. 分页指标监控:每页返回商品数量、空页次数、重试次数、重复商品数量;写入监控表;
  2. 字段缺失告警:统计商品缺失itemId、价格、主图的比例,超过阈值触发告警;
  3. 限流监控:429 计数,关键词任务自动降速;
  4. 数据校验:入库后校验 price>original_price 这类异常数据,标记脏数据。

五、风险与优化方向

  1. 唯品会接口会不定期调整返回 JSON 结构,source_raw 原始字段持久化非常重要,方便更新映射规则;
  2. 不要高频并发拉取,单关键词串行分页,多关键词任务做并发池控制;
  3. 价格字段区分:券前价、券后价、活动价,如果业务需要,归一化结构体可扩展字段存储多种价格;
  4. 增量更新:关键词搜索是全量快照,如果需要实时监控,可基于 itemId 做增量对比,只更新变化商品。

六、整体架构简图

【任务调度】 ↓ 【请求层:签名、http客户端、分级重试、节流】 ↓ 【分页容错层:空页计数、去重、断点续采、结束判定】 ↓ 【字段归一化层:安全取值、SKU统一、脏数据过滤】 ↓ 【存储层:标准商品表 + 原始JSON备份表】 ↓ 【上层业务:比价、选品、报表】

如果你需要,我可以继续输出:

  1. 可直接复制的MySQL 建表语句(标准商品表 + 任务表);
  2. Java 版本实现分页 + 归一化代码;
  3. 改成适合文档发布的 SEO 优化完整版技术文章。

今天 09:22

搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案

搜好货平台关键词商品搜索接口:分页容错与商品数据轻量化提取方案

业务场景:B2B 工业货源关键词检索、供应商监控、批发比价、货源选品;搜好货属于 B2B 工业电商,和唯品会零售接口差异很大:多规格报价区间、起订量、厂家信息、地区字段、接口返回大 JSON 冗余字段极多。 核心目标:稳定分页采集,只提取业务需要字段,裁剪冗余大块数据,降低内存、存储、带宽开销;同时解决 B2B 接口分页抖动、总数不准、跨页重复、报价字段异构问题。

一、搜好货关键词搜索接口基础特性

搜好货(912688)商品搜索接口,B2B 货源搜索,GET/POST,带签名 + token 鉴权,入参:关键词、page、pageSize、筛选(地区、起订量、类目)、排序。

平台接口独有坑点

  1. total 总数不准:接口返回 total 仅为估算值,翻到后面会出现 total 大于实际可返回商品,不能用 total 循环计算最大页码;
  2. B2B 报价不是单一价格:大多是区间价(1-10 件 XX 元,10-100 件 XX 元),单品固定价、阶梯报价两种结构混杂;
  3. 商品字段冗余巨大:返回完整详情 HTML、长富文本介绍、多个附件、企业资质、历史询盘等大体积字段,轻量化必须直接丢弃,不解析;
  4. 分页不稳定:工业词搜索结果缓存强,同一 page 多次请求条目不一致;末尾页码容易返回空数组,属于缓存截断,不是真正无商品;
  5. 风控特点:相比 C 端平台,慢,超时多,401/403 token 失效频繁,不是简单 429;token 过期属于业务异常,不是重试能解决;
  6. 商品唯一 ID:goodsId,供应商 ID:companyId,一个厂家多条同款货源(跨页重复高发)。

说明:签名、token 获取、鉴权逻辑不在本文,聚焦分页容错 + 轻量化提取。

二、分页容错方案(适配 B2B 搜好货搜索接口)

2.1 简单分页(不推荐)

page=1 while page*pageSize <= total: resp = request(keyword,page) save(resp['data']['list']) page +=1

致命问题:total 虚高,会无限请求无效页码;B2B 缓存导致单页空,误判任务结束;同款货源跨页重复入库;token 失效没有中断保护。

2.2 增强分页容错设计

1)分级重试策略

表格

异常类型重试次数退避间隔处理规则
网络超时、连接重置3 次1s,2s,4s指数退避重试
HTTP 200,但 list 为空2 次3s重试后仍然空,空页计数 + 1,不直接退出
401/403 token 失效0 次-不重试,标记任务暂停,触发 token 刷新告警
5xx 服务端错误2 次5s重试

规则:token 过期属于身份失效,重试无效,直接暂停任务,避免锁账号。

2)分页结束判定(重点,B2B 接口核心)

放弃依靠 total 计算循环上限,采用双条件判定

  1. 连续2 页返回商品列表为空,判定关键词搜索任务正常结束;
  2. 配置硬上限 max_page(建议 30~50,搜好货后端搜索结果深度有限),page 超过 max_page 强制终止,防止死循环。

原因:搜好货搜索后端对工业词做结果截断,即便 total 显示几千条,实际最多只能拉几十页。

3)商品去重(B2B 特有:同厂家同款多条货源)
  • 主键:goodsId作为商品唯一 key;
  • Redis 临时集合:search:sh:dedup:{keyword},保存本轮关键词已采集 goodsId;
  • 规则:新拉取商品,goodsId 已存在则跳过入库,日志标记【重复货源】;

业务扩展:如果需要同款合并比价,可以增加companyId+产品标题md5做同款分组。

4)断点续采

任务持久化字段存入 Redis/MySQL:keyword、current_page、max_page、已采集goodsId集合、任务状态、token版本。 程序崩溃、网络中断、token 失效暂停后,重启从 current_page 继续采集,不需要从头抓取。

5)伪代码 PHP 分页主逻辑
$keyword = "工业不锈钢阀门"; $page = $task['current_page']; $maxPage = 40; $emptyPageCount = 0; $pageSize = 20; $redisDedupKey = "search:sh:dedup:".md5($keyword); while($page <= $maxPage){ $apiResult = shApiRequest($keyword,$page,$pageSize,$token); // 鉴权失效 if($apiResult['code'] == 401){ log_error("token失效,任务暂停 keyword:{$keyword}"); $task['status'] = 'pause'; saveTask($task); break; } $rawList = $apiResult['data']['list'] ?? []; if(empty($rawList)){ $emptyPageCount++; if($emptyPageCount >=2){ log_info("连续2页空,关键词任务结束"); break; } $page++; continue; } $emptyPageCount = 0; foreach($rawList as $rawGoods){ $goodsId = $rawGoods['goodsId']; if(redis()->sIsMember($redisDedupKey,$goodsId)){ log_info("重复商品 {$goodsId},跳过"); continue; } redis()->sAdd($redisDedupKey,$goodsId); // ===== 轻量化提取核心函数 ===== $lightItem = extractLightGoods($rawGoods); saveToDB($lightItem); } // 更新任务状态 $task['current_page'] = $page + 1; saveTask($task); $page++; usleep(1200*1000); // B2B接口放慢请求速率,降低风控 } redis()->del($redisDedupKey);

三、商品数据轻量化提取方案(本篇重点)

思路:不完整解析原始大 JSON,按需提取,直接丢弃大体积冗余字段。B2B 搜好货原始返回包含大量大字段:详情富文本、图片数组几十张、企业资质、视频、询盘记录、参数详情块,全部跳过不读取,减少内存占用。

3.1 轻量化提取原则

  1. 只提取业务必要字段,原始大字段直接丢弃,不做解析;
  2. 安全取值,key 不存在返回默认值,禁止直接访问数组下标;
  3. B2B 阶梯报价归一化:支持单一口价 / 多档阶梯报价两种结构统一输出;
  4. 供应商信息轻量化:只保留 companyId、厂家名称、地区,不读企业详情长文本;
  5. 可选:原始 JSON 按需异步落盘备份(可选,不阻塞主采集链路)。

3.2 轻量化输出结构体(精简版)

{ "goods_id": "", "title": "", "brand": "", "company_id": "", "company_name": "", "province": "", "city": "", "price_type": "", // fixed固定价 / ladder阶梯价 "price": 0, // 最低报价 "price_range": [], // 阶梯报价数组 [{min_num:1,price:50}] "min_order": 0, // 最小起订量 "main_image": "", "category_name": "", "sales": 0, "stock_status": "", "crawl_time": "" }

3.3 字段映射与轻量化规则

表格

标准字段原始候选字段默认值处理说明
goods_idgoodsId""商品唯一 ID
titletitle""trim 去除空格,清理换行
company_idcompanyId""供应商 ID
company_namecompanyName""厂家名称
provinceprovince""省份
citycity""城市
min_orderminOrder0最小起订量 B2B 核心字段
main_imagecoverImg""只取封面图,丢弃全部图片数组
salessaleNum0成交量

轻量化核心优化:放弃 imageList、detailHtml、attributeHtml、video、certificate 资质等超大字段,不去读取,减少 JSON 解析开销。

B2B 阶梯报价提取逻辑(搜好货特有)

两种结构:

  1. price单一固定价:price_type="fixed",price_range 空数组;
  2. ladderPrice数组,多档起订报价:price_type="ladder",price 取数组内最低价,price_range 存储阶梯列表。

3.4 PHP 轻量化提取函数 extractLightGoods

/** * 搜好货商品轻量化提取,丢弃冗余大字段 */ function extractLightGoods($raw){ $std = []; $std['goods_id'] = getSafe($raw,'goodsId',''); $std['title'] = trim(getSafe($raw,'title','')); $std['company_id'] = getSafe($raw,'companyId',''); $std['company_name'] = getSafe($raw,'companyName',''); $std['province'] = getSafe($raw,'province',''); $std['city'] = getSafe($raw,'city',''); $std['min_order'] = intval(getSafe($raw,'minOrder',0)); $std['main_image'] = getSafe($raw,'coverImg',''); $std['category_name'] = getSafe($raw,'categoryName',''); $std['sales'] = intval(getSafe($raw,'saleNum',0)); $std['crawl_time'] = date('Y-m-d H:i:s'); // B2B阶梯报价处理 $ladderList = getSafe($raw,'ladderPrice',[]); if(!empty($ladderList)){ $std['price_type'] = 'ladder'; $std['price_range'] = []; $minPrice = 9999999; foreach($ladderList as $lad){ $num = intval(getSafe($lad,'minNum',0)); $p = floatval(getSafe($lad,'price',0)); $std['price_range'][] = ["min_num"=>$num,"price"=>$p]; if($p < $minPrice) $minPrice = $p; } $std['price'] = $minPrice; }else{ $std['price_type'] = 'fixed'; $std['price'] = floatval(getSafe($raw,'price',0)); $std['price_range'] = []; } return $std; } // 安全取值,不存在返回默认值 function getSafe($arr,$key,$default=''){ if(!is_array($arr)) return $default; return isset($arr[$key]) ? $arr[$key] : $default; }

四、性能优化(轻量化带来的收益)

  1. JSON 解析开销下降:跳过详情 HTML、多张图片、资质文本,单商品 JSON 体积从几 KB~ 十几 KB 压缩到几百字节;
  2. 数据库存储压力:入库表字段精简,不存大文本,索引只建 goods_id、company_id、keyword;
  3. 内存占用:批量循环时,解析完提取字段后,原始大对象及时释放,避免内存持续上涨;
  4. 带宽减少:如果可以,请求层可和接口协商,尽量不返回详情富文本字段(部分接口支持参数控制返回字段)。

五、监控、脏数据校验与告警

  1. 分页指标监控:空页计数、重试次数、goodsId 重复率、token 失效次数;
  2. B2B 业务脏数据校验:
    • min_order <0 标记脏数据;
    • 阶梯报价价格异常(价格 <=0);
    • 标题为空、goodsId 为空;
  3. 告警:token 失效、连续 3 次接口 5xx、脏数据比例超过阈值;
  4. 日志分级:info 正常采集,warning 低返回条目,error 鉴权 / 接口异常。

六、风险与扩展优化

  1. 搜好货接口结构变更:轻量化只取少量核心字段,相比全量解析,接口改动影响面更小;建议异步单独备份原始 JSON(非主链路),方便字段映射修复;
  2. 并发策略:单个关键词串行分页,多关键词使用协程 / 线程池控制并发数,B2B 平台对并发敏感;
  3. 增量监控:关键词搜索是快照采集;如需监控价格变动,以 goodsId 为 key,对比新旧 price/min_order;
  4. 同款合并:业务如需供应商同款比价,在轻量化结构体基础上,增加标题 md5+companyId 做同款分组。

七、整体架构

任务调度层(关键词任务队列、状态持久化) ↓ HTTP请求层(签名、token、分级重试、节流、鉴权判断) ↓ 分页容错层(空页计数、goodsId去重、断点续采、结束判定) ↓ 轻量化提取层(只读取必要字段,丢弃富文本/大图等冗余数据,B2B阶梯报价归一) ↓ 存储层:轻量化商品表(主业务)|原始JSON备份表(异步可选) ↓ 上层业务:B2B货源比价、供应商监控、选品报表
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 16:18:21

DS1302 RTC芯片详解:51单片机驱动时序、BCD码转换与实战排障

DS1302这颗芯片&#xff0c;我在51单片机的项目里用了不少年。无论做电子时钟、定时插座、温湿度记录仪&#xff0c;还是给数据采集系统打时间戳&#xff0c;只要涉及“显示当前时间”或者“按日历运行”&#xff0c;它基本上是最省事的选择&#xff1a;三根线就能跟单片机通信…

作者头像 李华
网站建设 2026/10/15 16:18:07

西门子PLC与ABB510变频器Modbus通讯实战:参数读写与启停控制

简介&#xff1a;这份资源围绕西门子PLC与ABB510变频器的Modbus通讯展开&#xff0c;面向工控入门者、电气调试人员及自动化专业学生&#xff0c;解决PLC与变频器之间参数读写、启停控制和频率电流采集等实际通讯问题。包内共5个文件&#xff0c;以3张jpg图片、1个txt文本和1个…

作者头像 李华
网站建设 2026/10/15 16:18:03

智慧档案室一体化环境管控系统:从需求拆解到落地运维全流程实战

1. 从一份"没人愿意接手"的档案室改造需求说起档案室这个场景&#xff0c;很多做弱电、做环境监控、做物联网集成的朋友一听就头大。预算不算高&#xff0c;要求却一点不少&#xff1a;温湿度要稳、空气质量要盯、漏水要防、消防要联动、门禁要管、视频要留痕&#x…

作者头像 李华
网站建设 2026/10/15 16:16:51

CSS从入门到进阶:核心机制、布局实战与调试技巧

看到“CSS&#xff01;&#xff01;&#xff01;&#xff01;”这个标题&#xff0c;我第一反应是会心一笑。这四个感叹号里&#xff0c;大概率藏着某个深夜调样式调到怀疑人生的故事。CSS 大概是前端领域少有的一个“人人都说入门简单&#xff0c;但写起来总是血压升高”的技术…

作者头像 李华
网站建设 2026/10/15 16:16:15

Flutter跨端实战:OpenHarmony上实现剧本杀发起组队表单

1. 先拆需求&#xff1a;发起组队表单到底在解决什么问题做剧本杀组队类产品的人应该都有感触&#xff1a;用户从“有玩一局的念头”到“真正把车组起来”&#xff0c;中间最劝退的环节往往不是刷列表&#xff0c;而是发起组队那一刻要填的那张表单。这张表单如果字段太多、校验…

作者头像 李华
网站建设 2026/10/15 16:15:33

C++ vector底层原理与高性能使用避坑指南

1. 为什么今天还要深挖 vector——它早不是“能自动扩容的数组”那么简单你打开任何一本C入门书&#xff0c;翻到容器章节&#xff0c;vector几乎永远是第一个登场的角色。老师会说&#xff1a;“它像数组&#xff0c;但能自动变长。”学生点头记下&#xff0c;写个vector<i…

作者头像 李华