站点地图框架

Django 自带了一个高级站点地图生成框架来创建 sitemap XML 文件。

概况

站点地图是你网站上的一个 XML 文件,它告诉搜索引擎索引器你页面的变化频率,以及某些页面相对于你网站上其他页面的“重要性”。这些信息有助于搜索引擎对你的网站进行索引。

Django 站点地图框架通过让你用 Python 代码表达这些信息以自动创建这个 XML 文件。

它的工作原理很像 Django 的 联合框架。要创建一个站点地图,写一个 Sitemap 类,并将其指向你的 URLconf

安装

要安装站点地图应用,请按照以下步骤进行:

  1. 'django.contrib.sitemaps' 添加到你的 INSTALLED_APPS 配置中。
  2. 确保你的 TEMPLATES 配置中包含一个 DjangoTemplates 后端,其 APP_DIRS 选项设置为 True。默认有一个后端,所以你只需要改变这个配置就可以了。
  3. 确保你已经安装了 sites framework

(注意:站点地图应用不会安装任何数据库表。它需要进入 INSTALLED_APPS 的唯一原因是为了让 Loader() 模板加载器能够找到默认模板。)

初始化

views.sitemap(request, sitemaps, section=None, template_name='sitemap.xml', content_type='application/xml')

要在你的 Django 网站上激活站点地图的生成,在你的 URLconf 中添加这一行:

from django.contrib.sitemaps.views import sitemap

path(
    "sitemap.xml",
    sitemap,
    {"sitemaps": sitemaps},
    name="django.contrib.sitemaps.views.sitemap",
)

这告诉 Django 在客户端访问 /sitemap.xml 时建立一个站点地图。

站点地图文件的名称并不重要,但位置很重要。搜索引擎只对当前URL级别及以下的站点地图链接进行索引。例如,如果 sitemap.xml 位于你的根目录下,它可以引用您网站中的任何URL。但是,如果您的站点地图位于 /content/sitemap.xml,则只能引用以 /content/ 开头的 URL。

sitemap 视图需要一个额外的、必要的参数:{'sitemaps': sitemaps}sitemaps 应该是一个字典,它将一个简短的章节标签(例如,blognews)映射到它的 Sitemap 类(例如,BlogSitemapNewsSitemap)。也可以映射到一个 Sitemap 类的 实例 (例如,BlogSitemap(some_var))。

Sitemap

Sitemap 类是一个 Python 类,在你的网站地图中代表一个条目的“部分”。例如,一个 Sitemap 类可以代表你的博客的所有条目,而另一个则可以代表你的事件日历中的所有事件。

在最简单的情况下,所有这些部分都被归纳到一个 sitemap.xml 中,但也可以使用该框架生成一个站点地图索引来引用单个站点地图文件,每个部分一个。(参见下面的 创建一个站点地图索引 )。

Sitemap 类必须是 django.contrib.sitemaps.Sitemap 子类。它们可以存在于你代码库的任何地方。

一个例子

让我们假设你有一个博客系统,有一个 Entry 模型,你希望你的网站地图包括所有链接到你的个人博客条目。下面是你的网站地图类的样子:

from django.contrib.sitemaps import Sitemap
from blog.models import Entry


class BlogSitemap(Sitemap):
    changefreq = "never"
    priority = 0.5

    def items(self):
        return Entry.objects.filter(is_draft=False)

    def lastmod(self, obj):
        return obj.pub_date

注意:

  • changefreqpriority 分别是用作 <changefreq><priority> 元素的类属性。它们可以作为函数调用,就像本例中的 lastmod 一样。
  • items() 是一个返回 sequenceQuerySet 对象的方法。返回的对象将被传递给对应于站点地图属性的任何可调用方法(locationlastmodchangefreqpriority)。
  • lastmod 应该返回一个 datetime
  • 本例中没有 location 方法,但你可以提供该方法来指定对象的 URL。默认情况下,location() 调用每个对象的 get_absolute_url() 并返回结果。

Sitemap 类参考

class Sitemap

Sitemap 类可以定义以下方法/属性。

items

必须的。 一个返回对象的 sequenceQuerySet 的方法。框架并不关心它们是什么类型的对象,重要的是这些对象被传递给 location()lastmod()changefreq()priority() 方法。

location

可选的。 方法或属性。

