どうもaono1234と申します。記事がいいなと思ったらtwitterのフォローもお待ちしております‼
https://twitter.com/takeshi_program
皆さん、ブロック(コールバックメソッド)は使用されてますでしょうか?
自分は今日まで使ったことがありませんでした。😥
どうも使いどころが分からないんですよね~
しかし、やっと使いどころが1つ明らかになりましたので共有させて頂きます。
はじめに
本記事は以下の読者様が対象です。
-
プログラミング初心者
-
コールバックメソッド(ブロック)について知りたい
本記事ではRubyを題材に例題コードを作成しているため、コールバックではなくブロックと呼称させて頂きます。
先に結論
- ブロックを使うことで複数のメソッドをつなぎ合わせることができる。
- メソッドを拡張する際によりブロックを使えば、抽象的な名前に変更しなくてよくなる。
背景
私はオリジナルのスクレイピングコードを書いており、
以下のような自作メソッドを定義しておりました。
このメソッドはスクレイピングするページのurl集合url_list
を配列形式で渡された時、
そのページに特定の文字があればそのページのindex番号を返すというメソッドです。
削除ページ番号を教えてくれるメソッド
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
を以下のようにメインクラスで使っておりました。
delete_list = []
delete_list = delete_pages_select(driver, url_list, "class", "css-1hbyyce", "アウトソーシング", "派遣", "SES")
その後、別の機能も実装したいと思いました。
その機能とは「ページに特定のキーワードがあれば知らせる」というものです。
例えばこんな感じ↓
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かな~😎
ということで以下のように書きました。
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_select
もsearch_word_at_page
もどちらもdriver.get
を使っている所です。
driver.getとは何かというと seleniumでよく使うメソッドでgetメソッドでページを読み込むメソッドです。
どうなるかというと、例えば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リクエストの解決方法
result_list = []
result_list = delete_pages_select(driver, url_list, "class", "test", "アウトソーシング", "派遣", "SES")
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
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するものを選ぶぜ!😎って言っているのに、見えないところで検索もしてるよん!😘って感じで
名前とやっている事が乖離しているんですね~(詐欺やん!)
なのでメソッド名を命名し直したりする必要があるのですが、
自分はブロック(コールバックメソッド)を使うことで、楽に対応できました。
ブロックによる解決案
result_list = delete_pages_select(driver, url_list, "class", "css-1hbyyce", "アウトソーシング", "派遣", "SES"){|driver, url| search_word_at_page(driver, url, "class", "css-1m9s1sn", "1年以上")}
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
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.select
をmain.rb
で呼び出す際にsearch.word_at_page
も引数として渡せるようになりました。blockを使うことで大工事する必要がなくなりました!
おわりに
この経験からブロック(コールバック)とは何か?というのが少し見えてきました。
ブロックとは…メソッドの定義を大幅に変えることなく、拡張することができる便利な仕組みという捉え方をするようになりました。
また発見があれば記事にしたいと思います。
ありがとうございました。👍