先说一个反直觉的结论
如果你的站只有几十个页面,还没进沙盒期,那么你在 sitemap.xml 上花的每一分钟,大概率都在浪费。真正决定收录的,是"有没有外链、内容是否唯一、站点能不能被正常抓取"这三件事。sitemap 只是给爬虫递了一张地图,而爬虫手上的地图本来就够多了。
但站长大到几百上千页之后,情况会反转。爬虫的抓取配额(crawl budget)是有限的,它不会替你找遍每一页。这时候 sitemap 从"可有可无"变成"唯一能让爬虫知道'我这里有新东西'的主动通道"。尤其是那些内部链接浅、靠翻页才能挖出来的老文章——没有 sitemap,它们可能一年都不被抓一次。
所以我给 sitemap 的定位是:它不是收录的加速器,它是收录的保险单。这篇文章讲三件具体的事:怎么让 sitemap 本身正确、怎么让它活起来、以及如何用真实数据判断它到底有没有起作用。
第一步:先确认 sitemap 本身没有硬伤
在讨论策略之前,必须先排除低级错误。我审过几十个站,这五类问题出现频率最高:
1. 协议和域名不一致
站点是 https://www.example.com,而 sitemap 里写的是 http://example.com/page.html。Google 会把这两个当作不同的 URL,于是你提交了 1000 条链接,它认为其中 1000 条都是"另一个站"的地址,要么跳过、要么当成重定向处理。打开 sitemap 原文,grep -c 'http://' 一下,数字应该是 0。
顺带的:末位斜杠也要统一。Typecho 默认生成 /archives/123/(带斜杠),如果你在 sitemap 里写成 /archives/123,服务器会 301 到带斜杠的版本——每条链接多一跳 301,爬虫的抓取效率直接砍半。
2. 包含了不该出现的 URL
最常见的三类:搜索结果页(?s=keyword)、分页参数页(?page=2、?paged=3)、标签页(/tag/xxx/)。这三类都是无限组合,如果你把它们都放进 sitemap,爬虫会把配额全花在这些低价值页面上,真正的文章反而排不上队。
判断标准很简单:这个 URL 的内容,是不是另一个 URL 的子集或者重复?如果是,就别放进去。分页页的内容是列表页的翻页,标签页的内容是同一批文章的再组合——都不该进 sitemap,用 rel="next" 或者干脆只让它们被内部链接发现就够了。
3. lastmod 全是当前时间
这是最伤信任的一点。很多生成器为了省事,把 <lastmod> 写成脚本运行时间,于是每次爬虫来取 sitemap,1000 个 URL 全部显示"刚刚更新"。第一次爬虫会信,第二次发现内容没变,第三次之后它就彻底忽略你的 lastmod 字段——这等于你主动放弃了 sitemap 最有价值的一个信号。
<lastmod> 必须是文章真实的最后修改时间(Typecho 里是 modified 字段,不是 created),格式建议用 W3C 完整的 2026-11-07T14:22:31+08:00,带时区。只写日期(2026-11-07)也可以,但那样所有同一天改的文章时间粒度都是天,区分度下降。
4. 超过了单文件限制
单个 sitemap 文件最多 50000 个 URL 且未压缩不得超过 50MB。超了之后爬虫会直接报格式错误并整份丢弃,不是"取前 5 万条"——是整份不要。所以页数上万就要做 sitemap index(索引文件指向上百个子 sitemap),每个子文件控制在 4 万条左右留余量。
5. 返回了错误的内容类型或状态码
curl -sI https://www.example.com/sitemap.xml | head -8期望看到 HTTP/2 200、content-type: application/xml(text/xml 也行)。如果返回 text/html,说明你的服务器把请求交给了 CMS 的 404 页面处理再返回 200——这在 Nginx 配了 try_files ... /index.php 的站点上非常常见。爬虫看到 HTML 内容类型的"XML 文件",会直接判为无效。
第二步:让它自动更新,而不是手工上传
手工维护 sitemap 是注定失败的。你一定会忘,忘了之后 sitemap 就是一份过期地图,比没有更糟——因为它会让爬虫以为你已经三个月没发新东西。
Typecho 的做法是写一个独立的 PHP 文件直接查库输出,然后让 Nginx 把它当成静态路径。核心逻辑:
<?php
header('Content-Type: application/xml; charset=utf-8');
$db = new PDO('mysql:host=127.0.0.1;dbname=zz1984;charset=utf8mb4',
'user', 'pass');
$site = 'https://www.zz1984.com';
$rows = $db->query(
"SELECT cid, modified FROM typecho_contents
WHERE type='post' AND status='publish'
ORDER BY cid DESC LIMIT 40000"
)->fetchAll(PDO::FETCH_ASSOC);
echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
foreach ($rows as $r) {
$loc = $site . '/' . $r['cid'] . '.html';
$lm = date('c', $r['modified'] ?: time());
echo "<url><loc>{$loc}</loc><lastmod>{$lm}</lastmod></url>";
}
echo '</urlset>';几个要点。第一,status='publish' 是关键——草稿和私密文章的 status 不是 publish,不加这个条件会把它们一起漏出去,等于公开了未完成的内容。第二,modified 为 0(从未修改过的老文章)时 date('c', 0) 会给出 1970 年,必须 ?: time() 兜底,或者干脆省略 lastmod 字段——省略比给错误的更安全。第三,LIMIT 40000 是给以后的索引文件留的余量,别贴着 5 万写。
Nginx 侧加一条 rewrite,让 /sitemap.xml 转到这个 PHP:
location = /sitemap.xml {
rewrite ^ /sitemap.php last;
}然后一定要缓存。每次爬虫来都查一次全表,几千篇的站没关系,几万篇就会明显拖慢(而且爬虫会频繁来)。最简单的做法是在 Nginx 层给它加一小时缓存:
location = /sitemap.xml {
rewrite ^ /sitemap.php last;
}
location = /sitemap.php {
fastcgi_cache sitemap_cache;
fastcgi_cache_valid 200 1h;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}这里有个容易踩的坑:fastcgi_cache 默认会把 Set-Cookie 一起缓存下来。sitemap 不需要 cookie,加上 fastcgi_ignore_headers Set-Cookie Cache-Control; 避免缓存被污染。更简单的方案是让 PHP 自己输出 Cache-Control: public, max-age=3600 并交给 CDN 缓存,这样连 Nginx 缓存配置都省了。
第三步:通知搜索引擎,但别指望它立刻生效
sitemap 有了之后,要主动告诉搜索引擎。三条路,作用完全不同:
Search Console / 百度站长平台提交
这是最常规的。Google Search Console 里的"Sitemap"页面提交一次永久有效,之后它自己会按你 sitemap 里的 lastmod 判断要不要重取。所以提交状态没问题的话,不需要天天点"重新提交"——那个按钮点了也不会让它爬得更快,只是让它更早地来取一次 sitemap 文件本身。
真正有用的是"Sitemap 详情"里的覆盖率数据:已发现、已编入索引、已抓取但未编入索引。这三个数字的比例比提交动作重要得多,后面会讲怎么读。
IndexNow(Bing / Yandex / 部分中文引擎)
IndexNow 的好处是推送即通知,不用等爬虫。发布文章后立刻 POST 一个 URL 过去,Bing 通常在几分钟到几小时内抓取。协议极其简单:
curl -s -X POST https://api.indexnow.org/indexnow \
-H 'Content-Type: application/json' \
-d '{
"host": "www.zz1984.com",
"key": "你的32位key",
"keyLocation": "https://www.zz1984.com/你的key.txt",
"urlList": ["https://www.zz1984.com/2103.html"]
}'返回 200 或 202 表示接受。key 是你在站点根目录放一个同名 txt 文件的内容,用来证明站点归属。一个 key 可以配多个 URL,但一次别推几千条——IndexNow 的惯例是"只推送新增或修改的内容",把整站倒进去会被限流甚至封 key。我自己的做法是在发布流程的钩子里加一行 curl,每发一篇文章推一条。
百度:不用 IndexNow,用主动推送
百度走自己的接口 http://data.zz.baidu.com/urls?site=...&token=...,格式是纯文本一行一个 URL。限制是每天配额有限(新站通常只有 10 条/天),所以别浪费配额推列表页和标签页,只推真正的新文章。
有个细节很多人忽略:百度的主动推送是"推了就抓",但抓了不等于收录。它有个"内容质量过滤"环节,短文章、模板化内容、纯转载会被抓走但不出库。所以推之前先自查一下内容本身的原创度,比推得多更重要。
第四步:用真实数据判断 sitemap 有没有用
最后一步,也是最多人跳过的一步。判断 sitemap 有效性的指标不是"提交成功了没有",而是这三个比例的走势:
第一,已编入索引 / 已发现。如果这个比例长期低于 50%,说明你的内容本身有问题(重复、太薄、或者被认为是采集),不是 sitemap 的问题,加 sitemap 条目也没用。
第二,已抓取但未编入索引的数量。这个数字持续上涨是最危险的信号:爬虫来了,看了,决定不要。常见原因有三:页面内容与其它页面高度相似(比如同一篇转载了三个地方)、页面质量低于站点平均水平(比如空分类页)、或者页面上的主要内容依赖 JS 渲染而爬虫没执行。第三种情况值得单独确认——curl 一下页面,看 HTML 源码里有没有正文,如果只有 <div id="app"></div>,那搜索引擎看到的就是个空页面。
第三,最后抓取时间。在 URL 检查工具里看"上次抓取"的日期。如果一篇文章发布三个月,显示"从未抓取"或者"抓取于三个月前",但它在 sitemap 里明明就有——那说明这个站的抓取配额严重不足,问题出在站点整体权重或者服务器响应速度上,而不是 sitemap 的写法。
我一直用一张表记录这三个数,每周填一次。有意思的是,很多次"为什么没收录"的焦虑,在把三周的数据摆在一起之后自己就消解了——数字在慢慢涨,只是慢。而真正需要动手的情况(比例突然掉下来),数字会明确告诉你。
把这几件事变成一个自动化流程
把上面所有内容压成一份可执行的清单,每次发新文章的流程应该是这样的:
- 发布文章(确认
status='publish'、URL 是规范形式、页面 HTML 里有正文) curl一把新 URL 确认返回 200 且内容正常- sitemap 因为是动态生成的,自动就包含了新文章,无需额外操作
- POST 一条 IndexNow,推给 Bing 系
- 有配额的话,POST 一条百度主动推送
- 每周看一次 Search Console 的三个比例,记进表格
这六步里,1、2、4、5 可以全部挂在发布脚本里,第 3 步是动态生成天然满足,只有第 6 步需要人。也就是说日常只需要每周花五分钟看数据,剩下的都是自动的。
最后重申一遍开头那个结论:sitemap 不是收录的加速器。它能让爬虫知道你有什么,但决定爬虫要不要的,永远是内容和站点的整体可信度。我见过把 sitemap 做得完美但一篇都收不进去的站,也见过根本不做 sitemap 但每篇都被秒收的老站。先把站做好,再谈 sitemap——前者是地基,后者是门牌号。