AI GrowthBộ công cụ sáng tạo nội dung AI

Danh sách cải tạo GEO cho website: Sáu lớp điểm thông tin và thứ tự thực hiện

Chương 6: Phần thực chiến nâng cao Câu hỏi 103 ⏱ Khoảng 15 phút

Chương 6: Thực chiến nâng cao · Câu hỏi 103

Để đánh giá một đoạn văn bản trên website có thể được AI trích dẫn hay không, chỉ có một bài kiểm tra:hãy tách nó ra riêng, dán vào câu trả lời của người khác, xem nó còn đứng vững không.

Nếu đứng vững, nó là đơn vị có thể trích dẫn. Nếu không, nó chỉ là văn bản marketing. Mô hình không trích dẫn nguyên trang của bạn, nó lấy câu. Vì vậy điểm cải tạo không nằm ở trang, mà ở đoạn văn và trường dữ liệu.

Dưới đây trình bày theo ba phần「làm gì, thứ tự nào, nghiệm thu thế nào」. Tất cả công cụ có thể sao chép trực tiếp được tập trung ở phụ lục cuối bài, có thể sao chép nguyên đoạn thành danh sách độc lập.

Bài kiểm tra này cụ thể thực hiện thế nào

Lấy ngay trang này để minh họa. Mở songbing.com.cn/100wen/, vừa xem vừa đối chiếu, không cần chuẩn bị công cụ nào.

Bước một, viết ba câu hỏi. Người dùng sẽ hỏi thế nào, bạn viết như thế, đừng chuyển thành thuật ngữ ngành.

  1. Trang web này làm gì?
  2. Phù hợp với ai?
  3. So với các hướng dẫn SEO khác, có gì khác biệt?

Bước hai, lên trang chép câu. Tìm những câu vốn đã trả lời ba câu hỏi này, chép nguyên văn, không đổi một chữ nào. Trang chủ này chép được sáu câu:

  1. Dòng nhãn và tiêu đề: Sổ tay thực hành organic traffic tiếng Trung · 100 câu hỏi về organic marketing tiếng Trung
  2. Phụ đề: Hướng dẫn thực hành organic traffic tiếng Trung dành cho marketer, nhà khởi nghiệp, chuyên viên PR
  3. Dòng mô tả: Mỗi câu hỏi 800-1500 chữ, một câu hỏi làm rõ một điểm kiến thức, có thể đọc độc lập
  4. Dòng số liệu: 7 chương lớn, 103 bài hỏi đáp, 800+ chữ mỗi bài, cập nhật liên tục hàng tuần
  5. Điểm bán: Cuốn sách tiếng Trung đầu tiên trên toàn mạng hệ thống hóa về GEO/AEO, dạy bạn được AI đề xuất
  6. Điểm bán: Phủ sóng toàn nền tảng, Baidu + Douyin + Xiaohongshu + Zhihu + 微信搜一搜

Bước ba, ghép câu hỏi và câu lại đọc, xem liệu người đọc có hỏi tiếp một câu nữa không.

Người dùng hỏi Câu gốc trên trang Phản ứng đầu tiên của người đọc
Trang web này làm gì Sổ tay thực chiến organic traffic tiếng Trung · 100 câu hỏi về organic marketing tiếng Trung Hiểu một nửa. Biết đây là sổ tay, nhưng «organic marketing» vẫn phải nghĩ thêm một lớp
Trang web này làm gì Hướng dẫn thực hành organic traffic tiếng Trung dành cho marketer, nhà khởi nghiệp và chuyên viên PR Hiểu rồi. Đạt
Phù hợp cho ai đọc Mỗi câu hỏi 800-1500 chữ, một câu hỏi làm rõ một điểm kiến thức, có thể đọc độc lập Biết cách đọc, không biết viết cho ai. Trả lời lạc đề
Có đáng đọc không 7 chương lớn, 103 bài hỏi đáp, hơn 800 chữ mỗi bài, cập nhật liên tục hàng tuần Biết quy mô. Đạt
So với các hướng dẫn khác Cuốn sách tiếng Trung đầu tiên trên toàn mạng trình bày có hệ thống về GEO/AEO, hướng dẫn bạn được AI đề xuất Nửa tin. «Cuốn đầu tiên trên toàn mạng» không thể kiểm chứng
So với các hướng dẫn khác Baidu + Douyin + Xiaohongshu + Zhihu + WeChat Search Biết phạm vi bao phủ. Đạt

Bốn trong sáu câu dùng được. Đây mới là tình trạng thực tế của website: không phải đúng hết, cũng không phải bỏ hết, mà là tắc ở hai ba chỗ.

Hai câu bị tắc đó đều không cần viết lại cả đoạn, chỉ cần thay một cách diễn đạt:

Câu gốc: Cuốn sách tiếng Trung đầu tiên trên toàn mạng hệ thống hóa GEO/AEO

Vấn đề: «Đầu tiên trên toàn mạng» không thể kiểm chứng, AI không thể thuật lại, người đọc cũng không thể đối chiếu

Viết lại: Chương 3 gồm 15 câu hỏi chuyên về tìm kiếm AI, từ cơ chế trích dẫn đến cấu hình llms.txt đều có mẫu sao chép được

«Organic marketing» cũng vậy. Từ này trong ngành thì hiểu được, nhưng với người đọc lần đầu lại là từ lạ, chỉ cần thêm một câu giải thích phía trước là đủ:

Câu gốc: 100 câu hỏi về organic marketing tiếng Trung

Viết lại: 100 câu hỏi về organic marketing tiếng Trung —— không chạy quảng cáo, dựa vào nội dung và tìm kiếm để lấy organic traffic

Tiêu chí đánh giá chỉ có một:Người đọc lấy câu này, có thể tự đưa ra phán đoán hay không. Sau khi sửa hai chỗ này, cả sáu câu đều có thể đứng độc lập. Tổng khối lượng thay đổi là hai câu ngắn, không phải một lần cải bản.

Kết quả cuối cùng cần nói rõ trước: nếu ba câu hỏi trên trangkhông chép được một câu nào, không cần kiểm tra nữa. Trang này thiếu đơn vị câu trả lời, hãy bổ sung trực tiếp theo danh sách sáu lớp bên dưới.

Một, sáu lớp thông tin

Model trả lời một câu hỏi cần bốn loại nguyên liệu: bạn là ai, bạn làm được gì, dựa vào đâu để tin bạn, bạn khác người khác ở đâu. Trang web thiếu bất kỳ loại nào, câu hỏi thuộc loại đó sẽ không liên quan đến bạn.

Tầng Giải quyết gì Thiếu thì sẽ thế nào
1 Danh tính thực thể Xác nhận bạn là công ty nào Bị nhầm lẫn với thương hiệu cùng tên, hoặc đơn giản là không được nhắc đến
2 Ranh giới năng lực Đánh giá bạn có phải là câu trả lời hay không Chỉ được nhắc đến chung chung, không vào được vị trí đề xuất
3 Bằng chứng uy tín Đánh giá xem có dám dùng bạn hay không Nội dung được chấp nhận nhưng bị đánh dấu là không đáng tin cậy
4 So sánh khác biệt Thực hiện so sánh lựa chọn Không có vị trí của bạn trong câu hỏi lựa chọn
5 Lộ trình chuyển đổi Quy về một hành động Được nhắc đến nhưng không tạo ra lead
6 Máy đọc được Để năm lớp đầu được đọc Nội dung đối với AI coi như không tồn tại

Ba lớp đầu là tiền đề, ba lớp sau là bộ khuếch đại.

Lớp thứ nhất: Danh tính thực thể

Thông tin cần bổ sung: mối tương ứng giữa bốn yếu tố tên đầy đủ công ty, tên viết tắt thương hiệu, tên tiếng Anh, tên từng dùng; thời gian thành lập, nơi đăng ký, mã số tín dụng xã hội thống nhất; định nghĩa một câu về hoạt động chính; quan hệ trực thuộc giữa tên thương hiệu và tên sản phẩm; phiên bản duy nhất của tên, địa chỉ, điện thoại; liên kết hồ sơ bên ngoài trỏ đến cơ quan đăng ký kinh doanh, bách khoa, 公众号.

Hai yêu cầu dễ bị bỏ sót. Một làHoàn toàn dùng văn bản thuần, không làm thành hình ảnh hay biểu đồ. Thứ hai, cách diễn đạt ở bốn nơi phải nhất quán từng chữ; thông tin đăng ký kinh doanh, phần giới thiệu trên bách khoa, phần giới thiệu trên 公众号 và chân trang website viết khác nhau sẽ làm giảm độ tin cậy của thực thể.

Tầng này thường bị bỏ qua nhất, với lý do «thông tin đăng ký kinh doanh này ai mà xem».AI xem.

Tầng thứ hai: Ranh giới năng lực

Mỗi mảng nghiệp vụ viết một đoạn trả lời chuẩn, gồm sáu mục: câu định nghĩa, đối tượng áp dụng, trường hợp không áp dụng, sản phẩm bàn giao, chu kỳ, cách tính giá. Thêm khu vực phục vụ và điều kiện gia nhập, hạn chế và điều kiện tiên quyết.

«Không áp dụng» là phản trực giác nhất, cũng đáng giá nhất. Khi trả lời câu hỏi chọn giải pháp, mô hình thường phải đưa ra đồng thời lý do đề xuất và lý do loại trừ; toàn site không có câu loại trừ nào đồng nghĩa với việc tự loại mình khỏi danh sách ứng viên. Sẵn sàng viết rõ «trong trường hợp nào chúng tôi không phải giải pháp tối ưu» lại làm tăng xác suất được đề xuất.

