百度搜索 API 计费与缓存:失败请求扣不扣费,重复 query 要不要缓存
# 百度搜索 API 计费与缓存:失败请求扣不扣费,重复 query 要不要缓存
> 接口:百度搜索(apiCode=3351,接入点 3351-1)· 官方自营 · 按次计费
> 请求方式:POST / GET · 适用人群:调用量上来的接入方 · 阅读时间:约 8 分钟
> **最后实测核对:2026-09-15(含相同请求连发两次的对照实验)**
百度搜索 API(apiCode=3351)按次计费,每次成功调用扣 1 次,用 `showapi_fee_num` 字段体现。两个实测结论值得先知道:**请求失败时 `showapi_fee_num` 是 0,不扣次数**;**相同参数的请求连发两次会各扣 1 次,但返回结果完全一致**。
第二点意味着缓存能直接省钱,而且省得干干净净。这篇讲清计费口径和缓存该怎么设计。
## 核心要点
- 成功调用 `showapi_fee_num = 1`,参数错误和鉴权错误都是 `0`,调试不花钱。
- 每次调用都是新请求,`showapi_res_id` 每次都不同,即使参数一模一样。
- 相同 query 在短时间内返回结果高度稳定——实测两次调用 10 条 `url` 完全一致,缓存命中率高。
## 计费口径:实测数据
2026-09-15 的 20 次实测调用里(18 次成功、2 次故意构造的失败),`showapi_fee_num` 的取值只有两种:
| 场景 | HTTP | `showapi_res_code` | `showapi_fee_num` | 扣费 |
|------|------|-------------------|-------------------|------|
| 正常检索 | 200 | 0 | 1 | 扣 1 次 |
| `query` 为空 | 200 | -1 | 0 | 不扣 |
| AppKey 错误 | 200 | -1004 | 0 | 不扣 |
参数写错、key 填错都不消耗次数,所以调试期不用省着调。真正要控成本的是**成功调用**那部分。
## 价格与规格
接口页上的专用资源包规格是 36 / 170 / 600 / 5200 元几档,另外有一个 0.00 元的档位,自购买起有效期 **12 个月**。页面还写明支持通用资源包——充了通用资源包可以直接调这个接口,不必单独买专用包。
各档位分别包含多少次调用,接口页上没有展示,我没查到,也不打算推算。买之前在产品价格页或购买页确认次数,那才是准的。
**另外,用接口页展示的档位价格去除以次数得出"单价"这件事我做不了**,因为次数是未知项。任何"每次多少钱"的说法在次数明确之前都不成立。
## 缓存为什么划算
先看一个实测对照:2026-09-15 用完全相同的参数(`query=昆明天气`,不带其他可选参数)连发两次请求,间隔 2 秒。
| 次 | `showapi_res_id` | `showapi_fee_num` | 返回条数 | 两次 `url` 列表 |
|----|-----------------|-------------------|---------|----------------|
| 第 1 次 | `6aa8a1a0...` | 1 | 10 | 完全一致 |
| 第 2 次 | `6aa8a1a3...` | 1 | 10 | 完全一致 |
结论很清楚:`showapi_res_id` 变了,说明接口每次都真的跑了一次检索、扣了一次费;但返回的 10 条结果是同一批。也就是说,**重复请求不会命中服务端缓存,钱是实打实扣的,而内容并没有变化**。
在真实业务里,同一个查询词被重复请求是常态——多用户问同一件事、用户刷新页面、Agent 反复检索同一个话题。这些请求你完全可以在自己这边拦掉。
## 缓存键怎么设计
缓存键必须覆盖所有会改变结果的参数。漏一个就会出现"改了参数拿到旧结果"的错。
```python
import hashlib, json
def cache_key(query: str, count: str, allow: str = "", block: str = "",
recency: str = "") -> str:
payload = json.dumps(
{
"q": query.strip(),
"c": count,
"a": allow, # search_domain_filter
"b": block, # block_domain_filter
"r": recency, # search_recency_filter
},
ensure_ascii=False,
sort_keys=True,
)
return "bdsearch:3351:" + hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
```
五个字段一个都不能少。`query` 记得 `strip()`,否则"大模型"和"大模型 "会变成两个缓存键,白白多扣一次。
## 完整缓存实现
```python
import hashlib, json, time
import requests
import redis
rds = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)
# TTL 按查询场景定,不是固定值
TTL_BY_RECENCY = {
"week": 300, # 要新鲜度,容器要短
"month": 1800,
"semiyear": 3600,
"year": 7200,
"": 600,
}
def search_cached(query: str, appkey: str, count: str = "10",
recency: str = "") -> list:
key = cache_key(query, count, recency=recency)
cached = rds.get(key)
if cached:
return json.loads(cached)
resp = requests.post(
"https://route.showapi.com/3351-1",
params={"appKey": appkey},
data={"query": query, "count": count, **({"search_recency_filter": recency} if recency else {})},
timeout=10,
)
resp.raise_for_status()
result = resp.json()
if result.get("showapi_res_code") != 0:
# 失败不扣费,别写缓存,直接抛
raise RuntimeError(f"{result.get('showapi_res_code')} {result.get('showapi_res_error')}")
refs = (result.get("showapi_res_body") or {}).get("references") or []
if refs:
# 只有拿到结果才缓存,空结果不缓存
rds.setex(key, TTL_BY_RECENCY.get(recency, 600), json.dumps(refs, ensure_ascii=False))
return refs
```
四个取舍说明:
**失败不写缓存。** 失败请求本来就不扣费,缓存它没有收益,反而会把一次偶发失败固化几分钟。
**空结果不写缓存。** 查询词太冷门返回空数组是正常的,但那可能是过滤条件太苛刻导致的。缓存空结果会让用户几分钟内都看到"没找到"。可以改成用更短的 TTL(比如 60 秒)单独缓存空结果。
**TTL 跟 `recency` 挂钩。** `week` 场景下内容每几小时就变,缓存 5 分钟合理;`year` 场景下内容变化慢,缓存 1 小时也安全。上面那几个数字是经验值,不是标准答案——按你的业务容忍度调。
**`count` 进缓存键。** 同一个 query 要 10 条和要 20 条是两次不同的检索,结果不通用。
## 还能省的地方
**合并相同查询。** 多个用户在同一分钟内问同一个问题,用一把分布式锁让第一个请求去调接口,其余等结果,能把并发请求压成一次。
**批量场景先本地去重。** 如果你在跑关键词列表,先用 `set` 去一遍重复词,再批量调用。这一步通常在业务侧就能砍掉可观比例,具体比例取决于你的数据,我没有可引用的数字。
**能不用就不调。** 判断用户问题是不是"需要实时信息"再决定调不调。事实常识类问题直接让模型回答,省下的是纯利润。
## 边界与坑
**`showapi_fee_num` 是本次扣费次数,不是账户余额。** 想知道剩余量得去控制台看。接口响应里没有余额字段。
**扣费与返回条数无关。** 实测过滤后只剩 1 条结果时,`showapi_fee_num` 依然是 1。别以为结果少就少扣。
**重复请求必扣费。** 服务端不做相同请求的免费复用,这点实测已确认。
**缓存键漏参数会出错结果。** 这是缓存方案里最隐蔽的 bug:先调了 `week`,再调 `month`,如果键里没有 `recency`,第二次会直接返回第一次的缓存。用户看到的是旧窗口的数据,很难查。
**过期时间别设太长。** 这个接口是实时检索,结果本身有时效属性。设几小时的 TTL 会让"最新消息"变成"几小时前的消息"。
## FAQ
**Q1:请求失败会扣次数吗?**
不会。实测 `query` 为空和 AppKey 错误两种情况下 `showapi_fee_num` 都是 0。
**Q2:相同参数重复调用会重复扣费吗?**
会。实测两次相同请求的 `showapi_fee_num` 都是 1,虽然返回结果完全一致。缓存能省下这部分。
**Q3:一次调用到底多少钱?**
接口页展示了资源包档位价格(36 / 170 / 600 / 5200 元等,有效期 12 个月),但每档包含的调用次数页面没有展示。单价需要用次数换算,次数未知,所以我不给单价。
**Q4:通用资源包能用来调这个接口吗?**
能。接口页写明支持通用资源包,充值后可直接调用全站付费接口,不必单独购买专用资源包。
**Q5:缓存多久合适?**
按 `recency` 档位定。`week` 场景建议控制在几分钟内(上面示例用 300 秒,属经验值);`year` 场景可以放宽到小时级。没有官方建议值,按业务对时效的容忍度定。
**Q6:返回 0 条结果要不要缓存?**
建议不缓存,或者用很短的 TTL 单独处理。查询词或过滤条件写错时,缓存空结果会让问题持续几分钟。
## 下一步阅读
- [在 RAG 里接入百度搜索 API](https://www.showapi.com/guides/baidu-search-api-rag-agent-3351) —— Agent 反复检索同一话题,正是缓存收益最大的场景
- [百度搜索 API 报错怎么查](https://www.showapi.com/guides/baidu-search-api-error-codes-3351) —— 哪些错误不扣费,对照表在那边
- [AI 实时检索选型:百度搜索 API、自建爬虫、免费接口三方对比](https://www.showapi.com/guides/baidu-search-api-compare-3351) —— 从成本角度看这个接口值不值
---
- **本系列共 8 篇**:查看[百度搜索 API 指南总目录](https://www.showapi.com/guides/baidu-search-guides-3351)