【Rails初心者】Google/bingの横断検索プラグラムを作ってみて学んだこと

この記事は最終更新から4年以上が経過しています。情報が古くなっている可能性があります。

こんにちは、しばです。
今回はRuby on Railsのお話。
Web制作の幅を広げたい&Webサービスとかクローラーに興味があったため、4月頭からオンラインの学習サービスを利用して勉強していました。(【Progate】Ruby学習コース → 【ドットインストール】Ruby入門 → 【Progate】Ruby on Rails5学習コース)

この間一通り受講が終わったので、シンプルなWebスクレイピングプログラムを作ってみました!

作り方

今回は主に以下のような作業の効率化を目的として、検索エンジンを横断検索してくれるプログラムをローカル環境で作ろうとしました。

  • 特定のキーワードの競合記事のチェック
  • 特定のキーワードのランキングのチェック
  • 自分の記事がランキング上位に入っているか

StatCounterさんのこちらのデータをみると、日本での検索エンジンのシェア率は2018/3時点でGoogle60.71%、Yahoo24.68%、Bing4.13%の順とのこと。

Yahooは実質Googleと同じものなので省略、Bingはシェア率が結構低いですが少なくとも利用者がいるということで、今回の対象検索エンジンはGoogleとBingに決めました。
ついでに最新情報もわかるよう、オプションとして最終更新日の指定を加えました。

完成品

ということで完成品がこちら。


入力してsubmitボタンをクリックすると…

ばーん

スクレイピングした結果が表示!

Rails

それではメインとなるルーティング、コントローラ、ビューの中身です。

ルーティング

Rails.application.routes.draw do
  get '/' => 'home#index'
  post '/' => 'home#index'
end

コントローラ

class HomeController < ApplicationController

	require "open-uri"

	def index
		@keyword = nil
		@string = nil
		@engine = nil
		@update = 0
		if params[:engine]
			@engine = params[:engine]
			# @engineの内容 ... 両方ともチェック済みの場合 {"0"=>"google", "1"=>"bing"}
		end
		if params[:update] != 0
			@update = params[:update]
		end
		if params[:keyword]
			@keyword = params[:keyword]
			date = "0"
			if @engine && @engine["0"]
				case @update
					when "1"
					 date = "d"
					when "2"
					 date = "w"
					when "3"
					 date = "m"
					else
					 date = nil
				end
				url = "https://www.google.co.jp/search?as_qdr=#{date}&as_q=#{@keyword}"
				url_escape = URI.escape(url)
				user_agent = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Safari/537.36'
				charset = nil
				html = open(url_escape, "User-Agent" => user_agent) do |f|
				  charset = f.charset
				  f.read
				end

				@strings =  html.scan(%r{<h3 class="r">(.+?)</h3>})
			end
			if @engine && @engine["1"]
				case @update
					when "1"
					 date = 'ex1:"ez1"'
					when "2"
					 date = 'ex1:"ez2"'
					when "3"
					 date = 'ex1:"ez3"'
					else
					 date = nil
				end
				url = "http://www.bing.com/search?filters=#{date}&q=#{@keyword}"
				url_escape = URI.escape(url)
				user_agent = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.63 Safari/537.36'
				charset = nil
				html = open(url_escape, "User-Agent" => user_agent) do |f|
				  charset = f.charset
				  f.read
				end

				@strings2 = html.scan(%r{<h2>(.+?)</h2>})
			end
		end
	end
end

ビュー

<div class="bg-info">
	<h1 class="container text-white pt-2 pb-2">Cross-search</h1>
</div>
<div class='container mt-4'>
<section>
<%= form_tag("/", { class: 'row' }) do %>
<div class='form-group col-sm-5'><input type="text" name="keyword" class="form-control" placeholder="keyword" value="<%= @keyword %>"></div>
<div class='col-sm-3'>
	<label class="checkbox-inline"><input type="checkbox" name="engine[0]" value="google" <%= "checked" if @engine && @engine["0"] %>> google</label> 
	<label class="checkbox-inline"><input type="checkbox" name="engine[1]" value="bing" <%= "checked" if @engine && @engine["1"] %>> bing </label>
</div>
<div class='form-group col-sm-4'><select name="update" class="form-control">
	<option value="0" <%= "selected" if @update == "0" %>>last updated: 指定なし</option>
	<option value="1" <%= "selected" if @update == "1" %>>24時間以内</option>
	<option value="2" <%= "selected" if @update == "2" %>>7日以内</option>
	<option value="3" <%= "selected" if @update == "3" %>>1ヶ月以内</option>