Hai mục còn lại thường bị bỏ qua nhất là cách tính giá và điều kiện hạn chế. Không công khai giá cụ thể thì được, nhưng phải để AI có thứ để nói, nếu không nó chỉ có thể trả lời «cần tư vấn».

Tầng thứ ba: Bằng chứng uy tín

Cần bổ sung: tên tác giả và trang tác giả, số chứng chỉ năng lực và cơ quan cấp, số bằng sáng chế, số báo cáo kiểm định, dữ liệu công khai được từ các case khách hàng, nguồn dữ liệu, thời gian cập nhật trang, chính sách biên tập và kiểm chứng thực tế, tuyên bố miễn trừ trách nhiệm.

Tầng này quyết định AI có dám dùng nội dung của bạn hay không. Mọi con số phải kèmnguồn, phạm vi đo, thời gianđủ bộ ba.

Không đạt: tỷ lệ chuyển đổi tăng rõ rệt.

Đạt: tỷ lệ chuyển đổi dùng thử tăng từ 2,1% lên 3,4% (phạm vi đo: đăng ký đến trả phí, cửa sổ 14 ngày, nguồn: tracking nội bộ, tháng 3 năm 2026).

Về mục tuyên bố này, các site trong nước gần như không ai làm. Làm được là tạo khác biệt.

Tầng thứ tư: So sánh khác biệt

Bổ sung: bảng so sánh, ma trận năng lực, "những gì chúng tôi không làm" rõ ràng.

Đối tượng so sánh ghi theo loại giải pháp, không nêu tên đối thủ, vừa tránh rủi ro pháp lý, vừa bao phủ ngữ cảnh so sánh thực tế của người dùng. So sánh phải nêu cả cái giá phải trả; so sánh chỉ nói ưu điểm sẽ bị đánh giá là nội dung marketing và không thể lọt vào câu trả lời.

Tầng thứ năm: Lộ trình chuyển đổi

Cần thu gọn, không phải mở rộng: mỗi trang chỉ đưa một CTA chính; thông tin liên hệ nhất quán trên mọi kênh và cung cấp văn bản thuần có thể sao chép; ghi rõ cam kết phản hồi (bao lâu trả lời, ai trả lời); bổ sung điều khoản hậu mãi và bảo đảm.

Chỉ làm thông tin liên hệ dưới dạng biểu tượng hoặc mã QR đồng nghĩa AI không lấy được bất kỳ thông tin nào có thể thuật lại.

Tầng thứ sáu: Máy đọc được

Thực hiện theo thứ tự ưu tiên: kết xuất phía máy chủ hoặc tạo tĩnh; HTML ngữ nghĩa (mỗi trang một H1, H2 dùng câu hỏi, nội dung chính trong main và article); structured data; robots.txt phân biệt mục đích của AI crawler; llms.txt; sitemap phân vùng; quản trị canonical và hreflang; quản trị 404 và chuyển hướng; alt của hình ảnh và văn bản hóa dữ liệu then chốt.

Nếu chỉ có thể làm một việc,hãy làm kết xuất phía máy chủ. Trang không lấy được nội dung chính thì tám hạng mục còn lại đều vô nghĩa.

Structured data là thẻ đánh dấu, không phải nội dung. Câu không có trên trang thì viết vào chú thích cũng không thể xuất hiện. Ngược lại, chú thích thông tin không tồn tại trên trang là gian lận, sẽ bị phản tác dụng khi mô hình kiểm chứng chéo.

Hai, Thứ tự cải tạo

Cấp độ Nội dung Thời gian
P0 SSR hoặc tĩnh hóa; bổ sung đoạn nhận diện thực thể trong giới thiệu; bổ sung định nghĩa một câu cho mỗi trang; chiến lược robots và AI crawler; đánh dấu Organization và Article 2 tuần
P1 Tái cấu trúc các mục dạng hỏi đáp; trang tác giả, trang chứng nhận, trang case study; llms.txt; quản trị sitemap và hreflang 1 tháng
P2 Ma trận so sánh; thông tin giá; cơ chế giám sát Liên tục

Trong P0 cũng có thứ tự trước sau. Không thay đổi cách render thì mọi thứ phía sau chỉ là trang trí cho một trang không nhìn thấy được.

Ba, nghiệm thu

GEO không có dữ liệu backend, chỉ có thể mô phỏng câu hỏi và ghi chép. Xây dựng 40 đến 60 câu hỏi, chia làm bốn loại: nhu cầu danh mục (ai có thể làm việc gì), so sánh lựa chọn (A và B cái nào phù hợp hơn), điểm đau bối cảnh (giải quyết một vấn đề cụ thể như thế nào), tra cứu trực tiếp thương hiệu (công ty này làm gì, có đáng tin không).

