WordPress免插件sitemap实战指南:动态优先级、image:image、sitemapindex分页与缓存策略

WordPress免插件sitemap实战指南:动态优先级、image:image、sitemapindex分页与缓存策略
张文保 更新 32 分钟阅读 5,320 阅读
本文目录
  1. WordPress sitemap 的几种实现路径
  2. 路径一:原生 wp-sitemap.xml
  3. 路径二:插件方案
  4. 路径三:自定义 PHP 文件(本文方案)
  5. 完整 sitemap.php 代码(增强版)
  6. 相比原文方案的改进
  7. 暴露为 /sitemap.xml 的 rewrite 配置
  8. Apache (.htaccess)
  9. Nginx
  10. 避免与原生冲突
  11. 分页 sitemapindex 处理超大站
  12. 什么时候需要分页
  13. 分页方案
  14. 性能优化:缓存策略
  15. 问题:每次请求都查 DB
  16. 方案 A:文件缓存
  17. 方案 B:定时任务生成静态文件
  18. 方案 C:发文章触发增量更新
  19. 提交到搜索引擎
  20. Google Search Console
  21. 百度站长平台
  22. Bing Webmaster Tools
  23. robots.txt 声明
  24. 常见故障
  25. 故障 1:sitemap.xml 404
  26. 故障 2:XML 解析错误
  27. 故障 3:与 wp-sitemap.xml 内容重复
  28. 故障 4:内存溢出
  29. 故障 5:sitemap 里的 URL 是 HTTP 但站点已切 HTTPS
  30. 故障 6:分类页在 sitemap 但站点设了 noindex
  31. 故障 7:lastmod 时间错乱
  32. 常见问题解答
  33. 原生 wp-sitemap.xml 与自定义 sitemap 哪个更好?
  34. sitemap 多久更新一次?
  35. 多语言站点怎么做 sitemap?
  36. 需要给图片单独建 image-sitemap.xml 吗?
  37. sitemap 提交后多久能在 GSC 看到 URL 全部被抓?
  38. 能否用 Action 钩子让插件向 sitemap 注入额外 URL?
  39. 百度移动 sitemap 命名空间有什么用?
  40. sitemap 文件超过 50MB 怎么办?
  41. 能否给 sitemap 设访问频率限制?
  42. sitemap 里要不要包含已删除文章的 URL?
  43. 权威参考资料
摘要:中大型WordPress站动辄十万级URL,现成sitemap插件未必扛得住。本文给免插件的sitemap.php完整增强版实现——按五类内容输出、按文章新旧动态算changefreq和priority、嵌入特色图,再讲暴露成sitemap.xml的rewrite配置、分页sitemapindex应对超大站、缓存策略的性能优化、提交到搜索引擎和常见故障。

WordPress 生态里 sitemap 插件多到挑花眼:Yoast SEO、Rank Math、Google XML Sitemaps、All in One SEO 都自带 sitemap 功能,WordPress 5.5+ 还内置了原生 wp-sitemap.xml。但插件大多数把 sitemap 当作附属功能,配置项分散、URL 形态不可控、对超大站点(10 万+ URL)性能差。本文给出基于 wp-blog-header.php 直接生成 sitemap 的免插件方案,可定制 URL 类型组合、按 lastmod 优先级排序、自动分页输出 sitemapindex、支持百度移动 sitemap 命名空间,并扩展到与 WordPress 6.x 原生 sitemap 共存、CDN 缓存策略、与 GSC/百度站长平台的提交细节。

WordPress sitemap 的几种实现路径

路径一:原生 wp-sitemap.xml

WordPress 5.5+ 自动生成 wp-sitemap.xml,访问任意 WP 站点的根域加 /wp-sitemap.xml 都能看到。优点:零配置;缺点:URL 形态固定(每页 2000 条、按文章类型分页),无法自定义优先级、无法过滤特定栏目、无法加百度移动命名空间。

如果你完全没特殊需求,原生 sitemap 够用。本文方案适合需要定制的场景。

路径二:插件方案

