スクレイピング
映画のデータ
映画レビューサイトの実装をする前に、まずは映画レビューサイトに必要な映画のデータを用意する。映画レビューサイトではヘッダー部分からレビューを投稿することができる。
レビューを投稿するときにレビューをつける映画を選択する。
レビューはユーザーが生成する情報。では、この映画の情報はどこにあるのか?これはあらかじめ、アプリケーションのデータベースに映画の情報を入れておく。
プログラムで映画の情報を取得する
どうやってデータベースに映画の情報を用意するのか。1つ1つ自力でデータベースに映画のタイトルや画像の情報を入れるのは大変すぎるのでプログラムを使って映画の情報を自動で取得する。
そこで使うのがスクレイピングと呼ばれる技術。
スクレイピング
スクレイピングとは
スクレイピングとは、ウェブサイト上のHTMLからある特定のデータを抜き出す処理のことを言う。
外部のサーバーからデータを抽出し、集計をしたりするときに役立る。
スクレイピング
例えば、以下のようなHTMLのサイトがあった場合
1 2 3 4 5 |
<ul>
<li>TEST1</li>
<li>TEST2</li>
<li>TEST3</li>
</ul>
|
<ul><li>の中にある「TEST1、TEST2、TEST3」等の値を取り出すことを言う。
スクレイピングで映画情報を取得する
映画レビューサイトで使用するための映画情報をスクレイピングで取得する。
今回は映画の情報が欲しいので、MovieReviewという映画情報サイトから、スクレイピングで映画作品データを取得して「非公開」で利用します。
データの著作権があるため、外部公開はできないので要注意。
スクレイピングしてみる
では実際にスクレイピングでHTML内のデータを取得。スクレイピングにはMechanizeというGemが必要である。
Mechanize
Mechanizeはスクレイピングを行うためのGem。MechanizeのGemを入れるとMechanizeクラスが使えるようになる。このMechanizeクラスにはスクレイピングをするための様々なメソッドが用意されている。
Mechanizeを入れる。MechanizeはGemなのでGemfileにMechanizeを使用するという記述を行う。
GemfileにMechanizeを追記
Mechanizeという、スクレイピング用のGemを使用できるようにする。moooviディレクトリのGemfileの最終行に以下のように追記する。
1 2 3 |
#省略
gem 'mechanize'
|
Gemfileに記述されたGemをインストールするにはbundle install
を実行する。以下のコマンドを実行して、先程moooviディレクトリのGemfileに追記したMechanizeをインストールする。
エラーなくbundle installが成功すると、以下のような「bundle complete!」という表示がされる。
Mechanizeクラスを使ってみる
Mechanizeを使ってスクレイピングをするには、まずMechanizeクラスのインスタンスを生成する。
1 |
agent = Mechanize.new # Mechanizeクラスのインスタンスを生成
|
次にMechanizeクラスのインスタンスメソッドgetメソッドを使ってスクレイピングしたいウェブサイトのHTMLを取得する。
ウェブサイトのHTML情報を取得する
getメソッド
getメソッドはMechanizeクラスのインスタンスメソッド。get("スクレイピングしたいウェブサイトのURL")と引数にURLの文字列を指定することで、そのURLのウェブサイトのHTMLを取得。
1 2 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/") # MovieReviewのHTMLを取得
|
ここで取得できたpage
は単なるHTMLの文字列ではなくそのウェブサイトのHTMLの情報を持ったMechanize::Pageオブジェクト。
1 2 3 4 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/") # MovieReviewのHTMLを取得
puts page
# <Mechanize::Page:0x007fcdda803dd8>
|
page
がMechanize::Pageオブジェクトであることが確認できた。
HTML情報から指定のタグ要素の情報を検索
特定のhtmlタグ情報を取得するには searchメソッド を使う。
searchメソッド
searchメソッドはgetメソッドで取得したページの情報が入ったオブジェクトに対して使用する。これを使うと取得したウェブサイトのHTML情報の中から指定したHTML要素の内容を検索できる。
該当するHTMLのタグ要素が1つでも、返り値は配列の形式で返ってくる。
searchメソッドは以下のような使い方をする。
1 |
elements = Mechanize::Pageオブジェクト.search('セレクタ')
|
引数のセレクタとはCSSのようにHTMLのタグの要素名を指定する。'h1'
や'li a'
のような指定。また、クラス名でも.hoge
のように指定することができる。
では、例としてMovieReviewのページからh2要素のHTMLの情報を取得してみる。
1 2 3 4 5 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2')
# h2要素を検索
puts elements
|
これを実行すると以下のような出力を得られる。
1 2 3 4 5 6 7 8 9 10 |
<h2 class="entry-title index-entry-title">
<a href="/products/1" title="Single Post">あのひと</a>
</h2>
<h2 class="entry-title index-entry-title">
<a href="/products/2" title="Single Post">海よりもまだ深く</a>
</h2>
<h2 class="entry-title index-entry-title">
<a href="/products/3" title="Single Post">ディストラクション・ベイビーズ</a>
</h2>
#以下略
|
ちゃんと、MovieReviewのh2要素のHTMLの情報が取得できた。
次はh2要素の下のa要素のHTML情報を取得してみる。
1 2 3 4 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
puts elements
|
これを実行すると以下のような出力を得られる。
1 2 3 4 5 |
<a href="/products/1" title="Single Post">あのひと</a>
<a href="/products/2" title="Single Post">海よりもまだ深く</a>
<a href="/products/3" title="Single Post">ディストラクション・ベイビーズ</a>
<a href="/products/4" title="Single Post">少女椿</a>
# 以下略
|
このように、該当するHTMLのタグ要素全てが取得される。
例として、searchメソッドの返り値が本当に配列の形式になっていることを確認するために、以下のような例のコードを試してみる。
1 2 3 4 5 6 |
require 'mechanize'
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
puts elements[0]
|
これを実行すると以下のような出力を得られる。
1 |
<a href="/products/1" title="Single Post">あのひと</a>
|
このように、返ってきた値の1番目の要素をとりだすことができるので配列の形式で返ってきていることが分かる。
では、searchメソッドで指定した要素のHTMLからテキストやリンク先を取得する。
HTML情報からテキストを抜き出す
inner_textメソッド
searchメソッドで得られたHTML情報のテキストを取得したい場合、inner_textメソッドを使う。
例えば、先ほどのsearchメソッドから取得した'h2 a'
要素の一覧を見てみる。
1 2 3 4 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
puts elements
|
ターミナルの出力
1 2 3 4 5 |
<a href="/products/1" title="Single Post">あのひと</a>
<a href="/products/2" title="Single Post">海よりもまだ深く</a>
<a href="/products/3" title="Single Post">ディストラクション・ベイビーズ</a>
<a href="/products/4" title="Single Post">少女椿</a>
# 多すぎるので以下略
|
これらの要素のテキスト、すなわち「あのひと、海よりもまだ深く、ディストラクション・ベイビーズ...」が欲しい場合は以下のようにする
1 2 3 4 5 6 7 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
elements.each do |ele|
puts ele.inner_text
end
|
ターミナルの出力
1 2 3 4 5 6 |
あのひと
海よりもまだ深く
ディストラクション・ベイビーズ
少女椿
# 多すぎるので以下略
|
ちゃんとHTML情報のテキストだけが抜き出された。searchメソッドでは該当する要素のHTML情報がすべて取得できるのでeachメソッドを使って、1つ1つのオブジェクトに対してinner_textメソッドを呼び出す。
HTML情報から属性の値を抜き出す
get_attributeメソッド
aタグ要素のHTMLはリンク先のURLを値とする属性hrefを持っている。このようなHTMLの属性の値を取得したい場合、get_attributeメソッドを使う。get_attribute(属性)
で指定した属性の値を取得できる。また、.get_attribute(属性)
は[:属性]
と簡略化して書くこともできる。
1 2 3 4 5 |
<a href="/products/1" title="Single Post">あのひと</a>
<a href="/products/2" title="Single Post">海よりもまだ深く</a>
<a href="/products/3" title="Single Post">ディストラクション・ベイビーズ</a>
<a href="/products/4" title="Single Post">少女椿</a>
# 多すぎるので以下略
|
この中からhref="リンク先"
の「リンク先」を抜き出す。HTML情報のオブジェクトにメソッドget_attribute('href')
を使う。
1 2 3 4 5 6 7 |
agent = Mechanize.new
page = agent.get("http://review-movie.herokuapp.com/")
elements = page.search('h2 a') # h2要素の下のa要素を検索
elements.each do |ele|
puts ele.get_attribute('href') # puts ele[:href]としても良い
end
|
ターミナルの出力
1 2 3 4 5 |
/products/1
/products/2
/products/3
/products/4
# 多すぎるので以下略
|
ちゃんと属性hrefの値だけ抜き出すことができた。
このようにMechanizeクラスを使えばウェブサイト上のHTMLのデータを自由に取得することができる。
スクレイピングの手順まとめ
Mechanizeを使ったスクレイピングの手順。
- Mechanizeクラスのインスタンスを生成する
- Mechanizeクラスのインスタンスメソッドget(情報を取得したいウェブサイトのURL)で、ウェブサイトのHTML情報を取得する
- 欲しいデータのあるタグ要素をsearchメソッドで指定して取得する
- 取得したタグ要素のHTML情報にたいしてinner_textメソッド、またはget_attributeメソッドを使って欲しい値を取得する
実際にスクレイピングしてみる
Mechanizeを使って実際にスクレイピングをしてみる。ここでのスクレイピングの練習はRubyファイルを作成して行うので、スクレイピング用のRubyファイルscraping.rb
をデスクトップに作成する。
デスクトップにスクレイピング用のRubyファイルscraping.rb
を作成
テキストエディタを選択している状態で、
①command
+ N
でファイルを新しく作成。
②command
+ S
でファイルを保存。
③ファイル名を scraping.rb
と編集し、保存先にデスクトップを選択する。
では作成したRubyファイルscraping.rb
を テキストエディタで開く。
Mechanizeクラスは外部のクラスなのでそのままでは使えない。よって、Rubyファイルの一番上でMechanizeクラスを使うことを宣言しなければならない。宣言にはrequire
を使う。
scraping.rb
の一番上にMechanizeクラスを使うことを宣言する一行を追加する
1 |
require 'mechanize'
|
これでMechanizeクラスが使えるようになる。Mechanizeクラスのインスタンスを生成する。
1 2 3 4 |
require 'mechanize'
agent = Mechanize.new
puts agent # インスタンスを生成できたか確認
|
ターミナルで実行
上のような編集ができたら、ターミナルで 以下のようにコマンドを実行する。
以下の作業チェックに記載された結果が出力されるか確認する。
これでスクレイピングをする準備はできた。
スクレイピング練習
以下のスクレイピング用に用意したウェブページをスクレイピングしてみる。
https://app-mooovi.herokuapp.com/works/initial_scraping
「TEST1, TEST2, TEST3」と書いてあるだけ。この「TEST1, TEST2, TEST3」というテキストを取得する。まずはこのページのHTML構造を確認する。
実際のHTMLを確認してみる
https://app-mooovi.herokuapp.com/works/initial_scraping
上記のサイトにアクセスして、[command] + [option] + [u]を押してソースコードを表示させる。
“TEST1”, “TEST2”, “TEST3” は、pタグに囲まれていることがわかる。このようにスクレイピングをするときはスクレイピングしたいウェブページのHTML構造を確認することが大切。
では、このウェブページからスクレイピングで「TEST1, TEST2, TEST3」を抜き出すプログラムをscraping.rb
に書く。
まずはgetメソッドでウェブページのHTMLの情報を取得する。
getメソッドを使ってスクレイピング用ウェブページのHTML情報を取得する
1 2 3 4 |
require 'mechanize'
agent = Mechanize.new
page = agent.get("https://app-mooovi.herokuapp.com/works/initial_scraping")
|
HTML構造を確認すると「TEST1, TEST2, TEST3」はそれぞれpタグ要素に囲まれている。searchメソッドでpタグ要素を取得する。
1 2 3 4 5 6 |
require 'mechanize'
agent = Mechanize.new
page = agent.get("https://app-mooovi.herokuapp.com/works/initial_scraping")
elements = page.search('p')
puts elements
|
ここまで書けたら、下記のコマンドでscraping.rbを実行。
1 |
$ ruby scraping.rb #scraping.rbを実行
|
ターミナルには以下のように表示される。
1 2 3 |
<p>TEST1</p>
<p>TEST2</p>
<p>TEST3</p>
|
ちゃんとすべてのpタグ要素が取得できている。あとはinner_textメソッドでテキストを取得する。
1 2 3 4 5 6 7 8 9 |
require 'mechanize'
agent = Mechanize.new
page = agent.get("https://app-mooovi.herokuapp.com/works/initial_scraping")
elements = page.search('p')
elements.each do |ele|
puts ele.inner_text
end
|
pタグ要素は複数あるのでeachメソッドを使ってオブジェクト一つ一つにたいしてinner_textメソッドを使う。このプログラムを実行すると以下のようにターミナルに表示される。
1 2 3 |
TEST1
TEST2
TEST3
|
テキスト「TEST1, TEST2, TEST3」が取得できた。
MovieReviewからのスクレイピング
MovieReviewから映画のデータを取得する。ページへは以下のリンクから遷移できる。
http://review-movie.herokuapp.com/
このページには20件の映画情報が載っている。まずはこのページにある20件の映画情報をスクレイピングで取得する。
作品名、作品画像のURLを取得
映画の情報と言っても作品名、監督名、公開日など様々なものがあるが、とりあえず今回は最低限の情報として「作品名」と「作品画像のURL」を取得する。
映画情報のスクレイピング用のRubyファイルを作る
映画の「作品名」と「作品画像のURL」を取得するスクレイピングのプログラムを書くためのRubyファイルを作成。作品名のスクレイピング用のRubyファイルscraping_title.rb
、作品画像のスクレイピング用のRubyファイルscraping_image.rb
、をデスクトップに作成。
ウェブサイトのHTML構造を確認
スクレイピングするウェブサイトのHTML構造を調べる。
スクレイピングするページにウェブブラウザでアクセスして、HTMLを確認
http://review-movie.herokuapp.com/
作品名のHTML構造を確認
開発者ツールを使えば、HTMLが見やすい
- 作品名や作品画像を右クリック
- [要素の検証]をクリックして開発者ツールを立ち上げる
- HTMLが見やすく整形されて表示される
作品名を取得①
開発者ツールを利用して作品名がある要素のHTML構造を確認。
Chromeの開発者ツールを使って下記のようなHTML構造を確認
HTMLを確認してみると、作品名はどこに存在するかというのがわかると。
あとは、このHTML構造にたいしてスクレイピングのプログラムを書くだけ。作業ファイルはscraping_title.rb
。
問題4:MovieReviewに表示されている映画20件の「作品名」を取得する
作業ファイル:scraping_title.rb
ヒント:開発者ツールで確認したHTMLのタグ要素をsearchメソッドの引数にする
作品画像のURLを取得
画像に対して開発者ツールの要素の検証をして、HTMLの構造を確認する。作業ファイルはscraping_image.rb
。
では、作品名と同じように表示されている20件の映画の作品画像URLをスクレイピングで取得。
問題5:MovieReviewに表示されている映画20件の「作品画像のURL」を取得する
作業ファイル:scraping_image.rb
ヒント:取得するのはテキストでなく属性の値
作品画像のURLを取得②
試しに取得できた画像を開いてみる。
画像が小さい。あとカバー写真というよりは映画のワンシーンのような画像。これでは表示させるとき少し物足りない。
そこでちゃんとした作品画像を取り直す。MovieReviewでどれかの映画タイトルをクリックしてみる。その映画の個別ページに移動した。そこに良さそうな画像がある。
個別ページの作品画像のURLを取得
まずMovieReviewに表示されている映画ならどれでもいいので個別ページに移動する。
移動できたらそのページの作品画像URLを取得してみる。作業ファイルはscraping_image.rb
。先ほど書いたソースコードは使わないので消してしまう。
問題6:映画の個別ページに表示されている映画の「作品画像のURL」を取得する
作業ファイル:scraping_image.rb
ヒント:リンクは各自開いた映画の個別ページのものを使う
20件分すべての映画の個別ページから作品画像のURLを取得
20件すべての映画の個別ページから作品画像のURLを取得。もちろんすべてプログラムで自動で取得。
以下のような流れでスクレイピングする。
- MovieReviewで表示されている映画の個別ページのリンクを取得する
- 取得した個別ページのリンク1つ1つに対してスクレイピングをする
以下のようにスクレイピングのプログラムを書く。
10 11 12 13 14 15 16 17 18 19 |
require 'mechanize'
links = [] # 個別ページのリンクを保存する配列
agent = Mechanize.new
current_page = agent.get("http://review-movie.herokuapp.com/")
# 個別ページのリンクを取得
links.each do |link|
# 個別ページから作品画像のURLを取得
end
|
問題7:MovieReviewにある20件の映画すべての個別ページから作品画像のURLを取得する
データベースに映画のデータを保存
これで映画レビューサイトで必要な映画の情報「作品名」と「作品画像」がスクレイピングで取得できた。あとは得たこの情報をデータベースに入れるだけ。 ここからは映画レビューアプリケーションのディレクトリmooovi
で作業する。
映画情報のモデルを作成
映画の情報を扱うモデルとしてProductモデルを作成する。このProductモデルに対応するproductsテーブルは以下のカラムを持っている。
カラム名 | 型 | 情報 |
---|---|---|
title | String | 作品名 |
image_url | Text | 作品画像のURL |
Productモデルを作成し、上記のカラムを持ったテーブルの作成する。
問題8:映画レビューアプリケーションmoooviに指定したカラムを持つproductテーブルを作成しましょう
作業ディレクトリ:mooovi
ヒント:特になし
映画情報をデータベースに保存するメソッドを書く
では、今まで書いてきたスクレイピングのメソッドを利用して「MovieReview」から映画情報を取得し、データベースに保存するメソッドを書く。
先ほどまではDesktopにRubyファイルを作って実行していますが、今回は取得した情報をデータベースへ保存する必要があるのでmooovi
ディレクトリの中にファイルを作成し、ソースコードを書いていく。
mooovi > app > models 内にscraping.rbというファイルを作成。
この時、 テキストエディタ上から直接ファイルを作成。
①「mooovi」>「app」>「models」内にscraping.rbを作成。
②「scraping.rb」というファイル名で保存する
その後、以下のようなディレクトリ構造になっていることを確認する。
- app
- models
- scraping.rb
- models
記述するメソッドと実行方法
スクレイピングのためのコードを記述する
先ほど作成したscraping.rb
というファイルにスクレイピング関連のメソッドを記述していく。
1 2 3 4 5 6 7 8 9 10 11 |
class Scraping
def self.movie_urls
puts 'get movies link URL'
# ここに処理を書く
end
def self.get_product(link)
puts 'get movie information'
# ここに処理を書く
end
end
|
クラスメソッドmovie_urls
とget_product
がある。この中に具体的な処理を書いていく。
このクラスメソッドmovie_urls
は、rails c
をしている状態で実行することができる。
まずrails c
コマンドを打ち、クラスメソッドmovie_urls
を実行してみる。
1 2 3 4 |
"get movies link URL"と表示されればクラスメソッドmovie_urls
は正常に実行できている。
クラスメソッドmovie_urls
scraping.rb
に記述されているScrapingクラスのクラスメソッドmovie_urls
は表示されている20件分の映画の個別ページのリンクURLを取得して、そのリンクをクラスメソッドget_product
へ渡す処理をする。
1 2 3 4 |
def self.movie_urls
# 映画の個別ページのURLを取得
# get_product(link)を呼び出す
end
|
クラスメソッドget_product
scraping.rb
に記述されているScrapingクラスのクラスメソッドget_product
は引数として渡された個別ページのリンクURLを使って「作品名」と「作品画像のURL」をスクレイピングし、それらをproductsテーブルに保存する処理を書く。
1 2 3 4 |
puts 'get movies link URL'
とputs 'get movie information'
は消して良い。
映画情報をデータベースに保存
今まで書いたスクレイピングのRubyファイル、scraping_title.rb
とscraping_image.rb
を用いて映画の「作品名」と「作品画像のURL」を取得し、productsテーブルにその情報を保存する処理をscraping.rb
に書く。
reload!
コンソール起動中にコードを修正・追記した際、コンソールはリアルタイムで変更内容を反映してくれない。
その際は通常、一度exitして再度 rails c コマンドを打ちコンソールを再起動するという手順を踏むが、 reload!
コマンドを実行すると毎回exitせずともコンソールがコードの変更内容を読み込んでくれるので大変便利。
今後コンソールで何かを実行する前には reload!
コマンドの実行を忘れないようにする。
以下では、先ほど実行したScrapingクラスのクラスメソッド movie_urls を例にとって実際の動作を示している。
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
[1] pry(main)> Scraping.movie_urls
get movies link URL
#=> nil
## movie_urlsメソッド内の文字列「get movies link URL」を「リロードのテストです」に変更
#reload!をかけずに再度実行した場合は変更内容が反映されない
[2] pry(main)> Scraping.movie_urls
get movies link URL
#=> nil
#reload!をかけてから再度実行すると編集内容が反映される
[3] pry(main)> reload!
Reloading...
#=> true
[4] pry(main)> Scraping.movie_urls
# リロードのテストです
#=> nil
|
問題9:ProductsテーブルにMovieReviewから取得した映画20件の情報を保存する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Scraping
def self.movie_urls
#①linksという配列の空枠を作る
#②Mechanizeクラスのインスタンスを生成する
#③映画の全体ページのURLを取得
#④全体ページから映画20件の個別URLのタグを取得
#⑤個別URLのタグからhref要素を取り出し、links配列に格納する
#⑥get_productを実行する際にリンクを引数として渡す
end
def self.get_product(link)
#⑦Mechanizeクラスのインスタンスを生成する
#⑧映画の個別ページのURLを取得
#⑨inner_textメソッドを利用し映画のタイトルを取得
#①⓪image_urlがあるsrc要素のみを取り出す
#①①newメソッド、saveメソッドを使い、 スクレイピングした「映画タイトル」と「作品画像のURL」をproductsテーブルに保存
end
end
|
さらに映画の情報を充実させる
スクレイピングのメソッドを拡張してより充実した映画の情報を取得できるようする。
同じ映画の情報はデータベースに新規で保存できないように
いまスクレイピングのメソッドmovie_urls
を2回実行すると同じ映画が2つずつ保存されてしまう。つまり、メソッドが実行されるたびに同じ映画がデータベース上に増えていってしまう。これではまずいので同じタイトルの映画情報はデータベースに保存しないように注意する。
first_or_initializeメソッド
whereメソッドとともに使うことで、whereで検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを作るメソッド。
- ニックネームが"Shinbo"のユーザーがすでに保存されていれば取得し、保存されていなければ生成
1 |
user = User.where(nickname: "Shinbo").first_or_initialize
|
【例-1】
- すでにニックネームが"Shinbo"のユーザーが保存されている場合
1 2 |
user = User.where(nickname: "Shinbo").first_or_initialize
=> #<User id: 1, nickname: "Shinbo">
|
すでにデータベースのUsersテーブルにニックネームが"Shinbo"のレコードが保存されている場合、そのレコードのインスタンスが取得される。
【例-2】
- まだニックネームが"Shinbo"のユーザーが保存されていない場合
1 2 3 4 |
user = User.where(nickname: "Shinbo").first_or_initialize
=> #<User id: nil, nickname: "Shinbo">
p user.nickname
=> "Shinbo" # "Shinbo"がnicknameカラムに代入されている
|
ニックネームが"Shinbo"のレコードが無い場合は新しくUserモデルのインスタンスが生成されます。生成されたインスタンスは検索で使われた"Shinbo"がnicknameに代入された状態となっている。
first_or_initializeではnewメソッドと同様、インスタンスを生成しただけでデータベースに保存されていない。saveメソッドでデータベースに保存するのを忘れないように。
1 2 |
user = User.where(nickname: "Shinbo").first_or_initialize
user.save
|
MovieReviewですべてのページから映画の情報を取得できるようにする
いまのスクレイピングのメソッドはMovieReviewの1ページ目の20件の映画しか取得していない。そこでこれから、2ページ目以降の映画の情報もすべて取得するスクレイピングを書いていく。
MovieReviewはページネーションを利用している。各ページの下に次のページへのリンクがある。これを使えば次のページのURLリンクをたどることができる。
次のページへのリンク先がなくなったときにスクレイピングを終了させる。
問題11:MovieReviewの全てのページにある映画のタイトル、映画の画像を取得している。
作業ファイル:app/models/scraping.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class Scraping
def self.movie_urls
agent = Mechanize.new
links = []
# パスの部分を変数で定義(はじめは、空にしておきます)
next_url = ""
while true do
current_page = agent.get("http://review-movie.herokuapp.com/" + next_url)
elements = current_page.search(".entry-title a")
elements.each do |ele|
links << ele.get_attribute('href')
end
# 「次へ」を表すタグを取得
next_link = ???
# next_linkがなかったらwhile文を抜ける
unless ???
???
end
# そのタグからhref属性の値を取得
next_url = ???
end
links.each do |link|
get_product('http://review-movie.herokuapp.com' + link)
end
end
end
|
Productsテーブルに「監督名」、「あらすじ」、「公開日」のカラムを追加
映画の情報は「監督名」や「あらすじ」、「公開日」など様々なものがある。スクレイピングするならこれらの情報もまとめて取得しておきたいところ。
カラム名 | 型 | 情報 |
---|---|---|
director | string | 監督名 |
detail | text | あらすじ |
open_date | string | 公開日 |
rails generate migrationコマンド
テーブルにカラムを追加したり、削除するようなテーブルの構造を変えたいときもマイグレーションファイルを作成。作成にはrails generate migration
コマンドを使用する。このコマンドはマイグレーションファイルを新しく作るためのもの。
rails generate migration
に続けてクラス名を指定する。このコマンドで指定したクラス名のマイグレーションファイルが作成される。また generate
は g
と省略することができる。「db > migrate」に20141027115020_add_rate_to_products.rbというファイルが作成された。中身は以下のようになっている。
1 2 3 4 5 |
class AddRateToProducts < ActiveRecord::Migration[5.2]
def change
end
end
|
メソッドchange
の中にテーブル構造の変更を書く。
例えば、BooksテーブルにInteger型のカラムpriceを追加するためには以下の様なマイグレーションファイルを作成する必要がある。
1 2 3 4 5 |
class AddPriceToBooks < ActiveRecord::Migration[5.2]
def change
add_column :books, :price, :integer
end
end
|
add_column
add_column
をマイグレーションファイルのchangeメソッド内に書くとカラムの追加ができる。記述方法は以下。
1 |
add_column :テーブル名, :カラム名, :カラムの型
|
複数のカラムを追加する場合は以下のよう改行をして続けて記述する。
remove_column
マイグレーションファイルのchangeメソッドではカラムを追加だけでなく削除もできる。remove_column
をマイグレーションファイルのchangeメソッド内に書くとカラムの削除ができる。記述方法は以下。
1 |
remove_column :テーブル名, :カラム名, :カラムの型
|
マイグレーションファイルに書いたテーブルの変更内容をテーブルに反映させるにはターミナルでrake db:migrate
コマンドを実行する。
マイグレーションの実行
1 2 3 4 |
$ bundle exec rake db:migrate
== AddPriceToBooks: migrating ==========================
-- add_column(:books, :price, :integer)
-> 0.0500s
|
上の例ではマイグレーションファイルAddPriceToBooksが実行され、booksテーブル内にinteger型のpriceカラムが追加された。
このようにテーブルの変更を行うときは必ずマイグレーションファイルを作成。また、分からなくなった際はrails generate migrationコマンドを振り返る。
Sequel Proのようなデータベースを操作できるアプリケーションを使うとそのアプリケーションからカラムの追加や削除ができる。しかし、アプリケーションから操作してしまうと複数人で開発している場合、開発者ごとにデータベースのテーブル構造が異なるという問題が起きてしまう。そのため、テーブル構造を変えるときは必ずマイグレーションファイルを作成し、マイグレーションを実行することでテーブル構造を操作するようにする。
作業内容
- Productsテーブルに以下のカラムを追加する
カラム名 | 型 | 情報 |
---|---|---|
director | string | 監督名 |
detail | text | あらすじ |
open_date | string | 公開日 |
編集するファイル
- ターミナル
- db/migrate/(作成したマイグレーションファイル)
ヒント
- カラムを追加するために
rails g migrationコマンド
でマイグレーションファイルを作成 add_column
を使用してテーブルに指定のカラムを追加するような記述をマイグレーションファイルにする- マイグレーションファイルができたら、
bundle exec rake db:migrateコマンド
でマイグレーションファイルを実行
Sequel Proでカラムが追加されたか確認
きちんと追加されていれば、新たに3つのカラムがproductsテーブルの中に作成されている。
映画の個別ページから「監督名」、「あらすじ」、「公開日」を取得してデータベースに保存
カラムを追加したら、それらのデータを実際にデータベースに保存したい。MovieReviewで映画の個別ページには「監督名」、「あらすじ」、「公開日」が載っています。これらをスクレイピングで取得し、Productsテーブルに保存する。
これらの値を取得する際に注意しなくてはならないことは、作品によっては監督名等が記載されていない場合があるということ。この時、searchメソッドあるいはatメソッドで取得したタグがnilになっている恐れがある。これに対してget_attributeメソッド
やinner_textメソッド
を使うとNoMethodErrorとなってしまう。これを回避する必要がある。
さて、すでに取得してある映画情報にカラムを追加しなくてはいならない。first_or_initialize
メソッドを使えば条件に当てはまるレコードがテーブルに保存されていればそのレコードのインスタンスを取得できる。
取得したインスタンスのカラムを更新するにはインスタンス.カラム名 = 値
という記述方法を使う。
1 2 3 4 5 |
product = Product.where(条件).first_or_initialize
# カラムの更新
product.director = 監督名
# 中略
product.save
|
最後にsaveするのを忘れないようにする。
作業内容
- 映画の個別ページから「監督名」、「あらすじ」、「公開日」を取得する
- 取得した「監督名」、「あらすじ」、「公開日」の情報をインスタンスに更新する
- 実際にデータベースに保存する
編集するファイル
- app/models/scraping.rb
- ターミナル
ヒント
- 映画の個別ページで作品画像のURLを取得するときに、一緒にこれらの情報も取得
- カラムの更新には
「インスタンス.カラム名 = 値」
の式を使う - コードが書けたらコンソールで
Scraping.movie_urls
を実行
Sequel Proのproductsテーブルを確認して、データが保存されているか確認する
きちんと情報が保存されていれば、以下のように表示される。
以下が完成の一例。他の記述の方法もありますので参考まで。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Scraping
def self.movie_urls
agent = Mechanize.new
links = []
next_url = ""
while true
current_page = agent.get("http://review-movie.herokuapp.com/" + next_url)
elements = current_page.search('.entry-title a')
elements.each do |ele|
links << ele.get_attribute('href')
end
next_link = current_page.at('.pagination .next a')
break unless next_link
next_url = next_link.get_attribute('href')
end
links.each do |link|
get_product('http://review-movie.herokuapp.com' + link)
end
end
def self.get_product(link)
agent = Mechanize.new
page = agent.get(link)
title = page.at('.entry-title').inner_text if page.at('.entry-title')
image_url = page.at('.entry-content img')[:src] if page.at('.entry-content img')
director = page.at('.director span').inner_text if page.at('.director span')
detail = page.at('.entry-content p').inner_text if page.at('.entry-content p')
open_date = page.at('.date span').inner_text if page.at('.date span')
product = Product.where(title: title).first_or_initialize
product.image_url = image_url
product.director = director
product.detail = detail
product.open_date = open_date
product.save
end
end
|
ここでコンソールでScraping.movie_urlsを実行すると下の画像のようにデータを全て表示することなく途中で止まるということがあるが、「q」を押すと入力が可能になる。エラーではない。