1. 概要
前項もどうやらできました(2018年11月12日)。
次にやろうと思っているのが。
現在、ロボット用に「sitemap.xml」を置いています。
これの更新日時を手動で更新しているのですが、うまくやれば、自動で更新日を更新できそうな気がします。
.html 形式のサイトマップ作成をいじろうかと思いましたが、違う方法でやることにします。
下記のサイトが参考になりそうです。
「RubyからXMLファイルを動的に編集する - Qiita」
2. sitemap.xml の形式
わたしが設置している sitemap.xml の形式は「HTML - ウェブサーバに設置しておく情報」に掲載しているものの再掲になります(さらにそれは Yahoo Japan の「Sitemapsの記述方法」のほぼ丸写し)が
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlset>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>「URL」</loc>
<lastmod>「最終更新日」</lastmod>
<changefreq>「更新頻度」</changefreq>
<priority>「優先度」</priority>
</url>
</urlset>
てな感じになっております。
「最終更新日」の箇所を自動更新したいってことなのです。
3. .ini ファイルの内容
下記のような内容の .ini ファイルを作成します。
[input]
file = 入力元 XML ファイル名をフルパスで設定
[output]
file = 出力先 XML ファイル名をフルパスで設定
[https://.../] ← 前項の sitemap.xml の「URL」を記述
file = 更新日をチェックするファイル名をフルパスで記述
directory = 更新日をチェックするファイルを再帰的に検索する場合のディレクトリを記述
exclude = 上記で除外するディレクトリがあれば記述
最後のセクションは sitemap.xml で自動更新の対象としたいものをすべて記述します。
サイトのセクション内の要素はホワイトスペースで区切って複数定義を可能とします。
4. メインモジュール
1ファイルで記述するには長くなったので分割して掲載します。
実際2つクラスを内包していますが、ファイルを分割するのが面倒だったもので・・・。
まずは、他モジュールの require の箇所。
=begin
XML 形式のサイトマップを更新する
=end
$LOAD_PATH << File.dirname(File.expand_path(__FILE__))
require('pry')
require('inifile')
require('rexml/document')
require('BaseClass')
require('ExceptLog')
pry、inifile、rexml/document 以外は、自前のモジュールで、これまでに出現しているものの使いまわしです。
メインの部分
begin
$parameter = Hash.new
ini = MyIniFile.new
$parameter = ini.load(File.dirname(File.expand_path(__FILE__))+'/sitemapXML.ini')
modified = Modified.new
$parameter.each do | key, value |
if ((key == :input) || (key == :output))
next
end
# puts("[#{self.class.name}.#{__method__}] key=[#{key}] value=[#{value}]")
$parameter[key][:modified] = modified.date(value)
end
file = File.open($parameter[:input]) # XML ファイル入力
doc = REXML::Document.new(file) # XML ファイル読み込み
doc.elements.each('urlset/url') do | url |
key = url.elements['loc'].text
if ($parameter.has_key?(key))
if ($parameter[key][:modified] != nil) # 更新日付が設定されていれば更新日付を設定
url.elements['lastmod'].text = $parameter[key][:modified]
end
end
end
File.write($parameter[:output], doc) # XML ファイル出力
rescue => error
exeptlog = ExceptLog.new
exeptlog.execute(self, error)
exit 1
ensure
end
151~152行は、ini ファイルの内容を後で使いやすいように加工しているモジュールで後述します。
156~164行は、URI ごとに最終更新日を取得している箇所です。このモジュールについても後述します。
166~167行で、現在の sitemap.xml を取得。
169~178行で、URI ごとに最終更新日を設定し直しています。
180行で更新した内容を出力しています。
5. .ini ファイルの取得と加工
.ini ファイルを読んで、後で使いやすいように加工しています。
class MyIniFile < BaseClass
def initialize
end
def load(file)
parameter = Hash.new
begin
inifile = IniFile.load(file) # 読み込み
inifile.sections.each do | section |
if (section == 'input') # [input] セクション
parameter[:input] = inifile[:input]['file'] # file を設定
next
end
if (section == 'output') # [output] セクション
parameter[:output] = inifile[:output]['file'] # file を設定
next
end
# 上記以外はサイトの定義
parameter[section] = Hash.new
if (inifile[section]['file']) # file アイテム
parameter[section][:file] = inifile[section]['file'].split(/\s/)
end
if (inifile[section]['directory']) # directory アイテム
parameter[section][:directory] = inifile[section]['directory'].split(/\s/)
end
if (inifile[section]['exclude']) # exclude アイテム
parameter[section][:exclude] = inifile[section]['exclude'].split(/\s/)
end
end
rescue => error
exeptlog = ExceptLog.new
binding.pry
exeptlog.execute(self, error)
exit 1
ensure
return parameter
end
end
26~34行は単純ですね、入出力の XML ファイル名をハッシュにしているだけです。
38~50行は、URI ごとにファイル・再帰検索ディレクトリ・除外ディレクトリを配列の形にして、ハッシュにしています。
6. 最終更新日の検出
URI ごとに最終更新日を検出して返すモジュールです。
class Modified < BaseClass
def initialize
end
def update(file, date)
stat = File::Stat.new(file)
if (date == nil)
date = stat.mtime
else
if (stat.mtime > date)
date = stat.mtime
end
end
return date
end
def date(parameter)
date = nil
begin
parameter.each do | key, value |
if (key == :file) # ファイルの場合
value.each do | file |
date = update(file, date)
end
next
end
if (key == :directory) # ディレクトリの場合
value.each do | directory |
begin
Dir.chdir(directory) # ディレクトリ移動
join = File.join('**', '*.html') # 配下の .html ファイルを集める
glob = Dir.glob(join)
skip = nil
glob.each do | bare | # 相対パスファイル名
fullpath = directory + '/' + bare
if (parameter[:exclude]) # 除外ディレクトリが存在する場合は
parameter[:exclude].each do | exclude | # 除外ディレクトリと一致するディレクトリ以下は
if (fullpath[0,exclude.length] == exclude)
skip = 'true' # スキップする
break
end
end
end
if (skip)
next
end
date = update(fullpath, date)
end
rescue => error
exeptlog = ExceptLog.new
binding.pry
exeptlog.execute(self, error)
exit 1
end
end
end
end
rescue => error
exeptlog = ExceptLog.new
binding.pry
exeptlog.execute(self, error)
exit 1
ensure
if (date == nil)
return nil
else
return date.strftime("%Y-%m-%d")
end
end
end
end
68~80行は、ファイル名をフルパスでもらって、新しい日付を返すメソッドです。
初回は、日付に nil が設定されているので、無条件にそのファイルの更新日付を設定します。
86行からがミソかな。
87~92行は、ファイル名が指定されているものは、そのファイルの更新日付を取得します。
94~122行は、ディレクトリしていのあるものの処理です。
97~99行で、ディレクトリ以下の .html ファイルを収集して配列にしています。
104行で、配列に設定しているファイル名の1分にフルパスを設定しています。
106~117行で、除外指定しているディレクトリ以下のファイルであれば、チェックをスキップします。
119行で、チェック対象となっているファイルの更新日付を取得しています。