Yoast、Rank Math 等插件覆盖了大多数定制需求。月流量 10 万以下的站点用插件最省事。但插件的代价是:增加 PHP 内存占用、与其它插件可能冲突、部分高级功能(lastmod 精确控制)需要付费 Pro 版。

路径三:自定义 PHP 文件(本文方案)

把 sitemap.php 放在站点根目录,require wp-blog-header.php 加载 WordPress 完整环境,然后用 get_posts、get_pages、get_terms 等 WP API 直接生成 XML 输出。优点:完全可控、零依赖、性能可调优;缺点:需要懂 PHP,超大站点要手写分页逻辑。

完整 sitemap.php 代码(增强版)

以下代码放到 WordPress 站点根目录的 sitemap.php:

<?php
/**
 * WordPress 自定义 sitemap 生成器
 * 输出 sitemap.xml 含首页、文章、单页、分类、标签
 */
require __DIR__ . '/wp-blog-header.php';

header('Content-Type: text/xml; charset=UTF-8');
header('HTTP/1.1 200 OK');
header('X-Robots-Tag: noindex, follow', true);

/* 配置 */
$config = [
    'posts_per_page'  => 1000,    // 单 sitemap 文件最多 URL 数(协议上限 50000)
    'include_pages'   => true,
    'include_categories' => true,
    'include_tags'    => true,
    'include_authors' => false,   // 多作者站点可开
    'exclude_post_types' => ['attachment', 'revision', 'nav_menu_item'],
    'exclude_categories' => [],   // 隐藏栏目 term_id 数组
    'exclude_tags'    => [],
];

echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
   . 'xmlns:xhtml="http://www.w3.org/1999/xhtml" '
   . 'xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" '
   . 'xmlns:mobile="http://www.baidu.com/schemas/sitemap-mobile/1/">' . "\n";

/* 工具函数:输出一条 url */
function output_url($loc, $lastmod = '', $changefreq = 'weekly', $priority = '0.5', $images = []) {
    echo '  <url>' . "\n";
    echo '    <loc>' . esc_url($loc) . '</loc>' . "\n";
    if (!empty($lastmod)) {
        $time = is_numeric($lastmod) ? $lastmod : strtotime($lastmod);
        echo '    <lastmod>' . gmdate('Y-m-d\TH:i:s+00:00', $time) . '</lastmod>' . "\n";
    }
    echo '    <changefreq>' . $changefreq . '</changefreq>' . "\n";
    echo '    <priority>' . $priority . '</priority>' . "\n";
    foreach ($images as $img) {
        echo '    <image:image>' . "\n";
        echo '      <image:loc>' . esc_url($img['url']) . '</image:loc>' . "\n";
        if (!empty($img['title'])) {
            echo '      <image:title><![CDATA[' . $img['title'] . ']]></image:title>' . "\n";
        }
        echo '    </image:image>' . "\n";
    }
    echo '  </url>' . "\n";
}

/* 1. 首页 */
$last_modified = get_lastpostmodified('GMT');
output_url(home_url('/'), $last_modified, 'daily', '1.0');

/* 2. 文章 */
$post_types = get_post_types(['public' => true]);
$post_types = array_diff($post_types, $config['exclude_post_types']);

$args = [
    'post_type'      => $post_types,
    'post_status'    => 'publish',
    'numberposts'    => $config['posts_per_page'],
    'orderby'        => 'modified',
    'order'          => 'DESC',
];
$myposts = get_posts($args);
foreach ($myposts as $post) {
    setup_postdata($post);

    /* 取文章首图 */
    $images = [];
    if (has_post_thumbnail($post->ID)) {
        $thumb_url = get_the_post_thumbnail_url($post->ID, 'large');
        if ($thumb_url) {
            $images[] = ['url' => $thumb_url, 'title' => get_the_title($post->ID)];
        }
    }

    /* 优先级算法:根据更新频率与点击量动态计算 */
    $age_days = (time() - get_the_modified_time('U', $post->ID)) / 86400;
    if ($age_days < 7) {
        $changefreq = 'daily'; $priority = '0.9';
    } elseif ($age_days < 30) {
        $changefreq = 'weekly'; $priority = '0.7';
    } elseif ($age_days < 365) {
        $changefreq = 'monthly'; $priority = '0.6';
    } else {
        $changefreq = 'yearly'; $priority = '0.4';
    }

    output_url(
        get_permalink($post->ID),
        get_the_modified_time('U', $post->ID),
        $changefreq,
        $priority,
        $images
    );
}
wp_reset_postdata();