Chạy từng câu trong các mô hình chủ lưu bằng phiên mới độc lập, mỗi câu hỏi ít nhất hai lần, ghi lại nguyên văn câu trả lời.

Chỉ số Định nghĩa
Tỷ lệ đề cập Số câu có xuất hiện tên thương hiệu / tổng số câu
Tỷ lệ đề cập đầu tiên Số câu thương hiệu đứng ở vị trí đầu tiên / tổng số câu
Tỷ lệ đề xuất ưu tiên Số câu được đề xuất rõ ràng hoặc diễn đạt là phù hợp hơn / tổng số câu
Tỷ lệ trích dẫn website chính thức Số câu trích dẫn hoặc liên kết đến website chính thức / tổng số câu
Độ chính xác của sự thật Số câu hỏi có thông tin thương hiệu được trình bày đúng / Số câu hỏi được đề cập

Trong đó, tỷ lệ trích dẫn website chính thức là chỉ số trực tiếp nhất của việc cải tạo website; các mục còn lại còn chịu ảnh hưởng từ nội dung ngoài trang.

Phải bao phủ 3 đến 5 mô hình. Căn cứ là một nghiên cứu quan sát tháng 9 năm 2026 bao gồm ChatGPT, Copilot, Google, Perplexity:96,4% URL được trích dẫn chỉ xuất hiện trong một công cụ duy nhất, trung vị độ trùng lặp URL giữa các công cụ là 0 (bài báo Scoring With the Engine, arXiv:2609.22655). Chỉ kiểm tra một mô hình rồi kết luận thì coi như chưa kiểm tra.

Cột giá trị nhất trong bảng ghi chép là «câu gốc được trích dẫn». Nó cho bạn biết câu nào trên website chính thức thực sự được chọn. Chuyển câu này lên vị trí phía trước hơn, vào khối có cấu trúc hơn, là cách tối ưu đơn điểm hiệu quả nhất.

Nhịp độ: giữ lại đường cơ sở trước khi cải tạo, sau khi hoàn thành thì kiểm tra lại một lần vào tháng thứ 1 và tháng thứ 3, sau đó chạy cố định vào một ngày mỗi tháng.

Bốn, sáu hiểu lầm phổ biến

Một, cho rằng thêm structured data là xong. Đánh dấu giải quyết việc máy nhận diện, không giải quyết việc có nội dung hay không. Không có câu nào có thể trích xuất, đánh dấu đầy đủ đến đâu cũng sẽ không được trích dẫn.

Hai, chỉ tối ưu trang chủ. Những gì được trích dẫn là các trang cụ thể. Trang sản phẩm, trang hỏi đáp, trang case study mới là chủ thể; trang chủ chỉ chịu trách nhiệm nói rõ danh tính thực thể.

["Ba. Coi "dẫn đầu ngành" là điểm khác biệt."] ["Những từ kiểu này không trích xuất được, cũng không kiểm chứng được, chẳng khác nào nói suông."]

["Bốn là, cho rằng chỉ cần mở AI crawler là sẽ được indexing."] ["Đó chỉ là tấm vé vào cửa; việc có được trích dẫn hay không do chất lượng nội dung quyết định."]

["5. Coi robots là biện pháp bảo mật."] ["Đây không phải là hệ thống phân quyền; nếu thực sự cần bảo mật thì đừng đặt nó trên trang công khai."]

VI. Hoàn tất chỉ với một lần sửa. [ "Mô hình đang thay đổi, đối thủ đang điều chỉnh, thông tin của bạn cũng đang lỗi thời. Đây là công việc bảo trì, không phải sản phẩm bàn giao dự án." ]

Câu hỏi thường gặp

Q:Việc cải tạo website giúp ích cho GEO đến mức nào?

["A: Trang web chính thức là nguồn tín hiệu chuẩn để AI đối chiếu chéo. Trước khi sửa, AI nhận được một trang không đọc được nội dung chính, không nhận diện được thực thể, xác suất được trích dẫn rất thấp. Sau khi sửa, ít nhất nó đã có điều kiện được truy hồi và được trích lấy. Nhưng cần lưu ý, cải tạo trang web chính thức chỉ giải quyết «có căn cứ để trích dẫn», việc có thực sự được trích dẫn hay không còn phụ thuộc vào hệ sinh thái nội dung ngoài trang, không có phương pháp nào có thể cam kết mức tăng cụ thể."]

Q:Chỉ cải tạo website là đủ chưa?

