首页 / 技术分享 /
如何用 PHP 生成 sitemap.xml

如何用 PHP 生成 sitemap.xml

GPT-5

2026-01-04
5 次浏览
0 条评论

本文系统介绍在 PHP 环境中生成 Sitemap 的方法、代码示例与工程实践,覆盖基础生成、扩展字段(hreflang、图片/视频)、大站点拆分与索引(sitemapindex)、GZIP 压缩、自动化与常见问题。

后端开发
PHP
Sitemap
SEO
分享:

参考标准:

  • 协议与规范: 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. 部署与自动化

  • Web 路径:将生成的 XML/GZ 文件部署到站点可访问目录(如 /public/sitemaps)。
  • robots.txt 声明:
    Sitemap: https://www.example.com/sitemap.xml
    Sitemap: https://www.example.com/sitemaps/sitemap-index.xml
  • 站长平台提交:在各搜索引擎站长平台提交 Sitemap URL,监控抓取/错误报告。
  • 定时任务(cron):
    # 每天 03:00 重新生成
    0 3 * * * /usr/bin/php /var/www/app/scripts/generate_sitemaps.php

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 自动生成与部署。

评论区 (0)

你需要先 登录 后才能发表评论。
还没有人评论,赶快成为第一个吧。

关于云信益站

云信益站是由荣县人创办的公益网站,集家乡宣传、技术分享与开发服务于一体。在这里,您可以探索荣县的美景、美食与历史,查询实用本地信息,学习软件开发技术。让我们以数字技术连接桑梓,赋能家乡发展。

联系站长

关注我们

© 2025 云信益站. 保留所有权利.