如果是方法,则应返回 items() 返回的给定对象的绝对路径。

如果它是一个属性,它的值应该是一个字符串,代表一个绝对路径,用于 items() 返回的 每一个 对象。

在这两种情况下,“绝对路径”指的是不包含协议或域名的 URL。例如:

  • Good: '/foo/bar/'
  • Bad: 'example.com/foo/bar/'
  • Bad: 'https://example.com/foo/bar/'

如果没有提供 location,框架将调用 items() 返回的每个对象的 get_absolute_url() 方法。

如果要指定 'http' 以外的协议,请使用 protocol

lastmod

可选的。 方法或属性。

如果它是一个方法,它应该接受一个参数——一个由 items() 返回的对象——并以 datetime 返回该对象最后修改的日期/时间。

如果是属性,其值应该是 datetime,代表 items() 返回的 每一个 对象的最后修改日期/时间。

如果站点地图中的所有项目都有一个 lastmod,那么由 views.sitemap() 生成的站点地图将有一个 Last-Modified 头,等于最新的 lastmod。你可以激活 ConditionalGetMiddleware,让 Django 对请求做出适当的响应,并提供 If-Modified-Since 头,防止发送没有变化的站点地图。

paginator

可选的。

这个属性返回一个 Paginatoritems()。如果你批量生成网站地图,你可能想覆盖这个属性作为一个缓存属性,以避免多次调用 items()

changefreq

可选的。 方法或属性。

如果它是一个方法,它应该接受一个参数——一个由 items() 返回的对象——并以字符串形式返回该对象的变化频率。

如果是属性,其值应该是一个字符串,代表 items() 返回的 每一个 对象的变化频率。

changefreq 的可能值,无论你是使用方法还是属性,都是:

  • 'always'
  • 'hourly'
  • 'daily'
  • 'weekly'
  • 'monthly'
  • 'yearly'
  • 'never'
priority

可选的。 方法或属性。

如果它是一个方法,它应该接受一个参数——一个由 items() 返回的对象——并以字符串或浮点数的形式返回该对象的优先级。

如果它是一个属性,它的值应该是一个字符串或浮点数,代表 items() 返回的 每一个 对象的优先级。

priority 的示例值:0.41.0。页面的默认优先级是 0.5。更多信息请参见 sitemaps.org documentation

protocol

可选的。

该属性定义了网站地图中 URL 的协议('http''https')。如果没有设置,则使用请求网站地图的协议。如果站点地图是在请求之外建立的,则默认为 'http'

4.0 版后已移除: 在 Django 5.0 中,在请求上下文之外建立的网站地图的默认协议将从 ’http'``改为 ``'https'

limit

可选的。

该属性定义了网站地图每一页所包含的 URL 的最大数量。其值不应超过默认值 50000,这是 Sitemaps protocol 中允许的上限。

i18n

可选的。

一个布尔属性,用于定义该网站地图的 URL 是否应使用所有的 LANGUAGES 生成。默认值是 False

languages

可选的。

启用 i18n 时用于生成备用链接的 语言代码 sequence。默认为 LANGUAGES

alternates

可选的。

一个布尔属性。当与 i18n 结合使用时,生成的 URL 将有一个备用链接列表,使用 hreflang attribute 指向其他语言版本。默认为 False

x_default

可选的。

一个布尔属性。当 True 时,由 alternates 生成的备用链接将包含一个 hreflang="x-default" 回退条目,其值为 LANGUAGE_CODE。默认值是 False

get_latest_lastmod()
New in Django 4.1.

Optional. A method that returns the latest value returned by lastmod. This function is used to add the lastmod attribute to Sitemap index context variables.

By default get_latest_lastmod() returns:

get_languages_for_item(item)
New in Django 4.2.

Optional. A method that returns the sequence of language codes for which the item is displayed. By default get_languages_for_item() returns languages.

捷径

站点地图框架为常见的情况提供了一个方便的类:

class GenericSitemap(info_dict, priority=None, changefreq=None, protocol=None)

The django.contrib.sitemaps.GenericSitemap class allows you to create a sitemap by passing it a dictionary which has to contain at least a queryset entry. This queryset will be used to generate the items of the sitemap. It may also have a date_field entry that specifies a date field for objects retrieved from the queryset. This will be used for the lastmod attribute and get_latest_lastmod() methods in the in the generated sitemap.