/* 3. 单页面 */
if ($config['include_pages']) {
    $pages = get_pages(['sort_column' => 'post_modified', 'sort_order' => 'desc']);
    foreach ($pages as $page) {
        output_url(
            get_page_link($page->ID),
            get_post_modified_time('U', false, $page->ID),
            'weekly',
            '0.6'
        );
    }
}

/* 4. 分类 */
if ($config['include_categories']) {
    $cats = get_terms([
        'taxonomy' => 'category',
        'hide_empty' => true,
        'exclude' => $config['exclude_categories'],
    ]);
    if (!is_wp_error($cats)) {
        foreach ($cats as $cat) {
            output_url(get_term_link($cat), '', 'weekly', '0.7');
        }
    }
}

/* 5. 标签 */
if ($config['include_tags']) {
    $tags = get_terms([
        'taxonomy' => 'post_tag',
        'hide_empty' => true,
        'exclude' => $config['exclude_tags'],
    ]);
    if (!is_wp_error($tags)) {
        foreach ($tags as $tag) {
            output_url(get_term_link($tag), '', 'monthly', '0.4');
        }
    }
}

/* 6. 作者 */
if ($config['include_authors']) {
    $authors = get_users(['who' => 'authors', 'has_published_posts' => true]);
    foreach ($authors as $author) {
        output_url(get_author_posts_url($author->ID), '', 'monthly', '0.3');
    }
}

echo '</urlset>' . "\n";
echo '<!-- Generated at ' . gmdate('c') . ' -->' . "\n";

相比原文方案的改进

  1. 动态优先级:按文章修改时间动态计算 changefreq 与 priority,新文章优先级高、老文章优先级低。Google 抓取调度更友好。
  2. image:image 子标签:每篇文章带上特色图(如有),让 Google 同步索引图片,带 image search 流量。
  3. 多 post_type 支持:自动覆盖所有公开的自定义文章类型(products、portfolio、event 等),不局限于普通 post。
  4. 排除配置:可配置 exclude_categories 与 exclude_tags 跳过隐私栏目(仅会员可见)。
  5. Authors 可选:多作者博客可开启作者页索引;个人博客关闭。
  6. X-Robots-Tag:sitemap 本身 noindex 不进 SERP,避免“sitemap.xml”这个 URL 被搜索引擎索引。
  7. UTF-8 显式声明:避免中文字符在某些编码环境下乱码。

暴露为 /sitemap.xml 的 rewrite 配置

Apache (.htaccess)

在 .htaccess 文件 RewriteBase / 之下加:

RewriteRule ^sitemap\.xml$ /sitemap.php [L]

Nginx

站点配置 server 块内加:

location = /sitemap.xml {
    rewrite ^ /sitemap.php last;
}

避免与原生冲突

WP 5.5+ 自动接管 wp-sitemap.xml 路径。如果你不想走 WP 原生而走自定义,加 functions.php:

/* 关闭 WordPress 原生 sitemap */
add_filter('wp_sitemaps_enabled', '__return_false');

分页 sitemapindex 处理超大站

什么时候需要分页

sitemap 协议规定单文件最多 50000 URL 与 50MB(未压缩)。但实际上为了让 Google 抓取更稳定,建议单文件 5000-10000 URL。10 万 URL 站点必须分页。

分页方案

把 sitemap.php 改造为“不带参数输出 sitemapindex,带 page 参数输出 urlset”:

<?php
require __DIR__ . '/wp-blog-header.php';
header('Content-Type: text/xml; charset=UTF-8');

$page_size = 5000;
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 0;

