こんにちは、フロントエンジニアの茨木です。
本記事ではRailsアプリでクロールディレクティブを安全・効率的に設定する仕組みをご紹介したいと思います。
Web上にあるページは、クローラーと呼ばれるロボットに巡回されて検索エンジンにインデックス登録されます。大規模なサイトにおいてはページを効率よくインデックス登録させる必要があります。その際にクロールディレクティブと呼ばれる様々な設定が必要ですが、管理が複雑になってきます。この問題に対して、VASILYでの解決方法をご紹介します。同じような境遇の方々の参考になれば幸いです。
クロールディレクティブとは
クロールディレクティブは、クローラーにサイト巡回の仕方を伝えるための設定です。これにより、クローラーに対してページをインデックス登録させない、ページ内のリンクを辿らせないといった設定を行うことができます。クロールディレクティブによりどのような設定ができるかはGoogle公式ページにも記載があります。
ここでは、弊社で重要視している3つのクロールディレクティブをご紹介します。
noindex
noindexはページをインデックス登録させないことを示すディレクティブです。これを重複コンテンツや低品質コンテンツのページに指定することで、サイトの評価低下やそれによるクローラーリソースの割当減少を防止できます。noindexをhtml内で設定する場合にはheadタグ内に以下のタグを挿入します。
<meta name="robots" content="noindex">
nofollow
nofollowは、クローラーにページ内のリンクを辿らせないことを示すディレクティブです。noindexを指定したページへのリンクにnofollowを指定することで、クローラーリソースの無駄遣いを防止できます。nofollowはheadタグ内のmetaタグか任意のaタグで指定が可能です。metaタグで指定した場合にはページ内の全リンクに、aタグで指定した場合には自身のリンクにnofollowが設定されます。
例1) metaタグでのnofollow設定
<meta name="robots" content="noindex">
例2) aタグでのnofollow設定
<a href="hogehoge.html" rel="nofollow">リンク</a>
canonical
canonicalは正規URLを示すディレクティブです。同じコンテンツに複数のURLでアクセスできる場合、非正規URLのページにcanonicalを設定することでクローラーに正規のURLを通知することができます。canonicalはheadタグ内のlinkタグで指定が可能で、href属性で正規URLを設定します。
例) linkタグによるcanonical設定
<link rel="canonical" href="https://www.iqon.jp/">
IQONにおけるクロールディレクティブと課題
クロールディレクティブの仕様
IQONは、アイテム・コーディネート・相談・記事といった多くのコンテンツを持っています。そして、それらの各ページにクロールディレクティブを設定しています。コンテンツの中には検索エンジンからのランディングに適さないページもあり、そのようなページにはnoindexを指定しています。 以下の表はURLとそれに対応するクロールディレクティブの例です。
URL | index/noindex | canonical |
---|---|---|
https://www.iqon.jp/ (トップページ) |
index | - |
https://www.iqon.jp/ask/ (相談ページ) |
noindex | - |
https://www.iqon.jp/ask/solved/ (解決済み相談ページ) |
index | - |
https://item.iqon.jp/20219512/ (アイテム詳細ページ) |
index | https://item.iqon.jp/20219511/ |
https://item.iqon.jp/brand/a.v.v/18/ジャケット/?price_max=30000 (アイテム検索ページ) |
noindex | - |
アイテム検索ページにはカテゴリ、価格、袖丈といった様々な絞込条件があります。絞込条件によっては検索エンジンからのランディングに適さない場合もあるので、絞込条件に応じてnoindexを設定しています。「a.v.v、ジャケット、長袖、5000円以下、セール」で検索した場合は、以下のようなURLになります。
https://item.iqon.jp/brand/a.v.v/18/ジャケット/?price_max=5000&sleeve_length=長袖
各絞込条件に応じてnoindexを制御しているので、以下の例のようにロジックが複雑になっています。
uri = URI.parse(request.original_url) path = uri.path query_params = URI.decode_www_form(uri.query).to_h if path !~ /^\/brand\// if query_params.price_max.to_i > 50000 || query_params.sleeve_length.present? @noindex = true end elsif query_params.price_max.to_i > 100000 @noindex = true end if @items.length < 10 @noindex = true end
課題
これまでに述べたように、IQONでは複雑なクロールディレクティブを設定しています。そのため、以下の課題がありました。
- noindexページへのリンクにnofollowを効率よく設定できない
- クロールディレクティブが複雑なために設定ミスが発生する
nofollowは先に述べた通り、各noindexページへのリンクに対して設定するのが望ましいです。しかし、そのためにはそれぞれのリンクにおいてnoindex設定条件を考慮しなければなりません。これを愚直にやるのは非効率なだけでなく、メンテナンス性の観点でも良くありません。
また、複雑なクロールディレクティブは設定ミスも引き起こすことがあります。特にnoindexの設定ミスは、インデックス数減少に伴うPV数減少など、致命的な問題を引き起こしかねません。
そこで、上記の課題を解決するための仕組みをご紹介します。
noindex・nofollow設定を効率化する仕組み
設定ロジックの共通化
noindex・nofollow設定の効率化のために、まずnoindex設定ロジックの共通化が必要です。IQONではnoindex設定ロジックを管理するNoindexManagerというクラスを定義し、それを任意の場所で利用できるようにしました。
lib/noindex_manager.rb
class NoindexManager def initialize(url, context) @url = url @context = context end # 実際に判定を行うインスタンスメソッド。noindexの場合にtrueを返す。 def noindex? # 末尾にスラッシュがあるとうまく認識できないので予め正規表現で削る path = Rails.application.routes.recognize_path(@url.sub(/\/$|\/\?.*/, '')) # アクションに対応する判定ロジックを呼び出す send("check_#{path[:controller]}_#{path[:action]}".to_sym) end # アイテム検索ページのnoindex判定ロジック def check_item_index uri = URI.parse(@url) path = uri.path query_params = URI::decode_www_form(uri.query).to_h if path !~ /^\/brand\// if query_params.price_max.to_i > 50000 || query_params.sleeve_length.present? return true end elsif query_params.price_max.to_i > 100000 return true end if @context[:item_count] < 10 return true end return false end end
NoindexManagerはURLとコンテキストを用いてnoindexの判定を行います。コンテキストには「URLに含まれないがnoindex判定に用いられる値」を渡します。これにより、URLに含まれないアイテム件数などを判定条件に含めることができます。 NoindexManagerはURLとコンテキストによってインスタンスが生成され、インスタンスメソッドNoindexManager#noindex?によって実際の判定が行われます。
NoindexManager.new('https://item.iqon.jp/', {item_count: 150}).noindex?
ここでは説明のため省略していますが、実際には簡単な判定ロジックの場合にYAMLファイルで設定できるような仕組みがあります。
nofollowを考慮したリンク生成ヘルパー
前述のNoindexManagerを更に便利に使うために、nofollowを考慮できるリンク生成ヘルパーを定義しています。Rails標準のヘルパーであるlink_toと同様のインターフェースで使用できるようにしています。NoindexManagerで用いられるコンテキストは、オプションのcontextキーで設定できるようになっています。
application_helper.rb
def nofollow_link_to(body, url, options = {}, &block) url = url_for(url) html_options = options.reject { |k, _v| k == :context } nofollow = NoindexManager.new(url, options[:context]).noindex? ? 'nofollow' : nil html_options[:rel] ||= nofollow if block.present? link_to(url, html_options, &block) else link_to(body, url, html_options) end end
application_helper.rbに一度定義すると、ビューにおいてlink_toと同様に使うことができます。
index.html.slim
ul.items - @items.each |item| li.item = nofollow_link_to(item.name, item.url, {context: item.count})
クロールディレクティブの正確性を担保する仕組み
クロールディレクティブの設定ミスを防止するために、クロールディレクティブをテストできる仕組みを整備しました。IQONではcontrollerテストで出力HTMLを解析することで、クロールディレクティブの設定に問題ないかテストしています。クロールディレクティブの確認にユーザーアクションは不要なので、Selenium等を用いたクライアントテストは実施していません。 これから、実際にnoindexやnofollow、canonicalをどのようにテストしているのかをご紹介します。
ページのnoindex設定のテスト
noindexのテストでは、まず出力HTMLからname="robots"を持つmetaタグを抽出します。そして、その抽出されたタグのcontent属性に「noindex」「nofollow」という文字列があるかどうかを判定します。判定のためのメソッドをspec_helper.rbに定義しておくことで、各controllerのテストでnoindexやnofollowの設定を確認できるようになります。
spec_helper.rb
def meta_robots meta_tag = response.body.slice(/(<meta[^>]+?name="robots".+?\/>)/) content = meta_tag.slice(/(?<=content=")(.+?)(?=")/) index = content =~ /noindex/ ? :noindex : :index follow = content =~ /nofollow/ ? :nofollow : :follow {index: index, follow: follow} end
item_controller_spec.rb
describe 'GET :index' do context 'meta robots content is `noindex,follow`' do it do response_robots = meta_robots expect(response_robots[:index]).to be_falsey expect(response_robots[:follow]).to be_truthy end end end
ページのcanonical設定のテスト
canonicalのテストもnoindexのテストと同様に行っています。
spec_helper.rb
def meta_canonical link_tag = response.body.slice(/(<link[^>]+?rel="canonical".+?\/>)/) link_tag.slice(/(?<=href=")(.+?)(?=")/) end
item_controller_spec.rb
describe 'GET :index' do context 'meta canonical content is `https://item.iqon.jp/1000000/`' do it { expect(meta_canonical).to eq('https://item.iqon.jp/1000000/') } end end
まとめ
noindex設定ロジックの共通化や、nofollowを考慮できるリンク生成ヘルパーの導入により、noindex・nofollow設定を効率よく管理できるようになりました。 また、クロールディレクティブをテストできる仕組みを導入したことにより、クロールディレクティブの正確性を担保できるようになりました。
最後に
VASILYでは新技術でWebサービスを進化させたいエンジニアを募集しています。
興味がある方は以下のリンクをご覧ください。