WordPress 免插件 sitemap 实战指南:动态优先级、image:image、sitemapindex 分页与缓存策略
WordPress 5.5+ 自带 wp-sitemap.xml 但定制能力弱,插件方案性能差。本文给出基于 wp-blog-header.php 的免插件 sitemap.php 完整实现,含按文章年龄动态计算 priority、特色图 image:image 子标签、sitemapindex 分页、文件缓存与发文章触发的增量更新策略,并附与原生 sitemap 共存禁用方案。
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";相比原文方案的改进
- 动态优先级:按文章修改时间动态计算 changefreq 与 priority,新文章优先级高、老文章优先级低。Google 抓取调度更友好。
- image:image 子标签:每篇文章带上特色图(如有),让 Google 同步索引图片,带 image search 流量。
- 多 post_type 支持:自动覆盖所有公开的自定义文章类型(products、portfolio、event 等),不局限于普通 post。
- 排除配置:可配置 exclude_categories 与 exclude_tags 跳过隐私栏目(仅会员可见)。
- Authors 可选:多作者博客可开启作者页索引;个人博客关闭。
- X-Robots-Tag:sitemap 本身 noindex 不进 SERP,避免“sitemap.xml”这个 URL 被搜索引擎索引。
- 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-NC-SA 4.0