はじめに
業務で顧客からの依頼でsitemap.xmlの作成を依頼されました。sitemap.xmlの仕様自体いまいち理解していなかった+いくつかハマりポイントがあったので、今後のメモ代わりに調査結果・実装内容を書き留めておきます。
使用技術
- FW: Ruby on Rails
- gem: sitemap-generator
- AWS: S3
主な内容
- sitemap.xml.gzを複数ファイルに分けて作成する方法
- S3連携の補足
- S3アップロードが必要な場合
- S3アップロード時の選択肢
- S3アップロード時の注意点
- pingの仕様
- robots.txtの動的な記述
sitemap-generatorについて
基本的な設定などについては以下の参考記事が抜群にわかりやすいので、こちらをご参照下さい。
【Rails】 sitemap_generatorを使ってサイトマップを作成しよう
※本記事では上記を前提として、その上でハマった点とその解消方法に焦点を当てます
sitemap.xml.gzを複数ファイルに分けて作成する方法
今回の要件に、すべてを1つのsitemap.xml.gzにまとめるのではなく、記事はarticles-sitemap.xml.gzに、静的ページはstatics-sitemap.xml.gzに…というように種類ごとにサイトマップをまとめて作成してほしい、というものがありました。
日本語記事は見つからなかったのですが、READMEの記述がわかりやすかったです。
sitemap-generator README
参考までに記載方法は以下のようになります。
SitemapGenerator::Sitemap.default_host = "http://www.example.com"
SitemapGenerator::Sitemap.create do
group(:filename => 'article-details') do
Article.find_each do |article|
add article_path(article), lastmod: article.updated_at, priority: 0.7
end
end
end
SitemapGenerator::Sitemap.create do
group(:filename => 'statics') do
add '/about'
add '/contact'
end
end
bundle exec rake sitemap:refreshなどでsitemapを生成すると以下のようなファイルが作成されます。
article-details.xml.gzstatics.xml.gzsitemap.xml.gz
このうち、sitemap.xmlはインデックス用のxmlでarticle-details.xmlとstatics.xml.gzの場所をクローラーに伝えてくれます。
sitemapの仕様を決めているsitemap.orgの公式ドキュメントでは、インデックスファイルの場合はsitemapindexタグの使用が必須と記載されていますが、sitemap-generatorで生成されるインデックスファイルはちゃんとこの仕様に則っていました。
S3連携の補足
冒頭に参考を示したpikawakaさんの記事にもS3設定の方法が記載されていますが、S3アップロードの必要性やオプション、S3側の設定などについてハマったので対策をまとめます。
S3アップロードが必要な場合
herokuのようなPaaSでデプロイしている場合には必要だと思います。
EC2などの場合には設定によりそうです。
具体的には、例えばherokuの場合、以下のような仕様があるためsitemap.xml.gzの配信には向いていません。
- dyno内で生成されたファイルは一時ファイルという扱いになり永続化されない
- 一時ファイルは再起動時に削除される
- dynoは24時間に一度再起動される
※参考
つまりデプロイ後にheroku内でコマンドを実行して生成されたファイルは削除されます。
dyno再起動の時間を設定・heroku schedulerで毎日再起動直後に生成するようなこともできなくはないかもしれませんが、手間や不確実性を考えるとS3のほうが合理的だと思います。
S3アップロード時の選択肢
pikawakaさんの記事ではS3Adapter(内部的にはFog)を使用していますが、他にも以下の選択肢があります。
個人的に特に活用できそうだと感じたものは以下です。
- SDK
- CarrierWave
特にアップローダーとしてCarrierWaveが設定済のプロジェクトであれば、わざわざS3の情報を環境変数から取ってくるコードを書かなくて良くなるので、CarrierWaveでいいんじゃないかと思いました。
CarrierWaveと組み合わせて使うには以下のWIKIがわかりやすかったです。
GitHubリポジトリ内のWIKI
S3アップロード時の注意点
S3周りだと以下でハマりました。
- ACLs周りのエラー
- パブリックアクセスに対する公開設定
1.ACLs周りのエラー
要するに、S3の仕様がかわり、ACLs(アクセスコントローラーリスト)がデフォルトで無効になったということです。
ACLsはS3に保存されるファイルごとに、公開・非公開を制御しています。
これが無効だと、ACLsを設定しようとするリクエストに対して400番が返るようになり、落ちます。
sitemap-generatorの文脈でいうと、SDKAdapterを使用する際のaclオプションの設定や、S3Adapterをinitializeする際にpublic: falseを明示しないとエラーがでます。
※以下の34〜35行目あたりでS3Adapterをデフォルトでpublic: trueにしている記述があります
S3Adapterの実装
2. パブリックアクセスに対する公開設定
これもACLsまわりの設定が原因です。
S3はデフォルトでパブリックアクセスがすべて無効になっています。
なので適切に設定を変えないと、クローラーからのアクセスも拒否されます。
方法としてACLを有効にするという手段もありますが、公式がデフォルトで無効にしているのでバケットポリシーで特定のパスだけパブリックアクセスを許可するのが正しいと考えています。
やり方は以下のとおりです。
- S3 コンソールでバケットを開き、「アクセス許可」 → 「ブロックパブリックアクセス (bucket settings)」 を確認
- 以下のチェックを外す
- 新しいパブリックバケットポリシーをブロック
- 任意のパブリックバケットポリシーをブロック
- 「アクセス許可」 → 「バケットポリシー」のセクションに移動し、編集ボタンを押下
- 以下を参考にバケットポリシー用のJSONを記述
※上記の場合sitemap-generator側で以下のように記述しています。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPublicReadSitemapsOnly", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket-name/sitemaps/*" } ] }SitemapGenerator::Sitemap.sitemaps_path = "sitemaps/"
S3のバケットポリシーの記述方法はいろいろ設定できるようなので、AI活用しつつ、公式ドキュメントでエビデンスを確認するのが良さそうです。
AWS公式ドキュメント
pingの仕様
bundle exec rake sitemap:refreshを実行すると、pingが送信される、
と公式ドキュメントには記載があり、実装もそうなっていそうですが、ちょっと注意が必要そうです。
sitemap-generator公式ドキュメント: 検索エンジンへのping
まず、デフォルトでは送信先の検索エンジンが設定されていないので、送信したい場合には検索エンジンの指定のためのコードを記述する必要があります。
次に、そもそもGoogleやBingはpingを受け付けていません。
Googleのお知らせ
Bingのsitemap仕様
なので、設定自体は可能であっても実務的には意味はないと考えています。
robots.txtの動的な記述
ステージング環境を使用している場合、ステージングと本番でrobots.txtの出し分けを行う必要があります。(Basic認証を入れるなどしていれば不要ですが)
方法はいろいろあると思うのですが、個人的にはコントローラー出だし分けるのがシンプルで良かったです
# routes.rb
Rails.application.routes.draw do
# クローラー向け
get '/robots.txt' => 'robots#show'
.
.
.
end
# robots_contoroller.rb
class RobotsController < ActionController::Base
def show
if ENV['RAILS_ENV'] == 'production'
content = <<~TXT
User-agent: *
Sitemap: https://bucket-name.s3.region.amazonaws.com/sitemaps/sitemap.xml.gz
TXT
else
content = <<~TXT
User-agent: *
Disallow: /
TXT
end
render plain: content, content_type: 'text/plain'
end
end
以上です!