网站 Sitemap

Odoo 网站 Sitemap 为什么不是“自动吐个 XML”就够了:页面枚举、动态路由与抓取优先级主链路讲透

很多人以为 Odoo 的 sitemap.xml 只是把页面 URL 列出来,但 website 模块实际要处理可枚举路由、动态模型转换器、默认语言上下文、去重和 lastmod 推断,核心目标是让搜索引擎抓到“该抓的公开页面”,而不是把整套路由系统原样暴露出去。

网站
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

Odoo 的 sitemap.xml 不是“把所有 URL 导出来”这么简单。

website.models.website 在生成 sitemap 时,真正做的是一套 可抓取页面筛选机制

  • 页面记录要先满足公开与可访问条件;
  • 控制器路由不是全部进入 sitemap,只有声明了 sitemap 能力的才会参与;
  • 动态路由要靠 converter 去枚举真实记录,而不是把路由模板直接吐出去;
  • 输出前还会做 URL 规范化和去重;
  • 语言上下文默认回到网站默认语言,避免同一页面在多语言下无限膨胀。

所以它解决的核心问题不是“生成一个 XML 文件”,而是:

如何让搜索引擎看到值得抓、能够抓、抓了也不会脏掉索引的页面集合。


一、为什么 sitemap 不是路由清单,而是“公开内容清单”

很多人第一次接触 Odoo 网站,会误以为 sitemap 应该把系统里所有前台地址都输出给搜索引擎。

但源码恰恰说明,Odoo 不这么想。

website.py 的 sitemap 生成逻辑里,它先处理的是站点页面,再处理控制器路由。这个顺序很关键:

  1. 先把天然属于网站内容层的页面纳入考虑;
  2. 再看控制器里有没有明确支持 sitemap 的动态入口。

这说明 Odoo 把 sitemap 当成“可公开内容地图”,而不是技术层的 URL 列表。

对业务团队来说,这个差别很大:

  • 内容页进入 sitemap,意味着你希望它被搜索引擎发现;
  • 功能页、跳转页、参数页、实验页不一定应该进去;
  • 能访问,不等于值得被索引。

如果把 sitemap 理解错,最常见的问题就是:

  • 大量低价值页面被抓取;
  • 搜索引擎预算浪费在筛选页、参数页或重复 URL 上;
  • 真正重要的产品页、博客页、活动页反而抓不深。

二、为什么 Odoo 要区分“可枚举路由”和“不可枚举路由”

源码里最值得注意的一段,是它遍历 router.iter_rules() 之后,并不会无脑输出所有 rule。

它会先检查:

  • routing['sitemap'] 是否显式为 False
  • sitemap 是否是 callable;
  • rule 本身是否具备可枚举条件;
  • 动态 converter 能不能真正生成记录。

这背后其实是在回答一个现实问题:

一个路由是不是能稳定列举出真实页面?

举几个典型例子:

  • /shop/product/<product>:可以,因为 product 记录能枚举;
  • /blog/<blog>/<post>:可以,因为 blog/post 是真实对象;
  • /search?q=...:通常不应该,因为它不是固定内容页;
  • 某些带鉴权、带上下文、带临时参数的控制器:也不该进 sitemap。

所以 Odoo 用 sitemap 钩子把“技术上存在的路由”和“内容上值得公开的页面”切开了。

这正是成熟网站系统的做法。


三、为什么动态路由不是吐模板,而是要靠 converter 逐条展开

源码里有一段很重要:它会读取 rule._converters,然后逐步构造 values,对每个 converter 调用 generate()

这意味着 Odoo 生成 sitemap 时,并不会输出这种东西:

  • /shop/product/<product>
  • /event/<event>
  • /blog/<blog>/<post>

而是会尝试把它们展开为真实 URL:

  • /shop/product/odoo-bookcase-25
  • /event/odoo-experience-2026
  • /blog/product-team-1/seo-checklist-18

这一步特别重要,因为搜索引擎要的是“能抓的链接”,不是开发者理解的“路由模板”。

更细一点看,源码还会处理 converter 的 domain:

  • 如果模型有 website_id 字段,会自动补上当前网站的范围;
  • 如果查询字符串参与过滤,会转成 domain 再做筛选;
  • 记录还会带上默认语言上下文。