if ($page === 0) {
    /* 输出 sitemapindex */
    $total_posts = wp_count_posts('post')->publish;
    $total_pages = max(1, (int)ceil($total_posts / $page_size));

    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
    for ($p = 1; $p <= $total_pages; $p++) {
        echo '  <sitemap>' . "\n";
        echo '    <loc>' . home_url('/sitemap.xml?page=' . $p) . '</loc>' . "\n";
        echo '    <lastmod>' . gmdate('c') . '</lastmod>' . "\n";
        echo '  </sitemap>' . "\n";
    }
    /* 单独的 sitemap:分类、标签、单页 */
    echo '  <sitemap><loc>' . home_url('/sitemap.xml?type=taxonomy') . '</loc></sitemap>' . "\n";
    echo '</sitemapindex>' . "\n";
    exit;
}

/* 输出某分页的 urlset */
$offset = ($page - 1) * $page_size;
$myposts = get_posts([
    'post_type' => 'post',
    'post_status' => 'publish',
    'numberposts' => $page_size,
    'offset' => $offset,
    'orderby' => 'modified',
    'order' => 'DESC',
]);

echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
foreach ($myposts as $post) {
    echo '  <url>' . "\n";
    echo '    <loc>' . esc_url(get_permalink($post->ID)) . '</loc>' . "\n";
    echo '    <lastmod>' . gmdate('c', get_post_modified_time('U', false, $post->ID)) . '</lastmod>' . "\n";
    echo '  </url>' . "\n";
}
echo '</urlset>' . "\n";

性能优化:缓存策略

问题:每次请求都查 DB

1 万文章的 sitemap 单次生成耗时 5-10 秒。如果 Googlebot 高频请求 sitemap.xml(通常每天 5-20 次),CPU 与 DB 压力都不小。

方案 A:文件缓存

给 sitemap.php 加一段:

$cache_file = WP_CONTENT_DIR . '/cache/sitemap_' . md5($_SERVER['QUERY_STRING']) . '.xml';
$cache_ttl = 3600; // 1 小时

if (file_exists($cache_file) && (time() - filemtime($cache_file) < $cache_ttl)) {
    readfile($cache_file);
    exit;
}

ob_start();
/* ... 原 sitemap 生成逻辑 ... */
$content = ob_get_contents();
file_put_contents($cache_file, $content);
ob_end_flush();

方案 B:定时任务生成静态文件

WordPress 有 wp-cron,注册定时任务:

add_action('wp', function() {
    if (!wp_next_scheduled('regenerate_sitemap')) {
        wp_schedule_event(time(), 'hourly', 'regenerate_sitemap');
    }
});
add_action('regenerate_sitemap', function() {
    $url = home_url('/sitemap.php');
    $content = wp_remote_retrieve_body(wp_remote_get($url));
    file_put_contents(ABSPATH . 'sitemap-cache.xml', $content);
});

nginx 优先返回静态 sitemap-cache.xml,没有时回源到 sitemap.php。

方案 C:发文章触发增量更新

挂 hook 到 publish_post:

add_action('publish_post', function() {
    /* 立刻重新生成 sitemap,覆盖缓存 */
    $url = home_url('/sitemap.php');
    $content = wp_remote_retrieve_body(wp_remote_get($url));
    file_put_contents(ABSPATH . 'sitemap-cache.xml', $content);
});

这样新文章发布即刻反映到 sitemap,搜索引擎下次抓取就能拿到。

提交到搜索引擎

Google Search Console

左侧菜单“站点地图”-填入 sitemap.xml-提交。提交后 Google 通常 24-48 小时内开始抓取。GSC 后台能看到“已发现 N 个 URL”“已索引 M 个 URL”的进度。

百度站长平台

百度搜索资源平台 - 资源提交 - sitemap。注意百度对 sitemap 抓取频率较低,提交后 2-3 天才开始。

Bing Webmaster Tools

同 Google 流程。Bing 还能直接 ping:http://www.bing.com/ping?sitemap=https://example.com/sitemap.xml

robots.txt 声明

robots.txt 里声明 sitemap 位置:

Sitemap: https://example.com/sitemap.xml

所有遵守 robots.txt 的爬虫(包括 Google、Bing、Yandex)会自动发现。

常见故障

故障 1:sitemap.xml 404

多数是 rewrite 规则没生效。Apache 检查 .htaccess 是否启用(mod_rewrite 装了吗);Nginx 检查 location 规则放置位置(可能被前面的 location 拦截)。

