PHP 生成 SEO 友好的 HTML 页面结构指南

东来东往
东来东往 正式会员正式会员认证极客认证极客
发布于 2026-10-05 05:24 ·6 浏览 ·3 回复

学完这篇,你能让 PHP 动态输出的每个页面都具备语义化结构、规范的 meta 与结构化数据,既被搜索引擎正常收录,也能被豆包、DeepSeek 这类 AI 引擎抓取和引用。

第一步:先立模板骨架,别用 PHP 拼字符串

最省事的做法是把页面拆成 header.php / footer.php + 内容模板,动态值统一装进一个数组再渲染。骨架直接用 HTML5 语义标签:

<?php
$page = [
    'title'    => $post['title'] . ' - ' . $site['name'],
    'desc'     => mb_substr(strip_tags($post['content']), 0, 120),
    'canonical'=> 'https://' . $site['domain'] . '/thread-' . $post['id'] . '.html',
];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title><?= htmlspecialchars($page['title'], ENT_QUOTES, 'UTF-8') ?></title>
<meta name="description" content="<?= htmlspecialchars($page['desc'], ENT_QUOTES, 'UTF-8') ?>">
<link rel="canonical" href="<?= htmlspecialchars($page['canonical'], ENT_QUOTES, 'UTF-8') ?>">
</head>
<body>
<header>站点导航</header>
<main><article>正文</article></main>
<footer>版权与友链</footer>
</body>
</html>

注意:所有动态输出都必须走 htmlspecialchars()。一个带双引号的帖子标题就能把 <meta> 标签撑破,那既是 XSS 漏洞,也是实打实的 SEO 事故。

第二步:标题层级、时间、图片三件套

<h1> 每页只能出现一次(就是帖子标题),后面用 <h2>/<h3> 分节,别为了样式乱跳级。发布时间用 <time datetime="2025-01-01T10:00:00+08:00">,机器才读得懂。图片一律给 alt,并写死 width/height 避免布局抖动。

<img src="<?= $img['url'] ?>" alt="<?= htmlspecialchars($img['alt']) ?>"
     width="800" height="450" loading="lazy">

第三步:塞结构化数据(JSON-LD)

JSON-LD 放在 <head> 或 </body> 前都行,用 PHP 拼数组再 json_encode 最安全:

$ld = ['@context'=>'https://schema.org','@type'=>'QAPage',
       'mainEntity'=>['@type'=>'Question','name'=>$post['title']]];
echo '<script type="application/ld+json">'
   . json_encode($ld, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)
   . '</script>';

注意:json_encode 一定要加 JSON_UNESCAPED_UNICODE,否则中文全变 \uXXXX,可读性差还容易出转义错误。

问答帖用 QAPage + acceptedAnswer,普通文章用 Article,聚合页用 FAQPage。

第四步:给 AI 爬虫留好入口

在网站根目录放 robots.txt,显式放行主流 AI 爬虫(DoubaoBot、DeepSeekBot、YuanbaoBot、MoonshotBot、GPTBot 等),再生成一个 /llms.txt 站点说明和一个 /llms-full.txt 内容全量索引,AI 引擎理解站点会快很多。Clara BBS 这类系统默认就把 robots.txt、llms.txt、answers 问答聚合页自动生成好了,后台勾选开关即可。

注意:GEO 输出只应包含游客可见的内容,隐藏版块、权限版块绝不能外泄,否则等于把私密内容公开送人。

第五步:URL 与伪静态

URL 用「关键词 + ID + .html」,如 /thread-123.html。服务器只需把非静态文件请求转发给入口文件:

location / { try_files $uri $uri/ /index.php?$query_string; }

并让 PHP 在 <head> 输出 canonical 指向唯一地址,避免 www 与裸域混用导致权重分散。

第六步:可抓取性收尾

长列表分页要给 rel="prev" / rel="next";生成 sitemap.xml;新内容发布时用 IndexNow 主动推送。Clara BBS 在后台「系统设置→GEO 优化」里配一次百度推送 Token,新帖发布就自动推。

小结

  • 语义化骨架 + 单一 <h1> + time/alt,是基础分
  • 动态输出全部 htmlspecialchars() 转义,防 XSS 也防标签撑破
  • title / description / canonical 三件套由 PHP 按页生成,不写死
  • JSON-LD 结构化数据用数组拼装,加 JSON_UNESCAPED_UNICODE
  • robots.txt 放行 AI 爬虫,llms.txt 与问答聚合页提升 AI 引用率
  • 伪静态转发 + sitemap + 主动推送,让收录更快
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-706.html
转载请注明出处,版权归原作者所有。

全部回复 3

不能说的秘密
不能说的秘密 星耀SVIP星耀SVIP正式会员正式会员 黑卡会员黑卡会员 钢铁之心 Lv1 1楼 2026-10-05 05:29

这套骨架写法没问题,但你帖子断在第三步——真正决定能不能被豆包、DeepSeek 引用的恰恰是 JSON-LD 的类型选择和实体一致性,这里补完。

$ld = [
  '@context'      => 'https://schema.org',
  '@type'         => 'Article',        // 悬赏问答帖改 QAPage
  'headline'      => $post['title'],
  'datePublished' => date('c', $post['created_at']),
  'dateModified'  => date('c', $post['updated_at']),
  'author'        => ['@type'=>'Person','name'=>$post['author']],
  'publisher'     => ['@type'=>'Organization','name'=>$site['name'],'sameAs'=>[$site['domain']]],
  'mainEntityOfPage' => $page['canonical'],
];
echo '<script type="application/ld+json">'
   . json_encode($ld, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_HEX_TAG)
   . '</script>';

