下拉框词和相关搜索词批量抓取技巧:接口直连与无头浏览器实战

下拉框词和相关搜索词批量抓取技巧:接口直连与无头浏览器实战

关键词研究wl5net2026-11-15 12:36:34A+A-
约 8117 字 · 预计阅读 17 分钟

掌握下拉框词和相关搜索词批量抓取技巧,最直接省事的办法就是把两类数据拆开处理:下拉框词直接抓包调公开的联想词 JSON 接口,配合“核心词+字母数字”做批量裂变;相关搜索词则用带会话保持的轻量请求或 Playwright 无头浏览器抓取搜索结果页 DOM,并用相对位置 XPath 定位提取。

下拉框词和相关搜索词批量抓取技巧:接口直连与无头浏览器实战

很多做内容策划、电商选品或者工具开发的朋友,动不动就去买按条计费的第三方词库,或者一上来就套用臃肿的分布式爬虫框架,结果钱花了、服务器跑满了,抓回来的还是一堆过时词。其实不管是做行业需求调研、AI 提示词库搭建,还是日常找长尾选题,只要摸清搜索引擎和内容平台的底层请求逻辑,几十行 Python 代码就能在本地稳稳跑通整套采集流。


核心方案速览:别把两类词混为一谈

在动手写代码之前,必须先搞清楚一个基本事实:下拉框词和相关搜索词根本不是同一个接口吐出来的数据,用一套代码硬抓往往事倍功半。

  • 下拉框词(Search Suggestions):用户在输入框每敲一个字符,前端就会实时向后端的联想词接口(Suggest API)发一次异步请求。这类接口通常设计得极轻,返回体是纯 JSON 或 JSONP,反爬阈值相对较低、响应极快,非常适合用 requests 或 httpx 直接调接口做高并发裂变。
  • 相关搜索词(Related Searches):通常位于搜索结果页(SERP)底部或侧边栏的“大家还在搜/相关搜索”模块。它是跟随整个搜索结果页一起渲染或延迟加载的,风控非常严格。直接裸请求大概率会遇到安全验证页、空白模板或者动态混淆的 CSS 类名。

基于这个差异,一线实操中最稳妥的分层方案是:

  1. 纯接口直连流:专攻下拉框词,抓取快、资源消耗极低,适合批量跑几百上千个种子词的横向扩展。
  2. 协议头伪装流:用 curl_cffi 或带完整 Cookie/Headers 的 Session 抓取搜索结果页,用容错率高的 XPath 抽取相关搜索,适合体量中等且目标站点未强制开启 JS 校验的场景。
  3. 无头浏览器兜底流:上 Playwright 或 DrissionPage,直接模拟真实浏览器渲染并接管局部拦截,专治各种动态加密参数和轻量滑块验证。

实操细节深度拆解

一、 下拉框词批量抓取:抓包找接口与字母裂变

以前写这类脚本喜欢去抄网上的老旧接口,结果跑起来不是报 403 就是编码乱码。最靠谱的做法是自己花半分钟打开浏览器抓一次包,拿到当前正在用的真实接口。

1. 抓包定位真实接口(通用方法)

打开 Chrome 隐身窗口,按 F12 切换到 Network(网络) 面板,勾选 Preserve log 并筛选 Fetch/XHR。点击清空当前记录,然后在搜索框里随便输入一个测试词(比如“云服务器”),此时面板里会瞬间多出一条请求。点开查看 Response(响应),如果里面直接包含下拉框里显示的那些词组,这就是你要找的目标接口。

顺便吐槽一下,有些老教程还在教人用正则硬扒早期的 JSONP 老接口,其实现在主流平台基本都有返回标准 JSON 的接口参数(比如把请求参数里的 json=1 保留,或者直接用自带的 sugrec 类接口),能直接 resp.json() 解析就别自己写正则去剥外层回调函数。

2. “字母+疑问词”裂变实战代码

单个词抓下拉框往往只给十来条结果,想批量挖出海量长尾词,核心技巧是组合后缀/前缀裂变(比如在种子词后面依次拼接 a-z、0-9 以及“怎么、哪个好、多少钱”等高频修饰词)。

