
一、开篇文章写得很完整但AI偏偏引用了最没用的一段做 GEO 时经常会遇到一个很诡异的问题文章明明写了答案。 FAQ明明也有。 对比表也放了。 总结也写了。但 AI 生成答案时偏偏引用了一个很边缘的段落。比如用户问GEO和SEO有什么区别你的文章里明明有一张完整对比表SEO关注搜索结果页GEO关注生成式AI答案。 SEO看排名和点击GEO看理解、引用和答案覆盖。但 AI 最后召回的却是在AI时代内容优化变得越来越重要。这句话错吗不算错。有用吗像接口返回了success: true但data: null。看起来成功实际上没法用。问题可能不在文章本身而在切块策略。在 AI 检索和生成场景里内容通常不会整篇塞进上下文而是会先被切成一个个 chunk文章 → 切块 → 向量化/索引 → 检索 → 召回 → 生成答案如果切块切得不好GEO 内容就会出现三个问题答案被切断问题和答案分家表格、步骤、FAQ 被拆碎AI 召回了相关但不完整的片段本文就用 Python 写一个小实验对比 3 种常见切块策略1. 固定长度切块 2. 按段落切块 3. 按标题层级切块看看到底哪种方式更适合 GEO 内容。二、问题现场为什么切块会影响GEO假设你有一篇文章# GEO和SEO有什么区别 ## 什么是GEO GEO是生成式引擎优化关注内容是否能被AI理解、引用并整合进答案。 ## GEO和SEO的区别 | 维度 | SEO | GEO | |---|---|---| | 优化对象 | 搜索结果页 | AI生成答案 | | 核心目标 | 排名和点击 | 理解和引用 | ## 常见问题 ### GEO会替代SEO吗 不会。GEO不是SEO的替代品而是AI搜索场景下的扩展。如果按固定长度粗暴切块可能变成chunk 1: # GEO和SEO有什么区别 ## 什么是GEO GEO是生成式引擎优化... chunk 2: ## GEO和SEO的区别 | 维度 | SEO | GEO |... chunk 3: 核心目标 | 排名和点击 | 理解和引用 | ## 常见问题... chunk 4: ### GEO会替代SEO吗 不会。GEO不是SEO的替代品...这里的问题是对比表被切碎了。 FAQ问题和答案可能分离。 标题和正文上下文可能断开。AI 检索时可能只召回chunk 3里面有“排名和点击、理解和引用”但缺少标题上下文。机器会问这到底是在比较什么这就是 GEO 内容切块的核心问题内容不是写完就结束还要保证它被切开后每一块仍然能独立表达有效语义。三、GEO友好的chunk应该长什么样一个适合 GEO 的 chunk至少要满足 4 个条件条件说明语义完整不要把问题和答案拆开上下文清楚最好保留所属标题信息密度高不只是空泛引言可独立引用单独拿出来也能回答部分问题低质量 chunk这种方式在很多场景下都非常重要因此需要持续优化。高质量 chunkGEO和SEO的区别在于SEO主要关注搜索结果页中的收录、排名和点击GEO主要关注生成式AI问答场景中的理解、引用和答案覆盖。两者不是替代关系而是优化目标发生了扩展。后者更适合被 AI 引用因为它独立、完整、明确。四、实战目标写一个chunk策略对比工具我们要做一个工具输入一篇 Markdown输出不同切块策略的质量报告。检测维度包括chunk数量 平均长度 过短chunk数量 过长chunk数量 是否保留标题上下文 FAQ问题答案是否分离 每个chunk的信息密度整体流程如下flowchart TD A[Markdown文章] -- B[固定长度切块] A -- C[段落切块] A -- D[标题层级切块] B -- E[chunk质量评估] C -- E D -- E E -- F[输出对比报告] F -- G[推荐更适合GEO的切块策略]这不是一个复杂模型而是一个内容工程工具。GEO 不一定每一步都要上大模型。有些问题先用规则就能发现一大半。五、项目结构新建项目目录geo-chunk-lab/ ├── chunk_lab.py ├── sample_article.md └── reports/说明文件作用chunk_lab.py主程序sample_article.md测试文章reports/输出报告六、准备测试文章创建sample_article.md# GEO和SEO有什么区别一文讲清AI搜索时代的内容优化逻辑 ## 什么是GEO GEO是Generative Engine Optimization中文可以理解为生成式引擎优化。它关注内容是否能被生成式AI检索、理解、引用并整合进答案。 GEO不是SEO的替代品而是AI搜索场景下对内容优化目标的一种扩展。 ## SEO关注什么 SEO主要关注搜索引擎结果页包括页面收录、关键词排名、点击率和自然流量。 如果一个页面无法被搜索引擎发现那么它进入AI检索链路的概率也会降低。 ## GEO关注什么 GEO主要关注生成式AI问答场景包括内容是否能被AI理解、是否能被引用、是否能被组织进答案。 GEO更强调问题覆盖、结构化表达、事实依据、FAQ和语义关系。 ## GEO和SEO的区别 | 维度 | SEO | GEO | |---|---|---| | 优化对象 | 搜索结果页 | 生成式AI答案 | | 核心目标 | 排名和点击 | 理解和引用 | | 内容重点 | 关键词和页面结构 | 问题、证据、语义和上下文 | | 评价方式 | 排名、点击、流量 | 提及率、引用率、答案覆盖率 | ## 常见问题 ### GEO会替代SEO吗 不会。GEO不是SEO的替代品。很多AI系统仍然依赖可检索、可索引的网页内容因此SEO仍然是基础能力。 ### FAQ为什么适合GEO FAQ把用户问题和明确答案组织在一起让机器更容易识别页面能够回答哪些具体问题。 ### 结构化数据能保证AI引用吗 不能。结构化数据可以提升页面机器可读性但不能保证AI一定引用内容。GEO更像是提升被理解和引用概率的系统性方法。 ## 总结 GEO的重点不是堆关键词而是让内容在被切块、检索和生成时仍然保持清晰的问题、答案、证据和边界。这篇文章故意包含标题段落表格FAQ总结很适合测试不同切块策略。七、核心代码实现3种切块策略创建chunk_lab.pyimport csv import re from pathlib import Path from datetime import datetime def load_text(file_path): path Path(file_path) if not path.exists(): raise FileNotFoundError(f文件不存在: {file_path}) return path.read_text(encodingutf-8) def clean_text(text): text re.sub(r\n{3,}, \n\n, text) return text.strip() def fixed_size_chunks(text, size300, overlap50): 固定长度切块。 优点简单。 缺点容易切断标题、表格、FAQ。 text clean_text(text) chunks [] start 0 while start len(text): end start size chunk text[start:end].strip() if chunk: chunks.append(chunk) if end len(text): break start max(0, end - overlap) return chunks def paragraph_chunks(text, max_chars500): 按段落切块。 优点比固定长度更自然。 缺点标题和正文可能分离。 paragraphs [p.strip() for p in re.split(r\n\s*\n, clean_text(text)) if p.strip()] chunks [] buffer for paragraph in paragraphs: if not buffer: buffer paragraph continue if len(buffer) len(paragraph) 2 max_chars: buffer \n\n paragraph else: chunks.append(buffer) buffer paragraph if buffer: chunks.append(buffer) return chunks def heading_chunks(text): 按H2标题层级切块。 每个H2及其下面内容作为一个chunk。 H1会作为全局上下文加入每个chunk。 text clean_text(text) h1_match re.search(r^#\s(.)$, text, flagsre.MULTILINE) h1 h1_match.group(1).strip() if h1_match else parts re.split(r(?^##\s), text, flagsre.MULTILINE) chunks [] for part in parts: part part.strip() if not part: continue if part.startswith(# ) and not part.startswith(## ): continue if h1: chunk f# {h1}\n\n{part} else: chunk part chunks.append(chunk) return chunks def extract_h2(chunk): match re.search(r^##\s(.)$, chunk, flagsre.MULTILINE) return match.group(1).strip() if match else def has_question(chunk): return bool(re.search(r|\?|为什么|如何|怎么|是否|能不能|会不会, chunk)) def has_answer_signal(chunk): signals [ 因为, 可以, 不能, 不会, 不是, 主要, 包括, 因此, 建议, 通常, 取决于 ] return any(signal in chunk for signal in signals) def has_table(chunk): return bool(re.search(r^\|.\|$, chunk, flagsre.MULTILINE)) def has_heading_context(chunk): return bool(re.search(r^#\s., chunk, flagsre.MULTILINE)) and bool( re.search(r^##\s., chunk, flagsre.MULTILINE) ) def information_density(chunk): 简单计算信息密度 命中定义、对比、FAQ、表格、边界、流程等信号越多密度越高。 signals { definition: [是, 可以理解为, 全称, 指], comparison: [区别, 对比, 不是, 而是, 维度], faq: [, FAQ, 常见问题], evidence: [例如, 包括, 表格, 数据, 指标], boundary: [不能保证, 不一定, 取决于, 适合, 不适合], geo: [GEO, 生成式引擎优化, AI, 引用, 答案] } score 0 matched_types [] for signal_type, words in signals.items(): if any(word in chunk for word in words): score 1 matched_types.append(signal_type) return score, matched_types def detect_faq_split(chunks): 检测FAQ问题和答案是否可能被拆开。 如果一个chunk以H3问题结尾且没有答案信号视为风险。 risky_indices [] for index, chunk in enumerate(chunks): lines [line.strip() for line in chunk.splitlines() if line.strip()] if not lines: continue last_line lines[-1] if last_line.startswith(###) and ( in last_line or ? in last_line): risky_indices.append(index 1) continue if has_question(chunk) and not has_answer_signal(chunk): risky_indices.append(index 1) return risky_indices def evaluate_chunks(strategy_name, chunks): rows [] faq_split_risks set(detect_faq_split(chunks)) for index, chunk in enumerate(chunks, start1): density_score, density_types information_density(chunk) length len(chunk) too_short length 120 too_long length 800 row { strategy: strategy_name, chunk_id: index, length: length, h2: extract_h2(chunk), has_heading_context: has_heading_context(chunk), has_question: has_question(chunk), has_answer_signal: has_answer_signal(chunk), has_table: has_table(chunk), density_score: density_score, density_types: | .join(density_types), too_short: too_short, too_long: too_long, faq_split_risk: index in faq_split_risks, preview: chunk.replace(\n, )[:160] } rows.append(row) return rows def build_summary(strategy_name, rows): total len(rows) if total 0: return { strategy: strategy_name, chunk_count: 0, avg_length: 0, short_chunks: 0, long_chunks: 0, heading_context_rate: 0, faq_split_risks: 0, avg_density: 0 } return { strategy: strategy_name, chunk_count: total, avg_length: sum(row[length] for row in rows) / total, short_chunks: sum(1 for row in rows if row[too_short]), long_chunks: sum(1 for row in rows if row[too_long]), heading_context_rate: sum(1 for row in rows if row[has_heading_context]) / total * 100, faq_split_risks: sum(1 for row in rows if row[faq_split_risk]), avg_density: sum(row[density_score] for row in rows) / total } def export_csv(rows, output_file): Path(output_file).parent.mkdir(parentsTrue, exist_okTrue) headers [ strategy, chunk_id, length, h2, has_heading_context, has_question, has_answer_signal, has_table, density_score, density_types, too_short, too_long, faq_split_risk, preview ] with open(output_file, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesheaders) writer.writeheader() for row in rows: writer.writerow(row) def print_summary(summaries): print( * 100) print(GEO切块策略对比报告) print( * 100) print( f{策略:18} {chunk数:8} {平均长度:10} {过短:8} f{过长:8} {标题上下文:12} {FAQ拆分风险:12} {平均密度:10} ) print(- * 100) for item in summaries: print( f{item[strategy]:18} f{item[chunk_count]:8} f{item[avg_length]:10.2f} f{item[short_chunks]:8} f{item[long_chunks]:8} f{item[heading_context_rate]:11.2f}% f{item[faq_split_risks]:12} f{item[avg_density]:10.2f} ) print( * 100) def recommend_strategy(summaries): 简单推荐规则 - FAQ拆分风险越少越好 - 标题上下文率越高越好 - 平均密度越高越好 - 过短/过长越少越好 scored [] for item in summaries: score 0 score item[heading_context_rate] * 0.25 score item[avg_density] * 12 score - item[faq_split_risks] * 15 score - item[short_chunks] * 5 score - item[long_chunks] * 5 scored.append((item[strategy], score)) scored.sort(keylambda x: x[1], reverseTrue) return scored[0] def main(): text load_text(sample_article.md) strategies { fixed_size: fixed_size_chunks(text, size300, overlap50), paragraph: paragraph_chunks(text, max_chars500), heading: heading_chunks(text) } all_rows [] summaries [] for strategy_name, chunks in strategies.items(): rows evaluate_chunks(strategy_name, chunks) all_rows.extend(rows) summaries.append(build_summary(strategy_name, rows)) print_summary(summaries) best_strategy, best_score recommend_strategy(summaries) print() print(f推荐策略{best_strategy}综合分{best_score:.2f}) timestamp datetime.now().strftime(%Y_%m_%d_%H%M%S) output_file freports/chunk_report_{timestamp}.csv export_csv(all_rows, output_file) print(fCSV报告已生成{output_file}) if __name__ __main__: main()八、运行脚本执行python chunk_lab.py示例输出 GEO切块策略对比报告 策略 chunk数 平均长度 过短 过长 标题上下文 FAQ拆分风险 平均密度 ---------------------------------------------------------------------------------------------------- fixed_size 5 284.20 0 0 20.00% 1 3.20 paragraph 5 263.60 0 0 0.00% 0 3.40 heading 7 238.57 0 0 100.00% 0 3.86 推荐策略heading综合分71.32 CSV报告已生成reports/chunk_report_2026_07_04_153000.csv这份报告说明固定长度切块容易丢标题上下文还可能拆开FAQ。 段落切块语义相对完整但缺少标题上下文。 标题层级切块上下文最好FAQ拆分风险低更适合GEO。九、为什么标题层级切块更适合GEO因为 GEO 内容通常天然是结构化的H1文章主题 H2核心问题 H3具体FAQ 正文答案、证据、边界按标题层级切块后每个 chunk 更像一个完整知识单元# GEO和SEO有什么区别一文讲清AI搜索时代的内容优化逻辑 ## GEO和SEO的区别 | 维度 | SEO | GEO | |---|---|---| | 优化对象 | 搜索结果页 | 生成式AI答案 | | 核心目标 | 排名和点击 | 理解和引用 |这个 chunk 单独拿出来也能知道文章主题是什么当前段落讲什么表格比较什么适合回答哪个问题这就比固定长度切出来的残片更适合 AI 检索和引用。十、踩坑实录切块不是越小越好很多人一听“AI上下文有限”第一反应是那我把内容切小一点。结果切成这样chunk 1: GEO是生成式引擎优化。 chunk 2: 它关注内容是否能被AI理解。 chunk 3: 不是SEO替代品。 chunk 4: 而是扩展。每一块都很短但语义碎了。AI 召回 chunk 2 时只知道“它关注内容是否能被AI理解”。问题是它是谁这就像你在日志里只看到Error: failed但没有 traceId、没有模块名、没有请求参数。短是短了没法定位。十一、切块也不是越大越好另一个极端是整篇文章一块塞进去。问题是检索粒度太粗相关段落被无关内容稀释AI 不容易定位具体答案成本高上下文占用大例如用户只问结构化数据能保证AI引用吗如果召回整篇 5000 字文章AI 要从一堆定义、对比、FAQ、总结里找答案。这就像为了查一个函数参数把整个项目 zip 发给你。理论上答案在里面。但你想辞职。十二、适合GEO的切块原则1. 保留标题上下文推荐格式H1文章标题 H2章节标题 正文内容这样每个 chunk 都知道自己属于哪个主题。2. FAQ问题和答案不要拆开错误切法chunk 1: ### GEO会替代SEO吗 chunk 2: 不会。GEO不是SEO的替代品...正确切法chunk: ### GEO会替代SEO吗 不会。GEO不是SEO的替代品...问题和答案必须在一起。3. 表格尽量整体保留对比表一旦拆开很容易失去意义。尤其是这种表| 维度 | SEO | GEO | |---|---|---| | 优化对象 | 搜索结果页 | AI生成答案 | | 核心目标 | 排名和点击 | 理解和引用 |如果只召回一行核心目标 | 排名和点击 | 理解和引用AI 还要猜这是在比较谁。4. chunk要能独立回答一个小问题每个 chunk 最好对应一个明确问题chunk主题可回答问题什么是GEOGEO是什么GEO和SEO区别GEO和SEO有什么区别常见问题GEO会替代SEO吗总结GEO优化重点是什么这样更符合生成式问答场景。十三、进阶给Markdown写一个GEO切块规范可以在内容生产阶段就约定格式# 文章主标题 ## 一个H2只解决一个核心问题 正文先给结论再给解释再给例子或边界。 ### FAQ问题必须和答案放在一起 答案不要只写一句话至少包含结论和原因。推荐规范每个H2尽量控制在300~800字。 每个H2只解决一个问题。 FAQ问题和答案不跨章节。 对比表不要拆成多段。 总结段保留核心概念。这样后续无论进入搜索索引、向量库还是 RAG 系统都更稳。十四、系统架构GEO切块在完整链路里的位置flowchart TD A[Markdown内容] -- B[结构化解析] B -- C[标题层级切块] C -- D[chunk质量检测] D -- E[向量化/索引] E -- F[用户问题检索] F -- G[召回chunk] G -- H[生成答案] H -- I[引用或不引用]很多 GEO 问题不是发生在生成阶段而是早在切块阶段就埋下了。如果 chunk 本身就是碎的后面检索再强也很难救。这就像数据库表设计烂了后面 SQL 再努力也会痛苦。十五、避坑指南GEO切块别踩这5个坑坑1按固定字数硬切固定字数简单但容易把标题、表格、FAQ切断。适合快速实验不适合直接作为高质量 GEO 内容切块方案。坑2把标题丢掉没有标题上下文的 chunk像没有函数名的代码片段。它可能有用但很难判断用途。坑3只看长度不看语义完整性chunk 长度合理不代表质量高。真正要看是否回答了问题 是否保留上下文 是否有完整结论 是否被切断坑4把FAQ拆碎FAQ 是 GEO 内容里最不应该被拆碎的结构之一。因为它天然就是问题 → 答案拆开就失去价值。坑5不做切块预览上线前至少抽查几个 chunk。不要等 AI 引用错了才发现内容早就被切碎了。十六、下一步行动给你的GEO内容跑一次切块体检可以按下面流程执行flowchart LR A[准备Markdown文章] -- B[运行chunk_lab.py] B -- C[查看三种切块报告] C -- D[检查FAQ是否拆分] D -- E[检查标题上下文] E -- F[选择切块策略] F -- G[进入检索/索引流程]建议先检查这几类页面页面类型为什么优先GEO概念页定义不能被切碎SEO对比页表格不能被拆散FAQ页问题答案必须在一起方法指南页步骤顺序不能乱总结页核心结论要完整保留一个简单标准FAQ拆分风险 0 标题上下文率 80% 过短chunk尽量少 表格尽量完整保留 每个chunk能独立回答一个问题十七、总结GEO不是只写给人看还要切给AI看最后总结一下。很多 GEO 内容不是写得不好而是被切坏了。定义被切断 FAQ被拆开 表格被打碎 标题上下文丢失 答案片段不完整这些问题都会影响 AI 的检索、理解和引用。本文用 Python 实现了一个 GEO 切块策略对比工具完成了固定长度切块段落切块标题层级切块chunk质量评估FAQ拆分风险检测标题上下文检测信息密度评分CSV报告输出推荐更适合GEO的切块策略SEO 时代我们常说内容要写给用户看。GEO 时代还要补一句内容也要切给AI用。一篇文章写得再完整如果切完之后每个 chunk 都像残缺的日志片段那 AI 引用错内容也不奇怪。所以下次做 GEO 内容前别只检查文章本身。也检查一下它被切开以后还能不能把话说明白