一、为什么要把「搜索报价」和「详情 API」结合起来
做供应链选品或采购比价时,最常见的做法是:在平台上按关键词搜一遍,把前几页的报价抄下来,然后凭感觉挑一个最便宜的。这种做法有两个明显问题:
报价不等于可成交价:搜索列表里的价格往往是起订价、阶梯价的第一档,或者是引流价,实际下单时可能因为规格、数量、运费而完全不同。
便宜不等于能供货:一个报价极低的供应商,可能库存为零、交期长达 30 天,或者根本没有稳定的生产能力。
因此更合理的思路是分两步走:先用关键词搜索做广度筛选,拿到候选供应商和报价区间;再调用商品详情 API 做深度评估,补齐质量指标和供货能力指标。本文就围绕这条链路,讲清楚数据结构设计、评分模型和工程实现。
二、整体链路设计
整条链路可以拆成四个阶段:关键词搜索、候选集清洗、详情补全、综合评分。用流程图表示如下:
flowchart TD
A[输入关键词与筛选条件] --> B[调用搜索接口获取报价列表]
B --> C[候选集清洗与去重]
C --> D[批量调用商品详情 API]
D --> E[质量维度打分]
D --> F[供货稳定性维度打分]
E --> G[加权综合评分]
F --> G
G --> H[输出排序结果与推荐清单]
H --> I[人工复核与询价验证]
这里的关键设计原则是:搜索接口负责「找得到」,详情接口负责「看得清」。不要把评估逻辑塞进搜索阶段,否则一旦评分规则调整,就要重新抓取全量列表,成本很高。
三、第一步:关键词搜索与报价采集
3.1 关键词的构造方式
直接用「螺丝」这种大词搜索,返回结果往往噪声极大。建议按「品类词 + 规格词 + 属性词」三层组合:
层级 示例 作用
品类词 不锈钢螺丝 确定大类,保证召回量
规格词 M6×20 缩小到具体型号,减少无效比价
属性词 304、沉头、防松 锁定材质与工艺,保证可比性
同时要准备一组同义词和别名,例如「304」与「SUS304」、「沉头」与「平头」,在搜索时做扩展召回,避免因为叫法不同漏掉优质供应商。
3.2 搜索结果的字段约定
搜索接口返回的列表项通常包含以下字段,建议在入库时统一成固定结构:
{
"supplier_id": "S10086",
"product_id": "P20240001",
"title": "304不锈钢沉头螺丝 M6*20",
"price_min": 0.12,
"price_max": 0.18,
"moq": 1000,
"unit": "个",
"sales_30d": 52000,
"shop_years": 6,
"location": "浙江温州"
}
注意 price_min 和 price_max 一定要成对保存。只记录最低价会导致后续比价失真,因为最低价往往对应最大起订量。
3.3 分页与限流
搜索接口一般都有分页上限和 QPS 限制。工程上建议:
每个关键词最多翻 5 到 10 页,超过之后结果相关性下降明显,性价比不高。
使用令牌桶做限流,遇到 429 或频控错误时按指数退避重试,不要死循环。
把「关键词 + 页码 + 抓取时间」作为幂等键,避免重复入库。
四、第二步:候选集清洗
搜索回来的原始列表通常有 30% 到 50% 是噪声,需要先做一轮清洗再进入详情评估,否则会浪费大量 API 配额。
去重:同一供应商的同一商品可能因为多个关键词重复出现,按 supplier_id + product_id 去重。
剔除明显不匹配项:标题里不含关键规格词的直接丢弃,例如搜 M6×20 却返回 M8 的结果。
剔除异常报价:用同批次报价的中位数做基准,低于中位数 30% 以上的先标记为「待核实」,不直接进入评分,避免被引流价带偏。
剔除无效店铺:店铺年限为 0、无成交记录、无资质信息的,优先级下调。
五、第三步:用详情 API 补齐评估维度
详情 API 是评估的核心数据源。它通常能提供搜索列表里没有的信息,我们把它拆成「质量」和「供货稳定性」两组指标。
5.1 质量维度指标
指标 字段示例 评估含义
材质与规格一致性 material、spec 详情描述是否与搜索关键词一致,防止挂羊头卖狗肉
质检与认证 certifications 是否有 ISO、SGS、ROHS 等第三方认证
用户评价 rating、review_count 评分与评价数量共同反映口碑可信度
退货率 return_rate 退货率高通常意味着质量波动大
详情完整度 image_count、desc_length 详情页信息越完整,供应商越规范
5.2 供货稳定性指标
指标 字段示例 评估含义
库存深度 stock 现货量能否覆盖预期采购量
交期 lead_time_days 承诺发货时间,越长风险越高
近 30 天成交 sales_30d 反映真实出货能力,而非刷单数据
准时发货率 on_time_rate 历史履约表现,最直接的稳定性信号
经营年限 shop_years 长期经营通常意味着供应链更成熟
阶梯价完整度 price_tiers 有清晰阶梯价的供应商,报价体系更规范
5.3 详情接口调用示例
下面用 Java 演示批量拉取详情并做基础校验,注意控制并发和超时:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ProductDetailFetcher {
private final HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
private final ExecutorService pool = Executors.newFixedThreadPool(8);
public CompletableFuture<String> fetch(String productId, String token) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/product/detail?product_id=" + productId))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + token)
.GET()
.build();
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(resp -> {
if (resp.statusCode() != 200) {
throw new IllegalStateException("detail api failed: " + resp.statusCode());
}
return resp.body();
});
}
public void fetchAll(List<String> productIds, String token) {
List<CompletableFuture<String>> futures = productIds.stream()
.map(id -> fetch(id, token))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((v, ex) -> {
if (ex != null) {
System.err.println("部分详情拉取失败: " + ex.getMessage());
}
pool.shutdown();
});
}
}
实际生产中建议再加一层本地缓存,同一个 product_id 在 24 小时内不重复请求,能显著降低配额消耗。
六、第四步:综合评分模型
把两组指标归一化后加权求和,得到一个 0 到 100 的综合分。下面是一个可直接落地的评分实现:
public class SupplierScorer {
public double score(SupplierDetail d) {
double quality = 0.0;
quality += normalize(d.getRating(), 0, 5) * 30;
quality += d.isHasCertification() ? 20 : 0;
quality += normalize(1 - d.getReturnRate(), 0, 1) * 20;
quality += normalize(d.getReviewCount(), 0, 5000) * 15;
quality += normalize(d.getImageCount(), 0, 20) * 15;
double stability = 0.0;
stability += normalize(d.getOnTimeRate(), 0, 1) * 35;
stability += normalize(d.getStock(), 0, 100000) * 25;
stability += normalize(1.0 / Math.max(d.getLeadTimeDays(), 1), 0, 1) * 20;
stability += normalize(d.getShopYears(), 0, 10) * 10;
stability += d.getPriceTiers() >= 3 ? 10 : 0;
return quality * 0.5 + stability * 0.5;
}
private double normalize(double value, double min, double max) {
if (max <= min) {
return 0;
}
double v = (value - min) / (max - min);
return Math.max(0, Math.min(1, v));
}
}
权重不是固定的,需要按品类调整。比如标准件更看重价格和交期,定制品更看重认证和评价;建议把权重抽到配置文件中,方便不同采购场景复用。
七、结果呈现与人工复核
评分只是辅助决策,最终输出建议包含三列信息:
综合分与分项分:让采购人员看到分数是怎么来的,而不是一个黑盒数字。
风险提示:例如「报价低于中位数 35%,建议核实」「交期 25 天,超出常规范围」。
原始链接与关键字段快照:方便回溯,也便于后续复现问题。
对于综合分排名前 5 的供应商,仍然建议做一轮人工询价验证,重点确认阶梯价、运费、开票方式和实际交期。API 数据能帮你快速缩小范围,但最终成交条件还是要靠沟通确认。
八、常见坑与优化建议
不要只看最低价:最低价往往对应最大起订量或引流款,务必结合 moq 和阶梯价一起看。
注意数据时效:库存和价格变化很快,建议给详情数据打上抓取时间戳,超过 7 天的数据在展示时标注「可能已过期」。
警惕刷单数据:成交量和评价数要交叉验证,销量极高但评价极少、或评价内容高度雷同的,需要降权处理。
做好失败兜底:详情接口部分失败时,不要整批丢弃,可以用搜索列表字段做降级评分,并标记「数据不完整」。
保留原始响应:把详情 API 的原始 JSON 落库,后续调整评分模型时可以离线重算,不必重新抓取。
九、小结
「关键词搜索 + 详情 API」的组合,本质上是把供应商评估从「凭经验拍脑袋」变成「有数据支撑的排序」。搜索负责广度,详情负责深度,评分模型负责把多维指标压缩成一个可比较的数值。落地时优先保证数据结构的统一和失败兜底,再逐步调优权重,这套链路就能稳定支撑日常的比价与选品工作。如有任何疑问,欢迎留言探讨!