["A: Không đủ. Website chính thức là trận địa chính, nhưng AI sẽ tổng hợp nhiều nguồn tin để đưa ra phán đoán. Sửa tốt website chính thức là để AI có căn cứ trích dẫn, nội dung trên 百科, 知乎, truyền thông ngành quyết định việc nó có sẵn lòng trích dẫn hay không. Cách nói của hai bên còn phải nhất quán, nếu không việc xác minh chéo sẽ đưa ra kết luận «thông tin không đáng tin cậy»."]

Q:Sau khi cải tạo bao lâu thì thấy hiệu quả?

["A: Ở tầng chỉ mục, thường mất hai đến bốn tuần để thấy thay đổi về crawling. Về trích dẫn ở tầng câu trả lời, thường cần một đến ba tháng, và cần tổng hợp quan sát từ nhiều mô hình. Câu trả lời của mô hình dao động lớn, khác biệt của một lần hỏi không thể coi là kết luận, hãy xem xu hướng của hai chu kỳ liên tiếp."]

["Hỏi: Website dựng bằng template có thể tối ưu GEO không?"]

["A: Nếu sửa được thì làm được. Điểm nghẽn thực sự có hai: cách render có thể chuyển sang xuất phía máy chủ không, và phần quản trị có thể tùy chỉnh head của trang không. Nếu cả hai đều không được, pipeline của tầng thứ sáu gần như vô dụng, nên cân nhắc đổi giải pháp xây dựng website. Năm tầng còn lại là vấn đề nội dung, không liên quan đến cách xây dựng website."]


Bài viết này là một phần của loạt bài 《100 câu hỏi về organic marketing tiếng Trung》.Xem mục lục đầy đủ →

["Phụ lục: Danh sách kiểm tra GEO cho website chính thức (có thể sao chép toàn bộ đoạn)"]

[ "Bạn có thể sao chép toàn bộ nội dung dưới đây và lưu thành" ] geo-site-checklist.md ["Sử dụng độc lập, cũng có thể in ra làm bảng tự kiểm tra. Trong bài"] {{}} là chỗ giữ chỗ, thay thế khi sử dụng.

0. Cách sử dụng

  1. Chạy lệnh tự kiểm tra ở mục 1 trước, dùng năm phút để đánh giá xem nền tảng kỹ thuật có vấn đề gì không.
  2. Đối chiếu từng mục kiểm tra sáu lớp ở mục 2 để đánh dấu, ghi lại các khoảng trống.
  3. Lên lịch theo thứ tự ưu tiên ở mục 7, chưa xử lý xong P0 thì không động đến P1.
  4. Dùng trực tiếp mẫu: structured data xem mục 3, robots xem mục 4, llms.txt xem mục 5, mẫu câu văn bản xem mục 6.
  5. Sau khi lên sóng, thiết lập giám sát theo mục 8.

1. Lệnh tự kiểm tra

Nội dung có nằm trong HTML không

curl -s https://{{DOMAIN}}/ | grep -E '<div id="(root|app|__next)"></div>'

Có kết quả xuất ra nghĩa là kết xuất thuần phía client. Không có kết quả thì đo độ dài nội dung:

curl -s https://{{DOMAIN}}/ | sed 's/<[^>]*>//g' | tr -d ' \n' | wc -m

Thấp hơn 500, cơ bản có thể kết luận nội dung không vào HTML.

H1 và structured data

curl -s https://{{DOMAIN}}/ | grep -c '<h1'
curl -s https://{{DOMAIN}}/ | grep -o '"@type" *: *"[^"]*"' | sort -u

Dòng đầu tiên phải là 1. Dòng thứ hai trống nghĩa là trang không có bất kỳ structured data nào.

AI crawler có bị chặn nhầm không

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}}/

Hai mã trạng thái không nhất quán, nghĩa là WAF hoặc quy tắc chống crawl đang chặn AI crawler. Điều này phổ biến hơn vấn đề của robots.txt.

Hai địa chỉ cố định

https://{{DOMAIN}}/robots.txt
https://{{DOMAIN}}/llms.txt

Kiểm tra riêng: robots có khai báo sitemap không, có ghi rõ quy tắc cho AI crawler không; llms.txt có tồn tại không.

2. Sáu lớp mục kiểm tra

【Tầng 1 Danh tính thực thể】
[ ] Tên công ty đầy đủ
[ ] Tên viết tắt thương hiệu
[ ] Tên tiếng Anh (điền nếu có)
[ ] Tên cũ (điền nếu có)
[ ] Thời gian thành lập
[ ] Nơi đăng ký
[ ] Mã số tín dụng xã hội thống nhất
[ ] Định nghĩa một câu về hoạt động chính (trong 40 ký tự)
[ ] Mối quan hệ trực thuộc giữa tên thương hiệu và tên sản phẩm, ghi rõ nhà sản xuất trên trang sản phẩm
[ ] Tên, địa chỉ, điện thoại, phiên bản duy nhất trên toàn trang
[ ] Liên kết hồ sơ uy tín bên ngoài (đăng ký kinh doanh, bách khoa, 公众号, GitHub, LinkedIn)
[ ] Tất cả nội dung trên là văn bản thuần, không phải hình ảnh

