先说一句大实话:互联网上所有“搜索联想词”都不是凭空冒出来的,它背后一定有一个接口在实时返回数据。百度搜索的“建议搜索”也一样,你在搜索框里敲一个字,页面下方立刻弹出的小词条,背后就是百度的前端在调用一个建议接口。这篇文章不打算只教你“你在页面上能看到它在哪”,而是要把“定位”这件事做透——从用户视角看到它,从开发者视角抓到它,最后把它变成你能自己控制的调用能力。
我最早接触这个需求,是帮团队做某行业关键词扩展。当时领导说“把百度搜索框下面的联想词都抓下来”,我想当然地以为直接请求首页解析 HTML 就行,结果折腾了好几个小时,发现联想结果根本不是搜索页 HTML 里的内容,而是通过异步接口拉回来的。后来老老实实打开开发者工具,把整个请求链路捋了一遍,才真正定位到那个建议接口。这篇文章就是那次完整过程的复盘,也是给所有卡在“不知道去哪找这个功能”的人一份可以直接照做的定位手册。
整个过程会分为几个步骤:先分清页面上的“建议搜索”到底是什么,再从前端展示层定位它的位置,然后从网络请求层抓到它的真实接口,最后拆解接口参数并给出脚本化调用的完整示例。如果你只是想知道“它藏在哪”,那么前两章就够了;如果你打算拿这些联想词做扩展分析,后三章才是真正的重点。
1. 这个“建议搜索”在百度页面里以什么形态出现
1.1 先区分三个容易混淆的“搜索词推荐”
做定位之前,我建议你先搞清楚一个事儿:百度搜索结果页里带“推荐”性质的词有三类,长相相似,但来源完全不同,很多人混为一谈,导致后来抓错对象。
第一类是这次的主角,搜索框内的“联想词”,也常被称为“搜索建议”或“自动补全”。它的特点是:你还没按回车,输入框下方就弹出一个灰色下拉层,词条跟着你的输入频率实时变化。比如你输入“旅”,它可能弹出“旅行”“旅游攻略”“旅行箱”等一串词。这类词由前端在每次输入时实时请求接口获得,和后续搜索结果页没有必然关系。
第二类是搜索结果页最底部的“相关搜索”。这个需要在按回车进入结果页之后才能看到,通常在页面尾部,以两列或三列形式排列,标题就是“相关搜索”四个字。它是基于你当前搜索词生成的另一批用户高频搜索词,属于整页搜索结果的一部分,数据也不是来自联想接口。
第三类是搜索结果上方的“百度为您找到相关结果约X个”,以及各种广告推荐位。这些更像是搜索系统给出的元信息,不是词条型推荐。
所以,当你问“如何定位百度搜索的百度建议搜索”时,务必先对标第一类:就是那个敲字时不断跳动的下拉联想层。把它和底部的相关搜索区分开,后面所有定位逻辑才不会跑偏。
1.2 建议搜索的触发条件与运行逻辑
理解它的触发逻辑,比单纯记住“它在搜索框下面”要有用得多。
建议搜索的运行逻辑可以拆成三步。第一步是监听输入框的 input 事件,当你输入第一个字符后,前端就进入“联想候选”模式。第二步是收集当前输入内容,连同一些环境参数,向建议接口发起异步请求。第三步是后端返回一组候选词,前端把候选词渲染成带高亮效果的列表,显示在输入框正下方。
这里有几个关键细节。
一是触发请求的时机。百度并不是每个按键都立刻发请求,它会做一次简单的节流或防抖处理,而且中文输入法状态下会有特殊处理。你用拼音输入法打“lvxing”,在拼音组合未上屏前,联想的候选词往往不触发;当你选了“旅行”并落在输入框里,请求才会真正发出去。这个细节对定位接口很重要,因为我在第一次抓包时就是被输入法状态给搞糊涂了,输入了半天一个请求都没抓到,后来才意识到是拼音状态没有触发。
二是联想层的出现条件。它只出现在搜索框处于焦点状态时,且必须有候选词返回;如果你输入的词完全没有任何联想结果,下拉层就不会渲染。
三是运行主体。建议搜索是一个典型的“前端异步渲染”组件,不是整页刷新。这意味着,我们没法通过在地址栏查看 URL 参数的方式直接定位它,必须借助开发者工具去观察页面发出的动态请求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用开发者工具定位前端展示层
2.1 在 Elements 面板里找到联想下拉列表
如果你只是想确认“这个功能在页面里到底是以什么结构存在的”,那么用浏览器开发者工具看 Elements 面板是最直接的。
操作步骤我按自己的习惯走一遍。
先开一个干净的浏览器标签页,访问百度首页,按 F12 打开开发者工具。切到 Elements 面板,然后用鼠标点击搜索框,随便输入一个有点区分度的词,比如“区块链”。我的建议是用一个不那么通用的词,方便后面在 DOM 里快速搜索定位。输入后别按回车,让联想下拉层保持展开状态。
接下来在 Elements 面板里按 Ctrl+F,搜索“区块链”三个字。你会看到联想下拉层的所有 DOM 节点。如果搜不到,就搜索“suggest”或者“result”这种常见类名,也可能直接找到那个浮层容器。
实际能看到的东西一般长这样的结构:
html复制<div class="sug-layer">
<ul>
<li>区块链技术</li>
<li>区块链是什么</li>
<li>区块链游戏</li>
</ul>
</div>
不同时期百度的前端类名会变,但整体结构万变不离其宗,无非是一个浮层容器里套一个列表,列表每一项承载一个候选词。
这里我想特别提醒一句:Elements 面板里看到的 DOM 是“已经渲染完成后的结果”,它只回答“联想层在页面结构里什么位置”这个问题,不会告诉你数据从哪来。很多初学者在这一层能看懂,但下一步就不知道怎么办了,于是去写一堆复杂的 DOM 解析脚本,结果页面结构一变就全废。这种思路我不推荐,真正的定位重点应该放在网络请求上。
2.2 展示层不可靠,接口层才稳定
我为什么说展示层不是重点?因为前端 DOM 是页面设计与交互的产物,改版极其频繁。今天叫 sug-layer,明天可能叫 result-panel,后天整个渲染逻辑都可能改成 Canvas 或虚拟滚动。如果你把定位思路建立在 DOM 解析上,等于把数据采集方案的稳定性绑在了百度的前端工程师手上,他们改一行类名,你的程序就要跟着改。
而接口层是另一回事。虽然接口参数也可能调整,但它的核心逻辑和返回结构要稳定得多。只要百度没有彻底改变搜索建议的产品形态,那个返回联想词的后端地址就会一直在那里。
所以我的个人经验是:借助展示层理解产品形态,用网络请求层定位数据来源。展示层定位到这个程度就够了,下面进入真正有价值的环节。
3. 在网络请求里抓到真正的建议接口
3.1 抓包操作:三步拿到接口地址
抓包是整个定位环节里最核心、也最有成就感的一步。实际操作就三步。
第一步,先打开开发者工具的 Network 面板。一定要注意:Network 面板里默认会显示各种资源请求,可能上百条,很容易看花眼。建议在顶部筛选器里直接点一下 Fetch/XHR 标签,只保留异步接口类请求,这样能把无关的图片、CSS、JS 脚本全部过滤掉。
第二步,勾选 Preserve log,保留请求日志。这一步很多人会漏掉,因为页面发生跳转时,网络记录默认会被清空。百度搜索框的场景虽然不发整页跳转,但为了稳妥,最好还是勾上。
第三步,回到百度首页,把焦点放在搜索框里,输入一个词。这时候你回到 Network 面板,会发现底部冒出一批新请求。里面可能有一大堆跟踪统计类和日志上报类的请求,不用管它们,我们要找的是那个响应结果包含候选词的请求。
正常情况下,你会看到一个名称类似 sugrec 的请求。地址通常长这样:
text复制https://www.baidu.com/sugrec?prod=pc&wd=%E5%8C%BA%E5%9D%97%E9%93%BE&...
我印象中这个接口名 sugrec 全称应该是 “suggest recommend”,看起来就是“推荐建议”的缩写。响应体是一段 JSON 或 JSONP,里面能直接看到你输入的那批候选词。这就对了。
3.2 用浏览器地址栏直接验证接口
抓到接口地址后,建议你不要急着写代码,先用最朴素的方式验证一下它是不是真的“裸接口”。
什么叫做“裸接口”?就是直接在浏览器地址栏里输入这个 URL,不用任何特殊环境、不用模拟点击,看它能不能在不登录、不带 Cookie 的情况下返回数据。
我常用的验证方法是:把 Network 面板里该请求的 URL 整体复制出来,新建一个标签页,直接粘贴访问。如果返回了一段以 JSON 开头或者带有回调函数的文本,里面还能看到“区块链技术”“区块链是什么”这些词,那就说明这个接口就是我们要定位的目标。
这一步看似简单,但特别值得做。因为有些接口虽然被前端调用了,但它的调用是依赖页面环境令牌的,比如要带特定的 Cookie、要带页面里的某个 token,一旦脱离页面就直接拒绝。如果你验证时发现地址栏访问拿不到数据,那这个接口大概率不能直接用,还得继续往更底层挖。
以 sugrec 这类接口的经验来说,它往往不强制要求登录态,只要带上正常的 User-Agent 和 Referer 就能拿到数据。但这里我不展开讨论绕过逻辑,只提醒一点:任何公开接口都要在合理频率和合理用途范围内使用。
3.3 一眼识别你抓到的请求类型
一开始抓包你会遇到一个常见困惑:请求太多了,到底哪个才是建议接口?
我给一个判断方法。按照响应体来区分,比按照地址来区分要可靠得多。在 Network 面板里逐个点击请求,看右边的 Response 预览。如果响应是一大段压缩过、混淆过的 JS 或图片字节,那就不是接口。如果响应是结构化文本,并且里面包含人类可读的关键词列表,那它就算得上是目标候选。
实际抓包中你还会遇到一批所谓“日志上报”接口,响应往往是空对象或“{}”,它们也是异步请求,但和联想词毫无关系,可以直接忽略。
为了减少干扰,我的操作习惯是:清空当前所有请求记录,然后只输入一次搜索词,不要多操作,这样新增的请求列表基本就能锁定在这一个动作产生的范围内。如果你发现请求还是太多,可以再输入一个很长、很生僻的词组,这样联想结果会减少,接口请求也会更清晰地暴露出来。
4. 接口参数与返回结构逐项拆解
4.1 请求参数:哪些字段决定返回结果
定位到接口之后,很多人会直接复制完整的 URL 拿去调用,但完整 URL 里有太多无意义参数,我们需要理解哪些是真正的控制参数。
根据我抓到的请求,比较关键的参数大概有这么几个:
| 参数名 | 含义 | 影响程度 |
|---|---|---|
| prod | 产品形态标识,如 pc 代表电脑端 | 高,不同端返回不同联想词池 |
| wd | 你输入的搜索词,关键词核心 | 非常高 |
| rn | 返回候选词数量 | 高,控制返回条数 |
| cb | JSONP 回调函数名 | 中,影响返回格式 |
| ie | 输入编码,如 utf-8、gbk | 低,编码声明 |
| p | 一些版本或场景标记 | 低,通常不影响理解 |
wd 不用多说,它就是你在搜索框里输入的那个词。rn 注意一下,你把它设成 10 或者 20,返回的候选词数量会对应变化,但百度会对单次请求的候选词数量做上限控制,不是你想要多少就给你多少。
prod 这个参数值得认真对待。百度搜索建议机制其实支持很多产品线,比如地图、知道、贴吧,都有各自的推荐体系。你在百度首页 pc 端搜索框里拿到的,是 pc 端网页版的联想词;如果你用手机端页面,prod 参数会不一样,返回结果也不同。所以做数据采集时,一定要根据业务场景明确要的是哪一端的数据。
4.2 返回 JSON 的结构和常见字段
建议接口的返回格式整体比较简洁。一个典型的 JSON 响应结构大致如下:
json复制{
"q": "区块链",
"p": true,
"g": [
{ "q": "区块链技术", "type": "sug", "sa": "..." },
{ "q": "区块链是什么", "type": "sug", "sa": "..." }
]
}
最外层 q 是你输入的查询词,g 是候选词数组。g 里每一项的 q 字段,就是需要在搜索框下拉层里展示的词条。
有时你还会看到 type 字段,它可能标示这个词条是普通联想词还是某种特殊推荐词。多数情况下我们只需要把数组里每一项的 q 取出来用。
有一个容易忽略的点:某些响应的候选词会带有前后空格或特殊控制字符。这些字符可能在浏览器渲染时不可见,但一旦你保存到 CSV 或写进数据库,就会发现字符串看起来一样,用代码一比对却不相等。建议对候选词做一次字符串清洗,去掉首尾空白和不可见控制符,再做业务处理。
4.3 为什么你复制的请求链接变了,返回值也跟着变
你可能会发现一个现象:同一个关键词,上午请求返回一批词,下午请求返回另一批词。这并不一定是接口定位错了,而是联想词本身具有较强的时间属性。
搜索结果和联想的底层推荐逻辑,会考虑搜索热度、内容时效性、用户画像等因素。比如你输入“iPhone”,在新品发布会前后,联想词会大量出现新品相关的词条;发布会结束一两个月后,那些词条可能慢慢被新的热点替换。这是正常的数据动态变化,不是 bug。
理解这一点对做关键词扩展特别有价值:如果你想采集某类词的稳定联想结果,建议在多个时间点各采集一轮,再求交集或按出现频率排序,这样能过滤掉大量短期热点噪声,留下相对稳定的核心联想词。
5. 从定位到落地:脚本化获取百度建议词
5.1 一个最小可用的 Python 示例
定位的最终目的是落地使用。这里我给出一个我常用的最小化 Python 脚本,可以直接复制运行。它的核心逻辑就是模拟浏览器向建议接口发请求,然后解析 JSON 中 g 数组里的候选词。
python复制import requests
import json
def get_baidu_suggest(keyword, count=10):
url = "https://www.baidu.com/sugrec"
params = {
"prod": "pc",
"wd": keyword,
"rn": count,
"ie": "utf-8",
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36",
"Referer": "https://www.baidu.com/",
}
resp = requests.get(url, params=params, headers=headers, timeout=5)
resp.raise_for_status()
data = resp.json()
result = [item.get("q", "") for item in data.get("g", [])]
return result
if __name__ == "__main__":
words = get_baidu_suggest("区块链", count=10)
for word in words:
print(word)
运行这个脚本前,需要先安装 requests 库:
bash复制pip install requests
如果一切正常,你会看到类似下面这样的输出:
text复制区块链技术
区块链是什么
区块链游戏
区块链浏览器
区块链钱包
...
脚本里几个细节我解释一下。
第一,Referer 头我特意加了百度首页。虽然建议接口不一定强制校验 Referer,但带上它能更好地模拟真实调用环境,减少被识别的概率。第二,超时设置成 5 秒,避免某个请求卡死导致整个脚本挂住。第三,我没有写 Cookie,因为这种接口通常不需要登录态;如果遇到需要 Cookie 的情况,可以在浏览器复制一份完整的请求头出来对比,可能需要携带 BAIDUID 等基础 Cookie。
5.2 把联想词用于关键词扩展的实践套路
拿到单批联想词只是第一步,实际工作中我们往往要让联想词“长出”更多词。这就是关键词扩展的思路,核心套路叫做“迭代式联想”。
做法很简单:先用一批种子词调用建议接口,拿到第一批联想词;再随机抽取一部分联想词作为新的输入词,继续调用接口;如此循环两三轮,就可以把词表从几十个扩展到几百个甚至上千个。
举个例子。种子词是“旅游”,第一轮接口返回“旅游攻略”“旅游签证”“旅游保险”等十来个词。第二轮拿“旅游攻略”作为输入词,它又返回“旅游攻略大全”“国内旅游攻略”“云南旅游攻略”等新词。这些二次衍生词中,很多是你在第一轮完全看不到的长尾词,也是做内容选题和 SEO 关键词规划时更有价值的素材。
我在实际项目里常用的扩展参数是:每轮每个词取前 5 个联想词,跑三轮,词量增长曲线大约是从 20 个种子扩展到 300 到 500 个候选词,再经过人工标注和去重,最终保留约 200 个高质量关键词。这个比例不是绝对的,但能说明这套方法的产出效率。
需要注意的是,联想词里经常出现语义重复的词条,比如“旅游攻略”和“旅游攻略大全”。扩展后必须做一次去重清洗。我的做法是:去掉标点符号后按输入词和联想词完全一致的去重,再对明显包含关系(比如一个词是另一个词的子串)的词条做人工筛选,避免得到一堆相同意图的噪音词。
6. 高频踩坑:被风控、数据不完整、接口变动
6.1 最常见的几种拦截特征
接口定位成功不代表就能一直稳定使用。我在实际运行中遇到过不少异常表现,这里把最常见的几种列出来,帮你快速判断是不是被风控了。
第一种是响应正常但 g 数组为空。请求返回 200,JSON 也解析成功,但里面一个候选词都没有。这种一半可能是当前关键词真的没有任何联想词,另一半就是接口开始限流,尤其是你短时间内请求次数过多的时候,接口会“静默降级”,用空结果来糊弄你,而不是直接报错。
第二种是返回 JSONP 但回调函数异常。虽然我们前面说接口支持 JSON 格式,但有些网络环境下服务端会强制返回 JSONP。如果返回的脚本里函数名和你预期的不一致,脚本解析就会失败。
第三种是开始跳验证码或返回安全验证页面。这个通常在短时间内高频请求后出现,说明服务端已经识别到非人类请求行为。遇到这种情况,最正确的做法是立即停止请求,而不是继续换 IP 或伪造请求头去硬碰。
6.2 合规使用与替代方案
关于数据采集,我想多说几句边界问题,因为这直接关系到你是不是能长久地使用这套定位成果。
百度建议搜索接口是一个面向用户交互的公开服务,它设计出来是给浏览器搜索框用的,不是给人批量下载数据的。当你从“定位一个功能”转向“批量调用一个接口”时,就已经站在公共服务的合理使用边界上了。如果你的需求是给个人学习、研究、做一次性的数据分析,适度调用问题不大;如果你的产品是商业化的关键词工具、舆情监控系统,并且预计每天有大量查询量,那么必须考虑数据的合规来源。
我见过的合规替代方案主要有三类。一是直接使用百度提供的开放平台能力,例如百度统计、百度搜索资源平台里的关键词工具,这些是为站长和开发者设计的正规渠道。二是购买第三方关键词数据服务或权威数据服务商的授权数据,省心也省力。三是基于自己的站点数据做关键词收集,虽然量级没有百度那么大,但完全合规且竞争度判断更真实。
我的建议是:先把定位和调试跑通,确认接口逻辑没有理解误差,再做小规模的验证性采集;如果产品阶段进入量产环节,尽早切换到合规数据源,千万不要在核心业务上依赖一个随时可能调整的公共接口。
最后再分享一个我在排查接口问题时积累的小技巧:不要只盯着一个网络环境去定位。百度对不同的浏览器、不同的网络出口、不同的 Cookie 状态给出的行为会有细微差异。我第一次在办公室网络抓包时,接口地址稳定复现;后来回家用自己的网络又抓了一遍,发现多了一个参数。后来我靠的就是把两套网络抓包结果放一起对比,才确认哪个是核心参数、哪个是环境变量。如果你也遇到说不清道不明的接口异常,记住一个原则:先去排查环境差异,再怀疑接口本身,不要一上来就想着换代理、伪造 header,那样只会让问题更难定位。