prioritychangefreqprotocol 关键字参数允许为所有 URL 指定这些属性。

例如

下面是一个使用 GenericSitemapURLconf 例子:

from django.contrib.sitemaps import GenericSitemap
from django.contrib.sitemaps.views import sitemap
from django.urls import path
from blog.models import Entry

info_dict = {
    "queryset": Entry.objects.all(),
    "date_field": "pub_date",
}

urlpatterns = [
    # some generic view using info_dict
    # ...
    # the sitemap
    path(
        "sitemap.xml",
        sitemap,
        {"sitemaps": {"blog": GenericSitemap(info_dict, priority=0.6)}},
        name="django.contrib.sitemaps.views.sitemap",
    ),
]

静态视图的站点地图

通常情况下,你希望搜索引擎爬虫能够索引那些既不是对象详情页也不是简单页面的视图。解决的办法是在 items 中明确列出这些视图的 URL 名称,并在站点地图的 location 方法中调用 reverse()。例如:

# sitemaps.py
from django.contrib import sitemaps
from django.urls import reverse


class StaticViewSitemap(sitemaps.Sitemap):
    priority = 0.5
    changefreq = "daily"

    def items(self):
        return ["main", "about", "license"]

    def location(self, item):
        return reverse(item)


# urls.py
from django.contrib.sitemaps.views import sitemap
from django.urls import path

from .sitemaps import StaticViewSitemap
from . import views

sitemaps = {
    "static": StaticViewSitemap,
}

urlpatterns = [
    path("", views.main, name="main"),
    path("about/", views.about, name="about"),
    path("license/", views.license, name="license"),
    # ...
    path(
        "sitemap.xml",
        sitemap,
        {"sitemaps": sitemaps},
        name="django.contrib.sitemaps.views.sitemap",
    ),
]

创建站点地图索引

views.index(request, sitemaps, template_name='sitemap_index.xml', content_type='application/xml', sitemap_url_name='django.contrib.sitemaps.views.sitemap')

站点地图框架还能够创建一个站点地图索引,该索引能够引用单独的站点地图文件,每个部分在你的 sitemaps 字典中定义一个。唯一不同的用法是:

下面是上面例子的相关 URLconf 行的样子:

from django.contrib.sitemaps import views

urlpatterns = [
    path(
        "sitemap.xml",
        views.index,
        {"sitemaps": sitemaps},
        name="django.contrib.sitemaps.views.index",
    ),
    path(
        "sitemap-<section>.xml",
        views.sitemap,
        {"sitemaps": sitemaps},
        name="django.contrib.sitemaps.views.sitemap",
    ),
]

这将自动生成一个 sitemap.xml 文件,同时引用 sitemap-flatpages.xmlsitemap-blog.xmlSitemap 类和 sitemaps 字典完全没有变化。

If all sitemaps have a lastmod returned by Sitemap.get_latest_lastmod() the sitemap index will have a Last-Modified header equal to the latest lastmod.

如果你的一个网站地图有超过 50,000 个 URL,你应该创建一个索引文件。在这种情况下,Django 会自动对网站地图进行分页,索引也会反映出来。

如果你没有使用普通的站点地图视图——例如,如果它是用缓存装饰器包装的——你必须为你的站点地图视图命名,并将 sitemap_url_name 传给索引视图:

from django.contrib.sitemaps import views as sitemaps_views
from django.views.decorators.cache import cache_page

urlpatterns = [
    path(
        "sitemap.xml",
        cache_page(86400)(sitemaps_views.index),
        {"sitemaps": sitemaps, "sitemap_url_name": "sitemaps"},
    ),
    path(
        "sitemap-<section>.xml",
        cache_page(86400)(sitemaps_views.sitemap),
        {"sitemaps": sitemaps},
        name="sitemaps",
    ),
]
Changed in Django 4.1:

Use of the Last-Modified header was added.

自定义模板

如果你希望为网站上的每个站点地图或站点地图索引使用不同的模板,你可以通过 URLconf 向 sitemapindex 视图传递一个 template_name 参数来指定它。

from django.contrib.sitemaps import views