下面是一段可以直接拿去改参数运行的 Python 脚本,包含了接口请求、安全空值处理、请求头随机化以及防止 Excel 打开乱码的导出逻辑:

import time
import random
import csv
import string
import requests

# 以主流PC端联想接口结构为例,实际使用可根据抓包结果替换URL与参数键名
SUGGEST_URL = "https://www.baidu.com/sugrec"

USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
]

def fetch_dropdown_words(query: str) -> list[str]:
    params = {
        "prod": "pc",
        "wd": query
    }
    headers = {
        "User-Agent": random.choice(USER_AGENTS),
        "Accept": "application/json, text/plain, */*",
    }
    try:
        resp = requests.get(SUGGEST_URL, params=params, headers=headers, timeout=8)
        resp.raise_for_status()
        data = resp.json()
        # 踩坑提示:接口无推荐词时,字段可能不存在或返回 null,用 or [] 兜底最安全
        items = data.get("g") or []
        return [item.get("q").strip() for item in items if item.get("q")]
    except Exception as e:
        print(f"[异常] 查询词 '{query}' 抓取失败: {e}")
        return []

def batch_expand_keywords(seed_words: list[str], output_file: str):
    # 构造裂变后缀:空字符(原词) + 26个小写字母 + 常见需求修饰词
    suffixes = [""] + list(string.ascii_lowercase) + ["怎么", "推荐", "多少钱", "区别"]
    seen_words = set()

    # 踩坑提示:导出给运营或业务同事看,务必用 utf-8-sig,否则 Windows Excel 双击打开必乱码
    with open(output_file, mode="w", newline="", encoding="utf-8-sig") as f:
        writer = csv.writer(f)
        writer.writerow(["种子词", "构造查询词", "下拉框词"])

        for seed in seed_words:
            for sfx in suffixes:
                query = f"{seed} {sfx}".strip() if sfx else seed
                results = fetch_dropdown_words(query)
                for word in results:
                    if word not in seen_words:
                        seen_words.add(word)
                        writer.writerow([seed, query, word])
                # 控制节奏,别把单IP往死里刷,随机抖动停顿比固定 sleep 1 更像真人
                time.sleep(random.uniform(0.4, 1.1))

if __name__ == "__main__":
    seeds = ["服务器运维", "AI知识库"]
    batch_expand_keywords(seeds, "dropdown_keywords.csv")
    print("抓取完成,已去重保存。")

实操避坑点:

  • 如果遇到某些小众平台只吐 JSONP(返回格式类似 window.sug({"s":["词1","词2"]})),别急着换库,用 re.search(r'\((\{.*\})\)', resp.text) 把大括号里的 JSON 抠出来再 json.loads() 即可。
  • 空格很关键:构造裂变词时,“核心词 + 空格 + 字母” 和 “核心词 + 字母” 出来的联想词往往完全不同。比如“云主机 a”和“云主机a”,前者容易出英文缩写或品牌后缀,后者经常触发拼音首字母匹配(如“云主机安全”),业务需要全覆盖时建议两组都跑一遍。

二、 相关搜索词批量抓取:页面定位与防拦截

相关搜索词藏在完整的搜索结果页里。很多新手习惯右键“复制 selector”,拿到一串诸如 .rs_1x8a9 > div:nth-child(2) > a 的类名写死在代码里。这在实战里是大忌——大厂前端构建工具每次发版都会重新生成哈希类名,脚本用不了几天就全线报错空跑。

1. 稳定的页面定位技巧

放弃依赖随机类名,改用语义锚点 + 相对位置 XPath。不管前端样式怎么改,页面上给人看的“相关搜索”或“大家还在搜”这几个汉字标题通常是稳定的。

在浏览器里检查相关搜索区域的 HTML 结构,找到包裹标题和下方所有链接的共同父级容器,用 XPath 的 contains(text(), '相关搜索') 先定位到标题,再通过 following-sibling(兄弟节点)或 ancestor(祖先节点)往下找所有的 <a> 标签文本。

2. Playwright 无头浏览器实战模板

针对直接发 HTTP 请求经常被跳到安全验证页的平台,直接用 Playwright 拉起无头浏览器是最省心、维护成本最低的方案。它可以完整走完 JS 渲染和基础指纹校验,还能顺手把下拉框和相关搜索一锅端。