故障 2:XML 解析错误

sitemap.php 输出前有 BOM 或空白字符。检查 require wp-blog-header.php 这一行之前没有任何 echo / print / 空白行。文件保存为 UTF-8 无 BOM。

故障 3:与 wp-sitemap.xml 内容重复

WP 原生 sitemap 与你的自定义 sitemap 同时存在,搜索引擎会同时抓取造成混乱。在 functions.php 里关掉原生:add_filter('wp_sitemaps_enabled', '__return_false');

故障 4:内存溢出

get_posts numberposts=-1 拉所有文章会让 PHP 内存爆炸。改成分批:每次取 1000 条,循环输出,用 wp_reset_postdata 释放对象。或者改用 WP_Query 的 paged 参数迭代。

故障 5:sitemap 里的 URL 是 HTTP 但站点已切 HTTPS

WP siteurl 设置错误。后台“设置-常规”里 WordPress 地址与站点地址都改成 https。或者 functions.php 强制:force_ssl_admin(true);

故障 6:分类页在 sitemap 但站点设了 noindex

SEO 插件(Yoast/Rank Math)的“分类页 noindex”设置不会自动从 sitemap 排除。要在 sitemap.php 里手动判断:

$noindex = get_post_meta($post->ID, '_yoast_wpseo_meta-robots-noindex', true);
if ($noindex == '1') continue;

故障 7:lastmod 时间错乱

WP 默认时区与 UTC 转换没处理好。统一用 GMT 时间:gmdate('c', $time),时区固定 UTC。

常见问题解答

原生 wp-sitemap.xml 与自定义 sitemap 哪个更好?

原生省事但定制能力弱。如果你需要自定义 priority / 加 image / 排除特定栏目,用自定义。否则原生即可。

sitemap 多久更新一次?

用文件缓存方案(1 小时)够新鲜。重要发布触发即时刷新。Googlebot 一般每天访问 sitemap 1-5 次,1 小时缓存能覆盖 90% 抓取请求。

多语言站点怎么做 sitemap?

每个语言一份 sitemap,通过 sitemapindex 列出。每个 url 块加 xhtml:link rel=alternate hreflang 标记其它语言版本。WPML/Polylang 插件有自己的 sitemap 生成器但不一定符合需求,可以参考本文方案魔改。

需要给图片单独建 image-sitemap.xml 吗?

不需要。Google 推荐把图片信息嵌入文章 sitemap 里(image:image 子标签),而不是单独的 image sitemap。本文方案已经做了。

sitemap 提交后多久能在 GSC 看到 URL 全部被抓?

“已发现 N 个 URL”通常 1-2 天显示。“已索引”需要 1-4 周。新站完成全部索引可能 1-3 个月。

能否用 Action 钩子让插件向 sitemap 注入额外 URL?

能。在 sitemap.php 加 do_action:do_action('custom_sitemap_extra_urls');,其它插件可以 add_action 在这里 echo 额外的 url 块。

百度移动 sitemap 命名空间有什么用?

声明 xmlns:mobile 后,可以给某些 URL 加 <mobile:mobile type="mobile" /> 标记为移动版本。百度搜索会按移动适配规则索引。Google 不读这个命名空间。

sitemap 文件超过 50MB 怎么办?

分页 sitemapindex 是首选。次选 gzip 压缩输出 sitemap.xml.gz,Google 与百度都支持。

能否给 sitemap 设访问频率限制?

能。nginx 层 limit_req 限制 sitemap.xml 每 IP 每分钟 10 次。但建议不限,因为正常爬虫都不会超频。

sitemap 里要不要包含已删除文章的 URL?

不要。已删除文章会返回 404,sitemap 里出现死链会让搜索引擎降低对你的信任度。get_posts post_status=publish 已经自动过滤。

权威参考资料

分享到
标签
版权声明

本文标题:《WordPress免插件sitemap实战指南:动态优先级、image:image、sitemapindex分页与缓存策略》

本文链接:https://zhangwenbao.com/wordpress-free-plug-in-automatically-updates-sitemap-xml.html

版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交