你从内容集合一把生成了 sitemap.xml。文件 18 MB,7.3 万条 URL。Search Console 要么报 Couldn't fetch,要么解析到的数量刚好卡在 50,000——读到上限就把剩下的悄悄丢了。索引位置 5 万之后的页面,再也不会从 sitemap 路径被发现。
最快修法:单文件上限是硬的,别再纠结怎么把一个文件压小。把 URL 切成多个子 sitemap,每个 <= 50,000 条(建议 2.5 万),再写一个 sitemap index 文件把它们列出来,把这个 index 放在 robots.txt 原本指向的那个 URL 上(/sitemap.xml),然后在 Search Console 重新提交 index。下面讲怎么切、怎么命名、怎么验证、怎么确认 Google 处理了每个子文件。
硬上限(2026 年 6 月核实)
sitemaps.org 协议写得很明确,Google 全部严格执行:
| 上限 | 数值 | 作用对象 |
|---|---|---|
| 每个 sitemap 的 URL 数 | <= 50,000 个 <loc> | 单个 urlset 文件 |
| 每个 sitemap 的大小 | <= 50 MB(52,428,800 字节)未压缩 | 单个 urlset 文件 |
| 每个 index 的子 sitemap 数 | <= 50,000 | 单个 sitemapindex 文件 |
| 每个 index 的大小 | <= 50 MB 未压缩 | 单个 sitemapindex 文件 |
| 每个 Search Console 资源的 sitemap 数 | 最多提交 500 个 | 整个资源 |
两件事最坑人。第一,gzip 用于传输没问题,但 Google 拿 未压缩 大小去比 50 MB 上限,所以一个 6 MB 的 .gz 解压成 70 MB 照样失败。第二——这一点跟很多人的直觉相反——只有 <loc> 算进 5 万上限。嵌在 <url> 块里的 <xhtml:link rel="alternate" hreflang="..."> 注解 不 增加 URL 计数(Google 已确认:计数器只在 <loc> 上跳,不在 alternate 上)。但它们确实会占 50 MB 大小的份额。
来源:sitemaps.org 协议、Google:Build and submit a sitemap、Google:Manage sitemaps with index files。
你属于哪一类
| 症状 | 大概率原因 | 跳到 |
|---|---|---|
| 解析数量卡在 50,000 | 超过 URL 上限 | 按数量切 |
文件不到 5 万 URL 却报 Couldn't fetch | 未压缩超 50 MB | 按大小切 |
文件很大但 <loc> 数看着不高 | hreflang/图片把字节撑大了 | 按大小切,不是按数量 |
| 提交了 index 但只有一个子文件被处理 | index 只列了一个文件或列了自己 | 修 index |
| 切出来某个文件还是超上限 | 按字母切,不是按数量 | 按数量重切 |
| Google 不理你的新文件 | robots.txt 还写着旧文件 | 修 robots.txt |
常见原因
1. 单 sitemap 生成器,没分块
构建脚本不管多少都写进一个 sitemap.xml。<loc> 数过 5 万之前都没事。
怎么判断:grep -c '<loc>' public/sitemap.xml。到了或超过 50,000,就是超过 URL 上限了。(别用 wc -l——压缩过的 XML 可能把所有 URL 塞在一行里。)
2. URL 不到 5 万但未压缩超过 50 MB
每条 URL 可能带长 <loc>、<lastmod>、好几个 <xhtml:link> hreflang 标签和 <image:image> 块。注解一多,URL 还没到 5 万就能撞 50 MB。
怎么判断:ls -lh public/sitemap.xml。未压缩过 50 MB,不管多少 URL 都得切。
3. Hreflang 撑大的是文件大小,不是 URL 数
en/zh 双语站每个 <url> 加 2-3 个 <xhtml:link>。这些 alternate 不 算进 5 万 URL 上限——只有 <loc> 算——但它们占字节,能把你顶过 50 MB。注意另一种情况:如果你把 en 和 zh 写成两个独立 <url> 块(两个 <loc>),那每个都算,于是 3 万页的双语站其实有约 6 万个 <loc>,已经超线。
怎么判断:对比 grep -c '<loc>' public/sitemap.xml(真实 URL 数)和 grep -c '<url>' public/sitemap.xml。<loc> 接近 5 万就是数量问题;<loc> 没事但文件很大就是大小问题。
4. Sitemap index 指向自己或者漏了
你做了 index 但只列了一个子文件(还是原来那个 7.3 万的),或者不小心把自己也列进去了。
怎么判断:cat public/sitemap.xml。应该是 <sitemapindex> 里有多个 <sitemap><loc> 子项指向不同子文件,每个都在上限内。任何子项都不该指回 index 本身。
5. 按字母而不是按数量切
简单粗暴:按 slug 首字母切 sitemap-a.xml、sitemap-b.xml。如果 30 万 URL 里有 8 万都以 “p” 开头,那个文件还是爆。
怎么判断:for f in public/sitemap-*.xml; do echo "$f $(grep -c '<loc>' "$f")"; done。任何文件过 50,000,说明切分依据错了——按数量切,别按字母。
6. 压缩后小于 50 MB 但解压后超
Google 看未压缩大小。8 MB 的 sitemap.xml.gz 解压到 80 MB 也会失败。
怎么判断:gzip -l public/sitemap.xml.gz 会打印压缩前后字节数。未压缩那一列必须低于 52,428,800。
7. Sitemap 文件跟 robots.txt 不一致
你切成了 sitemap-1.xml、sitemap-2.xml,但 robots.txt 还写着 Sitemap: https://example.com/sitemap.xml 指向旧 urlset,或者什么都不指。
怎么判断:curl -s https://yoursite.com/robots.txt | grep -i sitemap。应该列 sitemap-index 的 URL。
最短修复路径
第 1 步:定切分大小和方案
保守目标:每文件 25,000 个 <loc> URL 或 25 MB——硬上限的一半,这样两次部署之间内容暴涨或某批 lastmod 变大也不会顶线。按内容类型分组,子文件出问题时好定位:
sitemap-articles-1.xml…sitemap-articles-N.xmlsitemap-categories.xmlsitemap-tags.xmlsitemap-pages.xml(静态页)
第 2 步:分块生成子 sitemap
// scripts/generate-sitemaps.mjs
import fs from 'node:fs';
const CHUNK = 25000;
const articles = JSON.parse(fs.readFileSync('articles.json', 'utf8'));
const total = articles.length;
const numFiles = Math.ceil(total / CHUNK);
for (let i = 0; i < numFiles; i++) {
const chunk = articles.slice(i * CHUNK, (i + 1) * CHUNK);
const urls = chunk.map(a => `<url><loc>https://example.com/articles/${a.slug}/</loc><lastmod>${a.modifiedAt}</lastmod></url>`);
fs.writeFileSync(
`public/sitemap-articles-${i + 1}.xml`,
`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${urls.join('')}</urlset>`
);
}
第 3 步:生成 sitemap index
const indexEntries = [];
for (let i = 1; i <= numFiles; i++) {
indexEntries.push(`<sitemap><loc>https://example.com/sitemap-articles-${i}.xml</loc><lastmod>${new Date().toISOString()}</lastmod></sitemap>`);
}
indexEntries.push(`<sitemap><loc>https://example.com/sitemap-categories.xml</loc></sitemap>`);
fs.writeFileSync(
'public/sitemap.xml',
`<?xml version="1.0" encoding="UTF-8"?>\n<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${indexEntries.join('')}</sitemapindex>`
);
顶层文件还叫 sitemap.xml,原有 robots.txt 引用不用改——只是它现在是 <sitemapindex> 不是 <urlset>。index 本身没有自己的 URL;千万别加一个指回 sitemap.xml 的 <sitemap> 项。
第 4 步:更新 robots.txt
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
index 就在同一个 URL,所以这行可以原样不动——只要确认它指向的是 index,而不是残留的旧 urlset。
第 5 步:每个文件都验证
for f in public/sitemap*.xml; do
echo -n "$f: locs="
grep -c '<loc>' "$f"
xmllint --noout "$f" && echo " valid XML"
done
每个子文件 <loc> 数 <= 50,000,未压缩低于 50 MB。index 里是 <sitemap> 项,不是 <url> 项。xmllint(来自 libxml2)能查出未转义的 &、坏标签、编码问题——这些正是让 Google 报 Couldn't fetch 的原因。
第 6 步:在 Search Console 重新提交
Search Console → Indexing → Sitemaps。删掉原来那个单 sitemap 项。在 Add a new sitemap 里填域名后面的路径(比如 sitemap.xml)然后 Submit。只提交 index 就够了——Google 会自动发现并处理子文件。你也可以在 Sitemaps 报告 里手动触发抓取。
第 7 步:确认修好了
- Sitemaps 报告里,index 那行显示 Status: Success,Discovered URLs 是所有子文件的合计(重复只算一次)。几天内看它涨过 5 万。
- 点进每个子 sitemap;都不该是
Couldn't fetch。如果有,直接在浏览器打开那个文件 URL,查 HTTP status 和 XML。 - 在 index(或某个子文件)上点 See page indexing,把 Page Indexing 报告过滤成这些 URL,看已索引数往上走。
被发现不等于被索引——索不索引仍由 Google 决定——但每个合法的 <loc> 现在至少能被 发现,这正是之前被截断的文件做不到的。
哪些情况可能不是你操作错了
URL 不到 5 万的站,切分帮不上忙。这个上限只在规模上去之后才咬人;5 千 URL 的站别去分片。
容易误判的情况
误判为 crawl-budget 问题。Crawl budget 确实存在,但主要影响百万级 URL 的大站。5 万每 sitemap 的硬上限更明确、更简单,先排除它。
预防建议
- 生成 sitemap 时硬性分块(比如每文件 2.5 万个
<loc>)。 - CI 里部署前校验 XML;任何文件超 40 MB 或 4 万 URL 就让构建失败。
robots.txt永远指向一个 canonical 的 sitemap-index URL。- 构建输出里记录每个 sitemap 的大小和
<loc>数,趋势在撞线之前就能看到。 - 传输用 gzip,但断言未压缩大小始终低于 52,428,800 字节。
FAQ
- hreflang 的 alternate URL 算进 5 万上限吗? 不算。只有
<loc>算。<url>块里的<xhtml:link rel="alternate">注解不增加计数,但会占 50 MB 大小份额。 - 能不用 index、直接分别提交多个 sitemap 吗? 可以,但 index 是标准,每个资源能扩到 500 个 sitemap,比手动提交一堆文件好维护太多。
- 50 MB 上限是按压缩前还是压缩后算? 压缩前——Google 看未压缩大小(52,428,800 字节)。gzip 只帮传输提速。
- Bing 也是 5 万上限吗? 是的。5 万 / 50 MB 是 sitemaps.org 协议官方上限,主流搜索引擎都遵守。
- 一个 index 能列多少个子 sitemap? 最多 5 万,且 index 自己也得低于 50 MB。多数站根本到不了这量级。