12
2

More than 1 year has passed since last update.

ブロック、コールバックの使い方の一例

Last updated at Posted at 2022-11-07

どうもaono1234と申します。記事がいいなと思ったらtwitterのフォローもお待ちしております‼
https://twitter.com/takeshi_program

皆さん、ブロック(コールバックメソッド)は使用されてますでしょうか?
自分は今日まで使ったことがありませんでした。😥

どうも使いどころが分からないんですよね~
しかし、やっと使いどころが1つ明らかになりましたので共有させて頂きます。

はじめに

本記事は以下の読者様が対象です。

  • プログラミング初心者

  • コールバックメソッド(ブロック)について知りたい

本記事ではRubyを題材に例題コードを作成しているため、コールバックではなくブロックと呼称させて頂きます。

先に結論

  • ブロックを使うことで複数のメソッドをつなぎ合わせることができる。
  • メソッドを拡張する際によりブロックを使えば、抽象的な名前に変更しなくてよくなる。

背景

私はオリジナルのスクレイピングコードを書いており、
以下のような自作メソッドを定義しておりました。

このメソッドはスクレイピングするページのurl集合url_listを配列形式で渡された時、
そのページに特定の文字があればそのページのindex番号を返すというメソッドです。
削除ページ番号を教えてくれるメソッド

delete_pages.select.rb
def delete_pages_select(driver, url_list, element_type, element_name, *filter)
    element_type = element_type.to_sym if element_type.class == String #element_typeが文字列だったらシンボルに変換する
    delete_index = []
    block_result = []
    url_list.each_with_index{|url, index| #
      puts "execute #{url} #{index}/#{url_list.size} "
      driver.get url                                   
      sleep(1)
      puts "get page"
      article = driver.find_element(element_type, element_name).text
      puts "find element"
      delete_index << index if article.include?(filter, all: false)
    }
    puts "seleted delete_index"
    delete_index
  end

include?メソッドは通常引数に配列は取れないのですが、以下のページを参考に配列形式でも引数として取得できるように拡張させています。(配列内のどれかのワードが含まれていればtrueを返すように)
https://qiita.com/takaram/items/fdc2b1897d68b3f2d848

この自作メソッドdelete_pages_selectを以下のようにメインクラスで使っておりました。

main
delete_list = []
delete_list = delete_pages_select(driver, url_list, "class", "css-1hbyyce", "アウトソーシング", "派遣", "SES")

その後、別の機能も実装したいと思いました。

その機能とは「ページに特定のキーワードがあれば知らせる」というものです。
例えばこんな感じ↓

search_word_at_pag.rb
def search_word_at_page(driver, url, element_type, element_name, *search_words)
    element_type = element_type.to_sym if element_type.class == String    
    driver.get url    
    text = driver.find_element(element_type, element_name).text
    result_index =[]
    search_words.each{|search_word|
      result_index << search_word if text.include?(search_word)
    }
    result_index
  end

これで2つのメソッドを定義できたのであとはmain.rbでがっしゃんこすればOKかな~😎
ということで以下のように書きました。

main
delete_list = []
search_result_list = []
delete_list = delete_pages_select(driver, url_list, "class", "test", "アウトソーシング", "派遣", "SES")
url_list.each{
    search_result_list = search_word_at_page(driver, url, "class", "test", "1年以上")
}

これでちゃんと動作はしたのですが、1つ問題がありました。

問題

上のmain.rbの問題とはdelete_pages_selectsearch_word_at_pageもどちらもdriver.getを使っている所です。
driver.getとは何かというと seleniumでよく使うメソッドでgetメソッドでページを読み込むメソッドです。

参考記事: https://qiita.com/mochio/items/dc9935ee607895420186

どうなるかというと、例えば1000ページ分の内容をmain.rbでスキャンしたとします。そうすると、ページは2倍の2000回getリクエストされます。
なぜならばdelete_pages_selectメソッドsearch_words_at_pageメソッド2つのメソッドが各自でページをgetしているからです。😢

なのでget 1回でdelete_pages_selectメソッドの処理とsearch_words_at_pageメソッドの処理ができるようにしたいと思います。

複数getリクエストの解決方法

main.rb
result_list = []
result_list = delete_pages_select(driver, url_list, "class", "test", "アウトソーシング", "派遣", "SES")
delete_pages.select.rb
def delete_pages_select(driver, url_list, element_type, element_name, *filter)
    element_type = element_type.to_sym if element_type.class == String
    delete_index = []
    block_result = []
    url_list.each_with_index{|url, index|
      puts "execute #{url} #{index}/#{url_list.size} "
      driver.get url     #http GETリクエスト
      search_word_at_page(driver, url, "class", "test", "1年以上") #追加コード
      sleep(1)
      puts "get page"
      article = driver.find_element(element_type, element_name).text
      puts "find element"
      delete_index << index if article.include?(filter, all: false)
    }
    puts "seleted delete_index"
    delete_index
  end
search_word_at_pag.rb
def search_word_at_page(driver, url, element_type, element_name, *search_words)
    element_type = element_type.to_sym if element_type.class == String
    text = driver.find_element(element_type, element_name).text
    result_index =[]
    search_words.each{|search_word|
      result_index << search_word if text.include?(search_word)
    }
    result_index
  end

delete_pages.selectメソッドの中でsearch_words_at_pageメソッドを呼び出すようにしました!!🤗

…ただ、このコードも問題があります。😱
これ、メソッド名はdeleteするものを選ぶぜ!😎って言っているのに、見えないところで検索もしてるよん!😘って感じで
名前とやっている事が乖離しているんですね~(詐欺やん!)

なのでメソッド名を命名し直したりする必要があるのですが、
自分はブロック(コールバックメソッド)を使うことで、楽に対応できました。

ブロックによる解決案

main.rb
result_list = delete_pages_select(driver, url_list, "class", "css-1hbyyce", "アウトソーシング", "派遣", "SES"){|driver, url| search_word_at_page(driver, url, "class", "css-1m9s1sn", "1年以上")}
delete_pages.select.rb
def delete_pages_select(driver, url_list, element_type, element_name, *filter, &block) ######ブロックを追加
    element_type = element_type.to_sym if element_type.class == String
    delete_index = []
    block_result = []
    url_list.each_with_index{|url, index|
      puts "execute #{url} #{index}/#{url_list.size} "
      driver.get url
      sleep(1)
      puts "get page"
      article = driver.find_element(element_type, element_name).text
      puts "find element"
      delete_index << index if article.include?(filter, all: false)
      block_result << block.call(driver, url) if block_given? ######ブロックを呼び出す
    }
    puts "seleted delete_index"
    [delete_index, block_result]
end
search_word_at_page.rb
def search_word_at_page(driver, url, element_type, element_name, *search_words)
    element_type = element_type.to_sym if element_type.class == String
    text = driver.find_element(:id, "__next").text
    result_index =[]
    search_words.each{|search_word|
      result_index << search_word if text.include?(search_word)
    }
    result_index
end

ブロックを追加することでdelete_pages.selectmain.rbで呼び出す際にsearch.word_at_pageも引数として渡せるようになりました。blockを使うことで大工事する必要がなくなりました!

おわりに

この経験からブロック(コールバック)とは何か?というのが少し見えてきました。
ブロックとは…メソッドの定義を大幅に変えることなく、拡張することができる便利な仕組みという捉え方をするようになりました。

また発見があれば記事にしたいと思います。
ありがとうございました。👍

12
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2