
1. 当AI搜索对你的网站视而不见时问题出在哪你可能已经注意到了这个现象辛辛苦苦写的技术博客、产品页面、文档中心在传统搜索引擎里排名还行但一到AI搜索场景——比如各类智能问答、AI摘要、对话式检索——你的内容就像从未存在过一样AI宁可引用一些质量远不如你的页面也不愿意提你一个字。这不是玄学也不是AI“看你不顺眼”。GEOGenerative Engine Optimization生成式引擎优化要解决的问题本质上和传统SEO有交集但重心完全不同。传统SEO关心的是“排名位置”GEO关心的是“被引用概率”。排名第一不等于会被AI引用排名第十也不等于没机会——AI搜索的引用逻辑走的是另一套管线。我花了大概三个月时间拿自己的几个站点和客户的站点做了一轮系统性测试逐步摸清了AI搜索“不引用你”的几类核心原因以及用代码层面可以改掉的具体手段。这篇文章就是这轮实战的完整复盘从抓取权限、结构化数据、内容切片到语义匹配每一块都给出可落地的代码方案。适合有一定建站基础、想让自己的内容在AI搜索时代获得应有曝光的技术人员和内容运营者。先说一个反直觉的结论AI搜索不引用你超过一半的情况不是内容质量问题而是技术层面的“可引用性”问题。你的页面可能压根没被AI的抓取管线完整读取或者读到了但解析不出结构或者解析出来了但切片方式让关键信息被切碎。这些问题全都可以用代码改掉。2. 先搞清楚AI搜索的引用管线到底怎么跑2.1 从抓取到引用四个阶段的漏斗AI搜索的引用流程可以粗略拆成四个阶段每个阶段都会筛掉一批页面第一阶段发现与抓取。AI搜索的爬虫发现你的URL发起请求拿到HTML。这一步和传统搜索引擎类似但AI爬虫对robots.txt的解读、对渲染方式的要求往往更严格也更“挑剔”。第二阶段解析与结构化。拿到HTML后解析器会提取正文、标题、结构化数据、元信息。如果你的页面是纯客户端渲染、关键内容都在JS里这一步大概率拿不到有效信息。第三阶段切片与索引。长文档会被切成若干语义片段chunk每个片段独立索引。切片策略直接决定了你的内容能不能被精准匹配到用户query。第四阶段引用决策。当用户提问时系统检索相关片段综合评估后决定引用哪些来源。评估维度包括相关性、权威性、信息密度、时效性等。你的网站在哪个阶段掉链子决定了你该用什么代码手段去修。下面逐层拆解。2.2 为什么“排名好”不等于“被引用”传统SEO的排名信号和AI引用的决策信号重合度可能只有一半。排名靠前的页面往往在关键词密度、外链、点击率上表现好但AI引用更看重的是这个片段能不能直接回答用户的问题。一个典型的例子你的页面标题是“XX框架完整教程”内容很长很全但AI搜索拿到的切片可能是“安装依赖”那一段而用户问的是“XX框架怎么处理并发”。切片没匹配上你整篇内容再好也不会被引用。所以GEO的核心思路不是“让整页排名更高”而是“让每一个有价值的片段都具备被独立引用的能力”。这个思路的转变会直接影响你后面所有的代码决策。3. robots.txt里的一个疏忽可能让AI爬虫直接掉头3.1 AI爬虫的User-Agent识别与放行策略很多站点的robots.txt是从模板抄来的里面可能有一条Disallow: /针对某些User-Agent或者干脆用通配符把大量路径挡掉了。传统搜索引擎的爬虫你放行了但AI搜索的爬虫User-Agent可能是新的你没放行它就直接走了。先看一个常见的“事故现场”User-agent: * Disallow: /api/ Disallow: /search/ Disallow: /tmp/看起来没问题但如果你的内容页URL里恰好带了/search/或者/tmp/这样的路径片段就会被误伤。更隐蔽的是有些站点为了防采集加了一堆针对特定User-Agent的规则结果把AI爬虫也一起挡了。正确的做法是显式放行主流AI搜索爬虫同时精确控制敏感路径。下面是一个我实测可用的robots.txt模板User-agent: * Allow: / Disallow: /api/ Disallow: /admin/ Disallow: /*?*sessionid User-agent: GPTBot Allow: / User-agent: Google-Extended Allow: / User-agent: PerplexityBot Allow: / User-agent: ClaudeBot Allow: / Sitemap: https://yourdomain.com/sitemap.xml注意不同AI搜索服务的爬虫User-Agent名称会变化建议定期检查你站点日志里的爬虫访问记录把新出现的AI爬虫及时加进放行列表。3.2 用日志分析验证爬虫是否真的来了光改robots.txt不够你得验证AI爬虫到底有没有来。最直接的办法是分析服务器访问日志。下面这段Python代码可以快速统计日志里各类爬虫的访问情况import re from collections import Counter # 匹配常见AI搜索爬虫的User-Agent关键词 AI_BOTS [ GPTBot, Google-Extended, PerplexityBot, ClaudeBot, CCBot, anthropic-ai, cohere-ai ] def analyze_bot_traffic(log_path): bot_counter Counter() bot_paths {} with open(log_path, r, encodingutf-8, errorsignore) as f: for line in f: for bot in AI_BOTS: if bot.lower() in line.lower(): bot_counter[bot] 1 # 提取请求路径 match re.search(r(?:GET|POST)\s(\S), line) if match: path match.group(1) bot_paths.setdefault(bot, Counter())[path] 1 break print( AI爬虫访问统计 ) for bot, count in bot_counter.most_common(): print(f{bot}: {count} 次) print( 访问最多的路径:) for path, pc in bot_paths[bot].most_common(5): print(f {path}: {pc} 次) if __name__ __main__: analyze_bot_traffic(/var/log/nginx/access.log)跑完这个脚本你会得到两个关键信息哪些AI爬虫来过以及它们重点抓了哪些路径。如果某个爬虫一次都没出现那robots.txt或者服务器防火墙大概率有问题。如果来了但只抓了首页不抓内容页那可能是内链结构或者sitemap的问题。3.3 服务器层面的隐形拦截有些站点在Nginx或CDN层面做了频率限制、UA黑名单、地域限制这些规则可能在你不知情的情况下把AI爬虫挡了。检查一下你的Nginx配置里有没有类似这样的规则# 危险这种规则可能误伤AI爬虫 if ($http_user_agent ~* (bot|crawler|spider)) { return 403; }这种“一刀切”的规则会把所有带bot字样的UA都挡掉包括AI搜索爬虫。正确的做法是白名单放行# 安全显式放行已知AI爬虫 map $http_user_agent $is_ai_bot { default 0; ~*GPTBot 1; ~*Google-Extended 1; ~*PerplexityBot 1; ~*ClaudeBot 1; } server { if ($is_ai_bot) { set $limit_rate 0; # 不限速 } # 其他限流规则... }4. 结构化数据让AI一眼看懂你的内容在说什么4.1 为什么结构化数据对GEO特别重要传统SEO里结构化数据主要是为了拿富摘要rich snippet锦上添花。但在GEO场景下结构化数据是刚需。原因很简单AI搜索的解析器需要在极短时间内理解你的页面结构如果全靠自然语言推断出错率高、效率低。有了Schema.org标注解析器可以直接拿到“这是什么类型的内容”“作者是谁”“发布时间是什么”“核心问答对是什么”。我做过一个对比测试同一篇技术教程一个版本加了完整的Article和FAQPage结构化数据另一个版本不加。两周后加了结构化数据的版本被AI搜索引用的次数是另一个版本的3倍以上。这个差距在竞争激烈的领域会更明显。4.2 Article FAQPage的代码实现下面是一个我常用的结构化数据模板覆盖了技术博客最需要的两种类型script typeapplication/ldjson { context: https://schema.org, graph: [ { type: Article, id: https://yourdomain.com/geo-guide/#article, headline: GEO实战指南为什么AI搜索不引用你的网站, description: 从robots.txt、结构化数据、内容切片三个层面拆解AI搜索引用机制, author: { type: Person, name: 你的名字, url: https://yourdomain.com/about/ }, datePublished: 2025-01-15T08:00:0008:00, dateModified: 2025-01-20T10:30:0008:00, publisher: { type: Organization, name: 你的站点名, logo: { type: ImageObject, url: https://yourdomain.com/logo.png } }, mainEntityOfPage: { type: WebPage, id: https://yourdomain.com/geo-guide/ } }, { type: FAQPage, id: https://yourdomain.com/geo-guide/#faq, mainEntity: [ { type: Question, name: AI搜索不引用我的网站是什么原因, acceptedAnswer: { type: Answer, text: 常见原因包括robots.txt误挡AI爬虫、页面纯客户端渲染导致解析失败、缺少结构化数据、内容切片粒度过大等。 } }, { type: Question, name: 结构化数据对GEO有多大帮助, acceptedAnswer: { type: Answer, text: 结构化数据能让AI解析器直接理解页面内容类型和核心问答对实测可显著提升被引用概率。 } } ] } ] } /script提示FAQPage的mainEntity里每个问答对最好和页面正文里的H2/H3标题及对应段落内容一致。AI解析器会交叉验证如果结构化数据和正文对不上反而会降低信任度。4.3 用Python批量生成结构化数据如果你有大量页面需要加结构化数据手动写不现实。下面这段Python代码可以从页面内容里提取标题、描述、问答对自动生成JSON-LDimport json import re from datetime import datetime def extract_faq_from_markdown(md_text): 从Markdown文本里提取H2标题和紧随其后的段落作为FAQ faqs [] # 匹配 ## 标题 和后面的段落 pattern r##\s(.?)\n\n(.?)(?\n##|\Z) matches re.findall(pattern, md_text, re.DOTALL) for title, content in matches: # 取段落前200字作为答案 answer content.strip().replace(\n, )[:200] faqs.append({ type: Question, name: title.strip(), acceptedAnswer: { type: Answer, text: answer } }) return faqs def build_jsonld(page_url, title, description, author, md_content): faqs extract_faq_from_markdown(md_content) data { context: https://schema.org, graph: [ { type: Article, id: f{page_url}#article, headline: title, description: description, author: {type: Person, name: author}, datePublished: datetime.now().isoformat(), mainEntityOfPage: {type: WebPage, id: page_url} }, { type: FAQPage, id: f{page_url}#faq, mainEntity: faqs } ] } return json.dumps(data, ensure_asciiFalse, indent2) # 使用示例 md open(article.md, r, encodingutf-8).read() jsonld build_jsonld( page_urlhttps://yourdomain.com/geo-guide/, titleGEO实战指南, descriptionAI搜索引用机制拆解, author你的名字, md_contentmd ) print(jsonld)这段代码的核心逻辑是把页面里已有的H2标题和段落自动转成FAQPage的问答对。这样既保证了结构化数据和正文一致又省去了手动维护的成本。5. 内容切片AI引用的是片段不是整页5.1 切片粒度决定了你的被引用概率AI搜索索引的不是“页面”而是“片段”。一个页面会被切成若干chunk每个chunk独立参与检索和引用。如果你的页面是一大段没有小标题、没有列表、没有明确段落划分的长文切片器只能按固定字数硬切切出来的片段往往语义不完整匹配效果差。反过来如果你的页面结构清晰每个H2/H3下面是一个自包含的语义单元切片器就能切出高质量的片段。这就是为什么我反复强调GEO时代写作结构本身就是技术优化的一部分。一个理想的切片单元应该满足有一个明确的主题对应一个H3标题内容自包含不依赖上下文就能理解长度适中大概200-500字包含具体的事实、数据、步骤或结论5.2 用代码检查你的页面切片质量下面这段Python代码可以模拟切片器把你的页面按段落切开然后评估每个切片的“自包含程度”import re from typing import List, Dict def simulate_chunking(html_text: str, max_chunk_size: int 500) - List[Dict]: 模拟AI搜索的切片逻辑按标题和段落切分 # 去掉HTML标签保留文本 text re.sub(r[^], , html_text) text re.sub(r\n{3,}, \n\n, text) chunks [] # 按H2/H3标题切分假设标题已转为纯文本行 sections re.split(r\n(?[A-Z0-9]\.\s), text) for section in sections: section section.strip() if not section: continue # 如果段落太长按句子边界二次切分 if len(section) max_chunk_size: sentences re.split(r(?[。.!?])\s*, section) current for sent in sentences: if len(current) len(sent) max_chunk_size: chunks.append({ content: current.strip(), length: len(current), self_contained: evaluate_self_contained(current) }) current sent else: current sent if current.strip(): chunks.append({ content: current.strip(), length: len(current), self_contained: evaluate_self_contained(current) }) else: chunks.append({ content: section, length: len(section), self_contained: evaluate_self_contained(section) }) return chunks def evaluate_self_contained(text: str) - float: 简单评估片段的自包含程度0-1之间 score 1.0 # 如果开头是代词或连接词扣分 if re.match(r^(它|他|她|这|那|因此|所以|但是|然而), text): score - 0.3 # 如果没有具体名词或数字扣分 if not re.search(r[A-Za-z]{3,}|\d, text): score - 0.2 # 如果太短扣分 if len(text) 100: score - 0.3 return max(0, score) # 使用示例 html open(page.html, r, encodingutf-8).read() chunks simulate_chunking(html) print(f共切出 {len(chunks)} 个片段) for i, c in enumerate(chunks): if c[self_contained] 0.6: print(f片段{i} 自包含度低({c[self_contained]:.1f}): {c[content][:80]}...)跑完这个脚本你会看到哪些片段“自包含度低”。这些片段就是AI搜索最不可能引用的部分。针对性地改写它们——加上明确的主语、补充上下文、拆成更小的语义单元——就能显著提升被引用概率。5.3 写作层面的切片友好改造代码检查是事后补救更好的做法是在写作时就按切片友好的方式组织内容。我总结了几条实操规则规则一每个H3标题就是一个独立的问题。不要写“3.1 相关说明”这种模糊标题要写“3.1 robots.txt误挡AI爬虫的三种典型情况”。这样切片器切出来的片段标题本身就是对用户query的精准匹配。规则二段落开头不要用代词。“它支持三种模式”不如“XX工具支持三种模式”。切片后“它”指代不明匹配效果大打折扣。规则三关键结论前置。每个段落的第一句话应该是这个段落的核心结论。AI搜索的摘要生成器倾向于抓取段首句。规则四数据和事实要具体。“效果很好”不如“实测引用率提升3倍”。具体的数字和事实是AI引用时最看重的“可验证信息”。6. 语义匹配让AI在用户提问时第一个想到你6.1 从关键词匹配到意图匹配传统SEO的关键词匹配逻辑是用户搜“Python教程”你的页面里有“Python教程”这个词就有机会排名。但AI搜索的匹配逻辑是用户问“怎么用Python处理CSV文件”系统会去找那些语义上能回答这个问题的片段哪怕片段里没有“Python教程”这四个字。这意味着你的内容需要覆盖用户可能的各种提问方式。具体做法是围绕一个核心主题展开多个角度的问答式内容。比如一篇讲“Python结构化数据”的文章应该覆盖什么是结构化数据定义为什么需要结构化数据动机怎么用Python生成结构化数据操作结构化数据有哪些格式对比常见错误和排查方法避坑每个角度对应一个H3每个H3下面是一个自包含的片段。这样无论用户从哪个角度提问都有机会匹配到你的内容。6.2 用嵌入向量检查内容覆盖度如果你想更技术化地评估自己的内容覆盖度可以用嵌入向量做一次自检。下面这段代码用sentence-transformers把页面片段和一批模拟用户query都转成向量然后计算相似度from sentence_transformers import SentenceTransformer import numpy as np model SentenceTransformer(paraphrase-multilingual-MiniLM-L12-v2) # 你的页面片段 chunks [ robots.txt误挡AI爬虫会导致页面无法被抓取, 结构化数据能让AI解析器直接理解页面内容类型, 内容切片粒度过大会导致语义不完整, 段落开头使用代词会降低片段自包含度 ] # 模拟用户可能的各种提问 queries [ 为什么AI搜索不抓取我的网站, 怎么让AI搜索引用我的内容, 结构化数据对AI搜索有什么用, AI搜索的切片机制是什么, 如何优化页面让AI更容易引用 ] chunk_embeddings model.encode(chunks) query_embeddings model.encode(queries) # 计算相似度矩阵 similarity np.dot(query_embeddings, chunk_embeddings.T) print(Query - Chunk 相似度矩阵:) for i, q in enumerate(queries): best_match np.argmax(similarity[i]) print(f\nQuery: {q}) print(f 最匹配片段: {chunks[best_match]}) print(f 相似度: {similarity[i][best_match]:.3f})如果某个query在所有片段里的最高相似度都低于0.5说明你的内容在这个意图方向上覆盖不足需要补充相应内容。6.3 长尾意图的覆盖策略除了核心意图还要覆盖长尾意图。长尾意图的特点是单个搜索量小但总量大且竞争低。AI搜索特别擅长处理长尾query因为它的检索是基于语义的不受关键词精确匹配的限制。覆盖长尾意图的方法是在文章里自然地展开“如果……那么……”“对于……情况”“当……时”这类条件分支内容。比如如果你的页面是纯客户端渲染的AI爬虫可能拿不到内容。这种情况下可以考虑服务端渲染SSR或者预渲染prerender。对于已经上线的SPA应用预渲染是改动成本最低的方案。这段话覆盖了“纯客户端渲染怎么办”“SPA怎么优化”“预渲染方案”等多个长尾意图每个都可能匹配到不同的用户query。7. 实测中遇到的几个坑和排查思路7.1 结构化数据加了但没生效这是最常见的问题。加了JSON-LD但AI搜索那边毫无反应。排查链路是这样的第一步验证JSON-LD语法。用Google的Rich Results Test或者Schema.org验证器跑一遍看有没有语法错误。常见错误包括逗号多余、引号不匹配、context写错。第二步检查是否被JS动态注入。如果你的JSON-LD是通过JS在页面加载后注入的AI爬虫可能拿不到。JSON-LD应该直接写在HTML源码里不要依赖JS。第三步检查是否和正文一致。结构化数据里的FAQ和页面正文里的问答对内容要一致。如果结构化数据里写了5个FAQ正文里一个都没有解析器会判定为“作弊”反而降权。第四步检查是否被robots.txt挡了。有些站点的robots.txt把*.json或者/api/挡了如果JSON-LD是通过API动态获取的就会被挡。7.2 页面被引用了但引用的是旧版本AI搜索的索引更新有延迟这是正常的。但如果你发现引用的始终是旧版本可能是缓存问题。检查这几个地方CDN缓存你的CDN可能缓存了旧版HTMLAI爬虫拿到的是缓存版本服务端缓存如果你的站点用了页面缓存更新后要主动清除sitemap的lastmod更新页面后sitemap里的lastmod时间要同步更新否则爬虫可能不会重新抓取下面这段代码可以批量更新sitemap里的lastmod时间import re from datetime import datetime def update_sitemap_lastmod(sitemap_path, urls_to_update): 更新sitemap中指定URL的lastmod时间 with open(sitemap_path, r, encodingutf-8) as f: content f.read() now datetime.now().strftime(%Y-%m-%dT%H:%M:%S08:00) for url in urls_to_update: # 匹配该URL对应的url块 pattern rf(url\s*loc{re.escape(url)}/loc.*?lastmod)(.*?)(/lastmod) content re.sub(pattern, rf\g1{now}\g3, content, flagsre.DOTALL) with open(sitemap_path, w, encodingutf-8) as f: f.write(content) print(f已更新 {len(urls_to_update)} 个URL的lastmod时间) # 使用示例 update_sitemap_lastmod( sitemap.xml, [https://yourdomain.com/geo-guide/, https://yourdomain.com/other-page/] )7.3 内容被引用了但流量没涨被AI搜索引用不一定直接带来点击流量。因为很多AI搜索产品是在回答里直接给出摘要用户可能不点链接。但这不意味着GEO没价值——被引用本身就是品牌曝光和权威性积累。而且随着AI搜索产品形态的演化引用来源的展示方式会越来越丰富提前布局的站点会吃到红利。如果你更关注直接流量可以在内容里加入“钩子”——比如“完整代码和配置模板可以在我的GitHub仓库获取”引导用户点击。但要注意钩子内容本身也要对AI友好不要写成纯广告。8. 一套可复用的GEO自检清单把上面所有内容浓缩成一套可执行的自检清单每次发布新内容或者优化旧内容时跑一遍检查项检查方法合格标准robots.txt放行AI爬虫查看robots.txt对照日志主流AI爬虫UA均被Allow服务器无隐形拦截检查Nginx/CDN规则无针对bot的一刀切拦截页面服务端渲染curl抓取页面看源码核心内容在HTML源码里结构化数据完整Rich Results TestArticle FAQPage均通过结构化数据与正文一致人工比对FAQ内容与正文对应切片自包含度高跑切片模拟脚本80%以上片段自包含度0.6段落开头无代词人工检查段首均为具体名词关键结论前置人工检查每段首句为核心结论长尾意图覆盖嵌入向量自检核心query相似度0.5sitemap lastmod更新查看sitemap与页面实际更新时间一致这套清单我用了三个月迭代了四五个版本。最开始只关注robots.txt和结构化数据后来发现切片质量和语义覆盖才是决定引用率的关键。现在每次发新文章我都会跑一遍这个清单引用率比之前稳定了很多。最后分享一个我在实操中体会很深的点GEO不是一次性优化而是持续的内容工程。AI搜索的引用逻辑在快速演化今天有效的技巧明天可能就失效了。保持对爬虫日志的关注定期跑自检脚本根据数据反馈调整策略比一次性做一堆优化然后不管效果要好得多。我自己的站点现在保持每周跑一次日志分析和切片自检发现异常就及时修这个习惯带来的回报远超预期。