这说明 sitemap 不是静态文件拼接,而是一次 基于 ORM 和当前网站上下文的内容枚举过程


四、为什么 Odoo 要做 URL 去重和尾斜杠规范化

源码里专门定义了 _norm(url)

  • / 保持不变;
  • 其他 URL 统一去掉尾部 /
  • 再通过 url_set 去重。

很多人会低估这一步的重要性。

如果不做规范化,很容易出现这些问题:

  • /page/page/ 被当成两个地址;
  • 同一个 callable sitemap 被重复计算多次;
  • 同一记录被不同 rule 展开后重复进入 sitemap;
  • 搜索引擎看到重复 URL,canonical 信号被稀释。

Odoo 用非常务实的方法解决它:

  1. 先把 URL 归一;
  2. 再用集合去重;
  3. 对 callable sitemap 还做一次函数级去重,避免重复计算同一个 endpoint。

这说明它优化的不只是 SEO,还有生成成本。

对于记录多、模块多、路由多的网站,这种“少算一次、少吐一次”非常值钱。


五、为什么 sitemap 输出默认语言,而不是把所有语言版本一股脑塞进去

在源码里,无论是 callable sitemap 还是 converter 生成记录,Odoo 都反复带上:

with_context(lang=self.default_lang_id.code)

这透露出一个设计态度:

sitemap 的主输出,首先围绕网站默认语言建立。

这样做有几个好处:

  • 避免每种语言都把同一套内容再列一遍,造成规模暴涨;
  • 保证 slug、标题、查询结果在统一语言语境下生成;
  • 让搜索引擎先抓到“主版本”,再通过其他机制处理多语言关系。

很多团队一看到多语言,就想把所有版本全部堆进 sitemap。

但如果内容翻译质量不一致、URL 规则不统一、公开语言还没全部准备好,这样反而会让索引质量下降。

Odoo 在默认实现里偏保守,实际上更适合大多数企业站。


六、为什么 lastmod 不是装饰字段,而是内容更新信号

源码在页面记录部分会汇总 last_dates,再写回 record['lastmod']

这说明 Odoo 并不是只关心“页面存在”,还关心:

这个页面最近有没有值得重新抓取的变化。

虽然 lastmod 不保证搜索引擎一定按它行动,但它至少能传达出两层信号:

  • 某些页面长期稳定;
  • 某些页面最近刚更新,值得更快重抓。

对博客、活动、产品目录这种持续更新的网站来说,lastmod 的价值比很多人想象的大。

因为它影响的不是单个页面,而是整个站点的抓取节奏。


七、实战里最容易误解的 3 件事

1. 不是所有能访问的 URL 都应该进 sitemap

搜索页、实验页、临时活动页、带复杂参数的页面,经常“技术上能打开”,但并不适合作为索引入口。

2. 不是路由写了就会自动有好 sitemap

如果你的控制器没有正确声明 sitemap 逻辑,或者动态 converter 没法安全枚举,Odoo 不会帮你魔法补全。

3. sitemap 质量比数量更重要

一个塞满低质量 URL 的 sitemap,看起来很“全”,其实会拖累抓取预算。


八、给实施和运营团队的建议

如果你在做 Odoo 官网或内容站,应该把 sitemap 当成抓取策略的一部分来维护:

  • 定期检查哪些页面真的应该公开索引;
  • 对动态控制器明确设计 sitemap 行为;
  • 避免把筛选页、重复页、参数页混进主 sitemap;
  • 多语言站先明确默认语言与主索引版本;
  • 对重要内容页关注更新时间是否能正确传达。

换句话说,sitemap 不是发布动作的尾巴,而是信息架构的一部分。


最后的结论

Odoo 的 sitemap 生成机制,本质上是在做三件事:

  • 枚举真实公开内容;
  • 过滤不适合抓取的技术路由;
  • 把结果规范化后交给搜索引擎。

所以它从来都不是“自动导出 XML”那么简单。

真正值得学的地方在于:

Odoo 把 sitemap 当成“网站公开内容边界”的表达,而不是程序路由表的副本。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。