几百个 URL 的站点地图,用脚本拼一份 XML 就能完成。到了百万级,问题会变成:数据读取是否拖慢数据库、分片是否超限、生成中断会不会留下半个文件,以及发布后的索引是否指向尚未上传的分片。
XML 的写法并不复杂,难的是让这份清单持续准确,并且能够重复生成、检查和发布。
本文结合 Sitemap Generator 和 XML Sitemap Validator,说明从小规模样例验证到百万级生产流水线的实现思路。两个工具适合本地生成与结构检查;百万级任务需要放到服务端批处理流程中完成。
先把协议限制写进生成器
标准 URL Sitemap 有两个同时生效的上限:每份文件最多 50,000 个 URL,未压缩大小最多 52,428,800 字节。即使使用 gzip,也要按解压后的大小检查。Sitemap index 同样有条目数和大小限制。Sitemaps 协议 对这些限制有明确规定。
对于 100 万个 URL,只考虑数量:
1,000,000 ÷ 50,000 = 20 份子站点地图
这是理论下限。URL 很长,或者增加图片、多语言等扩展信息时,文件可能先触及字节上限。
生产系统可以把单片目标设为 40,000 条,并预留大小余量。此时约需 25 份子文件。这个数值是工程选择,协议没有要求必须采用。
分片判断应发生在写入下一条记录之前:
当前条数 + 1 是否超限?
当前字节数 + 下一条 XML + 闭合标签是否超限?
任一条件成立:
关闭当前文件,创建下一片,再写入记录。
计算大小要使用序列化后的 UTF-8 字节数。字符串长度无法准确代表文件大小,中文字符、实体转义都会影响结果。
先筛 URL,再生成 XML
数据库里有 100 万条记录,不意味着应该提交 100 万个地址。
建议建立一个专门的“可提交 URL”数据集,将发布状态、首选地址和索引资格落实到查询条件中。草稿、已删除页面、重定向地址、noindex 页面和重复参数页,应在生成前排除。
Google 建议在 Sitemap 中列出希望出现在搜索结果里的 canonical URL,而不是把同一内容的所有访问地址都提交上去。提交也不保证抓取或收录。Google 的 Sitemap 构建指南 说明了这一使用边界。
例如:
保留:
https://example.com/products/123/
排除:
https://example.com/products/123/?utm_source=email
https://example.com/old-product-123/ ← 已跳转
https://example.com/preview/123/ ← 预览页面
去重需要遵守站点路由规则。不能为了“统一格式”,擅自把路径全部转小写,或删除所有查询参数;有些系统会用这些差异区分不同内容。
数据读取要分批,输出要流式
下面这种实现,在小站点上很方便:
查询全部记录 → 构建 URL 数组 → 拼接完整 XML → 写文件
百万级任务采用它,内存里可能同时存在数据库结果、URL 数组、XML 字符串和压缩缓冲区。
更合适的方式是使用稳定游标分批读取,只查询生成所需字段,例如 ID、路径和内容更新时间。每批记录完成资格检查后,逐条序列化并写入输出流。
游标可以基于递增主键:
WHERE id > 上次处理的 ID
ORDER BY id
LIMIT 批次大小
批次大小根据数据库负载调整。输出端还要处理背压:存储写入速度下降时,暂停继续读取,避免数据在内存中堆积。
一次任务最好使用一致性快照,或固定本轮数据边界。否则生成期间发生新增、删除或状态变更,可能让不同分片反映不同时间点的数据。需要特别注意,单纯固定最大 ID 只能限制新增范围,不能冻结已有记录的状态。
分片稳定,比文件数量少更有用
按每 40,000 条顺序切片容易实现,但前面的记录发生删除后,后续边界可能整体移动,造成大量文件重写。
更新频繁的站点可以考虑按内容类型、语言、稳定 ID 区间或哈希桶分组:
/sitemaps/articles-0001.xml.gz
/sitemaps/articles-0002.xml.gz
/sitemaps/products-0001.xml.gz
/sitemaps/products-0002.xml.gz
稳定分组方便增量更新,也方便排查某类页面是否集中出现问题。不过,任何分组都可能继续增长,每个桶内部仍然需要检查数量和大小。
根索引只列出子文件:
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemaps/articles-0001.xml.gz</loc>
</sitemap>
<sitemap>
<loc>https://example.com/sitemaps/products-0001.xml.gz</loc>
</sitemap>
</sitemapindex>
将索引放在站点根目录,子文件放在下级目录,通常更容易维护。Google 对索引引用文件的站点与路径范围有具体要求;跨站提交需要另行配置。大型 Sitemap 管理文档 列出了这些条件。
URL 编码和 XML 转义要按顺序处理
一个地址可以在浏览器里正常打开,却让 XML 解析失败。
例如原始地址:
https://example.com/search?category=books&page=2
写入 XML 后应当是:
<loc>https://example.com/search?category=books&page=2</loc>
处理顺序是:先按 URL 规则构造地址,再按 XML 文本规则转义。不要对整个 URL 直接调用 encodeURIComponent(),否则连协议分隔符和路径分隔符也会被编码。
同样要防止二次转义。已经写成 & 的 XML 文本,再转义一次会得到 &amp;,解析后就不再是预期地址。
lastmod 应该来自内容更新记录
每天重建 Sitemap,不代表里面每个页面都在当天更新。
页面条目中的 lastmod 应反映页面最近一次有意义的修改;索引条目中的 lastmod 则描述对应子 Sitemap 文件的修改时间。两者的数据来源不同。
Google 表示,会在持续准确、可验证的情况下使用 lastmod,并忽略 priority 和 changefreq。因此,给全站统一写上当天日期、daily 和 1.0,没有必要。Google Sitemap 字段说明
没有可靠的内容更新时间时,省略可选字段,比填写一个虚假值更合适。
校验要覆盖结构、数据和线上状态
这几类检查可以分开执行:
| 检查层 | 检查内容 | 能确认什么 |
|---|---|---|
| XML 解析 | 标签闭合、非法字符、命名空间 | 文件能否正确解析 |
| 协议结构 | 根节点、条目类型、loc、字段格式 |
是否符合相关结构规则 |
| 数据检查 | 重复地址、主机名、数量、字节大小 | 清单是否符合发布约束 |
| 线上检查 | HTTP 状态、重定向、canonical、robots | 实际页面是否与清单一致 |
XML Sitemap Validator 可以在浏览器本地检查 URL Sitemap 或 Sitemap index,报告结构、重复位置及部分字段问题。当前接受最多 2 MiB 的未压缩 XML,不解压 .xml.gz,也不请求条目中的地址。
因此,它适合检查样例、小型文件和索引。大型文件应由服务端采用流式解析器执行同类检查;解析成功以后,还需要另行检查线上状态。
Sitemap Generator 适合从已审核的 URL 列表生成样例 XML,会做格式检查、去重和转义,当前单次最多 5,000 个 URL。可以先用它确认输出格式,再将相同规则落实到批量生成任务中。
最后发布索引,保留上一版
生成任务还需要考虑失败时用户会看到什么。
一种可恢复的发布顺序是:
生成带版本号的子文件
→ 校验全部文件
→ 上传并确认可读取
→ 发布引用这些文件的新索引
→ 保留旧文件一段时间后再清理
这样,生成或上传中断时,线上索引仍然指向上一批完整文件。使用 CDN 或对象存储时,也要考虑旧索引缓存,避免刚更新索引就删除旧分片。
每次构建可以保留一份清单,记录版本、候选 URL 数、排除原因、各片条数、未压缩大小和校验结果。若某天提交量突然下降,就能定位是内容下线、筛选规则变化,还是某个读取批次失败。
百万级 Sitemap 的验收,应落在几个具体结果上:合格 URL 没有遗漏,分片没有越界,索引引用完整,新旧版本可以切换,失败原因能够追查。上线后再结合访问日志和 Search Console 检查读取与索引情况,才能判断这条生成流水线是否持续有效。