urlpatterns = [
    path(
        "custom-sitemap.xml",
        views.index,
        {"sitemaps": sitemaps, "template_name": "custom_sitemap.html"},
        name="django.contrib.sitemaps.views.index",
    ),
    path(
        "custom-sitemap-<section>.xml",
        views.sitemap,
        {"sitemaps": sitemaps, "template_name": "custom_sitemap.html"},
        name="django.contrib.sitemaps.views.sitemap",
    ),
]

这些视图返回 TemplateResponse 实例,允许你在渲染前轻松定制响应数据。更多细节,请看 TemplateResponse 文档

上下文变量

在自定义 index()sitemap() 视图的模板时,可以依赖以下上下文变量。

索引

The variable sitemaps is a list of objects containing the location and lastmod attribute for each of the sitemaps. Each URL exposes the following attributes:

  • location: The location (url & page) of the sitemap.
  • lastmod: Populated by the get_latest_lastmod() method for each sitemap.
Changed in Django 4.1:

The context was changed to a list of objects with location and optional lastmod attributes.

Sitemap

变量 urlset 是一个应该出现在站点地图中的 URL 列表,每个 URL 都会暴露出 Sitemap 类中定义的属性。

  • alternates
  • changefreq
  • item
  • lastmod
  • location
  • priority

i18nalternates 被启用时,alternates 属性可用。它是一个其他语言版本的列表,包括可选的 x_default 回退,对于每个 URL。每个候补是一个字典,有 locationlang_code 键。

为每个 URL 添加了 item 属性,以便更灵活地定制模板,如 Google news sitemaps 。假设 Sitemap 的 items() 会返回一个带有 publication_datatags 字段的项目列表,类似这样就可以生成一个 Google News 兼容的网站地图。

<?xml version="1.0" encoding="UTF-8"?>
<urlset
  xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
  xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
{% spaceless %}
{% for url in urlset %}
  <url>
    <loc>{{ url.location }}</loc>
    {% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
    {% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
    {% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
    <news:news>
      {% if url.item.publication_date %}<news:publication_date>{{ url.item.publication_date|date:"Y-m-d" }}</news:publication_date>{% endif %}
      {% if url.item.tags %}<news:keywords>{{ url.item.tags }}</news:keywords>{% endif %}
    </news:news>
   </url>
{% endfor %}
{% endspaceless %}
</urlset>

通知谷歌

当你的网站地图发生变化时,你可能想“ping”一下谷歌,让它知道要重新索引你的网站。站点地图框架提供了一个函数来实现这个功能: django.contrib.sitemaps.ping_google()

ping_google(sitemap_url=None, ping_url=PING_URL, sitemap_uses_https=True)

ping_google 接受这些可选的参数:

  • sitemap_url - 网站地图的绝对路径(例如:'/sitemap.xml' )。

    如果没有提供这个参数,ping_google 将在你的 URLconf 中进行反向查找,查找名为 'django.contrib.sitemaps.views.index' 的 URL,然后是 ``'django.contrib.sitemaps.views.sitemap'``(无需其他参数)来自动确定网站地图 URL。

  • ping_url - 默认为谷歌的 Ping 工具:https://www.google.com/webmasters/tools/ping

  • sitemap_uses_https - 如果你的网站使用 http 而不是 https,设置为 False

ping_google() 如果不能确定你的站点地图 URL,会引发异常 django.contrib.sitemaps.SitemapNotFound

首先向谷歌注册!

ping_google() 命令只有在你在 Google Search Console 注册了你的网站时才有效。

调用 ping_google() 的一个有用的方法是在模型的 save() 方法:

from django.contrib.sitemaps import ping_google


class Entry(models.Model):
    # ...
    def save(self, force_insert=False, force_update=False):
        super().save(force_insert, force_update)
        try:
            ping_google()
        except Exception:
            # Bare 'except' because we could get a variety
            # of HTTP-related exceptions.
            pass

然而,一个更有效的解决方案是在定时脚本中调用 ping_google(),或者其他一些预定任务。该函数向谷歌的服务器发出 HTTP 请求,所以你可能不想在每次调用 save() 时引入网络开销。

通过 manage.py 通知谷歌

django-admin ping_google [sitemap_url]

Once the sitemaps application is added to your project, you may also ping Google using the ping_google management command:

python manage.py ping_google [/sitemap.xml]
--sitemap-uses-http

如果你的网站地图使用 http 而不是 https,请使用这个选项。