【Tầng 2 Ranh giới năng lực】
[ ] Câu định nghĩa: là gì, một câu
[ ] Đối tượng áp dụng
[ ] Trường hợp không áp dụng (kèm gợi ý giải pháp thay thế)
[ ] Danh mục sản phẩm bàn giao
[ ] Chu kỳ bàn giao và điều kiện tiên quyết
[ ] Phương thức hoặc logic định giá
[ ] Khu vực phục vụ, ngôn ngữ, điều kiện gia nhập
[ ] Hạn chế và điều kiện tiên quyết

【Tầng 3 Bằng chứng uy tín】
[ ] Tên tác giả + trang tác giả (lý lịch, chứng chỉ, liên kết danh tính bên ngoài)
[ ] Số chứng chỉ năng lực và cơ quan cấp
[ ] Số bằng sáng chế
[ ] Số báo cáo kiểm nghiệm và cơ quan kiểm nghiệm
[ ] Trường hợp khách hàng: ngành, bối cảnh, dữ liệu có thể công bố
[ ] Mỗi con số kèm nguồn, phạm vi đo, thời gian
[ ] Trang hiển thị thời gian cập nhật lần cuối
[ ] Trang quan trọng lưu giữ lịch sử sửa đổi
[ ] Chính sách biên tập và kiểm chứng thông tin
[ ] Tuyên bố miễn trừ trách nhiệm

【Tầng 4 So sánh khác biệt】
[ ] Bảng so sánh (so sánh theo loại giải pháp, không nêu tên đối thủ)
[ ] Ma trận năng lực (phân loại theo tính năng / tình huống / quy mô)
[ ] Ghi rõ "chúng tôi không làm gì"
[ ] Trong so sánh ghi rõ chi phí và đánh đổi

【Tầng 5 Lộ trình chuyển đổi】
[ ] Mỗi trang chỉ có một CTA chính
[ ] Thông tin liên hệ nhất quán trên mọi kênh
[ ] Thông tin liên hệ có văn bản thuần có thể sao chép
[ ] Cam kết phản hồi: bao lâu trả lời, ai trả lời
[ ] Điều khoản hậu mãi và bảo đảm

【Tầng 6 Máy đọc được】
[ ] Kết xuất phía máy chủ hoặc tạo tĩnh
[ ] Mỗi trang một H1
[ ] H2 sử dụng câu hỏi thực
[ ] Nội dung chính nằm trong main / article
[ ] Dữ liệu cấu trúc Organization
[ ] Dữ liệu cấu trúc Article
[ ] Dữ liệu cấu trúc BreadcrumbList
[ ] Product / Service (theo loại trang)
[ ] FAQPage (chỉ đánh dấu hỏi đáp hiển thị)
[ ] robots.txt khai báo rõ trình thu thập AI
[ ] llms.txt đưa lên
[ ] sitemap phân vùng và lastmod thực
[ ] canonical thống nhất
[ ] hreflang tự nhất quán khép kín (trang đa ngôn ngữ)
[ ] Không có soft 404
[ ] alt hình ảnh đầy đủ
[ ] Dữ liệu quan trọng có phiên bản văn bản

3. Mẫu structured data

Thực thể tổ chức (chèn vào mỗi trang toàn site)

{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://{{DOMAIN}}/#organization",
  "name": "{{Tên đầy đủ thương hiệu}}",
  "alternateName": ["{{Tên viết tắt thương hiệu}}", "{{Tên tiếng Anh}}"],
  "legalName": "{{Tên đầy đủ đăng ký kinh doanh}}",
  "url": "https://{{DOMAIN}}/",
  "logo": {
    "@type": "ImageObject",
    "url": "https://{{DOMAIN}}/logo.png",
    "width": 512,
    "height": 512
  },
  "foundingDate": "{{2021-03-15}}",
  "description": "{{Định nghĩa một câu, hoàn toàn khớp với đoạn đầu trang Giới thiệu}}",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "{{Địa chỉ chi tiết}}",
    "addressLocality": "{{Thành phố}}",
    "addressRegion": "{{Tỉnh}}",
    "postalCode": "{{Mã bưu chính}}",
    "addressCountry": "CN"
  },
  "contactPoint": [{
    "@type": "ContactPoint",
    "telephone": "{{+86-xxx}}",
    "contactType": "sales",
    "areaServed": "CN",
    "availableLanguage": ["zh-CN", "en"]
  }],
  "sameAs": [
    "{{Liên kết hồ sơ đăng ký kinh doanh}}",
    "{{Liên kết bách khoa}}",
    "{{Liên kết 公众号 hoặc mạng xã hội}}",
    "{{GitHub / LinkedIn / trang tuyển dụng}}"
  ],
  "knowsAbout": ["{{Năng lực cốt lõi 1}}", "{{Năng lực cốt lõi 2}}", "{{Năng lực cốt lõi 3}}"]
}

