一、这个项目在做什么
大模型搜索正在改变流量入口。Perplexity、ChatGPT Search、Google AI Overview 让用户不再点链接,而是直接看 AI 生成的答案。传统 SEO 优化"排名和点击",GEO(Generative Engine Optimization)优化的是"内容被 AI 引用的概率"。
问题是,真实平台是黑盒。你不知道它用什么 embedding 模型,不知道它怎么分块,不知道它的重排和引用归因策略,无法复现、无法控制变量。你能做的只有盲目发布内容,然后祈祷被引用。
所以我在网吧电脑上搭了一套 GEO 仿真环境。目标很明确:复现 GEO 核心链路中「可抓 → 可解析 → 可引用 → 可复现」四个确定性环节,作为理解 RAG 检索原理、验证内容策略的假设验证机。
这套环境不是效果预测器,它不能告诉你"这样改能让 Perplexity 引用率提升 30%"。它是一台假设验证机,能快速排除无效方向、验证变量敏感性,再把通过验证的策略带到真实环境做小规模灰度测试。
机器是网吧公用机,H 盘重启会还原,所有成果靠百度网盘持久化。配置意外地不错:AMD Ryzen 7 9800X3D、48GB DDR5、NVIDIA RTX 5070(11.9GB 显存,CUDA 可用)。没有管理员权限,不能装 Docker、WSL,所以全部用便携工具,解压即用,通过 localhost 通信,天然隔离。
技术选型是这样的。Nginx 提供假品牌站,模拟真实品牌官网;Python 脚本做爬虫,模拟 Googlebot 的抓取行为;Qdrant 做向量库,模拟 Perplexity 的索引层;Ollama 跑 qwen2.5:3b 和 nomic-embed-text,分别模拟 LLM 生成和 embedding;自写 RAG 脚本模拟 Perplexity 的检索层;metrics 脚本模拟 Similarweb AI Brand Visibility 的可见度测量。
整个环境不连外网,不针对真实平台,不做黑帽操作,只做安全研究和假设验证。
二、踩过的坑,比想象中多
第一个坑:Ollama 的第三方 start.bat 是坏的
下载的 Ollama 便携包里有 start.bat,但它要下 gemma4 模型、启动 Caddy 和 webui,而且下载地址指向不存在的版本号 v0.20.7。直接跑会失败。
解决方案是放弃这个 bat,改用官方 zip 包,自写 start-ollama.bat。bat 内容很简单:设置 OLLAMA_MODELS 指向 H 盘模型目录,设置 OLLAMA_HOST 为 127.0.0.1:11434,然后启动 ollama.exe serve。
这里有个重要发现:Ollama 的实际可执行文件在 H:\GEO\tools\ollama\servers\ollama.exe,不在根目录。26MB,版本 0.34.2。
第二个坑:Qdrant 的 --storage 参数不存在
启动 Qdrant 时想指定数据目录,用了 --storage 参数,报错:
text
error: unexpected argument '--storage' found
改用 --config-path 指定配置文件。但配置文件写错了,写成顶层 storage_path:,不生效,Qdrant 回退到默认 ./storage。
正确写法是嵌套结构:
yaml
storage:
storage_path: "H:\\GEO\\project\\data\\qdrant_storage"
启动命令是:
cmd
start "" /B "H:\GEO\tools\qdrant\qdrant.exe" --config-path "H:\GEO\project\qdrant_config.yaml"
日志里看到 storage path H:\GEO\project\data\qdrant_storage 和 Loading raft state from ...raft_state.json,才确认配置生效。
第三个坑:qdrant-client 在 WinPython 下 ReadTimeout
这是最麻烦的一个。curl 访问 Qdrant 正常:
cmd
curl http://127.0.0.1:6333/healthz
→ healthz check passed
但 Python 的 qdrant-client 1.19.1 报 httpx.ReadTimeout: timed out。
试了七种修复都无效:localhost 改 127.0.0.1、timeout 加到 60、url 换 host/port、prefer_grpc=False、check_compatibility=False、NO_PROXY 环境变量、绕过 collection_exists 改用 get_collections。
于是做二分测试。先测 httpx 直连 Qdrant,200 正常。再测 requests 直连,也是 200 正常。再测 requests 调 Ollama embedding,返回 768 维向量。说明网络层完全没问题。
然后 monkeypatch httpx 的 send 方法,打印 qdrant-client 实际发的请求:
python
import httpx
orig = httpx.Client.send
httpx.Client.send = lambda self, req, **kw: (
print('SEND:', req.method, req.url),
print('HEADERS:', dict(req.headers)),
orig(self, req, **kw)
)[1]
输出:
text
SEND: GET http://127.0.0.1:6333/collections
SEND: GET http://127.0.0.1:6333
AttributeError: 'NoneType' object has no attribute 'status_code'
qdrant-client 先请求 /collections,再请求根路径 / 做版本兼容性检查,然后拿到一个 None 响应,取 .status_code 时崩溃。
但 curl、httpx、requests 单独访问根路径都返回 200 和正常 JSON。brotli 和 zstandard 库也都装着。所以问题不在网络、不在代理、不在解码库,而是 qdrant-client 1.19.1 构造 httpx client 的方式与当前环境不兼容,是 client 自身的 bug。
结论:放弃 qdrant-client,自写基于 requests 的 QdrantREST 封装。
python
import requests
class QdrantREST:
def __init__(self, url="http://127.0.0.1:6333", timeout=30):
self.url = url.rstrip("/")
self.timeout = timeout
def _req(self, method, path, **kwargs):
r = requests.request(method, f"{self.url}{path}",
timeout=self.timeout, **kwargs)
r.raise_for_status()
return r.json() if r.content else None
def create_collection(self, name, size, distance="Cosine"):
return self._req("PUT", f"/collections/{name}",
json={"vectors": {"size": size, "distance": distance}})
def upsert(self, name, points):
return self._req("PUT", f"/collections/{name}/points",
json={"points": points})
def search(self, name, vector, limit=3):
return self._req("POST", f"/collections/{name}/points/search",
json={"vector": vector, "limit": limit,
"with_payload": True})["result"]
反直觉的结论:绕过官方 SDK 直接用 REST API,代码更短、更透明、更好 Docker 化。在仿真场景里,"少依赖"比"用官方 SDK"更重要。
第四个坑:Nginx 缺 charset=utf-8 导致中文乱码
爬取 Brand A 站时,爬下来的中文全变成 ååçä>>ç>>。这是典型的 UTF-8 字节被当 Latin-1 解码。
排查发现 Nginx 返回的响应头是:
text
Content-Type: text/html
没有 charset=utf-8。requests 猜测编码猜成 ISO-8859-1,一路坏到 Qdrant。
修复是两层。Nginx 配置加一行:
nginx
server {
listen 8080;
charset utf-8;
...
}
爬虫里强制指定编码:
python
r = requests.get(url, timeout=30)
r.encoding = "utf-8"
教训是别让 requests 猜编码,中文站一律显式指定 UTF-8。
第五个坑:Ollama 后台进程会被网吧机器清理
ollama.exe serve 是用 start "" /B 启动的后台进程。关掉 CMD 窗口后,进程可能被杀。加上首次调用模型有冷启动(加载到 GPU 约 17 秒),如果 build_index.py 第一次 embed 就撞上冷启动,很容易超时。
解决方案是两个。一是每次开工先手动 warmup:
python
requests.post("http://127.0.0.1:11434/api/embeddings",
json={"model": "nomic-embed-text", "prompt": "warmup"},
timeout=300)
二是写一个 start-all.bat,一次拉起三个服务:
bat
@echo off
chcp 65001 >nul
echo === Ollama ===
taskkill /IM ollama.exe /F >nul 2>&1
set OLLAMA_MODELS=H:\GEO\tools\ollama\models
start "" /B "H:\GEO\tools\ollama\servers\ollama.exe" serve
echo === Qdrant ===
taskkill /IM qdrant.exe /F >nul 2>&1
start "" /B "H:\GEO\tools\qdrant\qdrant.exe" --config-path "H:\GEO\project\qdrant_config.yaml"
echo === Nginx ===
taskkill /IM nginx.exe /F >nul 2>&1
cd /d H:\GEO\tools\nginx
start "" /B nginx.exe
timeout /t 5 /nobreak >nul
echo === Health Check ===
curl -s http://127.0.0.1:6333/healthz
echo.
curl -s http://127.0.0.1:11434/api/tags
echo.
curl -s -I http://localhost:8080 | findstr "HTTP"
三、核心管线:四件套
整个索引构建流程就四个脚本,对应真实 AI 搜索链路里你能控制的那一段。
crawl.py 抓取假品牌站,用 requests 拿 HTML,BeautifulSoup 解析,按 section 提取标题和正文。它对应真实环境里的 Googlebot、GPTBot、PerplexityBot 抓取你的站。
chunk.py 做分块。中文 separators 顺序很重要,应该是 ["\n\n", "\n", "。", "?", "!", ";", ",", " ", ""],从粗到细。顺序颠倒会导致语义断裂。它对应真实平台把网页切成段落、做 embedding 前的准备。
build_index.py 调 Ollama embeddings API 算向量,存进 Qdrant。它对应真实平台用自研 embedding 模型把 chunk 向量化、存进向量库。
eval.py 用固定查询集评测。它对应真实平台收到 query 后的 ANN 检索。
中文分块的具体实现是这样的:
python
SEPARATORS = ["\n\n", "\n", "。", "?", "!", ";", ",", " ", ""]
def split_by_separators(text, chunk_size, separators):
if len(text) <= chunk_size:
return [text]
for sep in separators:
if sep and sep in text:
parts = text.split(sep)
chunks = []
buf = ""
for p in parts:
piece = p + sep if sep else p
if len(buf) + len(piece) <= chunk_size:
buf += piece
else:
if buf:
chunks.append(buf)
if len(piece) > chunk_size:
chunks.extend(split_by_separators(piece, chunk_size, separators[1:]))
buf = ""
else:
buf = piece
if buf:
chunks.append(buf)
return [c for c in chunks if c.strip()]
return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
这个实现有个特点:优先按粗粒度 separator 切,如果单块还超,递归用更细的 separator 切。这样既保证每块不超过 chunk_size,又尽量不切断语义。
四、第一轮基线实验:EXP-002
假品牌站是 Brand A,一个广州粤菜餐饮品牌。站上有六个 section:品牌介绍、招牌菜品、门店信息、常见问题、关于粤菜、联系方式。
建站的时候特意设计了不同结构:段落、列表、FAQ 的 dl/dt/dd、JSON-LD Schema。目的是后续做分块实验时能测出结构差异的影响。
参数是 chunk_size=500,overlap=50,无前缀,embedding 用 nomic-embed-text(768 维)。
结果每个 section 正好一块,共 6 块。因为每个 section 的总字数都小于 500,没被切。
查询集是 8 条,覆盖品牌、菜品、门店、FAQ、粤菜知识、联系方式。
跑出来的结果不理想:
查询"Brand A 有什么招牌菜",期望命中 dishes,实际 top-1 是 intro,分数 0.7301。
查询"Brand A 在广州有几家店",期望 intro 或 stores,实际 top-1 是 intro,分数 0.8185,算勉强正确。
查询"啫啫煲是什么",期望 dishes,实际 top-1 是 faq,分数 0.6583。
查询"Brand A 支持外卖吗",期望 faq,实际 top-1 是 intro,分数 0.7089。
查询"天河旗舰店在哪里",期望 stores,实际 top-1 是 faq,分数 0.7207。
查询"粤菜有什么特点",期望 about-cuisine,实际 top-1 是 faq,分数 0.6816。
查询"Brand A 有包间吗",期望 faq 或 stores,实际 top-1 是 intro,分数 0.7249。
查询"怎么联系 Brand A",期望 contact,实际 top-1 是 intro,分数 0.7141。
top-1 命中率只有 1/8 = 12.5%。
这个结果暴露了三个叠加问题。
第一个问题,intro 是万金油。它同时提到品牌、粤菜、招牌菜、门店数,任何"Brand A + 粤菜"相关的查询都和它中等相似。而且它是六块里最长的(205 字),向量被"平均"得厉害,反而和很多查询都沾边。
第二个问题,nomic-embed-text 的中文细粒度区分能力弱。这是 137M 的小模型,训练语料以英文为主。对"啫啫煲 vs 包间 vs 外卖"这种中文细粒度语义,区分度差。分数分布也能看出来:top-1 和 top-3 分差普遍只有 0.1 左右,说明模型对"哪个更相关"没有强判断。
第三个问题,每 section 一块,颗粒度太粗。faq 一块 271 字塞了 5 个问答,查询"支持外卖吗"时只有 1/5 相关,被其他 4/5 稀释了。
这三个问题在真实 RAG 系统里同样存在,只是被更大的模型和更精细的 rerank 掩盖了。仿真环境的价值就是让这些问题显形。
五、指标体系:不止 top-1
跑完 EXP-002 才意识到,只看 top-1 命中率太粗。完整的指标至少六类。
准确性指标是最基础的。top-1 命中率是正确答案排第一的比例。top-3 命中率是正确答案在前三的比例。MRR 是正确答案排名的倒数的平均值,比 top-1 更细,不只关心"是否第一",还关心"排多前"。真实平台通常取 top-5 到 top-10 做重排,所以 top-3 命中率比 top-1 更能反映真实情况------只要进了 top-3,就有机会被重排上来。
鲁棒性指标回答"换环境还管用吗"。跨 chunk_size 鲁棒性是同一策略在 chunk_size=100/200/300/500 下 top-1 命中率的标准差。跨模型鲁棒性是同一策略在 nomic/bge-m3/bge-zh 下的标准差。跨查询鲁棒性是同一策略在具体查询和抽象查询上的表现差异。标准差越小越鲁棒。
稳定性指标回答"结果可复现吗"。同参数多次跑的方差,如果每次都是 60% 说明可复现,如果 60%/45%/70% 跳说明有随机性。排序一致性,跑两次 top-3 的顺序是否一致。
区分度指标回答"模型有判断力吗"。top-1 与 top-3 的分差,分差大(>0.15)说明模型有强判断,分差小(<0.05)说明模型判断模糊。正负样本分差,正确答案的分数减去错误答案的分数。
效率指标回答"能规模化吗"。建索引耗时、查询耗时、显存占用。真实平台要求毫秒级,仿真里可以放宽,但要记录基线。
覆盖率指标回答"该召回的召回了吗"。召回率是正确答案能出现在 top-K 的比例。查询覆盖率是你的内容能覆盖多少种查询。
鲁棒性只是第二类。真实平台做两阶段检索(召回 50 → 重排 5),所以 top-3 命中率比 top-1 更能反映真实情况。
六、GEO 指标和 SEO 指标的区别
SEO 和 GEO 测的是两套不同东西。
SEO 的核心指标是排名、流量、权威度、技术、内容。关键词排名是某关键词下你的网页排第几。自然流量是从搜索引擎来的访问量。展现量是你的网页在搜索结果里被展示的次数。DA/DR 是域名权威度。抓取频次是搜索引擎爬你站的频率。索引量是被收录的页面数。
GEO 的核心指标是引用率、提及率、引用位置、引用上下文、跨引擎一致性。引用率是 AI 答案引用你内容的比例。提及率是 AI 答案提到你品牌名的比例。引用位置是你被引为 1 还是 5。引用上下文是 AI 引用你哪句话。跨引擎一致性是同一查询在 Perplexity、ChatGPT、Grok 的结果差异。
对应关系是这样的。SEO 的关键词排名不直接对应 GEO,因为 AI 不做关键词排名。SEO 的展现量对应 GEO 的 AI 答案里的提及率。SEO 的点击率对应 GEO 的 AI 答案里你被点开的比例。SEO 的抓取频次对应 GEO 的 AI 爬虫抓你站的频率。SEO 的索引量对应 GEO 的内容被 AI 收录的比例。
本质区别是,SEO 是位置导向,用户搜"广州粤菜",搜索结果页,你排第 3,用户点你,核心指标是排名。GEO 是内容导向,用户问"广州有什么好吃的粤菜",AI 生成答案,答案里引用 1 Brand A,核心指标是引用率。
SEO 优化"位置",GEO 优化"内容被引用的概率"。你项目里的 top-1、top-3 命中率,对应的是 GEO 的"召回率"和"引用位置"。
七、关键认知:向量只是第一层
很多人以为 GEO 优化就是让向量最近。方向对了一半。
内容被引用的概率等于向量相似度乘以重排得分乘以权威度加权乘以时效性加权乘以事实可信度乘以引用归因概率乘以商业运营因素。
向量相似度是召回层,你能本地仿真。重排得分是精排层,本地难仿真。权威度加权看域名和品牌,本地无法仿真。时效性加权看发布时间,本地无法仿真。事实可信度看内容质量,本地部分仿真。引用归因概率看 LLM 生成策略,本地无法仿真。商业运营因素看广告和合作,本地无法仿真。
本地四件套只仿真了第一项。但这是最重要的------它是"入场券"。向量层过不了,后面六层都没机会。
仿真价值在于快速排除"向量层就挂了"的内容策略。关键词堆砌会让向量被平均,召回率低,排除。段落太长会让主题混杂,召回率低,排除。没有结构会让语义不完整,召回率低,排除。排除完,剩下"向量层过关"的带到真实环境测后面六层。
八、可控与不可控:仿真环境的边界在哪里
上一节说向量只是第一层,后面六层你控制不了。那到底什么是你能控制的,什么是你不能控制的?这张地图决定了 GEO 仿真的边界,也决定了你该把精力花在哪里。
SEO 的可控与不可控
SEO 做了二十多年,边界相对清晰。
你能控制的是站内的一切:标题、描述、H 标签、正文关键词、内链结构、URL 命名、图片 alt、Schema 标注、robots.txt、sitemap.xml、页面加载速度、移动端适配、HTTPS、内容更新频率、内容长度、内容结构。
你不能控制的是搜索引擎的排名算法。Google 的 RankBrain、BERT、MUM,百度的惊雷、飓风,这些算法的权重、特征、更新节奏,你只能通过黑盒测试反推,不能直接读取。竞争对手的动作你也控制不了,你优化"广州粤菜",对手也在优化,排名是相对竞争。外链的获取、用户行为信号(点击率、停留时长、跳出率)、搜索引擎的索引策略,这些都不在你手里。
SEO 的核心矛盾是站内可控,站外不可控。所以 SEO 的工程方法一直是"把可控的做到极致,用可控的去影响不可控的"。
GEO 的可控与不可控
GEO 比 SEO 更复杂,因为多了一层"生成"。
你能控制的是内容本身:写什么、怎么写、用什么结构、多长、放什么关键词、要不要用 Q/A 格式、要不要加 Schema、要不要加数据来源。发布渠道你也控制,发在官网、发在知乎、发在 CSDN、发在公众号,渠道选择你决定。发布时机、更新频率、跨平台内容一致性,也都可控。
你不能控制的是 AI 平台的爬虫。GPTBot、PerplexityBot、ClaudeBot 什么时候抓、抓多少、抓不抓你的站,你控制不了。你的 robots.txt 它可能遵守,也可能不遵守。AI 平台的分块策略完全不可见,它把你的网页切成多大、按什么切、切几层,你都不知道。AI 平台的 embedding 模型,自研还是第三方、什么维度、什么训练数据,不公开。AI 平台的重排策略,Cross-Encoder 用什么模型、时效性权重多少、域名权威怎么算,不可见。AI 平台的引用归因,它决定引用哪句话、引为 1 还是 5、引用几处,你控制不了。
更麻烦的是 AI 平台的算法更新。ChatGPT 默认模型从 GPT-4o 换到 GPT-5,引用域名数就下降 20%,这种更新你完全无法预测。AI 平台的商业运营也在挤压引用位,ChatGPT 广告比例从 14% 涨到 26%。用户查询的个性化,同一个问题,不同用户、不同位置、不同语言,AI 给的答案可能不同。
GEO 的核心矛盾是内容可控,检索和引用不可控。你唯一能做的,是让内容在"被检索到"和"被引用"这两个环节的概率最大化,但你不能保证。
两张地图对比
可控部分的重叠是内容质量、内容结构、关键词、Schema、内链、更新频率,这些 SEO 和 GEO 都吃。
可控部分的差异是,SEO 能控制外链和站内技术细节,这些对 GEO 权重低。GEO 能控制内容一致性(跨平台),这在 SEO 里是间接信号。
不可控部分的重叠是平台算法、竞争对手、用户行为。
不可控部分的差异是,SEO 面对的是"排名算法",GEO 面对的是"检索 + 重排 + 生成"三层黑盒。GEO 的不可控层级更多,更黑。
这张地图有什么用
它决定了 GEO 仿真的边界。
你搭的仿真环境,覆盖的是你能控制的那一段:内容结构、分块策略、embedding 选择、检索方式。
你控制不了的那一段:平台爬虫、平台分块、平台 embedding、平台重排、平台引用、平台更新、商业运营,仿真里全都不覆盖。
所以仿真结论必须标注"这是关于可控层的结论"。比如正确表述是"chunk_size=200 + Q/A 结构,在本地检索层 top-1 命中率从 12.5% 提到 60%"。错误表述是"chunk_size=200 + Q/A 结构,能让 Perplexity 引用率提升 5 倍"。第一句是可控层的因果结论,第二句是跨层外推,不成立。
不可控的部分能做两件事。第一件,通过可控层间接影响不可控层。你控制不了平台的分块策略,但你可以让内容"无论怎么切都是完整语义单元",这样不管平台怎么切,你都占优。这就是"抗切"的内容设计。第二件,通过真实环境灰度测试反推不可控层。你发内容,观察 AI 平台有没有引用、引用哪句、引用位置,反推它的偏好。这是黑盒测试,不是精确复现。
仿真解决可控层,灰度解决不可控层。两者配合,才是完整的 GEO 工程方法。
九、GEO 仿真方法论
第一,不要追平台的具体参数。平台的 chunk_size 是黑盒,且不同文档可能不同,你追不到一个确定值。正确思路是把 chunk_size 当"环境变量",找在多个值下都有效的鲁棒策略。你控制不了平台的刀怎么切,但你能控制内容做成"切不坏"的形状。
第二,让内容自包含。不要写"我们有很多菜。啫啫煲用清远鸡。烧鹅用荔枝木",因为平台切错位置就断义。要写"【啫啫煲】选用清远走地鸡,砂锅现啫。【烧鹅】荔枝木炭烧,皮脆肉嫩",这样无论怎么切,每块都是完整主题。
第三,完整工作流是这样的。先在本地仿真跑 chunk_size 矩阵(100/200/300/500),找"在所有 chunk_size 下都有效的策略"。再把鲁棒策略用到真实内容,观察 2 到 4 周,看 AI 平台有没有引用。最后根据真实反馈反推平台偏好,迭代。
关键跳跃是从"追平台参数"到"找鲁棒策略"。
十、仿真环境不能模拟什么
仿真中你的站点一定会被抓到。真实中你的站点可能永远不被抓,或者抓了但不索引。
仿真中分块策略透明,你可以自己定义。真实中平台分块策略不可见,可能多层分块(文档级、段落级、句子级)。
仿真中嵌入模型固定,你自己选。真实中平台自研或私有部署,训练数据不公开。
仿真中重排逻辑自定。真实中平台 Cross-Encoder 评分特征不公开。
仿真中无个性化。真实中基于用户历史、位置、语言。
仿真中无算法更新。真实中平台频繁更新,ChatGPT 默认模型切换就导致引用域名数下降 20%。
仿真中结果 100% 可复现。真实中同一查询不同用户、不同时间结果不同。
这些差异决定了一个核心原则:仿真环境是假设验证机,不是效果预测器。它能快速排除无效方向、验证变量敏感性,但不能预测真实平台的绝对引用率。
十一、可能的工作路径
上面讲的是技术和方法,但真正落地 GEO,路径不止一条。根据你的资源、目标、时间,至少有三条路可走。
路径一:技术验证路线
适合有技术背景、想理解底层原理、愿意自己动手的人。
第一步,搭本地仿真环境,跑通四件套,建立基线。第二步,跑 chunk_size、overlap、前缀、embedding 模型四组参数矩阵,找到本地检索层 top-1 命中率超过 60% 的鲁棒策略。第三步,把鲁棒策略应用到真实内容上,不是发到网上,是先改造自己的博客、公众号、官网。第四步,选几个页面做真实环境灰度,发布后两到四周,手动查 Perplexity、ChatGPT、豆包,看有没有引用、引用哪句、引用位置,记录数据。第五步,根据真实反馈反推平台偏好,迭代内容策略。
这条路的价值是,你会真正理解 RAG 检索原理,知道为什么某些内容结构被引用、某些不被。但周期长,需要持续投入,短期看不到回报。
路径二:工具驱动路线
适合不懂技术、但想快速看到 GEO 效果的人。
第一步,选一个 GEO 监测工具,比如 GetCito、Profound、Similarweb AI Brand Visibility。这些工具能追踪你的品牌在 ChatGPT、Perplexity、Gemini、豆包里的提及率和引用率。第二步,用工具跑一次基线,看你的品牌当前在 AI 搜索里是什么状态,有多少查询提到你、引用位置是 1 还是 5、和竞品对比差多少。第三步,针对工具报出的弱项做内容优化。比如工具说你的品牌在 FAQ 类查询里提及率低,那你就大量生产 FAQ 结构的内容。第四步,优化后四到八周,再用工具跑一次,看指标变化。
这条路的价值是见效快、门槛低、有数据反馈。但缺点是你不知道"为什么有效",只是按工具的指引试错。而且工具本身也是黑盒,它的指标未必和真实 AI 平台完全一致。
路径三:内容运营路线
适合有内容团队、有品牌基础、想做长期 GEO 布局的人。
第一步,建立跨源一致性内容体系。AI 平台会做跨源交叉验证,品牌需要在五个以上不同类型的平台上有一致的内容。权威媒体、行业垂媒、百科、知识平台、社区,每个渠道都要有你的内容,而且品牌名、产品名、数据要一致。第二步,用结构化内容对抗分块不确定性。所有内容都按 Q/A 结构写、每段自包含、段首点题、加 Schema 标注。这样不管平台怎么切,每块都是完整语义单元。第三步,持续发布、持续更新。AI 平台有时效性加权,新内容更容易被引用。定期更新页面日期、补充新数据、增加新 FAQ。第四步,监测跨引擎一致性。同一查询在 Perplexity、ChatGPT、Grok、豆包里的结果差异,如果差异大说明平台偏好不同,需要针对性调整。
这条路的价值是长期复利,品牌在 AI 搜索里的可见度会持续提升。但需要内容团队持续投入,而且效果慢,六到十二个月才能看到明显变化。
三条路径怎么选
有技术背景、想理解原理、不急于短期回报,走路径一。不懂技术、想快速看到效果、有预算买工具,走路径二。有内容团队、有品牌基础、想做长期布局,走路径三。
三条路不互斥。最理想的是路径一加路径三:用仿真环境理解原理、找到鲁棒策略,然后用内容团队把这些策略规模化落地。路径二的工具可以用来做监测和反馈。
如果你已经完成路径一的第一步(搭环境、跑通四件套、建立基线),接下来是第二步(跑参数矩阵、找鲁棒策略)。这一步完成后,可以继续路径一做真实环境灰度,或者转向路径三用内容团队规模化落地。
建议先走完路径一的第二步和第三步。因为这两步投入不大,但能建立"什么是鲁棒策略"的判断力。有了这个判断力,无论后面走哪条路,都不会被"关键词堆砌""批量稿件"这类低效方法误导。
十二、给同行的建议
别迷信官方 SDK。qdrant-client 出问题时,直接上 REST API,代码更短更可控。在仿真场景里,"少依赖"比"用官方 SDK"更重要。
中文站一定显式声明 UTF-8。Nginx 加 charset utf-8;,爬虫加 r.encoding = "utf-8"。别让 requests 猜编码。
指标不要只看 top-1。top-3、MRR、分差、鲁棒性,四个一起看才知道问题在哪层。真实平台做两阶段检索,top-3 命中比 top-1 更能反映真实情况。
仿真不是复现,是筛选。快速排除无效方向,剩下的带到真实环境。不要把"向量层优化"误当成"GEO 优化全部"。
向量是地基不是全部。GEO 优化是"召回乘以重排乘以权威乘以时效乘以生成"的综合概率。本地仿真只覆盖第一层,但这是最重要的入场券。
小模型中文弱是硬伤。nomic-embed-text 跑中文细粒度检索,区分度明显不足。换 BGE-M3 是第一步。
让内容自包含。每段自带主题标签,无论平台怎么切都是完整主题。这是"抗切"的内容结构。
区分可控和不可控。可控的做到极致(内容结构、分块策略、跨平台一致性),不可控的用灰度测试反推(平台爬虫、分块、重排、引用)。别在不可控的事上浪费精力。
十三、结语
这套仿真环境的意义不在于预测真实平台的引用率,那做不到。它的意义在于把黑盒拆成可控变量,分块、embedding、检索,每一层都能单独调。它能快速排除无效方向,关键词堆砌、段落太长、无结构,本地就能否掉。它建立了可复现的基线,EXP-002 的 12.5% top-1,是后续所有优化的对照。
GEO 不是玄学,是工程。把不可控的真实平台,映射到可控的仿真环境,用工程方法找鲁棒策略,再带到真实环境验证。这是我认为目前最务实的方法论。
仿真环境最终要做的是理解 RAG 检索原理的训练场,而不是预演 GEO 投放效果的沙盘。你可以在本地沙盒里测试不同内容结构对自己的检索系统的影响,但不要指望这些结论能一比一映射到豆包或 ChatGPT 的引用行为上。真实 GEO 的博弈发生在算法黑箱、平台分发、跨源信任网络、用户反馈这些你无法搭建的层面。
但这不妨碍你把它当作一台假设验证机。先用它快速排除无效方向,再把通过验证的策略带到真实环境做小规模灰度测试。这条路,比盲目发布内容然后祈祷被引用,要靠谱得多。
代码仓库结构:
text
H:\GEO\
├── tools\
│ ├── WinPython\python\python.exe
│ ├── ollama\servers\ollama.exe
│ ├── ollama\start-ollama.bat
│ ├── qdrant\qdrant.exe
│ └── nginx\nginx.exe
└── project\
├── scripts\
│ ├── qdrant_rest.py
│ ├── crawl.py
│ ├── chunk.py
│ ├── build_index.py
│ └── eval.py
├── content\brand-a\index.html
├── qdrant_config.yaml
└── data\qdrant_storage\
