1. 问题背景
最近复盘一组品牌GEO监测数据时,发现了一个比较反直觉的现象:
同一个关键词,目标品牌在DeepSeek中进入推荐前三位,到了豆包只在回答末尾出现,在通义千问中甚至没有被提及。
这类差异说明,AI搜索并不存在一个统一、固定的品牌排行榜。
传统SEO监测通常围绕搜索结果页展开,可以直接记录关键词排名、落地页、展现量和点击量。AI回答则会受到平台检索源、问题表达、上下文、生成时间和模型随机性的共同影响。
因此,GEO监测不能只记录"品牌排第几",还要保存以下字段:
-
检测平台
-
原始问题
-
关键词类型
-
回答生成时间
-
目标品牌是否出现
-
目标品牌推荐位置
-
竞品名称及位置
-
回答原文
-
引用来源
-
品牌描述倾向
-
是否存在信息误述
本次测试选取100个关键词,分别在DeepSeek、豆包、通义千问、腾讯元宝和文心一言中执行,共得到500条回答记录。
整体实测结果如下:
-
目标品牌出现率:32%
-
平均推荐位置:5.7
-
进入前三位的比例:13%
-
竞品A出现率:61%
-
竞品A进入前三位的比例:42%
-
品牌词出现率:86%
-
采购决策词出现率:18%
数据表明,模型并非完全不认识目标品牌。
真正的问题是:当用户不输入品牌名称,只提出产品需求或采购场景时,AI更倾向于推荐竞品。
2. 工具与监测原理
一套可复现的AI搜索品牌监测流程,可以拆成三个阶段:
-
建立标准化问题集;
-
获取不同AI平台的完整回答;
-
统一提取品牌、位置和竞品指标。
本次测试没有尝试调用各平台未经确认的内部接口,也没有使用网页脚本高频抓取产品页面。
原因在于,不同AI产品的页面结构、登录状态和风控策略差异较大。直接使用浏览器自动化,容易受到验证码、页面更新和账号环境影响,难以长期复现。
更稳定的流程是:
-
使用固定词库执行多平台检测;
-
将完整结果导出为CSV;
-
在本地完成品牌匹配和指标统计;
-
保存原始回答,方便后续复核。
本次原始回答通过搜搜果统一导出,后续清洗、排名识别和指标计算均在本地脚本中完成。这里仅把它作为数据采集来源,不参与最终统计规则。
建议的数据结构如下:
task_id,platform,query,query_type,target_brand,response_text,detected_at
1,DeepSeek,适合制造企业的CRM有哪些,采购决策词,示例云,品牌A、品牌B和示例云均可用于客户管理,2026-07-01 10:00:00
2,豆包,适合制造企业的CRM有哪些,采购决策词,示例云,可重点比较品牌A和品牌C,2026-07-01 10:01:00
3,通义千问,适合制造企业的CRM有哪些,采购决策词,示例云,示例云适合中型制造企业,品牌A覆盖范围更广,2026-07-01 10:02:00
关键词建议至少划分为五类:
品牌词
品类词
场景词
对比词
采购决策词
分类统计很重要。
例如,一个品牌的整体出现率达到50%,看起来不算低。但拆分后可能发现,品牌词出现率为90%,采购决策词只有10%。
这说明AI能够识别品牌,却不会在用户选型时主动推荐它。
推荐位置的统一计算规则
AI回答通常不是标准化列表,因此需要先定义排名提取口径。
本次使用以下规则:
-
第一个被明确推荐的品牌记为第1位;
-
后续品牌按照文本出现顺序编号;
-
同一句并列出现时,按照文本顺序处理;
-
仅作为背景信息出现,不计入主动推荐;
-
未出现记为0;
-
已出现但无法识别顺序,记为99。
规则是否完美并不是最重要的,关键是整个检测周期必须保持一致。
如果第一次按照品牌首次出现位置统计,第二次又按照项目符号列表统计,两轮数据就无法直接比较。
3. 操作步骤
3.1 建立关键词库
创建keywords.csv:
query,query_type
示例云是什么,品牌词
客户管理系统有哪些,品类词
制造企业如何管理销售线索,场景词
示例云和品牌A有什么区别,对比词
适合中型制造企业的CRM怎么选,采购决策词
100个关键词可以按照以下比例分配:
品牌词:20个
品类词:20个
场景词:25个
对比词:15个
采购决策词:20个
不要把关键词全部设置成包含目标品牌的长尾问题。
例如:
适合华东地区中型制造企业使用的示例云CRM怎么样
这类问题已经把品牌名称写进提示词,模型命中目标品牌的概率自然更高。
更适合监测真实品牌可见度的问题如下:
中型制造企业CRM怎么选
制造业客户管理软件有哪些
销售线索较多的企业适合什么CRM
客户管理系统采购需要比较哪些指标
3.2 执行多平台检测
使用同一批关键词,依次测试:
DeepSeek
豆包
通义千问
腾讯元宝
文心一言
为了减少无关变量,需要固定以下条件:
-
每次使用新会话;
-
不追加历史上下文;
-
不进行连续追问;
-
使用同一批关键词;
-
尽量在相近时间完成;
-
保留完整回答内容;
-
记录生成时间;
-
每个问题至少重复3次。
原始结果页至少应展示:
任务名称
关键词总数
检测平台
问题原文
目标品牌是否出现
目标品牌推荐位置
主要竞品
完整回答
生成时间
不要只保存汇总截图。
当某个指标突然上涨或下降时,必须回到原始回答,检查是模型回答结构变化、品牌别名遗漏,还是统计规则出现问题。
3.3 配置品牌别名
同一品牌可能以中文名称、英文名称、产品简称或系列名称出现。
创建brands.yaml:
target:
name: 示例云
aliases:
- 示例云
- ExampleCloud
- 示例云CRM
- 示例客户管理系统
competitors:
- name: 品牌A
aliases:
- 品牌A
- BrandA
- A公司CRM
- name: 品牌B
aliases:
- 品牌B
- BrandB
- name: 品牌C
aliases:
- 品牌C
- BrandC
如果没有别名配置,回答中出现ExampleCloud时,程序可能将其识别为另一个品牌,最终低估目标品牌出现率。
3.4 编写统计脚本
安装依赖:
pip install pandas pyyaml
创建analyze_geo.py:
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
import pandas as pd
import yaml
DATA_FILE = Path("geo_results.csv")
CONFIG_FILE = Path("brands.yaml")
OUTPUT_FILE = Path("geo_summary.csv")
def load_config(path: Path) -> dict[str, Any]:
if not path.exists():
raise FileNotFoundError(f"配置文件不存在: {path}")
with path.open("r", encoding="utf-8") as file:
config = yaml.safe_load(file)
if "target" not in config:
raise ValueError("配置文件缺少target节点")
return config
def normalize_text(value: object) -> str:
if pd.isna(value):
return ""
text = str(value)
text = re.sub(r"\s+", " ", text)
return text.strip().lower()
def find_first_position(
text: str,
aliases: list[str],
) -> int | None:
positions: list[int] = []
for alias in aliases:
normalized_alias = normalize_text(alias)
position = text.find(normalized_alias)
if position >= 0:
positions.append(position)
return min(positions) if positions else None
def extract_brand_order(
text: str,
brands: list[dict[str, Any]],
) -> list[str]:
matched: list[tuple[str, int]] = []
for brand in brands:
position = find_first_position(
text,
brand["aliases"],
)
if position is not None:
matched.append((brand["name"], position))
matched.sort(key=lambda item: item[1])
return [name for name, _ in matched]
def main() -> None:
if not DATA_FILE.exists():
raise FileNotFoundError(f"数据文件不存在: {DATA_FILE}")
config = load_config(CONFIG_FILE)
dataframe = pd.read_csv(DATA_FILE)
required_columns = {
"platform",
"query",
"query_type",
"response_text",
}
missing_columns = required_columns - set(dataframe.columns)
if missing_columns:
raise ValueError(
f"CSV缺少必要字段: {sorted(missing_columns)}"
)
target_name = config["target"]["name"]
all_brands = [
config["target"],
*config.get("competitors", []),
]
dataframe["normalized_text"] = (
dataframe["response_text"].apply(normalize_text)
)
dataframe["brand_order"] = (
dataframe["normalized_text"].apply(
lambda text: extract_brand_order(
text,
all_brands,
)
)
)
def get_target_rank(order: list[str]) -> int:
if target_name not in order:
return 0
return order.index(target_name) + 1
dataframe["target_rank"] = (
dataframe["brand_order"].apply(get_target_rank)
)
dataframe["target_mentioned"] = (
dataframe["target_rank"] > 0
)
dataframe["top3"] = (
dataframe["target_rank"].between(1, 3)
)
platform_summary = (
dataframe.groupby("platform", as_index=False)
.agg(
sample_count=("query", "count"),
mention_rate=("target_mentioned", "mean"),
top3_rate=("top3", "mean"),
)
)
valid_rank = dataframe[
dataframe["target_rank"] > 0
]
rank_summary = (
valid_rank.groupby("platform", as_index=False)
.agg(
avg_rank=("target_rank", "mean")
)
)
platform_summary = platform_summary.merge(
rank_summary,
on="platform",
how="left",
)
platform_summary["mention_rate"] = (
platform_summary["mention_rate"] * 100
).round(2)
platform_summary["top3_rate"] = (
platform_summary["top3_rate"] * 100
).round(2)
platform_summary["avg_rank"] = (
platform_summary["avg_rank"].round(2)
)
platform_summary.to_csv(
OUTPUT_FILE,
index=False,
encoding="utf-8-sig",
)
print(platform_summary.to_string(index=False))
if __name__ == "__main__":
main()
运行:
python analyze_geo.py
示例输出:
platform sample_count mention_rate top3_rate avg_rank
DeepSeek 100 41.00 18.00 4.63
豆包 100 39.00 16.00 4.92
通义千问 100 21.00 8.00 6.71
腾讯元宝 100 34.00 14.00 5.38
文心一言 100 25.00 9.00 6.20
4. 结果解读
本次500条回答的汇总结果如下:
| 指标 | 目标品牌 | 竞品A |
|---|---|---|
| 回答出现率 | 32% | 61% |
| 前三位占比 | 13% | 42% |
| 平均推荐位置 | 5.7 | 2.8 |
| 品牌词出现率 | 86% | 62% |
| 采购决策词出现率 | 18% | 71% |
第一,品牌认知不等于品牌推荐。
目标品牌在品牌词中的出现率达到86%,说明模型能够识别品牌名称和基础资料。
但采购决策词出现率只有18%,意味着用户只描述需求、不输入品牌名时,AI很少主动推荐该品牌。
第二,不同平台之间存在明显差异。
目标品牌在DeepSeek中的出现率为41%,在通义千问中只有21%,相差20个百分点。
如果报告只展示表现最好的平台,就会明显高估品牌整体可见度。
第三,平均排名不能脱离竞品数据单独判断。
品牌平均位置从第6位提升到第5位,看起来有所改善。但如果竞品同期从第4位提升到第2位,双方实际差距反而扩大。
因此,GEO监测至少需要组合观察:
品牌出现率
前三位占比
平均推荐位置
竞品出现率
关键词类型覆盖率
平台差异
周期变化趋势
第四,品牌介绍类内容不一定能改善采购词表现。
如果品牌词数据正常,场景词和采购决策词持续偏低,继续增加公司介绍、发展历史和荣誉内容,通常难以直接改变推荐结果。
更需要补充的是:
-
产品适用客户;
-
具体应用场景;
-
选型标准;
-
产品能力边界;
-
客户案例;
-
参数对比;
-
实施和售后流程;
-
第三方验证信息。
5. 避坑清单
5.1 不要用一次回答代表长期排名
同一个问题连续测试多次,品牌顺序可能发生变化。
建议每个关键词至少执行3次,分别统计出现次数、中位排名和前三位占比。
5.2 不要只测试品牌词
品牌词已经包含目标品牌名称,结果通常偏高。
词库必须包含不带品牌名称的品类词、场景词和采购决策词。
5.3 不要频繁修改统计规则
首次测试按品牌出现顺序计算,复测时又按项目列表顺序计算,会造成趋势数据失真。
排名规则确定后,应写入项目说明并固定使用。
5.4 不要遗漏品牌别名
品牌中文名、英文名、简称和产品系列名称,都需要加入别名配置。
否则程序容易把同一品牌拆成多个对象。
5.5 不要把提及等同于推荐
下面这句话虽然出现了目标品牌,但并不属于主动推荐:
与示例云相比,品牌A更适合大型制造企业。
目标品牌在这里是对比对象。
更完整的分类应该包括:
主动推荐
中性提及
负面对比
不建议选择
信息误述
5.6 不要把监测结果当成优化结果
监测解决的是"发生了什么",例如品牌在哪个平台缺席、被哪些竞品反超、平均推荐位置是多少。
内容建设、信源扩展和公开信息治理,才属于后续执行环节。
5.7 不要使用过小的样本
只测试5个或10个关键词,结果很容易受到单次回答波动影响。
企业级监测建议满足以下最低条件:
关键词数量:50个以上
关键词类型:4类以上
平台数量:3个平台以上
单个问题重复次数:3次以上
观察周期:不少于7天
用于月度GEO验收时,可以扩展到100个固定关键词、5个平台,并保留每次回答原文。
最终需要判断的不是品牌是否偶尔出现,而是在统一词库、统一平台和统一统计规则下,品牌出现率、推荐位置与竞品差距是否持续改善。