import time
import random
from playwright.sync_api import sync_playwright

def scrape_related_searches(keywords: list[str]) -> dict[str, list[str]]:
    results = {}
    with sync_playwright() as p:
        # 调试阶段可以把 headless 改成 False,亲眼盯着看有没有弹验证码
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(
            viewport={"width": 1366, "height": 768},
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
        )
        # 屏蔽图片、字体、媒体文件加载,能大幅提速并省带宽
        context.route("**/*", lambda route: route.abort() 
                      if route.request.resource_type in ["image", "media", "font"] 
                      else route.continue_())

        page = context.new_page()

        for kw in keywords:
            try:
                # 演示以通用搜索页URL模式为例,实际替换为目标站点搜索URL
                target_url = f"https://www.baidu.com/s?wd={kw}"
                page.goto(target_url, wait_until="domcontentloaded", timeout=15000)

                # 模拟真人往下滚到页面底部,触发可能存在的懒加载相关搜索模块
                page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
                page.wait_for_timeout(1500)

                # 利用语义文字向上找父级容器,再取内部所有链接文本,抗改版能力强
                # 实际使用前需在目标页 F12 Console 里用 $x(...) 验证一次路径
                related_locators = page.locator(
                    "//div[contains(., '相关搜索') and not(.//div[contains(., '相关搜索')])]/following-sibling::*//a | "
                    "//div[contains(@id, 'rs')]//a"
                )

                raw_texts = related_locators.all_inner_texts()
                # 过滤空字符以及过长的误匹配标题
                clean_words = list(dict.fromkeys(
                    [t.strip() for t in raw_texts if t.strip() and len(t.strip()) < 35]
                ))
                results[kw] = clean_words
                print(f"[{kw}] 提取到 {len(clean_words)} 个相关词")

            except Exception as e:
                print(f"[{kw}] 页面抓取异常: {e}")
                results[kw] = []

            time.sleep(random.uniform(2.0, 4.0))

        browser.close()
    return results

if __name__ == "__main__":
    data = scrape_related_searches(["Docker容器部署", "向量数据库选型"])
    for k, v in data.items():
        print(k, "->", v)

实操避坑点:

  • 资源拦截省大钱:用 Playwright 抓文本数据时,一定要加上面代码里的 context.route 拦截图片和视频流。不仅能把单页加载速度提上去,跑在代理 IP 上时还能省下大把流量费。
  • 别用 networkidle 死等:很多搜索页后台常驻埋点统计请求,永远不会真正“网络空闲”,用 wait_until="domcontentloaded" 配合局部元素等待或短暂 wait_for_timeout 更稳定,不容易超时报错。

三、 批量落地的三大隐藏深坑与清洗技巧

脚本跑通只是第一步,真正开始做几千上万个词的批量抓取时,下面三个坑几乎每个人都会踩一遍。

  1. 递归拓展导致词库彻底失控(词义漂移) 很多人喜欢写个 while 队列:把抓出来的相关搜索词再塞回队列继续抓下一层。跑两层以后你会发现,本来查的是“工业服务器”,滚着滚着就滚到了“游戏主机”、“家用电脑装机”,最后抓回几万个完全不搭边的垃圾词。 解决办法:严格限制递归深度(通常到第 2 层就打住),并且加上硬性词根校验——新抓回来的词必须包含至少一个业务核心词根(例如必须包含“服务器”或“云”),不满足直接丢弃,不准入库也不准继续往下裂变。

  2. 单机高频触发风控与 TLS 指纹拦截 哪怕你用 requests 每次都换不同的 User-Agent,大厂网关还是能一眼认出你是 Python 脚本,因为底层握手时的 JA3/TLS 指纹跟真实浏览器完全不一样。一旦发现请求异常,要么直接返回空页面,要么要求强制登录。 解决办法:如果坚持走纯接口协议流而不开无头浏览器,把 requests 换成 curl_cffi(支持 impersonate="chrome120" 伪装真实浏览器 TLS 指纹);并发量较大时,切记不要单 IP 硬扛,接入按量计费的短效动态代理池,并把单进程并发数控制住,宁可多跑十分钟,也别把代理通道全打黑。

  3. 数据去重与脏数据清洗 抓完后直接丢进表格里往往没法用,因为不同入口抓到的词会有大量标点差异、空格差异甚至繁体/大小写重复(比如 "Python 爬虫" 和 "python爬虫")。建议入库前统一用脚本做一次标准化清洗:

import re

def normalize_keyword(text: str) -> str:
    # 统一转小写、去除首尾空白及特殊不可见控制字符
    text = text.lower().strip()
    # 将连续多个空格压缩为一个,或者视业务需求直接去除中英文间的多余空格
    text = re.sub(r'\s+', ' ', text)
    # 过滤掉纯数字、单字或包含乱码符号的无效词条
    if len(text) < 2 or text.isdigit():
        return ""
    return text

高频关联问题拓展

1. 为什么浏览器里明明看到了相关搜索,脚本抓回来的 HTML 里却找不到?

这种情况十有八九踩中了三个原因之一:

  • PC 端与移动端结构差异:检查你的 User-Agent 是不是错配成了手机端,或者响应体里其实走了移动端适配版面,两套页面的 DOM 结构完全不一样。
  • 异步懒加载:部分平台为了首屏秒开,底部的相关搜索是等页面滚动到底部后才发 XHR 请求加载的。直接 requests.get() 拿到的只是首屏骨架,根本没有底部数据;这也是前面推荐用 Playwright 模拟 window.scrollTo 滚动一下的原因。
  • 命中风控拦截页:把脚本抓回来的 resp.text 打印前 500 个字符看看,很多时候返回的状态码虽然是 200 OK,但页面内容其实是一句“请输入验证码以继续访问”。

2. 这套抓取技巧能直接套用到小红书、抖音或者电商平台吗?

核心逻辑完全相通,但落地方式要分场景微调:

  • Web 网页端(比如主流电商网页版、部分内容社区网页版)同样适用 F12 抓包找 Suggest 接口的打法。但要注意,社区与电商平台的搜索接口通常强绑定 登录态 Cookie 和 动态签名参数(如 X-Sign、X-Bogus)。
  • 如果只是中小规模的日常采集,没必要去硬啃前端混淆 JS 逆向签名,直接用 Playwright 登录后保存 storage_state(本地状态文件),后续脚本直接加载这个已登录的浏览器状态去打开搜索框模拟键盘逐字敲入(page.keyboard.type()),通过监听网络响应拦截(page.on("response", ...))把平台自己算好签名后返回的下拉框 JSON 截下来。这种“监听拦截流”比自己算加密参数稳妥得多,也几乎不会因为前端升级签名算法而瞬间失效。

3. 抓回来的几万条词,怎么快速筛出真正有价值的需求词?

别拿人工肉眼一行行翻,最省力的做法是先做词频聚合与意图分类: 用 Python 配合分词工具(如 jieba)或者简单的正则规则,把包含“怎么、教程、报错、修复”的归为教程/排错类;把包含“推荐、排行、对比、哪个好、替代”的归为评测/决策类;把包含“价格、费用、免费、下载”的归为转化/工具类。最后还可以把去重后的词表批处理喂给大模型 API,设定好业务角色让它按意图打标并剔除无效词,十几分钟就能整理出一份能直接用来排期或做产品规划的结构化词库。


结尾总结

跑通下拉框词和相关搜索词批量抓取技巧,核心不在于堆砌多复杂的爬虫架构,而在于选对工具做正确的事:下拉框词吃透抓包直连与字母前缀裂变,相关搜索词用无头浏览器配合稳定的语义定位做兜底,再把异常空值、Excel 编码乱码、词义漂移这些常见坑提前堵死。

最后提醒一句,技术上抓词很容易跑出几十万条数据,但词量多不等于价值高。实际工作中务必控制好请求频率、别去恶意冲击目标站点服务,把更多精力放在种子词设计、业务黑名单过滤和需求意图分类上,抓回来真正能落地变现或解决用户痛点的词,才算把这套工具用到实处。

点击这里复制本文地址 以上内容由未来技术网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

网站地图 robots.txt 网站死链 关于本站 工信部备案号浙ICP备2026012592号-2 浙公网安备33102402000669号