</select></div>
<p class="text-right col-sm-12"><input class="btn btn-default" type="submit" value="Submit"></p>
<% end %>
</section>

<hr>

<section>
<% if @keyword %>
<h2 class="text-success"><strong>Search results for <%= @keyword %></strong></h2>
<% end %>
<div class="row">
<% if @strings %>
<div class="<% if @strings && @strings2 %>col-sm-6<% else %>col-sm-12<%end%>">
	<h3>Google</h3>
	<div class='table-responsive'>
	<table class='table table-striped'>
		<tr><th>No.</th><th>Title/URL</th></tr>
		<% for i in 0...@strings.length do
		  url, title = (@strings[i][0].scan(%r{<a href="(.+?)".+?>(.+?)</a>}))[0] %>
		<tr><th><%= i + 1 %></th><td><%= link_to(title,url,{ :target => '_blank'}) %></td></tr>
		<% end %>
	</table>
	</div>
</div>
<% end %>
<% if @strings2 %>
<div class="<% if @strings && @strings2 %>col-sm-6<% else %>col-sm-12<%end%>">
	<h3>bing</h3>
	<div class='table-responsive'>
	<table class='table table-striped'>
		<tr><th>No.</th><th>Title/URL</th></tr>
		<% for i in 0...@strings2.length do
		  url, title = (@strings2[i][0].scan(%r{<a href="(.+?)".+?>(.+?)</a>}))[0] %>
		<tr><th><%= i + 1 %></th><td><%= link_to(strip_tags(title),url,{ :target => '_blank'}) %></td></tr>
		<% end %>
	</table>
	</div>
</div>
<% end %>
</div>
</section>

</div>

Google、Bingそれぞれの検索結果ページで使用されているURLパラメータを利用し、入力された条件にしたがってパラメータを変更・付与することで検索結果を取得してきています。

HTMLコードの抽出や加工が簡単に柔軟にできる「nokogiri」というgemもありますが、今回は使用していません。open-uriだけを読み込んでいます。

ちなみにビューの装飾はbootstrapを使ってみました。
初めて使ったけど便利でびっくり。ささっと整えられる…!ヽ(*゚ω゚)ノ 

完成したはいいものの…

無事完成し、何回か使用しているととある問題が発生しました。

連続して使うとGoogleの方で503 Server Unavableエラーが発生する(°Д°;

どうやらGoogleの検索画面へのアクセスは利用規約違反で、そういったアクセスがあった場合このエラーを返してくるとのこと。
ちなみに、Amazonも利用規約違反になるようです。

Google

たとえば、本サービスの妨害や、Google が提供するインターフェースおよび手順以外の方法による本サービスへのアクセスを試みてはなりません。

引用元)Google 利用規約 – ポリシーと規約 – Google

Amazon

この利用許可には、アマゾンサービスまたはそのコンテンツの転売および商業目的での利用、製品リスト、解説、価格などの収集と利用、アマゾンサービスまたはそのコンテンツの二次的利用、第三者のために行うアカウント情報のダウンロードとコピーやその他の利用、データマイニング、ロボットなどのデータ収集・抽出ツールの使用は、一切含まれません。

引用元)Amazon.co.jp ヘルプ: Amazon.co.jp 利用規約

Google様すいませんでした…_:(´ཀ`」 ∠):

その繋がりで調べてみたところ、スクレイピングはその他にも色々と注意する必要がありそうです。こちらの弁護士さんの記事に詳しく書かれていました。

(見出しのみ一部抜粋)

  • スクレイピングと著作権の問題
  • 利用規約違反(民事上の責任)
  • 不法行為責任
  • 偽計業務妨害罪の可能性(刑事上の責任)

参照元)【スクレイピングと法律】スクレイピングって法律的に何がOKで何がOUTなのかを弁護士が解説。

スクレイピングはサービス提供側が公式にサポートしたものではなく、色々と問題が発生しかねないため、APIが用意されてるならそれを使った方が良さそうですね。

どちらにせよ利用規約は重々守って、サービス提供側に負荷をかけないよう心がけて使うべきだと思いました。

まとめ

今回この横断検索プログラムを作ったのは、「アプリとか記事の反応を拾って次に活かせるようにしたいなぁ」という理由からでした。
なので今後は検索エンジンだけでなくSNSも検索対象にしたり、メール通知機能をつけたり、ログイン機能をつけたりということも考え中。

これらを実装するにあたり、今回学んだことを活かしてAPI・スクレイピングを使い分けていこうと思います!

参考