参考标准:
- 协议与规范: http://www.sitemaps.org/protocol.html
- 默认命名空间:
http://www.sitemaps.org/schemas/sitemap/0.9
- 扩展命名空间:
- 多语言(xhtml):
http://www.w3.org/1999/xhtml
- 图片:
http://www.google.com/schemas/sitemap-image/1.1
- 视频:
http://www.google.com/schemas/sitemap-video/1.1
- 新闻:
http://www.google.com/schemas/sitemap-news/0.9
1. 生成前的关键要点
- 单个 Sitemap 限制:最多 50,000 个 URL,未压缩大小不超过 50MB。
- 编码与类型:UTF-8;服务端返回
Content-Type: application/xml(或 text/xml)。
- URL 要求:绝对 URL(含协议与主机名),建议为页面的规范(canonical)URL,返回 200。
- 字段建议:
<loc> 必填。
<lastmod> 建议使用 ISO 8601(date('c') 输出的 YYYY-MM-DDThh:mm:ss±hh:mm)。
<changefreq>、<priority> 多为提示型,搜索引擎可能忽略。
- 目录组织:大站点请拆分多个 sitemap(可 gzip),并用
sitemapindex 汇总。
- 发现方式:在
robots.txt 中声明 Sitemap;也可在各搜索引擎站长平台提交。
2. 方法一:使用 DOMDocument 构建 sitemap.xml
DOMDocument 可精细控制命名空间、元素与属性,适合需要扩展的场景。
<?php
function buildSitemap(array $urls, string $outputFile): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
// 根元素 urlset,声明默认命名空间
$urlset = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$dom->appendChild($urlset);
foreach ($urls as $item) {
// $item = ['loc' => 'https://www.example.com/', 'lastmod' => '2026-01-04T08:00:00+08:00', 'changefreq' => 'daily', 'priority' => '1.0']
$url = $dom->createElement('url');
$loc = $dom->createElement('loc', htmlspecialchars($item['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8'));
$url->appendChild($loc);
if (!empty($item['lastmod'])) {
$lastmod = $dom->createElement('lastmod', $item['lastmod']); // 建议传 ISO 8601
$url->appendChild($lastmod);
}
if (!empty($item['changefreq'])) {
$url->appendChild($dom->createElement('changefreq', $item['changefreq']));
}
if (isset($item['priority'])) {
$url->appendChild($dom->createElement('priority', (string)$item['priority']));
}
$urlset->appendChild($url);
}
// 写入文件
$dom->save($outputFile);
}
// 示例调用
$urls = [
['loc' => 'https://www.example.com/', 'lastmod' => date('c'), 'changefreq' => 'daily', 'priority' => '1.0'],
['loc' => 'https://www.example.com/about/', 'lastmod' => '2025-12-20', 'changefreq' => 'monthly', 'priority' => '0.4'],
];
buildSitemap($urls, __DIR__ . '/sitemap.xml');
要点:
- 使用
createElementNS 在根节点声明命名空间。
- 文本内容用
htmlspecialchars(..., ENT_XML1) 以确保 &, <, > 等被正确转义。
lastmod 建议使用 date('c') 或数据库中记录的真实更新时间。
3. 方法二:使用 SimpleXML 快速生成
SimpleXML 适配场景:结构简单、扩展较少时。
<?php
function buildSitemapSimple(array $urls, string $outputFile): void {
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><urlset/>');
// 声明默认命名空间
$xml->addAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');
foreach ($urls as $item) {
$url = $xml->addChild('url');
$url->addChild('loc', htmlspecialchars($item['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8'));
if (!empty($item['lastmod'])) {
$url->addChild('lastmod', $item['lastmod']);
}
if (!empty($item['changefreq'])) {
$url->addChild('changefreq', $item['changefreq']);
}
if (isset($item['priority'])) {
$url->addChild('priority', (string)$item['priority']);
}
}
$xml->asXML($outputFile);
}
// 示例
$urls = [
['loc' => 'https://www.example.com/', 'lastmod' => date('c'), 'changefreq' => 'daily', 'priority' => '1.0'],
];
buildSitemapSimple($urls, __DIR__ . '/sitemap.xml');
4. 多语言/多地区(hreflang via xhtml:link)
在一个 URL 下添加各语言版本的互链,需声明 xhtml 命名空间。
<?php
function buildSitemapWithHreflang(array $pages, string $outputFile): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
// 声明默认与 xhtml 命名空间
$urlset = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$urlset->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
$dom->appendChild($urlset);
foreach ($pages as $p) {
// $p = ['loc' => 'https://www.example.com/product/123', 'alts' => [['lang'=>'en','href'=>'https://www.example.com/en/product/123'], ...]]
$url = $dom->createElement('url');
$url->appendChild($dom->createElement('loc', htmlspecialchars($p['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
foreach ($p['alts'] as $alt) {
// <xhtml:link rel="alternate" hreflang="en" href="..." />
$link = $dom->createElementNS('http://www.w3.org/1999/xhtml', 'xhtml:link');
$link->setAttribute('rel', 'alternate');
$link->setAttribute('hreflang', $alt['lang']);
$link->setAttribute('href', $alt['href']);
$url->appendChild($link);
}
$urlset->appendChild($url);
}
$dom->save($outputFile);
}
// 示例
$pages = [
[
'loc' => 'https://www.example.com/product/123',
'alts' => [
['lang' => 'en', 'href' => 'https://www.example.com/en/product/123'],
['lang' => 'fr', 'href' => 'https://www.example.com/fr/produit/123'],
['lang' => 'zh-CN', 'href' => 'https://www.example.com/zh-cn/product/123'],
['lang' => 'x-default', 'href' => 'https://www.example.com/product/123'],
],
],
];
buildSitemapWithHreflang($pages, __DIR__ . '/sitemap-hreflang.xml');
5. 图片/视频扩展示例(简版)
如需为页面提供图片或视频元数据,在 URL 条目内加入扩展字段并声明命名空间。
<?php
function buildSitemapWithImageVideo(array $items, string $outputFile): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$urlset = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$urlset->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1');
$urlset->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:video', 'http://www.google.com/schemas/sitemap-video/1.1');
$dom->appendChild($urlset);
foreach ($items as $i) {
$url = $dom->createElement('url');
$url->appendChild($dom->createElement('loc', htmlspecialchars($i['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
foreach ($i['images'] ?? [] as $img) {
$imgNode = $dom->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:image');
$imgNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:loc', htmlspecialchars($img['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
if (!empty($img['title'])) {
$imgNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-image/1.1', 'image:title', $img['title']));
}
$url->appendChild($imgNode);
}
if (!empty($i['video'])) {
$v = $i['video'];
$videoNode = $dom->createElementNS('http://www.google.com/schemas/sitemap-video/1.1', 'video:video');
$videoNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-video/1.1', 'video:thumbnail_loc', htmlspecialchars($v['thumbnail'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
$videoNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-video/1.1', 'video:title', $v['title']));
$videoNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-video/1.1', 'video:description', $v['description']));
// content_loc 或 player_loc 二选一
if (!empty($v['content_loc'])) {
$videoNode->appendChild($dom->createElementNS('http://www.google.com/schemas/sitemap-video/1.1', 'video:content_loc', htmlspecialchars($v['content_loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
}
$url->appendChild($videoNode);
}
$urlset->appendChild($url);
}
$dom->save($outputFile);
}
// 示例
$items = [
[
'loc' => 'https://www.example.com/article/123',
'images' => [
['loc' => 'https://cdn.example.com/images/123-cover.jpg', 'title' => '示例封面'],
],
'video' => [
'thumbnail' => 'https://cdn.example.com/thumbs/abc.jpg',
'title' => '演示视频',
'description' => '该视频展示产品功能。',
'content_loc' => 'https://video.example.com/stream/abc.mp4',
],
],
];
buildSitemapWithImageVideo($items, __DIR__ . '/sitemap-media.xml');
6. 大型站点:拆分与索引(sitemapindex)
当 URL > 50,000 或未压缩 > 50MB,需要拆分多个 sitemap-*.xml,并增加索引文件 sitemap-index.xml。
<?php
function writeSitemapChunk(array $urls, string $filePath): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$urlset = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$dom->appendChild($urlset);
foreach ($urls as $u) {
$url = $dom->createElement('url');
$url->appendChild($dom->createElement('loc', htmlspecialchars($u['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
if (!empty($u['lastmod'])) {
$url->appendChild($dom->createElement('lastmod', $u['lastmod']));
}
$urlset->appendChild($url);
}
$dom->save($filePath);
}
function buildSitemapIndex(array $sitemapFiles, string $outputFile): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$index = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'sitemapindex');
$dom->appendChild($index);
foreach ($sitemapFiles as $sf) {
// $sf = ['loc' => 'https://www.example.com/sitemaps/sitemap-1.xml.gz', 'lastmod' => '2026-01-04']
$s = $dom->createElement('sitemap');
$s->appendChild($dom->createElement('loc', htmlspecialchars($sf['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
if (!empty($sf['lastmod'])) {
$s->appendChild($dom->createElement('lastmod', $sf['lastmod']));
}
$index->appendChild($s);
}
$dom->save($outputFile);
}
7. 生成 .xml.gz(GZIP 压缩)
压缩有利于降低传输开销,索引可指向 .xml.gz。
<?php
function gzipFile(string $sourceXml, string $targetGz): void {
$data = file_get_contents($sourceXml);
if ($data === false) {
throw new RuntimeException("Failed to read {$sourceXml}");
}
$gz = gzopen($targetGz, 'wb9'); // 0-9 压缩级别
if ($gz === false) {
throw new RuntimeException("Failed to open {$targetGz}");
}
gzwrite($gz, $data);
gzclose($gz);
}
// 示例:将 sitemap-1.xml 压缩为 sitemap-1.xml.gz
gzipFile(__DIR__ . '/sitemap-1.xml', __DIR__ . '/sitemap-1.xml.gz');
8. 从数据源批量生成的完整示例
示例:从数据库/数据源分页生成多个 sitemap-*.xml.gz,并创建 sitemap-index.xml。
<?php
/**
* 假设 getUrlBatch($offset, $limit) 返回形如:
* [
* ['loc' => 'https://www.example.com/page-1', 'lastmod' => '2026-01-04T08:00:00+08:00'],
* ...
* ]
* 当数据耗尽返回空数组。
*/
const CHUNK_SIZE = 50000; // 每个 sitemap 的最大 URL 数量
$publicBase = 'https://www.example.com/sitemaps'; // 对外可访问路径前缀
$localDir = __DIR__ . '/sitemaps'; // 本地生成目录
@mkdir($localDir, 0775, true);
$chunkIndex = 1;
$offset = 0;
$sitemapFiles = [];
while (true) {
$batch = getUrlBatch($offset, CHUNK_SIZE);
if (empty($batch)) {
break;
}
// 写入本地 XML
$xmlPath = "{$localDir}/sitemap-{$chunkIndex}.xml";
writeSitemapChunk($batch, $xmlPath);
// 压缩为 .gz(可选)
$gzPath = "{$localDir}/sitemap-{$chunkIndex}.xml.gz";
gzipFile($xmlPath, $gzPath);
// 记录索引文件项
$sitemapFiles[] = [
'loc' => "{$publicBase}/sitemap-{$chunkIndex}.xml.gz",
'lastmod' => date('Y-m-d'),
];
$chunkIndex++;
$offset += CHUNK_SIZE;
}
// 生成索引文件
buildSitemapIndex($sitemapFiles, "{$localDir}/sitemap-index.xml");
// robots.txt 里声明(示例,实际需写到根目录的 robots.txt)
// Sitemap: https://www.example.com/sitemap.xml
// Sitemap: https://www.example.com/sitemaps/sitemap-index.xml
// 伪实现:按你的数据源替换为真实查询
function getUrlBatch(int $offset, int $limit): array {
// 这只是示例:实际应从数据库分页查询或通过生成器流式遍历
static $all = null;
if ($all === null) {
$all = [];
for ($i = 1; $i <= 120000; $i++) {
$all[] = [
'loc' => "https://www.example.com/page-{$i}",
'lastmod' => date('c'),
];
}
}
return array_slice($all, $offset, $limit);
}
// 复用前文方法
function writeSitemapChunk(array $urls, string $filePath): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$urlset = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'urlset');
$dom->appendChild($urlset);
foreach ($urls as $u) {
$url = $dom->createElement('url');
$url->appendChild($dom->createElement('loc', htmlspecialchars($u['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
if (!empty($u['lastmod'])) {
$url->appendChild($dom->createElement('lastmod', $u['lastmod']));
}
$urlset->appendChild($url);
}
$dom->save($filePath);
}
function gzipFile(string $sourceXml, string $targetGz): void {
$data = file_get_contents($sourceXml);
if ($data === false) {
throw new RuntimeException("Failed to read {$sourceXml}");
}
$gz = gzopen($targetGz, 'wb9');
if ($gz === false) {
throw new RuntimeException("Failed to open {$targetGz}");
}
gzwrite($gz, $data);
gzclose($gz);
}
function buildSitemapIndex(array $sitemapFiles, string $outputFile): void {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->formatOutput = true;
$index = $dom->createElementNS('http://www.sitemaps.org/schemas/sitemap/0.9', 'sitemapindex');
$dom->appendChild($index);
foreach ($sitemapFiles as $sf) {
$s = $dom->createElement('sitemap');
$s->appendChild($dom->createElement('loc', htmlspecialchars($sf['loc'], ENT_QUOTES | ENT_XML1, 'UTF-8')));
if (!empty($sf['lastmod'])) {
$s->appendChild($dom->createElement('lastmod', $sf['lastmod']));
}
$index->appendChild($s);
}
$dom->save($outputFile);
}
工程建议:
- 数据量大时优先分页/流式生成,避免一次性将全部 URL 加载到内存。
- 生成后上传至静态托管目录(Nginx/Apache),确保对外可访问。
- CI/CD 或定时任务(cron)每日/每小时生成并覆盖更新。
9. 使用成熟库(简化开发)
-
通用 PHP:samdark/sitemap(高性能、支持分片与 gzip)
composer require samdark/sitemap
<?php
use samdark\sitemap\Sitemap;
use samdark\sitemap\Index;
$sitemap = new Sitemap(__DIR__ . '/sitemaps/sitemap.xml');
for ($i = 1; $i <= 1000; $i++) {
$sitemap->addItem("https://www.example.com/page-{$i}", time(), Sitemap::WEEKLY, 0.8);
}
$sitemap->write(); // 可自动分片与 gz,详见文档
$index = new Index(__DIR__ . '/sitemaps/sitemap-index.xml');
$index->addSitemap('https://www.example.com/sitemaps/sitemap.xml');
$index->write();
-
Laravel 项目:spatie/laravel-sitemap
composer require spatie/laravel-sitemap
use Spatie\Sitemap\Sitemap;
use Spatie\Sitemap\Tags\Url;
$sitemap = Sitemap::create();
$sitemap->add(Url::create('/')->setLastModificationDate(now())->setPriority(1.0)->setChangeFrequency(Url::CHANGE_FREQUENCY_DAILY));
$sitemap->writeToFile(public_path('sitemap.xml'));
10. 部署与自动化
11. 常见问题与排查
- 相对 URL 或不含协议/主机名 → 使用绝对 URL。
- 命名空间遗漏 → 扩展字段(image/video/xhtml)无效。
- 包含 noindex 或被 robots.txt 禁止的页面 → 去除或调整策略。
lastmod 不真实或频繁“假更新” → 仅在内容实际变更时更新。
- HTTP 返回码异常(3xx/4xx/5xx)或重定向链过长 → 以最终 200 页面为准。
- 混用 http/https、带/不带 www → 统一规范化策略(canonical、一致域名)。
- 文件超限 → 分片并用索引;必要时使用 gzip。
12. 速查清单
- [ ] URL 为规范化绝对地址且返回 200。
- [ ]
lastmod 使用 ISO 8601 并与内容更新一致。
- [ ] 命名空间声明正确(默认 + 扩展)。
- [ ] 单文件不超过 50,000 URL 与 50MB;超出则拆分。
- [ ] 同步到
robots.txt,并在站长平台提交。
- [ ] 对媒体/新闻/多语言使用相应扩展字段。
- [ ] 有定时任务或 CI/CD 自动生成与部署。