技术博客
百度搜索 API 计费与缓存:失败请求扣不扣费,重复 query 要不要缓存

百度搜索 API 计费与缓存:失败请求扣不扣费,重复 query 要不要缓存

作者: 万维易源
2026-09-15
百度搜索计费Redis缓存成本优化
# 百度搜索 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)