Trang bài viết

{
  "@context": "https://schema.org",
  "@type": "Article",
  "@id": "https://{{DOMAIN}}/{{PATH}}#article",
  "headline": "{{tiêu đề, trong 110 ký tự}}",
  "description": "{{tóm tắt 80-140 từ, gồm tên thương hiệu và một câu định nghĩa năng lực}}",
  "image": ["https://{{DOMAIN}}/{{ảnh bìa}}"],
  "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": "{{tên tác giả}}",
    "jobTitle": "{{chức danh}}",
    "url": "https://{{DOMAIN}}/author/{{slug}}"
  },
  "publisher": { "@id": "https://{{DOMAIN}}/#organization" },
  "isPartOf": { "@id": "https://{{DOMAIN}}/#website" },
  "about": [{ "@type": "Thing", "name": "{{từ khóa chủ đề}}" }],
  "citation": ["{{URL nguồn trích dẫn bên ngoài}}"]
}

publisher.@id phải hoàn toàn giống với thực thể tổ chức @id nếu không hai nút sẽ bị ngắt kết nối trong đồ thị.

khối hỏi đáp

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "{{câu hỏi người dùng thực sự sẽ hỏi}}",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "{{40-120 từ, kết luận trước, không dẫn dắt}}"
    }
  }]
}

4. Mẫu robots.txt

# Loại truy xuất: bắt buộc cho phép
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: /

# Loại huấn luyện: quyết định theo chiến lược thương hiệu
User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: Google-Extended
Allow: /

User-agent: Bytespider
Allow: /

# Đóng toàn bộ đường dẫn nhạy cảm
User-agent: *
Disallow: /admin/
Disallow: /api/
Allow: /

Sitemap: https://{{DOMAIN}}/sitemap.xml

Không Disallow tài nguyên CSS và JS, một số search engine cần chúng để đánh giá cấu trúc trang.

5. Mẫu llms.txt

# {{Tên thương hiệu}}

> {{Định nghĩa một câu: cung cấp dịch vụ C cho khách hàng B trong ngành A, giải quyết vấn đề D. Đồng thời nêu rõ thời gian thành lập và địa điểm.}}

