公式サイトGEO改修チェックリスト:6層の情報ポイントと実行順序
第六章:実践上級編 · 第103問
公式サイトのコピーがAIに引用されるかどうかを判断するテストは1つだけです:それを単独で抜き出し、他人の回答に貼り付けて、それでも成立するかを見る。
成立すれば、それは引用可能な単位です。成立しなければ、それは単なるマーケティングコピーです。モデルはページ全体を引用せず、文を抜き出します。だから改修の着眼点はページではなく、段落とフィールドにあります。
以下では「何をするか、どの順序で、どう検収するか」の3段構成で説明します。そのままコピーして使えるツールはすべて文末付録にまとめており、独立したチェックリストとして丸ごとコピーできます。
このテストの具体的なやり方
本サイトをそのまま例にします。開いて songbing.com.cn/100wen/、見ながら照合してください。ツールを用意する必要はありません。
ステップ1、3つの質問を書く。 ユーザーがどう尋ねるかをそのまま書き、業界用語に書き換えないでください。
- このサイトは何をしているのか?
- 誰に向いているのか?
- 他のSEOチュートリアルと何が違うのか?
ステップ2、ページ上で文を書き写す。 これらの3つの質問に元々答えている文を探し、そのまま書き写し、一文字も変えないでください。このトップページからは6つ書き写せます:
- 眉標とタイトル:中国語オーガニックトラフィック実践マニュアル · 中国語オーガニックマーケティング100問
- サブタイトル:マーケター、起業家、PR従事者向けの中国語オーガニックトラフィック実践ガイド
- 説明行:各問800-1500字、1つの問題で1つの知識ポイントを徹底解説、独立して読める
- データ行:7大章、103問答、各800+字、毎週継続更新
- セールスポイント:ネット全体で初めてGEO/AEOを体系的に解説する中国語書籍、AIに推薦される方法を伝授
- セールスポイント:全プラットフォーム対応、Baidu+Douyin+Xiaohongshu+Zhihu+微信搜一搜
第三步,把问题和句子接起来读,看读者会不会再追问一句。
| ユーザーの質問 | ページ上の原文 | 読者の第一反応 |
|---|---|---|
| このサイトは何をしているのか | 中国語オーガニックトラフィック実践マニュアル · 中国語非課金マーケティング100問 | 半分理解。マニュアルだと分かるが、「非課金マーケティング」はもう一段考える必要がある |
| このサイトは何をしているのか | マーケター、起業家、PR従事者向けの中国語オーガニックトラフィック実践ガイド | 理解した。合格 |
| 誰に向いているか | 各問800〜1500字、1つの問題で1つの知識点を掘り下げ、単独で読める構成 | 読み方は分かるが、誰に向けて書かれたか分からない。的外れな回答 |
| 読む価値はあるか | 7大章、103問答、各800+字、毎週継続更新 | 規模が分かる。合格 |
| 他のチュートリアルと比較 | GEO/AEOを体系的に解説する中国語初の書籍、AIに推薦される方法を伝授 | 半信半疑。「ネット全体で初」は検証できない |
| 他のチュートリアルと比較 | 百度 + 抖音 + 小红书 + 知乎 + 微信搜一搜 | 網羅性が分かる。合格 |
六つの文のうち四つは使える。これこそが実際の公式サイトの状況だ:全部正しいわけでも全部ダメでもなく、2. 三箇所で詰まっている。
詰まった二つの文は、どちらも段落全体を書き直す必要はなく、一つの言い方を変えるだけで済む:
原文:ネット全体で初めてGEO/AEOを体系的に解説する中国語書籍
問題:「ネット全体で初」は検証できず、AIも言い換えられず、読者も照合できない
書き換え:第3章の全15問でAI検索を特集し、引用メカニズムからllms.txt設定までコピー可能なテンプレートを掲載
「非課金マーケティング」も同様。この言葉は業界内では通じるが、初めて来た読者には未知語であり、前に一句説明を添えれば十分:
原文:中国語非課金マーケティング100問
書き換え:中国語非課金マーケティング100問 —— 広告を出さず、コンテンツと検索でオーガニックトラフィックを獲得
判断基準はただ一つ:読者がこの一文を読んで、自分で判断できるかどうか。 この二箇所を直せば、六つの文すべてが単独で成立する。総変更量は二つの短い文であり、一度の改版ではない。
最後の結果を先に明確にしておく:もし三つの問題がページ上で一文も引用できなければ、これ以上テストする必要はない。このページには回答ユニットが欠けており、以下の六層リストに従って直接補完すればよい。
1. 六層情報ポイント
モデルが一つの問題に答えるには四種類の原料が必要だ:あなたは誰か、何ができるか、なぜ信頼できるか、他者とどこが違うか。公式サイトにいずれかが欠けていれば、その類の問題はあなたとは無関係になる。
| 階層 | 何を解決するか | 欠けるとどうなるか |
|---|---|---|
| 1 実体アイデンティティ | あなたがどの会社かを確認する | 同名ブランドと混同される、あるいは全く言及されない |
| 2 能力の境界 | あなたが答えかどうかを判断する | 漠然としか言及されず、推薦枠に入れない |
| 3 権威ある証拠 | あなたを使ってよいか判断する | 内容は採用されるが信頼できないと注記される |
| 4 差別化比較 | 選定比較を行う | 選定問題にあなたの位置がない |
| 5 コンバージョンパス | 一つのアクションに落とし込む | 言及されるがリードを生まない |
| 6 機械可読 | 最初の5層を読ませる | コンテンツがAIにとって存在しないのと同じ |
最初の3層は前提、後の3層は増幅器。
第1層:実体アイデンティティ
補うべき情報:会社の正式名称、ブランド略称、英語名、旧称の4者の対応関係;設立時期、登記地、統一社会信用コード;主力事業の一文定義;ブランド名と製品名の所属関係;名称・住所・電話の唯一バージョン;工商、百科事典、公众号を指す外部アーカイブリンク。
見落としやすい2つの要件。1つはすべてプレーンテキストで、画像や図表にしないこと。2つ目は4か所の記述が逐字一致していること。工商情報、百科事典の紹介、公众号の紹介、公式サイトのフッターで書き方が違うと、実体の信頼度が引き下げられる。
この層が最も飛ばされやすい。理由は「こんな工商情報を誰が見るのか」。AIが見る。
第2層:能力の境界
各事業ごとに標準回答段落を書き、6項目を含める:定義文、適用対象、不適用の状況、成果物、期間、価格方式。さらにサービス地域と参入条件、制限と前提条件。
「不適用」は最も直感に反するが、最も価値がある。モデルが選定問題に答える際、通常は推薦理由と除外理由を同時に示す必要がある。サイト全体に除外文がなければ、自分を候補から除外しているのと同じ。
もう2つ最も飛ばされやすいのは価格と制限条件。具体的な価格を公開しなくてもよいが、AIに語る材料を与えなければ、「要相談」としか答えられない。
第3層:権威の証拠
補うべき:著者署名と著者ページ、資格証明書番号と発行機関、特許番号、検査報告書番号、顧客事例の公開可能データ、データ出典、ページ更新日時、編集とファクトチェックの方針、免責事項。
この層がAIがあなたのコンテンツを使う勇気を持てるかを決める。すべての数字には出典、定義、時期の三点セットが必要。
不合格:コンバージョン率が顕著に向上。
合格:トライアルコンバージョン率が2.1%から3.4%に向上(定義:登録から課金まで、14日間ウィンドウ、出典:内部トラッキング、2026年3月)。
声明について、国内サイトではほとんど誰もやっていない。やれば差別化になる。
第4層:差別化比較
補足が必要:比較表、能力マトリクス、明確な「私たちがやらないこと」。
比較対象はソリューションの類型として書き、競合他社名は挙げない。法的リスクを避けつつ、ユーザーの実際の比較文脈もカバーできる。比較には必ずコストを書くこと。利点だけを語る比較はマーケティングコンテンツと判定され、回答に採用されない。
第五層:コンバージョンパス
収束させるのであって、増やすのではない:1ページにつきメインCTAは1つだけ。連絡先は全チャネルで統一し、コピー可能なプレーンテキストで提供。対応コミットメント(いつまでに、誰が返信するか)を明記。アフターサービスと保証条項を整備する。
連絡先をアイコンやQRコードだけにすると、AIは転述できる情報を一切得られない。
第六層:機械可読性
優先順位に従って実行:サーバーサイドレンダリングまたは静的生成;セマンティックHTML(1ページ1つのH1、H2は疑問文、本文はmainとarticle内);構造化データ;robots.txtでAIクローラーの用途を区別;llms.txt;sitemapの区分;canonicalとhreflangの管理;404とリダイレクトの管理;画像altと重要データのテキスト化。
もし一つしかできないなら、サーバーサイドレンダリングを行うこと。ページの本文がクロールできなければ、残り八項目はすべて無意味になる。
構造化データはタグであり、コンテンツではない。ページにない文は、マークアップに書いても生み出せない。逆に、ページに存在しない情報をマークアップするのは不正であり、モデルのクロス検証で反噬を受ける。
2. 改修順序
| レベル | コンテンツ | 工期 |
|---|---|---|
| P0 | SSRまたは静的化;会社概要にエンティティ身元の段落を追加;各ページに一文定義を追加;robotsとAIクローラー戦略;OrganizationとArticleのマークアップ | 2週間 |
| P1 | Q&A形式の小節への再構築;著者ページ、資格ページ、事例ページ;llms.txt;sitemapとhreflangの管理 | 1ヶ月 |
| P2 | 比較マトリクス;価格情報;モニタリング体制 | 継続 |
P0内部にも順序がある。レンダリング方式を変えなければ、後は見えないページに内装を施すだけになる。
3. 検収
GEOには管理画面データがないため、質問のシミュレーションと記録しかない。40〜60件の質問を作り、四種類に分ける:カテゴリ需要(誰が某かをできるか)、選型比較(AとBどちらが適切か)、シーンの課題(ある具体的な問題をどう解決するか)、ブランド直查(この会社は何をしているか、信頼できるか)。
主要モデルで独立した新規セッションを使い1件ずつ実行し、同じ質問を最低2回、回答原文を記録する。
| 指標 | 定義 |
|---|---|
| 言及率 | ブランド名が出現した質問数 / 総質問数 |
| 首位言及率 | ブランドが最初の位置にランクインした質問数 / 総質問数 |
| 優先推薦率 | 明確に推薦された、またはより適切と表現された質問数 / 総質問数 |
| 公式サイト引用率 | 公式サイトを引用またはリンクした質問数 / 総質問数 |
| 事実正確率 | ブランド関連の事実記述が正しい質問数 / 言及された質問数 |
このうち公式サイト引用率は公式サイト改修の最も直接的な指標であり、他の項目はサイト外コンテンツの影響も受ける。
必ず3〜5個のモデルをカバーすること。根拠は2026年9月のChatGPT、Copilot、Google、Perplexityを対象とした観測研究である:96.4%の被引用URLは単一のエンジンにのみ出現し、クロスエンジンURLの重複度の中央値は0である(論文 Scoring With the Engine、arXiv:2609.22655)。1つのモデルだけをテストして結論を出すのは、テストしていないのと同じだ。
記録表で最も価値のある列は「引用された原文」である。これは公式サイトのどの文が実際に選ばれたかを教えてくれる。この文をより前方の位置、より構造化されたブロックに移動させることが、最も効果的な単点最適化である。
ペース:改修前にベースラインを残し、完了後1ヶ月と3ヶ月に各1回再テストし、その後は毎月固定の日に実行する。
4. よくある6つの誤判断
1. 構造化データを追加すれば完了と思う。 アノテーションは機械認識を解決するが、コンテンツの有無は解決しない。抽出可能な文がなければ、どれだけ完全にマークしても引用されない。
2. トップページだけを最適化する。 引用されるのは具体的なページである。製品ページ、Q&Aページ、事例ページこそが主体であり、トップページはエンティティの身元を明確にする役割のみを担う。
3. 「業界トップ」を差別化と見なす。 こうした言葉は持ち出せず、検証もできない。言っていないのと同じだ。
4. AIクローラーを開放すれば収録されると思い込む。 それは入場券にすぎず、引用されるかどうかはコンテンツの品質で決まる。
5. robotsを機密保持の手段と見なす。 それは権限システムではない。本当に機密にしたいなら公開ウェブページに置くべきではない。
六、一度直せば終わりだと思う。 モデルは変わり、競合も変わり、あなたの情報も古くなる。これは保守作業であり、プロジェクトの成果物ではない。
よくある質問
Q:公式サイトの改修はGEOにどれほど効果がありますか?
A:公式サイトはAIのクロス検証における基準情報源だ。改修前、AIが手にするのは本文を読み取れず、エンティティも認識できないページで、引用確率は極めて低い。改修後は、少なくとも召回と摘取の条件を備える。ただし注意すべきは、公式サイトの改修は「引用の根拠がある」状態を解決するだけで、実際に引用されるかどうかはサイト外のコンテンツエコシステムにもかかっている。具体的な向上幅を約束できる方法は存在しない。
Q:公式サイトだけを改修すれば十分ですか?
A:不十分だ。公式サイトは主戦場だが、AIは複数の情報源を総合して判断する。公式サイトを改修するのはAIに引用の根拠を持たせるためであり、百科、知乎、業界メディア上のコンテンツが引用するかどうかを左右する。両者の主張は一致していなければならず、そうでなければクロス検証は「情報が信頼できない」という結論を導き出す。
Q:改修後どれくらいで効果が見えますか?
A:インデックス層では通常2〜4週間でクロールの変化が見られる。回答層での引用は、一般的に1〜3か月かかり、複数のモデルをまとめて観察する必要がある。モデルの回答は変動が大きく、単一の質問の差異を結論にしてはいけない。連続する2周期のトレンドを見ること。
Q:テンプレートサイトでもGEO最適化はできるか?
A:変更できればできる。本当のボトルネックは2つある。レンダリング方式をサーバーサイド出力に変更できるか、バックエンドでページのheadをカスタマイズできるかだ。両方とも不可なら、第六層のパイプラインはほぼ機能しない。サイト構築プランの変更を検討すべきだ。残りの五層はコンテンツの問題であり、サイト構築方式とは無関係だ。
本記事は「中国語オーガニックマーケティング100問」シリーズの一部です。完全な目次を見る →
付録:GEO公式サイト診断チェックリスト(全文コピー可)
以下の内容は全文コピーして、 geo-site-checklist.md として保存し、単独で使用するか、自己チェック表として印刷できる。文中の {{}} はプレースホルダーであり、使用時に置き換える。
0. 使用方法
- まず第1節の自己診断コマンドを実行し、5分で技術基盤に問題がないか判断する。
- 第2節の六層チェック項目に照らして一つずつチェックし、不足を記録する。
- 第7節の優先度に従ってスケジュールを組む。P0がゼロになる前にP1に手を付けてはいけない。
- テンプレートは直接利用可能:構造化データは第3節、robotsは第4節、llms.txtは第5節、コピー文の句式は第6節を参照。
- 公開後は第8節に従ってモニタリングを構築する。
1. 自己診断コマンド
本文は HTML 内にあるか
curl -s https://{{DOMAIN}}/ | grep -E '<div id="(root|app|__next)"></div>'
出力があればクライアントサイドレンダリングのみと判断。出力がなければ本文の長さを測定:
curl -s https://{{DOMAIN}}/ | sed 's/<[^>]*>//g' | tr -d ' \n' | wc -m
500 未満なら、本文が HTML に入っていないとほぼ判定できる。
H1 と構造化データ
curl -s https://{{DOMAIN}}/ | grep -c '<h1'
curl -s https://{{DOMAIN}}/ | grep -o '"@type" *: *"[^"]*"' | sort -u
1 件目は 1 であるべき。2 件目が空なら、ページに構造化データが一切ないことを示す。
AI クローラーが誤ってブロックされていないか
curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot" https://{{DOMAIN}}/
curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0 (Macintosh) AppleWebKit/537.36 Chrome/124.0 Safari/537.36" https://{{DOMAIN}}/
2 つのステータスコードが一致しない場合、WAF またはアンチクロールルールが AI クローラーをブロックしている。これは robots.txt の問題よりよくある。
2 つの固定アドレス
https://{{DOMAIN}}/robots.txt
https://{{DOMAIN}}/llms.txt
それぞれ確認:robots が sitemap を宣言しているか、AI クローラールールを明示的に書いているか;llms.txt が存在するか。
2. 六層チェック項目
【第一層 実体アイデンティティ】
[ ] 会社の正式名称
[ ] ブランド略称
[ ] 英語名(あれば記入)
[ ] 旧名称(あれば記入)
[ ] 設立日
[ ] 登記地
[ ] 統一社会信用コード
[ ] 主業務の一文定義(40 字以内)
[ ] ブランド名と製品名の所属関係、製品ページに出品元を明記
[ ] 名称・住所・電話、サイト全体で唯一のバージョン
[ ] 外部権威档案へのリンク(工商、百科、公众号、GitHub、LinkedIn)
[ ] 以上はすべてプレーンテキストで、画像ではない
【第二層 能力の境界】
[ ] 定義文:何であるか、一文で
[ ] 対象者
[ ] 不適用な状況(代替案の提案を含む)
[ ] 成果物リスト
[ ] 納品期間と前提条件
[ ] 価格設定方式または価格設定ロジック
[ ] サービス地域、言語、参入条件
[ ] 制限と前提条件
【第三層 権威ある証拠】
[ ] 著者署名 + 著者ページ(経歴、資格、外部アイデンティティリンク)
[ ] 資格証明書番号と発行機関
[ ] 特許番号
[ ] 検査報告書番号と検査機関
[ ] 顧客事例:業界、背景、公開可能なデータ
[ ] 各数値に出典、定義、時期を付記
[ ] ページに最終更新日時を表示
[ ] 重要ページに改訂履歴を保持
[ ] 編集とファクトチェックの方針
[ ] 免責事項
【第四層 差別化比較】
[ ] 比較表(比較するプランの種類であり、競合他社を名指ししない)
[ ] 能力マトリクス(機能 / シーン / 規模の区分)
[ ] 「私たちがやらないこと」を明記
[ ] 比較においてコストとトレードオフを明記
【第五層 コンバージョンパス】
[ ] 1 ページに主 CTA は 1 つだけ
[ ] 連絡先は全チャネルで一致
[ ] 連絡先にコピー可能なプレーンテキストあり
[ ] 対応約束:いつまでに、誰が返信するか
[ ] アフターサービスと保証条項
【第六層 機械可読】
[ ] サーバーサイドレンダリングまたは静的生成
[ ] 各ページに H1 は 1 つ
[ ] H2 に実際の疑問文を使用
[ ] 本文は main / article 内にある
[ ] Organization 構造化データ
[ ] Article 構造化データ
[ ] BreadcrumbList 構造化データ
[ ] Product / Service(ページタイプ別)
[ ] FAQPage(表示されている Q&A のみマークアップ)
[ ] robots.txt に AI クローラーを明示的に宣言
[ ] llms.txt を公開
[ ] sitemap を分割し lastmod を実際の値に
[ ] canonical を統一
[ ] hreflang が自己整合的に閉じている(多言語サイト)
[ ] ソフト 404 なし
[ ] 画像 alt が完全
[ ] 重要データにテキスト版あり
3. 構造化データテンプレート
組織エンティティ(サイト全体の各ページに注入)
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://{{DOMAIN}}/#organization",
"name": "{{ブランド正式名称}}",
"alternateName": ["{{ブランド略称}}", "{{英語名}}"],
"legalName": "{{工商登記正式名称}}",
"url": "https://{{DOMAIN}}/",
"logo": {
"@type": "ImageObject",
"url": "https://{{DOMAIN}}/logo.png",
"width": 512,
"height": 512
},
"foundingDate": "{{2021-03-15}}",
"description": "{{一文での定義、会社概要の冒頭段落と完全に一致}}",
"address": {
"@type": "PostalAddress",
"streetAddress": "{{詳細住所}}",
"addressLocality": "{{都市}}",
"addressRegion": "{{省}}",
"postalCode": "{{郵便番号}}",
"addressCountry": "CN"
},
"contactPoint": [{
"@type": "ContactPoint",
"telephone": "{{+86-xxx}}",
"contactType": "sales",
"areaServed": "CN",
"availableLanguage": ["zh-CN", "en"]
}],
"sameAs": [
"{{工商登記アーカイブのリンク}}",
"{{百科事典のリンク}}",
"{{公众号またはSNSのリンク}}",
"{{GitHub / LinkedIn / 採用ページ}}"
],
"knowsAbout": ["{{中核能力1}}", "{{中核能力2}}", "{{中核能力3}}"]
}
記事ページ
{
"@context": "https://schema.org",
"@type": "Article",
"@id": "https://{{DOMAIN}}/{{PATH}}#article",
"headline": "{{タイトル、110文字以内}}",
"description": "{{80〜140字の要約、ブランド名と能力定義を一文含む}}",
"image": ["https://{{DOMAIN}}/{{カバー画像}}"],
"datePublished": "{{2026-10-23T09:00:00+08:00}}",
"dateModified": "{{2026-10-23T09:00:00+08:00}}",
"inLanguage": "zh-CN",
"author": {
"@type": "Person",
"@id": "https://{{DOMAIN}}/author/{{slug}}#person",
"name": "{{著者名}}",
"jobTitle": "{{役職}}",
"url": "https://{{DOMAIN}}/author/{{slug}}"
},
"publisher": { "@id": "https://{{DOMAIN}}/#organization" },
"isPartOf": { "@id": "https://{{DOMAIN}}/#website" },
"about": [{ "@type": "Thing", "name": "{{主題語}}" }],
"citation": ["{{引用した外部ソースのURL}}"]
}
publisher.@id 組織エンティティの @id と完全に同一である必要があります。そうでなければ、2つのノードはグラフ内で分断されます。
Q&Aブロック
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [{
"@type": "Question",
"name": "{{ユーザーが実際に尋ねる質問}}",
"acceptedAnswer": {
"@type": "Answer",
"text": "{{40〜120文字、結論を先に、前置きなし}}"
}
}]
}
4. robots.txt テンプレート
# 検索系:必ず許可
User-agent: OAI-SearchBot
Allow: /
User-agent: ChatGPT-User
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: Baiduspider
Allow: /
User-agent: YisouSpider
Allow: /
# 学習系:ブランド戦略に応じて判断
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: Bytespider
Allow: /
# 機密パスはすべて拒否
User-agent: *
Disallow: /admin/
Disallow: /api/
Allow: /
Sitemap: https://{{DOMAIN}}/sitemap.xml
CSS と JS リソースを Disallow しないでください。一部のエンジンはページ構造の判断にこれらを必要とします。
5. llms.txt テンプレート
# {{ブランド名}}
> {{一文定義:A 業界の B 顧客に C サービスを提供し、D 課題を解決する。併せて設立時期と所在地を明記。}}
## 主要ページ
- [{{製品名}}](https://{{DOMAIN}}/{{path}}): {{このページが答える質問}}
- [{{ソリューション}}](https://{{DOMAIN}}/{{path}}): {{一文説明}}
- [料金](https://{{DOMAIN}}/pricing): {{料金体系を一文で}}
## 権威情報
- [会社概要](https://{{DOMAIN}}/about): 会社正式名称、設立時期、資格、チーム
- [導入事例](https://{{DOMAIN}}/cases): {{業界と公開可能な成果}}
- [よくある質問](https://{{DOMAIN}}/faq): {{対象トピック}}
## コンテンツ欄
- [{{欄名}}](https://{{DOMAIN}}/{{path}}/): {{欄の位置づけ}}
## 範囲と声明
- サービス地域:{{...}}
- 適用外の場面:{{...}}
- コンテンツ更新:{{頻度}}
## Optional
- [{{二次ページ}}](https://{{DOMAIN}}/{{path}}): 補足資料
冒頭の blockquote は段落ごと引用されるため、単独で成立する必要があります。各リンク後の説明文は省略できません。
6. 回答ユニットの文型
定義文
{{ブランド/製品}}は{{カテゴリ}}であり、{{対象}}に{{能力}}を提供し、{{課題}}の解決に用います。
境界文
{{条件A}}、{{条件B}}の場面に適します。{{反条件}}の場合は、{{代替案}}のほうが適しています。
比較文
{{プランタイプA}}と比べ、{{本製品}}は{{次元}}において{{差異}}があり、代償は{{トレードオフ}}です。
データ文
{{指標}}は{{数値}}(出典:{{機関または内部統計}}、定義:{{定義}}、時点:{{年月}})。
自己チェック基準:形容詞をすべて削除しても、文が成立するか。削除後に「私たちは高品質なサービス提供に努めています」しか残らないなら、それはマーケティングコピーです。
段落規範:2〜4文、結論は最初の文に。重要情報は表で。時期は絶対値で書き、「最近」とは書かない。数値には単位と定義を付ける。重要データを画像だけに置かない。
7. 優先度と工期
| レベル | コンテンツ | 工期 |
|---|---|---|
| P0 | SSR または静的化、エンティティ身分段、各ページの一文定義、robots と AI クローラー戦略、Organization と Article のマークアップ | 2週間 |
| P1 | Q&A形式の小節への再構築;著者ページ、資格ページ、事例ページ;llms.txt;sitemapとhreflangの管理 | 1ヶ月 |
| P2 | 比較マトリクス;価格情報;モニタリング体制 | 継続 |
P0 内部順序:レンダリング → エンティティ → 定義文 → クローラー戦略 → 構造化データ。前が整っていなければ、後は無効。
8. 受け入れ記録
質問セットの4種類の文型
カテゴリ需要:国内で{{カテゴリ}}を手がけるサービスプロバイダーにはどのようなものがあるか
選定比較:{{プランA}}と{{プランB}}のどちらが{{規模}}の{{対象}}に適しているか
シーンの課題:{{具体問題}}をどう解決するか
ブランド直接検索:{{ブランド}}は何をしているのか、信頼できるか
5つの指標
| 指標 | 定義 |
|---|---|
| 言及率 | ブランド名が出現した質問数 / 総質問数 |
| 首位言及率 | ブランドが最初の位置にランクインした質問数 / 総質問数 |
| 優先推薦率 | 明確に推薦された、またはより適切と表現された質問数 / 総質問数 |
| 公式サイト引用率 | 公式サイトを引用またはリンクした質問数 / 総質問数 |
| 事実正確率 | ブランド関連の事実記述が正しい質問数 / 言及された質問数 |
記録表のフィールド
日付 | モデル | 質問ID | 質問テキスト | ネット接続有無 | 言及有無 | 順位 | 推薦有無 | 感情 | 公式サイト引用有無 | 引用ページURL | 引用された原文 | 事実誤り
「引用された原文」は最も価値のある列だ:どの文が実際に選ばれたかが分かる。この文をより前方の、より構造化されたブロックへ移すことが、最も効果的な単点最適化となる。
ペース:改修前にベースラインを残し、完了後1ヶ月と3ヶ月にそれぞれ再測定し、その後は毎月固定の日に実行する。少なくとも3〜5モデルをカバーすること。
付:完全な SKILL ドキュメント(ワンクリックでコピー可能)
上記の付録は人が照合するためのチェックリストだが、ここにあるのは AI ツールに直接読み込める完全な SKILL だ。ワークフロー定義、6層チェックリスト、すべての構造化データテンプレート、AI クローラー戦略、回答ユニットの文型、受け入れ方法、そして標準ライブラリのみに依存するヘルスチェックスクリプトを含む。
コピー後、ファイル先頭の <!-- ===== 文件名 ===== --> コメントに従って対応するファイルに分割すればよい。ディレクトリ構造は以下の通り:
geo-site-revamp/
├── SKILL.md
├── scripts/audit_site.py
└── references/
├── audit-checklist.md
├── schema-templates.md
├── ai-crawler-policy.md
├── answer-unit-patterns.md
└── monitoring.md
<!-- ===== SKILL.md ===== -->
---
name: geo-site-revamp
description: 审计并改造企业官网以满足生成式引擎优化(GEO)要求,让品牌在 AI 搜索与对话式回答中被提及、被引用。当用户要做 GEO 官网改造、AI 搜索优化、官网结构化数据、llms.txt、AI 爬虫策略、AEO、品牌在 AI 回答中的可见度,或需要对站点做 GEO 体检时使用。包含可直接执行的站点体检脚本、六层信息点清单、JSON-LD 模板、答案单元句式与验收指标。
---
# GEO 官网改造
## 核心判断
被大模型引用的最小单位不是页面,而是一个能被单独摘走的句子或字段。改造的一切动作都服务于:让官网存在足够多、足够准、足够结构化的「答案单元」。
两类信息点必须同时补:
- **事实型**:可结构化、可交叉校验的客观字段(实体信息、参数、价格、资质)。
- **叙述型**:可整段摘录的定义句、边界句、对比句、数据句。
只补 schema 不改正文,不会被引用;只改正文不加结构化数据,召回效率低。
## 工作流
### 1. 确定范围
确认目标域名、主要语言、业务类型(B2B 服务 / 电商 / 内容站 / 多语言站),以及是否已有 sitemap、robots、llms.txt。业务类型决定第 2、4 层的具体写法。
### 2. 跑体检
```bash
python3 scripts/audit_site.py https://example.com --max-pages 5 --json geo-audit.json
```
脚本检查:robots 与各 AI 爬虫策略、llms.txt、sitemap、每页 H1/H2 数量、JSON-LD 类型、canonical、hreflang、alt 缺失、正文长度、纯 CSR 迹象,并用 GPTBot UA 复抓首页对比是否被反爬误拦。输出按 P0/P1/P2 排序的改造项。
脚本只覆盖第 6 层(机器可读载体)和第 1 层(实体 schema)。第 2-5 层是内容问题,必须人工读页面判断。
### 3. 逐层诊断
按 `references/audit-checklist.md` 的六层顺序过一遍,产出缺口表:
1. 实体身份:名称、成立时间、注册地、一句话定义、NAP、sameAs。
2. 能力边界:定义句、适用与不适用、交付物、计价、限制条件。
3. 权威证据:作者页、资质编号、案例数据、更新时间、事实核查政策。
4. 差异化对比:对比表、能力矩阵、「我们不做什么」。
5. 转化路径:唯一 CTA、可复制联系方式、响应承诺。
6. 机器可读:SSR、语义化 HTML、schema、robots、llms.txt、sitemap、canonical/hreflang。
### 4. 改造与产出
按 P0 → P1 → P2 推进。写代码时遵循:
- schema 按 `references/schema-templates.md` 注入,全站用同一个 `@id` 串成图;只标记页面真实可见的内容。
- 页面文案按 `references/answer-unit-patterns.md` 重写,一页八问必须答到五项以上。
- robots 与 llms.txt 按 `references/ai-crawler-policy.md` 落地,配置后用真实 UA 实测状态码与正文长度。
- 纯 CSR 站点必须先改服务端渲染或静态生成,其余优化在此之前没有意义。
### 5. 验收
按 `references/monitoring.md` 建品牌问题集并留基线。验收看四项:体检 P0 归零、官网引用率相对基线提升、事实准确率 ≥90%、核心品类问题能被官网信息覆盖。
## 资源索引
| 文件 | 用途 |
|---|---|
| `scripts/audit_site.py` | 站点 GEO 体检,输出优先级化改造项 |
| `references/audit-checklist.md` | 六层信息点清单、优先级、常见误判 |
| `references/schema-templates.md` | Organization/WebSite/Article/Product/FAQPage/Breadcrumb JSON-LD 模板 |
| `references/ai-crawler-policy.md` | AI 爬虫清单、robots 模板、llms.txt 模板 |
| `references/answer-unit-patterns.md` | 五类可引用的句式模板与段落规范 |
| `references/monitoring.md` | 问题集设计、指标定义、记录表、归因纪律 |
## 输出规范
- 诊断结果按「层级 → 缺口 → 改造动作 → 优先级」四列呈现,不要只给问题清单。
- 涉及多语言站时,检查各语言的实体信息是否一致,hreflang 是否闭合。
- 不要建议伪造评分、虚构客户、编造数据,AI 交叉验证会反噬品牌。
- 不要承诺「做了就能被引用」,GEO 只能提高概率,答案由模型侧决定。
<!-- ===== references/audit-checklist.md ===== -->
# 官网 GEO 改造六层信息点清单
改造目标不是「让爬虫收录页面」,而是「让大模型在回答某类问题时,愿意用你的官网内容当依据,并在答案里点到你的名字」。
被引用的最小单位不是页面,是**一个可以被直接摘走的句子或字段**。所以清单按两类信息点组织:
- **事实型**:可结构化、可交叉校验的客观字段(名称、地址、参数、价格、资质)。
- **叙述型**:可被整段摘录的结论句、定义句、对比句(是什么、适合谁、和谁不同)。
大多数官网的现状是事实散落、叙述空洞,两类都缺。
---
## 第 1 层 实体身份:AI 要能确认「你是哪家公司」
优先级最高且最常被忽略。大模型把品牌当作知识图谱中的一个实体,找不到唯一指代就会混同或干脆不提。
| 信息点 | 常见问题 | 改造要求 |
|---|---|---|
| 公司全称 / 简称 / 英文名 / 曾用名 | 全站只有品牌简称 | 「关于我们」首段一句话讲清四者对应关系 |
| 成立时间 / 注册地 / 统一社会信用代码 | 完全没有 | 纯文本列出,不要做成图片 |
| 主营一句话定义 | 「致力于打造生态闭环」 | 换成「我们为 A 行业的 B 客户提供 C 服务,解决 D 问题」 |
| 品牌名与产品名的隶属关系 | 产品页不提出品方 | 每个产品页标注「由 X 出品」 |
| NAP(名称/地址/电话) | 页脚、联系页、招聘页三处不一致 | 收敛为全站唯一版本 |
| sameAs 外部权威档案 | 无 | 工商、百科、公众号、GitHub、领英、招聘页互指 |
硬要求:外部档案上的说法必须与官网**逐字一致**。AI 交叉验证时不一致会降低实体置信度。
## 第 2 层 能力边界:AI 要能判断「你是不是这个问题的答案」
用户问 AI 时最常见的形式是「谁能做某件事」。每项业务都要有一份「标准答案段」,而不是营销文案。
- **定义句**:是什么,一句话,不超过 40 字。
- **适用对象**:明确「适合谁」和「不适合谁」。有边界的表述才可能被引用,因为 AI 要回答「是否适合我」。
- **交付物与周期**:细到可以列清单。
- **计价方式**:区间或计价逻辑。可以不公开价格,但不能让 AI 无话可说。
- **服务区域 / 语言 / 准入条件**。
- **限制与前置条件**:反直觉但关键,它是可信度的来源。
## 第 3 层 权威证据:AI 要能回答「凭什么信你」
- 作者或专家署名 + 个人页(履历、资质、可验证的外部身份)。
- 资质证书、专利、检测报告:写编号与发证机构,不要只放扫描图。
- 客户案例:行业、项目背景、可公开的结果数据。
- 数据出处:数字标注来源、统计口径、时间。
- 页面显示「最后更新时间」,关键页有修订记录。
- 编辑与事实核查政策、免责声明(国内站点几乎都没做,做了就是差异化)。
## 第 4 层 差异化对比:AI 要能回答「和别家比怎么样」
AI 回答选型类问题必然做比较。官网没有对比信息,AI 就去第三方来源拼凑,而第三方评价你控制不了。
- 与同类方案的对比表(对比「品类/方案类型」,不点名对手)。
- 能力矩阵:按功能、场景、规模分档。
- 明确写下「我们不做什么」「什么场景下我们不是最优解」。
## 第 5 层 转化与信任路径:AI 的答案要能落到一个动作
- 唯一首选行动入口:咨询 / 试用 / 报价 / 预约,一页只给一个主 CTA。
- 联系方式全渠道一致,提供可复制的纯文本(不要只有图标或二维码)。
- 服务响应承诺:多久回、谁回。
- 售后与保障条款。
## 第 6 层 机器可读载体:把前五层送出去
按优先级:
1. **服务端渲染或静态生成**。AI 爬虫普遍不执行 JS,纯 CSR 的正文等于不存在。P0。
2. **语义化 HTML**:每页一个 H1,H2 用真实问题句式,正文在 `main`/`article` 内。
3. **结构化数据**:`Organization`(含 `@id`、`sameAs`)、`WebSite`、`Article`(`author`/`datePublished`/`dateModified`)、`Product`/`Offer`、`FAQPage`、`HowTo`、`BreadcrumbList`。用 `@id` 把作者、发布方、主站实体串成一张图,避免孤岛。
4. **robots.txt 区分 AI 爬虫用途**(检索 vs 训练),见 `ai-crawler-policy.md`。
5. **llms.txt / ai.txt**:站点摘要 + 关键页面索引。
6. **sitemap.xml**:分语言、分内容类型,`lastmod` 真实。
7. **canonical 与 hreflang 治理**:多语言站必须自洽闭合,否则各语言版本互相镜像。
8. **404 与重定向治理**:软 404 会拖低站点质量判定。
9. **alt 与表格文本化**:关键数据不能只存在于图片里,AI 读不到图里的数字。
---
## 优先级与工期
| 级别 | 内容 | 工期 |
|---|---|---|
| P0 | SSR/静态化、实体身份段、每页一句话定义、robots 与 AI 爬虫策略、Organization 与 Article schema | 2 周 |
| P1 | 问答式小节重构、权威证据页(作者/资质/案例)、llms.txt、sitemap 与 hreflang 治理 | 1 个月 |
| P2 | 对比矩阵、计价信息、监测机制 | 持续 |
## 验收
- 体检脚本 P0 归零,P1 逐项消解:`scripts/audit_site.py <站点域名>`
- 品牌问题集的官网覆盖率(见 `monitoring.md`)
- 各模型下的提及率、首位提及率、首选推荐率、正负面提及率的变化
## 常见误判
- 加了一堆 schema 但正文没有可引用的句子 → 仍不会被引用。schema 是标签,不是内容。
- 只优化首页 → AI 引用的是具体页面,产品页、问答页、案例页才是主战场。
- 把「行业领先」「赋能」当差异化 → 这类词无法被摘取,等于没说。
- 给 AI 爬虫开白就等于被收录 → 只是入场券,能否被引用由内容质量决定。
<!-- ===== references/schema-templates.md ===== -->
# JSON-LD 模板(GEO 用)
原则:**全站一张图**,不是一堆碎片。用 `@id` 让文章、作者、发布方、主站互相指向同一个实体节点。
把占位符(`{{...}}`)替换后直接内联到 `<head>` 或 `<body>` 末尾。
## 1. 组织实体(全站每页注入,P0)
```json
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "https://{{DOMAIN}}/#organization",
"name": "{{公司全称}}",
"alternateName": ["{{品牌简称}}", "{{英文名}}"],
"legalName": "{{工商登记全称}}",
"url": "https://{{DOMAIN}}/",
"logo": {
"@type": "ImageObject",
"url": "https://{{DOMAIN}}/logo.png",
"width": 512,
"height": 512
},
"foundingDate": "{{YYYY-MM-DD}}",
"description": "{{一句话定义:为 A 行业的 B 客户提供 C 服务,解决 D 问题}}",
"address": {
"@type": "PostalAddress",
"streetAddress": "{{详细地址}}",
"addressLocality": "{{城市}}",
"addressRegion": "{{省}}",
"postalCode": "{{邮编}}",
"addressCountry": "CN"
},
"contactPoint": [{
"@type": "ContactPoint",
"telephone": "{{+86-xxx}}",
"contactType": "sales",
"areaServed": "CN",
"availableLanguage": ["zh-CN", "en"]
}],
"sameAs": [
"{{工商档案链接}}",
"{{百科链接}}",
"{{公众号或社媒链接}}",
"{{GitHub / 领英 / 招聘主页}}"
],
"knowsAbout": ["{{核心能力1}}", "{{核心能力2}}", "{{核心能力3}}"]
}
```
要点:
- `legalName` 与 `name` 分开,工商全称和对外品牌名不同时尤其重要。
- `sameAs` 至少 3 条,且每个链接页面上必须能找到公司全称。
- `knowsAbout` 填能力词,这是引擎判断「你属于哪个领域」的直接信号。
## 2. 站点节点
```json
{
"@context": "https://schema.org",
"@type": "WebSite",
"@id": "https://{{DOMAIN}}/#website",
"url": "https://{{DOMAIN}}/",
"name": "{{站点名}}",
"inLanguage": "zh-CN",
"publisher": { "@id": "https://{{DOMAIN}}/#organization" }
}
```
## 3. 文章页
```json
{
"@context": "https://schema.org",
"@type": "Article",
"@id": "https://{{DOMAIN}}/{{PATH}}#article",
"headline": "{{标题,<=110 字符}}",
"description": "{{80-140 字摘要,含实体名与一句能力定义}}",
"image": ["https://{{DOMAIN}}/{{封面图}}"],
"datePublished": "{{2026-01-01T09:00:00+08:00}}",
"dateModified": "{{2026-01-01T09:00:00+08:00}}",
"inLanguage": "zh-CN",
"author": {
"@type": "Person",
"@id": "https://{{DOMAIN}}/author/{{slug}}#person",
"name": "{{作者名}}",
"jobTitle": "{{职位}}",
"url": "https://{{DOMAIN}}/author/{{slug}}",
"sameAs": ["{{作者可验证的外部身份链接}}"]
},
"publisher": { "@id": "https://{{DOMAIN}}/#organization" },
"isPartOf": { "@id": "https://{{DOMAIN}}/#website" },
"about": [{ "@type": "Thing", "name": "{{主题词}}" }],
"citation": ["{{引用的外部权威来源 URL}}"]
}
```
`dateModified` 必须真实更新,否则是自毁可信度。`citation` 是 GEO 里价值最高的字段之一。
## 4. 产品 / 服务
```json
{
"@context": "https://schema.org",
"@type": "Product",
"@id": "https://{{DOMAIN}}/{{PATH}}#product",
"name": "{{产品名}}",
"description": "{{一句话定义 + 适用对象}}",
"brand": { "@id": "https://{{DOMAIN}}/#organization" },
"category": "{{品类}}",
"offers": {
"@type": "Offer",
"priceCurrency": "CNY",
"price": "{{价格或起价}}",
"priceSpecification": {
"@type": "PriceSpecification",
"priceCurrency": "CNY",
"minPrice": "{{起}}",
"maxPrice": "{{止}}"
},
"availability": "https://schema.org/InStock",
"url": "https://{{DOMAIN}}/{{PATH}}"
}
}
```
纯服务型业务用 `Service` + `areaServed` + `provider`,不要硬套 `Product`。
## 5. 问答页 / FAQ 区块
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [{
"@type": "Question",
"name": "{{用户真实会问的问题}}",
"acceptedAnswer": {
"@type": "Answer",
"text": "{{40-120 字直接回答,结论先行,不铺垫}}"
}
}]
}
```
只标记页面上**真实可见**的问答。隐藏内容标记 FAQ 会被判作弊。
## 6. 面包屑
```json
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "首页", "item": "https://{{DOMAIN}}/" },
{ "@type": "ListItem", "position": 2, "name": "{{栏目}}", "item": "https://{{DOMAIN}}/{{栏目路径}}/" }
]
}
```
## 7. 多语言站
每个语言版本各自注入本地化的 Organization 与 Article,`inLanguage` 与站点 `hreflang` 严格对应;`@id` 用同一路径 + 语言后缀区分,不要复用同一个 `@id`。
## 校验
- Google Rich Results Test、Schema Markup Validator 过一遍语法。
- 用同一 `@id` 检查是否真的连成图:文章 `publisher.@id` 是否等于组织节点 `@id`。
- 禁止把不存在的信息写进 schema(虚构评分、虚构价格),AI 交叉验证会反噬。
<!-- ===== references/ai-crawler-policy.md ===== -->
# AI 爬虫策略与 llms.txt 模板
## 先分清三种用途
AI 侧访问官网有三种动机,对应完全不同的决策:
| 用途 | 行为 | 你想要的 |
|---|---|---|
| 训练语料抓取 | 批量、低频、抓全站 | 视策略而定,多数 B 端品牌可以放开,换取模型内部知识 |
| 搜索索引构建 | 定期抓取,用于生成答案时召回 | **必须允许**,这是被引用的前提 |
| 用户触发的实时浏览 | 用户提问时当场打开你的页面 | **必须允许**,且页面要能快速渲染出正文 |
只关心「被引用」的话,第二、三类是关键;只禁第一类通常没有意义,因为同一批内容往往也服务检索。
## 机器人清单
以各厂商官方文档为准,UA 会变,配置前复核。
| UA 标识 | 归属 | 主要用途 |
|---|---|---|
| `GPTBot` | OpenAI | 训练与内容抓取 |
| `OAI-SearchBot` | OpenAI | 搜索索引,用于 ChatGPT 搜索答案 |
| `ChatGPT-User` | OpenAI | 用户触发实时访问 |
| `ClaudeBot` / `anthropic-ai` | Anthropic | 训练抓取 |
| `Claude-User` / `Claude-SearchBot` | Anthropic | 用户触发 / 检索 |
| `PerplexityBot` | Perplexity | 检索与引用 |
| `Google-Extended` | Google | 控制 Gemini 训练,不影响搜索收录 |
| `Bytespider` | 字节跳动 | 豆包 / 头条生态 |
| `Baiduspider` | 百度 | 百度搜索,兼供百度 AI 生态 |
| `YisouSpider` | 神马 / 夸克生态 | 检索 |
| `Applebot-Extended` | Apple | Apple Intelligence 训练控制 |
| `CCBot` | Common Crawl | 公共语料,被大量模型间接使用 |
| `meta-externalagent` | Meta | 训练抓取 |
## robots.txt 模板
```
# 搜索与检索类:必须允许,否则等于放弃被引用
User-agent: OAI-SearchBot
Allow: /
User-agent: ChatGPT-User
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Claude-SearchBot
Allow: /
User-agent: Baiduspider
Allow: /
User-agent: YisouSpider
Allow: /
# 训练类:按品牌策略决定,下面这组是「允许」
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: Bytespider
Allow: /
User-agent: CCBot
Allow: /
# 敏感路径对全部爬虫关闭(后台、报价策略、客户资料)
User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /uploads/private/
Allow: /
Sitemap: https://{{DOMAIN}}/sitemap.xml
```
注意事项:
- `Disallow: /` 只对匹配到的组生效,别指望一行挡住所有爬虫。
- 不要 `Disallow` 掉 CSS/JS 资源,部分引擎需要它判断页面结构。
- 反爬 WAF 对 AI UA 的误拦比 robots 更常见。配置后必须用真实 UA 实测:`curl -A "<UA>" -I https://{{DOMAIN}}/`,状态码和正文长度要与浏览器一致。
- 不要用 `Disallow` 当保密手段。真要保密就别放在公网页面里。
## llms.txt 模板
放在站点根,`https://{{DOMAIN}}/llms.txt`。目标是让模型用最少 token 理解你是谁、有哪些页面值得读。
```
# {{品牌名}}
> {{一句话定义:为 A 行业的 B 客户提供 C 服务,解决 D 问题。成立时间、所在地、规模等实体信息一句话内交代。}}
## 核心页面
- [{{产品/服务名}}](https://{{DOMAIN}}/{{path}}): {{一句话说明该页回答什么问题}}
- [{{解决方案名}}](https://{{DOMAIN}}/{{path}}): {{说明}}
- [定价](https://{{DOMAIN}}/pricing): {{计价方式一句话}}
## 权威信息
- [关于我们](https://{{DOMAIN}}/about): 公司全称、成立时间、资质、团队
- [客户案例](https://{{DOMAIN}}/cases): {{行业与可公开结果}}
- [常见问题](https://{{DOMAIN}}/faq): {{覆盖的问题主题}}
## 内容栏目
- [{{栏目名}}](https://{{DOMAIN}}/{{path}}/): {{栏目定位}}
## 边界与声明
- 服务区域:{{...}}
- 不适用场景:{{...}}
- 内容更新:{{更新频率}}
## Optional
- [{{次级页面}}](https://{{DOMAIN}}/{{path}}): 补充材料
```
写法要求:
- 首段 blockquote 就是「标准答案段」,模型可能整段引用,必须能独立成立。
- 每个链接后面必须有一句说明,否则只是一张 URL 表,价值极低。
- 全站信息变了要同步更新,过期摘要比没有更糟。
- 多语言站可为每种语言各出一份,用 `llms-<lang>.txt`,并在主文件里指路。
## ai.txt(可选)
若干社区约定在用,非官方标准。内容是允许/禁止 AI 使用的声明与联系邮箱。写了不亏,但不要指望它起决定作用。真正的控制手段是 robots 与访问控制。
<!-- ===== references/answer-unit-patterns.md ===== -->
# 可被引用的答案单元写法
模型不会整篇引用,它摘句子。所以每一段都要能单独成立:脱离上下文后,读者仍知道「谁、做什么、对谁、有什么条件」。
## 五类必备句式
### 1. 定义句(每页必有,放在首段第二句内)
模板:`{{品牌/产品}}是{{品类}},为{{对象}}提供{{能力}},用于解决{{问题}}。`
差:我们致力于以创新技术赋能企业数字化转型。
好:XX 是企业级数据备份服务,为 200 人以上的研发团队提供跨云增量备份,用于解决多环境数据一致性校验困难的问题。
判定标准:把句子里所有形容词删掉,信息量是否基本不变。变了说明靠形容词撑场面,删掉后句子仍然成立才合格。
### 2. 边界句(决定能否被回答「适不适合我」)
模板:`适用于{{条件 A}}、{{条件 B}}的场景;如果{{反条件}},更适合{{替代方案}}。`
写「不适合谁」不会丢客户,反而提升被引用概率,因为模型需要给出推荐理由和排除理由。全站没有排除句,等于把自己排除在选型类问答之外。
### 3. 对比句(用于选型问题)
模板:`与{{方案类型 A}}相比,{{本产品}}在{{维度}}上{{差异}},代价是{{取舍}}。`
对比「方案类型」而不是点名对手,既避免法律风险,又覆盖用户真实的比较语境。必须写出代价,只讲优势的对比会被判定为营销内容。
### 4. 数据句(提升可信度权重)
模板:`{{指标}}为{{数值}}(来源:{{机构/内部统计}},口径:{{定义}},时间:{{年月}})。`
没有来源的数字在 GEO 里是负资产。宁可给一个带口径的小数字,也不要给一个没出处的漂亮数字。
### 5. 问答句(H2/H3 直接写成问题)
用户向 AI 提问用的是问句,页面小节标题也用问句,召回匹配度最高。
- 好:`多环境备份如何保证一致性?`、`XX 和自建备份方案的差别在哪?`
- 差:`产品优势`、`技术架构`、`解决方案概述`
## 段落与版式规范
- 段落 2-4 句,一句一个信息点,不写长复合句。
- 结论先行,背景后置。第一句就是答案。
- 关键信息用表格承载:参数、对比、计价、资质。表格是最容易被完整摘取的格式。
- 列表项写成完整短句,不要写成「高效」「稳定」「安全」这类单词标签。
- 时间写绝对时间(2026 年 3 月),不写「近期」「目前」。
- 数字带单位和口径,百分比注明基数。
- 关键数据不要只放在图片或图表里,必须有一份文字版。
## 页面骨架检查
一页合格的产品/服务页,按顺序应该能回答:
1. 这是什么(定义句)
2. 解决谁的什么问题(场景)
3. 适合与不适合(边界句)
4. 具体交付什么、多久(清单)
5. 怎么计价(计价逻辑)
6. 谁在用、效果如何(案例 + 数据句)
7. 和别的方式比差在哪(对比句)
8. 怎么开始(唯一 CTA)
八项缺三项以上,这页基本不会被引用。
## 自检口诀
逐段问一句:**这段话能不能直接贴进别人的回答里,而不显得突兀?** 能,就是合格的答案单元;不能,就是给人看的营销文案。
<!-- ===== references/monitoring.md ===== -->
# 效果监测与验收
GEO 没有后台数据可看,只能靠「模拟用户提问 + 记录答案」。所以监测方案要先建起来,否则改造完无法判断有没有用。
## 1. 建品牌问题集
按四类各取 8-15 条,共 40-60 条。问题必须写成真实用户口吻,不要写成关键词。
| 类型 | 示例 | 考察点 |
|---|---|---|
| 品类需求 | 「国内做跨云数据备份的服务商有哪些」 | 是否被列入候选 |
| 选型对比 | 「A 方案和 B 方案哪个更适合 200 人研发团队」 | 是否被当作比较对象 |
| 场景痛点 | 「多环境数据库备份不一致怎么解决」 | 是否被当作解决方案 |
| 品牌直查 | 「XX 公司是做什么的,靠谱吗」 | 实体是否被正确识别 |
固定问题集,不要每次换题,否则无法纵向比较。
## 2. 探测方法
- 每个模型用**独立新会话**,避免上下文污染。
- 同一问题在同一模型至少跑 2 次,结果可能波动,取稳定表现。
- 记录答案原文,不要只记结论。
- 覆盖主流入口:DeepSeek、豆包、元宝、通义、Kimi、百度 AI 搜索,以及带联网检索的通用助手。
- 有联网能力的模式与纯模型模式分开记录,前者反映检索与引用能力,后者反映模型内部知识。
## 3. 指标定义
| 指标 | 定义 |
|---|---|
| 提及率 | 答案中出现品牌名的题数 / 总题数 |
| 首位提及率 | 品牌出现在第一个被推荐位置的题数 / 总题数 |
| 首选推荐率 | 被明确表述为「推荐」「更适合」「首选」的题数 / 总题数 |
| 官网引用率 | 答案引用或链接到官网的题数 / 总题数 |
| 正面提及率 / 负面提及率 | 含明确正/负面评价的题数 / 总题数 |
| 事实准确率 | 品牌相关事实(业务、规模、价格)表述正确的题数 / 被提及题数 |
「官网引用率」是官网改造最直接的效果指标。其他指标还受站外内容影响,改造周期内不要混为一谈。
## 4. 记录表字段
```
日期 | 模型 | 问题ID | 问题文本 | 是否强调联网 | 品牌是否提及 | 位次 | 是否推荐 | 情感 | 是否引用官网 | 引用页面URL | 引用到的原句 | 事实错误 | 截图/链接
```
「引用到的原句」这一列最重要:它直接告诉你官网上哪句话被模型选中了,把这句挪到更靠前的位置、更结构化的区块,是最有效的迭代动作。
## 5. 迭代节奏
- **改造前**:跑一遍全量问题集,留基线,同时跑体检脚本存档。
- **改造中**:每完成一层,抽 10 条核心问题复测。
- **改造后 1 个月、3 个月**:全量复测,对比基线。
- 每月固定一天跑,形成时间序列。
## 6. 归因纪律
- 模型答案波动大,单次差异不算结论,看连续两个周期的趋势。
- 官网改造主要影响「引用率」和「事实准确率」;提及率和首选推荐率还取决于第三方内容生态,不能全部归功或归咎于官网。
- 出现负面提及,先查信息源,再改官网对应页面的表述,不要直接删内容。
## 7. 验收标准
1. 体检脚本 P0 全部归零。
2. 问题集中「官网引用率」相对基线提升。
3. 「事实准确率」达到 90% 以上(说明实体信息已经统一)。
4. 核心品类问题中,官网信息能覆盖答案里的关键论点(人工比对)。
<!-- ===== scripts/audit_site.py ===== -->
保存为 scripts/audit_site.py,用法:
```bash
python3 scripts/audit_site.py https://example.com --max-pages 8 --json report.json
```
```python
#!/usr/bin/env python3
"""GEO 官网体检:抓取页面并输出可改造项清单。
只依赖标准库。检查项对应 references/audit-checklist.md 的前五层。
用法:
python3 audit_site.py https://example.com
python3 audit_site.py https://example.com --max-pages 8 --json report.json
python3 audit_site.py https://example.com --no-ai-ua # 跳过 AI 爬虫 UA 对比
"""
import argparse
import gzip
import json
import re
import ssl
import sys
import urllib.error
import urllib.request
from html.parser import HTMLParser
from typing import Dict, List, Optional
from urllib.parse import urljoin, urlparse
TIMEOUT = 20
BROWSER_UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
GPTBOT_UA = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.2; +https://openai.com/gptbot"
AI_BOTS = [
"GPTBot", "OAI-SearchBot", "ChatGPT-User", "ClaudeBot", "Claude-Web",
"anthropic-ai", "PerplexityBot", "Google-Extended", "Bytespider",
"Baiduspider", "YisouSpider", "Applebot-Extended", "CCBot", "meta-externalagent",
]
SKIP_TEXT_TAGS = {"script", "style", "noscript", "svg", "template"}
def fetch(url: str, ua: str = BROWSER_UA, timeout: int = TIMEOUT):
"""返回 (status, headers, body_text),异常时 status 为 None。"""
req = urllib.request.Request(
url,
headers={
"User-Agent": ua,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
},
)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
try:
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
raw = resp.read()
if resp.headers.get("Content-Encoding", "").lower() == "gzip":
try:
raw = gzip.decompress(raw)
except OSError:
pass
charset = resp.headers.get_content_charset() or "utf-8"
try:
text = raw.decode(charset, errors="replace")
except LookupError:
text = raw.decode("utf-8", errors="replace")
return resp.status, dict(resp.headers), text
except urllib.error.HTTPError as exc:
return exc.code, dict(exc.headers or {}), ""
except Exception as exc: # noqa: BLE001
return None, {"error": str(exc)}, ""
class PageParser(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.title = ""
self.meta: Dict[str, str] = {}
self.h1: List[str] = []
self.h2: List[str] = []
self.h3: List[str] = []
self.jsonld: List[str] = []
self.canonical: Optional[str] = None
self.hreflang = 0
self.imgs = 0
self.imgs_no_alt = 0
self.anchors = 0
self.text_chunks: List[str] = []
self.ld_json_raw: List[str] = []
self._stack: List[str] = []
self._skip_depth = 0
self._in = None
self._buf: List[str] = []
def handle_starttag(self, tag, attrs):
a = {k.lower(): (v or "") for k, v in attrs}
self._stack.append(tag)
if tag in SKIP_TEXT_TAGS:
self._skip_depth += 1
elif tag in ("h1", "h2", "h3", "title"):
self._in = tag
self._buf = []
elif tag == "meta":
key = (a.get("name") or a.get("property") or a.get("http-equiv") or "").lower()
if key:
self.meta.setdefault(key, a.get("content", ""))
elif tag == "img":
self.imgs += 1
if not a.get("alt", "").strip():
self.imgs_no_alt += 1
elif tag == "a":
self.anchors += 1
elif tag == "link":
rel = a.get("rel", "").lower()
if "canonical" in rel:
self.canonical = a.get("href", "")
if "alternate" in rel and a.get("hreflang"):
self.hreflang += 1
elif tag == "script" and a.get("type", "").lower() == "application/ld+json":
self._in = "ldjson"
self._buf = []
def handle_endtag(self, tag):
if self._stack and self._stack[-1] == tag:
self._stack.pop()
if tag in SKIP_TEXT_TAGS and self._skip_depth > 0:
self._skip_depth -= 1
if self._in == tag:
value = " ".join("".join(self._buf).split())
if tag == "title":
self.title = value
elif tag == "h1":
self.h1.append(value)
elif tag == "h2":
self.h2.append(value)
elif tag == "h3":
self.h3.append(value)
self._in = None
self._buf = []
elif self._in == "ldjson" and tag == "script":
self.ld_json_raw.append("".join(self._buf))
self._in = None
self._buf = []
def handle_data(self, data):
if self._in:
self._buf.append(data)
return
if self._skip_depth:
return
stripped = data.strip()
if stripped:
self.text_chunks.append(stripped)
def collect_ld_types(blocks: List[str]) -> List[str]:
types = set()
def walk(node):
if isinstance(node, dict):
t = node.get("@type")
if isinstance(t, str):
types.add(t)
elif isinstance(t, list):
types.update(x for x in t if isinstance(x, str))
for v in node.values():
walk(v)
elif isinstance(node, list):
for v in node:
walk(v)
for block in blocks:
block = block.strip()
if not block:
continue
try:
walk(json.loads(block))
except json.JSONDecodeError:
# 容错:抽取 @type 字面量
types.update(re.findall(r'"@type"\s*:\s*"([^"]+)"', block))
return sorted(types)
def parse_robots(text: str):
groups: List[Dict[str, object]] = []
cur: Optional[Dict[str, object]] = None
for line in text.splitlines():
line = line.split("#", 1)[0].strip()
if not line or ":" not in line:
continue
key, val = line.split(":", 1)
key = key.strip().lower()
val = val.strip()
if key == "user-agent":
if cur is None or cur.get("rules"):
cur = {"agents": [], "rules": []}
groups.append(cur)
cur["agents"].append(val)
elif key in ("allow", "disallow") and cur is not None:
cur["rules"].append((key, val))
return groups
def robots_policy(groups, bot: str):
"""返回 (matched_agent, 是否被整站禁止)。"""
for g in groups:
agents = [a.lower() for a in g["agents"]]
if any(a == bot.lower() for a in agents):
disallow_all = any(k == "disallow" and v == "/" for k, v in g["rules"])
allow_all = any(k == "allow" and v == "/" for k, v in g["rules"])
return g["agents"][0], disallow_all and not allow_all
for g in groups:
if any(a == "*" for a in g["agents"]):
disallow_all = any(k == "disallow" and v == "/" for k, v in g["rules"])
return "*", disallow_all
return None, False
def sitemap_urls(base: str, text: str) -> List[str]:
urls = re.findall(r"<loc>\s*([^<\s]+)\s*</loc>", text)
if "<sitemapindex" in text:
out = []
for sub in urls[:3]:
status, _, sub_text = fetch(sub)
if status == 200:
out.extend(re.findall(r"<loc>\s*([^<\s]+)\s*</loc>", sub_text))
return out
return urls
def audit(base: str, max_pages: int, check_ai_ua: bool) -> Dict[str, object]:
base = base.rstrip("/")
report: Dict[str, object] = {"site": base, "findings": [], "pages": []}
findings = report["findings"]
# --- robots.txt ---
status, _, robots_txt = fetch(base + "/robots.txt")
groups = parse_robots(robots_txt) if status == 200 else []
report["robots"] = {
"status": status,
"groups": len(groups),
"ai_bots": {bot: robots_policy(groups, bot)[1] if groups else None for bot in AI_BOTS},
"sitemap_declared": bool(re.search(r"(?im)^sitemap:", robots_txt or "")),
}
if status != 200:
findings.append(("P0", "robots.txt 缺失", "补一份,并显式声明各 AI 爬虫的允许策略与 sitemap 地址"))
else:
blocked = [b for b, v in report["robots"]["ai_bots"].items() if v is True]
if blocked:
findings.append(("P1", "以下 AI 爬虫被整站禁止: " + ", ".join(blocked), "确认是否为预期策略;误拦会让品牌在对应引擎中不可见"))
if not report["robots"]["sitemap_declared"]:
findings.append(("P2", "robots.txt 未声明 Sitemap", "加一行 Sitemap: <绝对地址>"))
# --- llms.txt ---
status, _, llms = fetch(base + "/llms.txt")
report["llms_txt"] = {"status": status, "bytes": len(llms or "")}
if status != 200:
findings.append(("P2", "llms.txt 缺失", "按 references/ai-crawler-policy.md 的模板补站点摘要与关键页面索引"))
# --- sitemap ---
status, _, sm = fetch(base + "/sitemap.xml")
urls = sitemap_urls(base, sm) if status == 200 else []
report["sitemap"] = {"status": status, "urls": len(urls)}
if status != 200 or not urls:
findings.append(("P1", "sitemap.xml 不可用或为空", "生成分区 sitemap,lastmod 用真实更新时间"))
# --- 待检页面 ---
targets = [base + "/"]
for u in urls:
if len(targets) >= max_pages + 1:
break
if u.rstrip("/") == base:
continue
targets.append(u)
all_types = set()
for url in targets:
status, headers, html = fetch(url)
if status != 200 or not html:
report["pages"].append({"url": url, "status": status})
continue
p = PageParser()
try:
p.feed(html)
except Exception: # noqa: BLE001
pass
body_text = " ".join(p.text_chunks)
ld_types = collect_ld_types(p.ld_json_raw)
all_types.update(ld_types)
report["pages"].append({
"url": url,
"status": status,
"bytes": len(html),
"text_chars": len(body_text),
"title_len": len(p.title),
"desc_len": len(p.meta.get("description", "")),
"h1": len(p.h1),
"h2": len(p.h2),
"h1_text": p.h1[0][:80] if p.h1 else "",
"jsonld_types": ld_types,
"canonical": bool(p.canonical),
"hreflang": p.hreflang,
"img_no_alt": p.imgs_no_alt,
"img_total": p.imgs,
"empty_root": bool(re.search(r'<div[^>]+id="(root|app|__next)"[^>]*>\s*</div>', html)),
})
# --- 页面级问题 ---
pages = [x for x in report["pages"] if x.get("status") == 200]
if pages:
no_h1 = [x["url"] for x in pages if x["h1"] == 0]
multi_h1 = [x["url"] for x in pages if x["h1"] > 1]
thin = [x["url"] for x in pages if x["text_chars"] < 600]
empty_root = [x["url"] for x in pages if x["empty_root"] and x["text_chars"] < 300]
no_ld = [x["url"] for x in pages if not x["jsonld_types"]]
no_canon = [x["url"] for x in pages if not x["canonical"]]
no_desc = [x["url"] for x in pages if x["desc_len"] < 30]
img_gap = sum(x["img_no_alt"] for x in pages)
if empty_root:
findings.append(("P0", "检测到纯客户端渲染迹象: " + ", ".join(empty_root[:3]), "改为 SSR/SSG,AI 爬虫普遍不执行 JS,正文等于不存在"))
if no_h1:
findings.append(("P0", "缺 H1 的页面: " + ", ".join(no_h1[:5]), "每页一个 H1,写成能独立回答问题的陈述句"))
if multi_h1:
findings.append(("P1", f"{len(multi_h1)} 个页面存在多个 H1", "收敛为一个,其余降级为 H2"))
if thin:
findings.append(("P1", f"{len(thin)} 个页面正文过短(<600 字): " + ", ".join(thin[:3]), "补足定义段、适用对象、交付物、限制条件"))
if no_ld:
findings.append(("P1", f"{len(no_ld)} 个页面无 JSON-LD", "按 references/schema-templates.md 注入 Organization/Article/FAQPage 等"))
if no_canon:
findings.append(("P2", f"{len(no_canon)} 个页面缺 canonical", "统一规范地址,避免 AI 侧把参数页当重复内容"))
if no_desc:
findings.append(("P2", f"{len(no_desc)} 个页面 meta description 过短或缺失", "写 80-140 字,含实体名与一句能力定义"))
if img_gap:
findings.append(("P2", f"{img_gap} 张图片缺 alt", "alt 写成名词短语,关键数据不要只存在于图片里"))
hreflang_pages = [x for x in pages if x["hreflang"] > 0]
if hreflang_pages and len(hreflang_pages) < len(pages):
findings.append(("P1", "hreflang 覆盖不完整", "所有语言版本互相声明且闭合,否则各语言页在 AI 侧互相镜像"))
# --- 实体层结构化数据 ---
entity_types = {"Organization", "LocalBusiness", "Corporation", "Brand", "Person"}
report["entity_schema"] = sorted(all_types & entity_types)
if not report["entity_schema"]:
findings.append(("P0", "全站未发现 Organization/LocalBusiness 结构化数据", "补组织实体节点,用 @id 与文章 publisher 串联"))
# --- AI 爬虫 UA 对比 ---
if check_ai_ua:
b_status, b_headers, b_html = fetch(base + "/")
g_status, g_headers, g_html = fetch(base + "/", ua=GPTBOT_UA)
bp, gp = PageParser(), PageParser()
try:
bp.feed(b_html)
gp.feed(g_html)
except Exception: # noqa: BLE001
pass
report["ai_ua_probe"] = {
"browser_status": b_status,
"gptbot_status": g_status,
"browser_chars": len(" ".join(bp.text_chunks)),
"gptbot_chars": len(" ".join(gp.text_chunks)),
}
if b_status != g_status or abs(len(" ".join(bp.text_chunks)) - len(" ".join(gp.text_chunks))) > 200:
findings.append(("P1", "对 GPTBot 与浏览器的响应不一致", "确认是否 waf/反爬误拦 AI 爬虫"))
order = {"P0": 0, "P1": 1, "P2": 2}
findings.sort(key=lambda x: order.get(x[0], 9))
report["summary"] = {k: sum(1 for f in findings if f[0] == k) for k in ("P0", "P1", "P2")}
return report
def main():
ap = argparse.ArgumentParser(description="GEO 官网体检")
ap.add_argument("url")
ap.add_argument("--max-pages", type=int, default=5)
ap.add_argument("--json", dest="json_out")
ap.add_argument("--no-ai-ua", action="store_true")
args = ap.parse_args()
report = audit(args.url, args.max_pages, not args.no_ai_ua)
print("=" * 62)
print("GEO 体检报告 " + report["site"])
print("=" * 62)
print("robots.txt : " + str(report["robots"]["status"]))
print("llms.txt : " + str(report["llms_txt"]["status"]))
print("sitemap : " + str(report["sitemap"]["status"]) + " / " + str(report["sitemap"]["urls"]) + " 条")
print("实体 schema: " + (", ".join(report["entity_schema"]) or "无"))
if report.get("ai_ua_probe"):
print("GPTBot 探测 : " + json.dumps(report["ai_ua_probe"], ensure_ascii=False))
print("\n-- 页面概览 --")
for p in report["pages"]:
if p.get("status") != 200:
print(f" [{p.get('status')}] {p['url']}")
continue
print(f" {p['url']}\n 正文 {p['text_chars']} 字 / H1 {p['h1']} / H2 {p['h2']} / schema {','.join(p['jsonld_types']) or '无'}")
print("\n-- 改造项 --")
if not report["findings"]:
print(" 未发现明显问题")
for level, issue, action in report["findings"]:
print(f" [{level}] {issue}\n → {action}")
print("\n汇总: P0 %d / P1 %d / P2 %d" % (
report["summary"]["P0"], report["summary"]["P1"], report["summary"]["P2"]))
if args.json_out:
with open(args.json_out, "w", encoding="utf-8") as fh:
json.dump(report, fh, ensure_ascii=False, indent=2)
print("JSON 已写入 " + args.json_out)
return 0 if report["summary"]["P0"] == 0 else 1
if __name__ == "__main__":
sys.exit(main())
```
ヒント:scripts/audit_site.py Python 3.8 以上が必要で、サードパーティライブラリのインストールは不要です。スクリプトは P0 問題を検出すると終了コード 1 で終了するため、CI に直接組み込めます。
