Mezzanine Sitemap.xmlを生成してみる

Mezzanine で、サイトマップが出力したくなりました。

  1. Robot.txt の作成&配置
  2. Mezzanine で Sitemap.xml の生成
  3. python manage.py ping_google でGoogle Blog 更新を通知

というところまでやってみましたので、結果を記載します。


環境情報

  • OS
    CentOS release 6.7 (Final)

  • Python Version
    Python 2.7.8

  • Package (必要そうなものだけ抜粋)
    Django (1.9.6)
    Mezzanine (4.1.0)


1. Robot.txt の作成&配置

検索したところ、以下のような記事が見つかります。

正直プラグイン入れてまでやるか? というところはありますが、
記事記載の、django-robots 2.0 : Python Package Index をインストール&設定しました。

1.1 pipでインストール

pip install django-robots

1.2 INSTALLED_APPSrobots 追加

INSTALLED_APPS = (
    "robots",
    "request",
    "admin_backup",

1.3 TEMPLATESdjango.template.loaders.app_directories.Loader を追加

TEMPLATESの、OPTIONSloaders として追加します。
'APP_DIRS': True は、コメントアウトしないと動作しないようなので、コメントアウトし、
以下のように記載にしました。

# List of callables that know how to import templates from various sources.
TEMPLATES = [{
              #u'APP_DIRS': True,
              u'BACKEND': u'django.template.backends.django.DjangoTemplates',
              u'DIRS': (u'${YOUR_TEMPLATE_DIR}'),
              u'OPTIONS': {u'builtins': [u'mezzanine.template.loader_tags'],
                           u'context_processors': (u'django.contrib.auth.context_processors.auth',
                                                   u'django.contrib.messages.context_processors.messages',
                                                   u'django.core.context_processors.debug',
                                                   u'django.core.context_processors.i18n',
                                                   u'django.core.context_processors.static',
                                                   u'django.core.context_processors.media',
                                                   u'django.core.context_processors.request',
                                                   u'django.core.context_processors.tz',
                                                   u'mezzanine.conf.context_processors.settings',
                                                   u'mezzanine.pages.context_processors.page'),
                           u'loaders': [
                                  "django.template.loaders.filesystem.Loader",
                                  "django.template.loaders.app_directories.Loader"
                           ]
                }
             }]

1.4 The “sites” framework がインストールされていることを確認

Mezzanine を使っていれば、デフォルトでインストールされているはずなので、
OFF にしていない限り確認不要となります。

1.5 マイグレーション

# マイグレーション COMMAND
python manage.py makemigrations
# マイグレーション 実行
python manage.py migrate

1.6 設定

  • urls.py に以下の記述を追加する。
    urlpatterns += [
        # add robots.txt URL
        url(r'^robots\.txt$', include('robots.urls')),
    

1.7 確認

  • 以下、URLにアクセスする。

robots.txt のURL

http://www.monotalk.xyz/robots.txt

上記URLにアクセスして出力される。 robots.txt

User-agent: *
Allow: /

Host: www.monotalk.xyz
Sitemap: http://www.monotalk.xyz/sitemap.xml

robots.txt が出力されていることを確認できたので、一旦これで OK とします。
出力内容がセキュリティとして微妙な気がしますので、
そのあたりの調整は別途、行う必要がありそうです。


2. Mezzanine で Sitemap.xml の生成

2.1 Mezzanine のデフォルト動作について

Mezzanine はデフォルトでSitemap.xmlが出力されます。
${YOUR_DOMAIN}/sitemap.xmlでアクセスすると以下のようなSitemap.xmlが出力されます。 ですので、出力内容を変えたいという場合以外、特に何もしないでOKです。

  • Sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<script/>
<url>
<loc>
${YOUR_DOMAIN}/blog/at-comfasterxmljacksondatabindexcunrecognizedpropertyexceptionfromunrecognizedpropertyexceptionjava51/
</loc>
<lastmod>2016-03-20</lastmod>
</url>
<url>
<loc>
${YOUR_DOMAIN}/blog/no-module-named-djangoutilslognullhandler-djangoutilslog-is-not-a-package/
</loc>
<lastmod>2016-01-11</lastmod>
</url>
<url>
<loc>
${YOUR_DOMAIN}/blog/javalangillegalargumentexception-invalid-bson-field-name-javaruntimename/
</loc>
<lastmod>2016-01-11</lastmod>
</url>
<url>
<loc>${YOUR_DOMAIN}/about/</loc>
</url>
<url>
<loc>${YOUR_DOMAIN}/blog/</loc>
</url>
<url>
<loc>
${YOUR_DOMAIN}/blog/uses-a-non-entity-orgeclipsepersistenceexceptionsvalidationexception/
</loc>
<lastmod>2016-01-14</lastmod>
</url>
<url>
<loc>
${YOUR_DOMAIN}/blog/gradle-could-not-find-method-provided-for-arguments/
</loc>
<lastmod>2016-03-14</lastmod>
</url>
<url>
<loc>${YOUR_DOMAIN}/blog/mezzanine-pagedown/</loc>
<lastmod>2016-01-11</lastmod>
</url>
</urlset>
</xml>

2.2 Mezzanine (4.1.0)の不具合

Python 2.7.8 環境下においては、Mezzanine (4.1.0) はバグがあるようで、
以下、エラーが発生し、対象のファイルにパッチをあてないとSitemap.xml が出力されませんでした。
Python 3 でも発生するかもしれませんが、試してはいないため不明です。

  • エラー内容
TypeError at /sitemap.xml
isinstance() arg 2 must be a class, type, or tuple of classes and types
Request Method: GET
Request URL:    http://www.monotalk.xyz/sitemap.xml
Django Version: 1.9.6
Exception Type: TypeError
Exception Value:    
isinstance() arg 2 must be a class, type, or tuple of classes and types
Exception Location: /usr/local/lib/python2.7/site-packages/django/db/models/fields/related.py in get_default, line 905
Python Executable:  /usr/bin/python
Python Version: 2.7.8
Python Path:    

以下のパッチをあてることで解消しました。
Mezzanine の次のVersionで改修されるようです。

        # DEL
        #home = self.model(title=_("Home"))
        # ADD
        class Home:
            title = _("Home")
        home = Home()
        #----
        setattr(home, "get_absolute_url", home_slug)
        items = {home.get_absolute_url(): home}

3. python manage.py ping_google でGoogle に Blog 更新を通知する。

Sitemap.xmlの出力に使用している。The sitemap framework | Django documentation | Djangoは、
ping_googleという、管理コマンドがあり、Google に Blog の ping 送信が行えます。
この管理コマンドは、google以外にも更新通知を送れるようなので、
別途管理コマンドを作成して、複数のサイトに更新通知を送るようにします。

3.1 Mezzanine のPROJECT_APP ディレクトリ配下に、管理コマンドディレクトリを作成する。

  • Applicationを作成
cd ${PROJECT_ROOT}
python manage.py startapp jobs
  • 管理コマンドディレクトリを作成
cd jobs
mkdir ./management
touch ./management/__init__.py
mkdir ./management/commands
touch ./management/commands/__init__.py
touch ./management/commands/ping_all_search_engines.py

3.2 settings.pyINSTALL_APPSjobs を追加する

INSTALLED_APPS = (
    "jobs",
    "robots",
    "request",

3.3 ping_all_search_engines.py を作成する。

  • 参考サイト

  • ping_all_search_engines.py (Django 1.8 で動作する)
    以下の内容で作成しました。 pingサーバーを参考サイトを元に記述してみましたが、RPC形式じゃないので、通信失敗します.. また、後日 Django 1.10 に Upgrade したら、django ImportError: cannot import name NoArgsCommand | Monotalk が原因で動作しなくなり、実装を修正しています。

    # -*- coding: utf-8 -*-
    
    from django.core.management.base import NoArgsCommand
    
    class Command(NoArgsCommand):
        def handle_noargs(self, **options):
    
           print "--------------------------------------------"
           print "ping_all_search_engines START"
           print "-------------------------"
    
           """
           Pings the popular search engines, Google, Yahoo, ASK, and
           Windows Live, to let them know that you have updated your
           site's sitemap. Returns successfully pinged servers.
           """
           from django.contrib.sitemaps import ping_google
           SEARCH_ENGINE_PING_URLS = (
             ('google', 'http://www.google.com/webmasters/tools/ping'),
             ('live', 'http://webmaster.live.com/ping.aspx'),
           )
    
           successfully_pinged = []
           failure_pinged = []
           sitemap_url = "http://www.monotalk.xyz/sitemap.xml"
           for (site, url) in SEARCH_ENGINE_PING_URLS:
               try:
                   ping_google(sitemap_url=sitemap_url, ping_url=url)
                   successfully_pinged.append(site)
               except:
                   failure_pinged.append(site)
    
           print "--------------------------------------------"
           print "ping_all_search_engines END"
           print "successfully_pinged >>>"
           print  successfully_pinged
           print "failure_pinged >>>"
           print  failure_pinged
           print "-------------------------"
    

  • ping_all_search_engines.py (Django 1.10 で動作する)
    Django 1.10 にUpgrade したところ動作しなくなり、且つログをちゃんと出力するため、
    printをloggerに書き換えました。

    # -*- coding: utf-8 -*-                                                                                                                                                             
    
    from django.core.management.base import BaseCommand
    from logging import getLogger
    
    logger = getLogger(__name__)
    
    class Command(BaseCommand):
    
        def handle(self, *args, **options):
    
           logger.info("ping_all_search_engines START")
    
           """
           Pings the popular search engines, Google, Yahoo, ASK, and
           Windows Live, to let them know that you have updated your
           site's sitemap. Returns successfully pinged servers.
           """
           from django.contrib.sitemaps import ping_google
    
           SEARCH_ENGINE_PING_URLS = (
             ('google', 'http://www.google.com/webmasters/tools/ping'),
             ('live', 'http://webmaster.live.com/ping.aspx'),
             ('goo', 'http://blog.goo.ne.jp/XMLRPC'),
             ('with2', 'http://blog.with2.net/ping.php'),
             ('google_blog_search_co_jp', 'http://blogsearch.google.co.jp/ping/RPC2'),
             ('google_blog_search_com', 'http://blogsearch.google.com/ping/RPC2'),
             ('blo', 'http://ping.blo.gs/'),
             ('blogmura', 'http://ping.blogmura.com/xmlrpc/hidmbp2r256f'),
             ('blogranking', 'http://ping.blogranking.net'),
             ('cocolog-nifty', 'http://ping.cocolog-nifty.com/xmlrpc'),
             ('dendou', 'http://ping.dendou.jp'),
             ('fc2', 'http://ping.fc2.com'),
             ('feedburner', 'http://ping.feedburner.com'),
             ('freeblogranking', 'http://ping.freeblogranking.com/xmlrpc'),
             ('drecom', 'http://ping.rss.drecom.jp'),
             ('sitecms', 'http://ping.sitecms.net'),
             ('pingoo', 'http://pingoo.jp/ping'),
             ('kuruten', 'http://ranking.kuruten.jp/ping'),
             ('pingomatic', 'http://rpc.pingomatic.com'),
             ('livedoor', 'http://rpc.reader.livedoor.com/ping'),
             ('weblogs', 'http://rpc.weblogs.com/RPC2'),
             ('serenebach', 'http://serenebach.net/rep.cgi'),
             ('newsgator', 'http://services.newsgator.com/ngws/xmlrpcping.aspx'),
             ('taichistereo', 'http://taichistereo.net/xmlrpc'),
             ('sourceforge', 'http://wpdocs.sourceforge.jp/Update_Services'),
             ('blogpeople', 'http://www.blogpeople.net/ping'),
             ('blogpeople_servlet', 'http://www.blogpeople.net/servlet/weblogUpdates'),
             ('blogstyle', 'http://www.blogstyle.jp'),
             ('i-learn', 'http://www.i-learn.jp/ping'),
             ('pubsub', 'http://xping.pubsub.com/ping'),       
           )
    
           SITEMAP_URLS = (
               "https://www.monotalk.xyz/sitemap.xml",
               "https://www.monotalk.xyz/blog/feeds/rss",
               "https://www.monotalk.xyz/blog/feeds/atom",
           )
    
           successfully_pinged = {}
           failure_pinged = {} 
    
           for elem_url in SITEMAP_URLS:
               for (site, url) in SEARCH_ENGINE_PING_URLS:
                   try:
                       ping_google(sitemap_url=elem_url, ping_url=url)
                       values = successfully_pinged.get(elem_url, None)
                       if values is None:
                           values = []
                       values.append(site)
                       successfully_pinged.update({elem_url:values})
    
                   except:
                       fail_values = failure_pinged.get(elem_url, None)
                       if fail_values is None:
                           fail_values = []
                       fail_values.append(site)
                       failure_pinged.update({elem_url:fail_values})
    
           import pprint
           logger.info("ping_all_search_engines END")
           logger.info("successfully_pinged >>>" + pprint.pformat(successfully_pinged, indent=4))
           logger.info("failure_pinged >>>" + pprint.pformat(failure_pinged, indent=2))
    

3.4 crontab で実行可能にする。

あとは、1日1回くらい実行されるようにcronを設定しておきます。
signal を使用して、Blog 記事更新時に更新通知ができるとかっこいいと思いましたが、
それはまた別の機会に試してみようと思います。

以上です。

コメント