## Trang cốt lõi
- [{{Tên sản phẩm}}](https://{{DOMAIN}}/{{path}}): {{Trang này trả lời câu hỏi gì}}
- [{{Giải pháp}}](https://{{DOMAIN}}/{{path}}): {{Mô tả một câu}}
- [Bảng giá](https://{{DOMAIN}}/pricing): {{Cách tính giá trong một câu}}

## Thông tin uy tín
- [Về chúng tôi](https://{{DOMAIN}}/about): Tên đầy đủ công ty, thời gian thành lập, chứng nhận, đội ngũ
- [Khách hàng tiêu biểu](https://{{DOMAIN}}/cases): {{Ngành và kết quả có thể công bố}}
- [Câu hỏi thường gặp](https://{{DOMAIN}}/faq): {{Các chủ đề bao phủ}}

## Chuyên mục nội dung
- [{{Tên chuyên mục}}](https://{{DOMAIN}}/{{path}}/): {{Định vị chuyên mục}}

## Phạm vi và tuyên bố
- Khu vực phục vụ: {{...}}
- Trường hợp không phù hợp: {{...}}
- Cập nhật nội dung: {{Tần suất}}

## Optional
- [{{Trang thứ cấp}}](https://{{DOMAIN}}/{{path}}): Tài liệu bổ sung

Đoạn blockquote đầu tiên sẽ được trích dẫn nguyên đoạn, phải có thể đứng độc lập. Câu mô tả sau mỗi liên kết không được bỏ qua.

6. Mẫu câu cho đơn vị trả lời

Định nghĩa
{{thương hiệu/sản phẩm}} là {{danh mục}}, cung cấp {{năng lực}} cho {{đối tượng}}, dùng để giải quyết {{vấn đề}}.

Ranh giới
Phù hợp với bối cảnh {{điều kiện A}}, {{điều kiện B}}; nếu {{phản điều kiện}}, {{phương án thay thế}} sẽ phù hợp hơn.

So sánh
So với {{loại phương án A}}, {{sản phẩm này}} {{khác biệt}} ở {{khía cạnh}}, đánh đổi là {{trade-off}}.

Dữ liệu
{{chỉ số}} là {{giá trị}} (nguồn: {{tổ chức hoặc thống kê nội bộ}}, định nghĩa: {{định nghĩa}}, thời gian: {{năm tháng}}).

Tiêu chí tự kiểm tra: xóa hết tính từ, câu có còn đứng vững không. Xóa xong chỉ còn «chúng tôi cam kết cung cấp dịch vụ chất lượng cao» thì đó là copy marketing.

Quy chuẩn đoạn văn: 2 đến 4 câu, kết luận ở câu đầu; thông tin then chốt dùng bảng; thời gian ghi giá trị tuyệt đối, không ghi «gần đây»; số liệu kèm đơn vị và định nghĩa; dữ liệu then chốt không được chỉ tồn tại trong hình ảnh.

7. Ưu tiên và thời gian thi công

Cấp độ Nội dung Thời gian
P0 SSR hoặc tĩnh hóa; đoạn định danh thực thể; mỗi trang một câu định nghĩa; chiến lược robots và AI crawler; đánh dấu Organization và Article 2 tuần
P1 Tái cấu trúc các mục dạng hỏi đáp; trang tác giả, trang chứng nhận, trang case study; llms.txt; quản trị sitemap và hreflang 1 tháng
P2 Ma trận so sánh; thông tin giá; cơ chế giám sát Liên tục

Thứ tự nội bộ P0: render → thực thể → câu định nghĩa → chiến lược crawler → structured data. Phần trước không động thì phần sau vô hiệu.

8. Ghi nhận nghiệm thu

Bốn kiểu câu hỏi trong bộ câu hỏi

Nhu cầu danh mục: có những nhà cung cấp {{danh mục}} nào tại Trung Quốc
So sánh lựa chọn: {{phương án A}} và {{phương án B}} cái nào phù hợp hơn với {{đối tượng}} quy mô {{quy mô}}
Điểm đau bối cảnh: {{vấn đề cụ thể}} giải quyết thế nào
Tra cứu thương hiệu: {{thương hiệu}} làm gì, có đáng tin không

Năm chỉ số

Chỉ số Định nghĩa
Tỷ lệ đề cập Số câu có xuất hiện tên thương hiệu / tổng số câu
Tỷ lệ đề cập đầu tiên Số câu thương hiệu đứng ở vị trí đầu tiên / tổng số câu
Tỷ lệ đề xuất ưu tiên Số câu được đề xuất rõ ràng hoặc diễn đạt là phù hợp hơn / tổng số câu
Tỷ lệ trích dẫn website chính thức Số câu trích dẫn hoặc liên kết đến website chính thức / tổng số câu
Độ chính xác của sự thật Số câu hỏi có thông tin thương hiệu được trình bày đúng / Số câu hỏi được đề cập

Trường của bảng ghi nhận

Ngày | Mô hình | ID câu hỏi | Nội dung câu hỏi | Có truy cập mạng | Có đề cập | Vị trí | Có đề xuất | Cảm xúc | Có trích dẫn website chính thức | URL trang được trích dẫn | Câu gốc được trích dẫn | Lỗi thực tế

«Câu gốc được trích dẫn» là cột giá trị nhất: nó cho bạn biết câu nào thực sự được chọn, chuyển câu này lên khối phía trước và có cấu trúc hơn chính là tối ưu đơn điểm hiệu quả nhất.

Nhịp độ: giữ baseline trước khi cải tạo, sau khi hoàn thành đo lại vào tháng 1 và tháng 3, sau đó chạy cố định một ngày mỗi tháng. Tối thiểu bao phủ 3 đến 5 mô hình.

Phụ lục: tài liệu SKILL đầy đủ (có thể sao chép một-click)

Phần phụ lục trên là danh sách để đối chiếu thủ công, còn đây là SKILL hoàn chỉnh có thể nạp trực tiếp vào công cụ AI. Nó bao gồm định nghĩa quy trình làm việc, danh sách kiểm tra sáu lớp, toàn bộ mẫu structured data, chiến lược AI crawler, mẫu câu cho answer unit, phương pháp nghiệm thu, và một script kiểm tra chỉ phụ thuộc vào thư viện chuẩn.

Sau khi sao chép, theo phần đầu tệp <!-- ===== Tên tệp ===== --> tách chú thích thành các tệp tương ứng. Cấu trúc thư mục như sau:

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
Định dạng Markdown, khoảng 30.489 từ
<!-- ===== 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())
```

Gợi ý:scripts/audit_site.py Yêu cầu Python 3.8 trở lên, không cần cài thư viện bên thứ ba. Script kết thúc với mã thoát 1 khi phát hiện vấn đề P0, có thể tích hợp trực tiếp vào CI.

Bài viết này là một phần của loạt bài 《100 câu hỏi về organic marketing tiếng Trung》.Xem mục lục đầy đủ →
Quét
Quét mã để theo dõi