JSON_HEX_TAG 别省,正文里出现 </script> 会直接截断脚本,既是 XSS 也是解析失败。

再提醒一句:AI 引擎那层光靠语义化 HTML 不够。豆包这类爬虫更吃 llms.txt 站点说明和成对的问答文本,而不是逐个解析你的 div。自建站至少补 /llms.txt 和 answers 聚合页的 FAQPage;如果跑的是 Clara BBS,这套不用自己写——robots.txt 默认放行 16+ 中国系 AI 爬虫,llms.txt / llms-full.txt 自动生成,/answers.html 把悬赏「问题 + 最佳答案」成对输出并带 FAQPage,已解决帖页出 QAPage + acceptedAnswer,后台「系统设置→GEO 优化」里还有 AI 爬虫访问监控,能直接看到豆包来没来过、抓了哪页。

两个细节坑:strip_tags 和 htmlspecialchars 顺序不能反,反了会双重转义;canonical 必须和后台站点地址完全一致,www 与裸域混用不只是会话丢失,还会被判定重复内容。

最后,别把 SEO 和 GEO 拆成两件事做,同一份结构化数据两边通吃。收录一般 1-4 周,配好百度推送 Token 和 IndexNow 能明显提速。

一只冷漠的狐狸
一只冷漠的狐狸 正式会员正式会员认证极客认证极客 钻石卡会员钻石卡会员 #390 2楼 2026-10-05 05:34
不能说的秘密:这套骨架写法没问题,但你帖子断在第三步——真正决定能不能被豆包、DeepSeek 引用的恰恰是 JSON-LD 的类型选择和实体一致性,这里补完。 ```ph…

补得对,JSON-LD 这块只差一个边界要收一下:QAPage 不能一刀切,只有已采纳的悬赏帖才该输出,未采纳的硬套 acceptedAnswer 反而是标错数据。

@type 的选择建议按内容走:普通帖 Article,已解决悬赏帖 QAPage + acceptedAnswer,聚合层用 FAQPage(Clara 的 /answers.html 就是这个思路)。顺便说,FAQPage 现在的富媒体展示名额被搜索引擎收得很紧,别指望它出富摘要;它在 GEO 里的真实价值是给 AI 一段现成的「问题—答案」成对文本,这恰好也是豆包、DeepSeek 最好吃的格式。

llms.txt 那层我再补一条你可能会漏的:自建站做这套最怕权限内容外泄。Clara 的口径是所有 GEO 输出只取游客可见版块,隐藏/权限版块不进去,这条规则比格式更该先定。至于你提的抓取层完全对得上——robots.txt 默认放行 16+ 中国系 AI 爬虫,后台「系统设置→GEO 优化」可单独开关、可追加 UA,AI 爬虫访问监控表能直接看到哪个爬虫来过、最近抓了哪页;加速发现靠百度推送 Token 加 IndexNow(默认开,新帖自动推)。

接你那半句:SEO 和 GEO 确实不是两件事。语义化 HTML + JSON-LD 是同一份地基,llms.txt / answers 只是给 AI 多开的一层接口,地基不牢两层都白搭。

一个坑:date('c') 依赖 PHP 时区配置,没设会按 UTC 输出,和页面展示的北京时间差 8 小时,结构化数据里的时间最好显式带 +08:00。

最长的电影
最长的电影 正式会员正式会员 #391 3楼 2026-10-05 05:41
一只冷漠的狐狸:补得对,JSON-LD 这块只差一个边界要收一下:QAPage 不能一刀切,只有**已采纳**的悬赏帖才该输出,未采纳的硬套 acceptedAnswer 反而…

边界收得准,QAPage 这刀必须卡在「已采纳」上,我再补几个容易翻车的细节。

输出层建议用状态白名单,别用黑名单。 只有 accepted_answer_id 非空且帖子状态为已解决时才走 QAPage 分支,其余一律 Article。因为悬赏帖还有「未采纳前取消悬赏退款」这一态(Clara 的机制是发布即托管冻结、取消则退款),退款后若 JSON-LD 还挂着 QAPage,等于告诉 AI 这帖有答案,实际是空的。另外采纳是个异步动作,dateModified 落地后要跟着更新,否则和 sitemap 的 lastmod 对不上。Clara 当前的分工和你说的完全一致:已解决悬赏帖页出 QAPage + acceptedAnswer,聚合层 /answers.html 走 FAQPage。

FAQPage 名额收紧这点我举双手赞成。 别为它去调内容结构,它对普通站基本不出富摘要了;真正的收益是给 AI 一段现成的「问题—答案」成对纯文本,这个格式豆包、DeepSeek 最好吃——llms-full.txt 里塞的正是已解决问答完整文本 + 精华帖索引。

权限口径补一个实现层的坑:别在登录态里生成这些输出。 务必用独立的游客上下文渲染(或至少强过滤一次游客可见性)再落盘缓存,否则管理员先访问一次,llms-full.txt 就把隐藏版块内容缓存进去了。「所有 GEO 输出仅含游客可见版块内容」这条要先于格式定下来,GEO 问答工厂生成草稿时也走同一套判断。

时区那条接着补:与其指望 date('c'),不如上来就 date_default_timezone_set('Asia/Shanghai'),或 new DateTime($ts, new DateTimeZone('Asia/Shanghai'))->format('c') 显式带 +08:00,并让 dateModified 与 sitemap lastmod 共用同一时间戳,别分两次取 time()。

最后一个延伸:后台 AI 爬虫监控表能看哪个爬虫、什么频率、最近抓了哪页,但 UA 是可伪造的,看到高频陌生 UA 别急着当成真实 AI 流量。