Ruby on Rails でwebスクレイピングをする:その2(コード解説)

前回同様結論こんな感じのコードで実装しています

# frozen_string_literal: true

class TradingValueHighRankingsController < ApplicationController
  require 'open-uri'

  URL = 'https://finance.yahoo.co.jp/stocks/ranking/tradingValueHigh?market=all&term=daily'

  def show
    @ranking = load_ranking
  end

  private

  def load_ranking
    htmls = read_htmls
    docs = parse_htmls(htmls)
    pick_ranking_data(docs)
  end

  def read_htmls
    # 200位まで取得するため対象サイトの4ページ目までを読み込む
    (1..4).each_with_object([]) do |i, array|
      array << URI.parse(URL + "&page=#{i}").open.read
    end
  end

  def parse_htmls(htmls)
    htmls.each_with_object([]) do |html, array|
      array << Nokogiri::HTML.parse(html)
    end
  end

  def pick_ranking_data(docks)
    docks.each_with_object([]) do |doc, array|
      doc.css('table tbody tr').each do |element|
        array << element.at_css('ul li').children.text
      end
    end
  end
end

上の行から

# frozen_string_literal: true

これは文字列を変更できないようにするためのマジックコメントになります。

※詳しい解説はこの記事がわかりやすいと思います

Rubyは仕様上定数で定義したものの変更が可能なため、このようなマジックコメントをつけておくと安全だと思います。

class TradingValueHighRankingsController < ApplicationController
  require 'open-uri'

  URL = 'https://finance.yahoo.co.jp/stocks/ranking/tradingValueHigh?market=all&term=daily'

クラス名の定義とopen-uriというruby標準のライブラリを読み込んでます。

そしてURLという定数にスクレイピングする対象のURLとクエリパラメータをセットしています。

クラス名は突貫で書いてしまったのでちょっとセンスないなと思っているので今後リファクタしたいですよね。。名前空間とか使って今後の機能拡張にも対応できるようにしたいところです。

  def show
    @ranking = load_ranking
  end

ここはrailsのアクションを呼び足してる個所ですね。このコントローラーファイルのアクションですべて完結させるのもいいんですがこの記事に感銘を受けて細かい関数を使ってシンプルに書くように心がけてみました(この辺は個人の好みによると思います。)。今後アクション数が増えたとしてもCRUDアクションのみしか増やさない方針です。(というかこのアクションindexが適切だったと思ってるのでそこもリファクタ予定w)

なのでprivate内の

  • load_ranking
  • read_htmls
  • parse_htmls
  • pick_ranking_data

を使って@rankingを定義しています。

各関数のやってること

load_rankingについては各関数を呼び足しながらpick_ranking_dataを最終的に呼び出す

read_htmlsでは

  def read_htmls
    # 200位まで取得するため対象サイトの4ページ目までを読み込む
    (1..4).each_with_object([]) do |i, array|
      array << URI.parse(URL + "&page=#{i}").open.read
    end
  end

1~4の範囲オブジェクトをループして4ページ分のヤフーファイナンスの対象ページ(今回であれば売買代金ランキング)を読み込んでくることをしています。

&page=#{i}で1~4までのページ数を指定しています。

※open-uriって結構webで調べると下記のような書き方をしてるのが多いですが今だとエラーになるので注意です。ruby3.0以降からですが

open(URL).read

じゃあっていうので

URI.open(URL).read

とかくとrubocopでリスクのある書き方だからよせと言われて今の書き方に落ち着きました。

parse_htmlsでnokogirigemを利用してHTMLを取り回しやすいように解析しています。

これも4ページ分HTML情報をとってきてるのでその数分ループさせています。

  def parse_htmls(htmls)
    htmls.each_with_object([]) do |html, array|
      array << Nokogiri::HTML.parse(html)
    end
  end

最後にpick_ranking_dataを呼び出して所定のテキスト情報(今回の場合は銘柄コード)を取り出して配列として返却する流れになっています

  def pick_ranking_data(docks)
    docks.each_with_object([]) do |doc, array|
      doc.css('table tbody tr').each do |element|
        array << element.at_css('ul li').children.text
      end
    end
  end

.css?at_css?って方はこの記事がおすすめです。これ見れば大概のwebページの情報を抜いてくることができると思います。

まとめ

いかがでしょうか?っていうほどのものでもないのですが、解説してみるとコードの不完全さがまだまだあるなと思うので近々のうちにリファクタしたものも共有できればなと思ってます!それでは!

コメント

タイトルとURLをコピーしました