手続き型のコードをオブジェクト指向型のコードに書きかえる
ボートレース(レガッタ)の結果をボートクラブ別に検索できるコードを前に書いた。
ローイングファン version1 - ローイングファンの日記
このコードが手続き型だったのでオブジェクト指向型に書きかえてみた。
(2018-07-20修正 コードの実行部分にwhile文を追記)
目次
ローイングファンver1.0から1.1へ
今回書きかえた際、クローリング先のURLはあらかじめコードに仕込むようにした。
例として2017年全日本軽量級選手権の女子舵手なしクォドルプルと男子エイトのURLを登録した。
日本ボート協会 大会情報
第39回全日本軽量級選手権大会
女子舵手なしクォドルプル
https://www.jara.or.jp/race/2017/2017lightweight_w4x.html
男子エイト
https://www.jara.or.jp/race/2017/2017lightweight_m8+.html
<注意>
WindowsPCでレース結果の検索をためしたい場合はWebアプリ版を使ってください。
ローイングファン
https://rowingfan.herokuapp.com/
ローイングファンver1.1のコード
rowingfan_ver1_1.rb
require 'open-uri' class RaceinfoCollector def initialize @raceInfoUrls = [ #行末のカンマを忘れずに "https://www.jara.or.jp/race/2017/2017lightweight_w4x.html", "https://www.jara.or.jp/race/2017/2017lightweight_m8+.html" ] @guidances = { #行末のカンマを忘れずに intro: "レース(レガッタ)情報をクローリングします。", yes: "つづける場合は 1 を入力", no: "中止する場合は 2 を入力", yesOrNo: "半角(1or2): ", wait: "クローリング中です。\n#{@raceInfoUrls.size}秒おまちください。", seeYou: "See you ...", decoHyphen: "----------" } end def crawl_results puts @guidances[:decoHyphen] puts @guidances[:intro] puts @guidances[:yes] puts @guidances[:no] puts @guidances[:decoHyphen] print @guidances[:yesOrNo] reply = gets.chomp.to_i if reply == 1 puts @guidances[:wait] #全レース結果が入る配列を作成 raceinfos_all = [] @raceInfoUrls.each do |url| # HTMLをよみこむ # :utf-8オプションをつけないとバイト列になる opened_html = open("#{url}","r:utf-8") html_by_event = opened_html.read opened_html.close sleep(1) #<重要!>クローリングの間隔をあける #シングルスカル種目のクルー名にある余計なタブを削除 html_by_event.gsub!(/<br.?\/><small>/,'') html_by_event.gsub!(/<\/small>/,'') # データを取得する # 大会名、年代、種目名(タグ付き) /<h1 class=\"title\">第\d+回(?<tag_tourname>.+)大会<\/h1>/ =~ html_by_event /<a href=\"(?<tag_year>\d\d\d\d).+><i class=\"glyphicon glyphicon-file\"><\/i>/ =~ html_by_event /<div class=\"panel-heading\">(?<tag_event>.+)の組合せと結果<\/div>\n/ =~ html_by_event #レース記号(タグ付き) /\Ahttps:\/\/www.+\/(?<tag_urlyear>\d\d\d\d)(?<tag_urltour>.+)_(?<tag_urlevent>.+).html\z/ =~ url #---id情報--- #RaceNo, 発艇日時, 組別 raceno_array = html_by_event.scan(/Race No:\s(\d+)<\/div>/) starttime_array = html_by_event.scan(/発艇時刻:<\/b>\s(\d+\/\d+\s\d+:\d+)<\/div>/) group_array = html_by_event.scan(/<b>組別:<\/b> <a href=\"\d+.+_tt.html.day\d+_\d+_\d+\">(.+)<\/a><\/div>/) #---順位別情報--- #順位, クルー名 rank_array = html_by_event.scan(/<tr>\n.*<td class=\"text-right\">(\d?)<\/td>/) crew_array = html_by_event.scan(/<td class=\"crew\">(.*)<\/td>/) #ラップ500, 1000, 1500, 2000 lap500_array = html_by_event.scan(/<td class=\"crew\">.*<\/td>\n.*<td class=\"text-center\">(\d*:?\d*.?\d*)<\/td>\n/) lap1000_array = html_by_event.scan(/<td class=\"crew\">.*<\/td>\n.+<\/td>\n.*<td class=\"text-center\">(\d*:?\d*.?\d*)<\/td>\n/) lap1500_array = html_by_event.scan(/<td class=\"text-center\">(\d*:?\d*.?\d*)<\/td>\n.*<td class=\"text-center\">.*<\/td>\n.*<td class=\"text-right\">\d*<\/td>\n/) lap2000_array = html_by_event.scan(/<td class=\"text-center\">(\d*:?\d*.?\d*)<\/td>\n.*<td class=\"text-right\">\d*<\/td>\n/) #レーンNo, 備考 laneno_array = html_by_event.scan(/<td class=\"text-right\">(\d?)<\/td>\n.*<td class=\"qualify\">.*<\/td>\n/) qualify_array = html_by_event.scan(/<td class=\"qualify\">(.*)<\/td>\n/) #id情報の融合 レースNoを基準にしてレース日時,組別の行と列を入れ替える #=> [ [ [RaceNo1], [5/10 8:00], [予選A] ], [ [RaceNo2], [5/10 8:20], [予選B] ], [ [RaceNo3], [5/10 8:40,予選C] ], ....] identifiers = raceno_array.zip(starttime_array,group_array) #id情報に年, 大会名, 種目名を追加する identifiers.each do |identifier| identifier.insert(1,tag_event) identifier.insert(0,tag_year,tag_tourname,) identifier.flatten! end #順位を基準にして,クルー名,ラップタイム,ゴールタイム,レーンNo,備考の行と列を入れ替える #=> [ [ [1], [crew], [500], [1000], [1500], [2000], [LaneNo], [備考] ], [ [2],....], [ [3],....] ] infos_by_rank = rank_array.zip(crew_array, lap500_array, lap1000_array, lap1500_array, lap2000_array, laneno_array, qualify_array) #1レース分の順位別情報を入れる配列を作成 infos_rank1_6 = [] #すべての順位別情報が入る配列を作成 infos_rank1_6_all = [] count = 0 infos_by_rank.each do |info_by_rank| count += 1 info_by_rank.flatten! #6着の情報の次で配列を空にする infos_rank1_6 = [] if count % 6 == 1 #1着から6着を1セットの配列にする infos_rank1_6 << info_by_rank #1着から6着が1セットになった配列を一つの配列に格納していく infos_rank1_6_all << infos_rank1_6 if count % 6 == 0 end #id情報と順位別レース結果の行と列を入れ替える #入れ替え後、ブロックに渡して配列に格納する identifiers.zip(infos_rank1_6_all) do |infos| raceinfos_all << infos.flatten! end end #//@raceInfoUrls.each do |url| return raceinfos_all else puts "" puts @guidances[:seeYou] exit end #//if reply == 1 end #def crawl_results end class RaceInfoManager attr_writer :race_results def initialize @race_results = [] @guidances = { #行末のカンマを忘れずに intro: "ボートクラブ別にレース結果を検索します。", enterClubname: "ボートクラブの名称を入力してください。\n名称: ", hit: "レースを表示します。", tocsv: "CSVファイルをつくりました。", unhit: "見つかりませんでした。", decoHyphen: "----------" } end def find_race puts @guidances[:decoHyphen] puts @guidances[:intro] print @guidances[:enterClubname] gotten_clubname = gets.chomp found_races = [] @race_results.each do |race_result| if /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[7] || /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[15] || /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[23] || /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[31] || /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[39] || /.*#{gotten_clubname}[A-Z]?\)?\z/i =~ race_result[47] found_races << race_result end end return found_races end def display_race_results(found_races) unless found_races.size == 0 puts @guidances[:decoHyphen] print "#{found_races.size}" puts @guidances[:hit] puts @guidances[:decoHyphen] sleep(1.5) found_races.each do |race_result| #年, 大会名の出力 puts "#{race_result[0]}#{race_result[1]}" #RaceNo, 種目, 組別, 日時の出力 puts "RaceNo #{race_result[2]} #{race_result[3]} #{race_result[5]}\n#{race_result[4]}" #順位, クルー名, 2000mタイム, 備考の出力 puts "#{race_result[6] }着 #{race_result[7] } #{race_result[11]} #{race_result[13]}" if race_result[7] != "" puts "#{race_result[14]}着 #{race_result[15]} #{race_result[19]} #{race_result[21]}" if race_result[15] != "" puts "#{race_result[22]}着 #{race_result[23]} #{race_result[27]} #{race_result[29]}" if race_result[23] != "" puts "#{race_result[30]}着 #{race_result[31]} #{race_result[35]} #{race_result[37]}" if race_result[31] != "" puts "#{race_result[38]}着 #{race_result[39]} #{race_result[43]} #{race_result[45]}" if race_result[39] != "" puts "#{race_result[46]}着 #{race_result[47]} #{race_result[51]} #{race_result[53]}" if race_result[47] != "" puts @guidances[:decoHyphen] end else puts @guidances[:decoHyphen] puts @guidances[:unhit] puts @guidances[:decoHyphen] end #//unless found_races.size == 0 end def write_csv(found_races) unless found_races.size == 0 found_races.each do |race_result| #CSVファイル名につける記号を作成する case race_result[1] when "全日本軽量級選手権" tourname_mark = "lightweight" when "全日本大学選手権" tourname_mark = "incolle" when "全日本選手権" tourname_mark = "alljapan" when "全日本新人選手権" tourname_mark = "freshperson" end case race_result[3] when "女子シングルスカル" event_mark = "w1x" when "男子シングルスカル" event_mark = "m1x" when "女子ダブルスカル" event_mark = "w2x" when "男子ダブルスカル" event_mark = "m2x" when "女子舵手なしペア" event_mark = "w2-" when "男子舵手なしペア" event_mark = "m2-" when "男子舵手つきペア" event_mark = "m2+" when "女子舵手なしクォドルプル" event_mark = "w4x" when "女子舵手つきクォドルプル" event_mark = "w4x+" when "男子舵手なしクォドルプル" event_mark = "m4x" when "男子舵手なしフォア" event_mark = "m4-" when "女子舵手つきフォア" event_mark = "w4+" when "男子舵手つきフォア" event_mark = "m4+" when "女子エイト" event_mark = "w8+" when "男子エイト" event_mark = "m8+" end File.open("#{race_result[0]}_#{tourname_mark}_#{event_mark}.csv","a:shift_jis") do |csv| csv.write "#{race_result[0]}#{race_result[1]} RaceNo #{race_result[2]}\n#{race_result[3]} #{race_result[5]}\n#{race_result[4]}\n" csv.write "rank,crew,500m,1000m,1500m,2000m,lane,remarks\n" csv.write "#{race_result[6] },#{race_result[7] },#{race_result[8] },#{race_result[9] },#{race_result[10]},#{race_result[11]},#{race_result[12]},#{race_result[13]},\n" csv.write "#{race_result[14]},#{race_result[15]},#{race_result[16]},#{race_result[17]},#{race_result[18]},#{race_result[19]},#{race_result[20]},#{race_result[21]},\n" csv.write "#{race_result[22]},#{race_result[23]},#{race_result[24]},#{race_result[25]},#{race_result[26]},#{race_result[27]},#{race_result[28]},#{race_result[29]},\n" csv.write "#{race_result[30]},#{race_result[31]},#{race_result[32]},#{race_result[33]},#{race_result[34]},#{race_result[35]},#{race_result[36]},#{race_result[37]},\n" csv.write "#{race_result[38]},#{race_result[39]},#{race_result[40]},#{race_result[41]},#{race_result[42]},#{race_result[43]},#{race_result[44]},#{race_result[45]},\n" csv.write "#{race_result[46]},#{race_result[47]},#{race_result[48]},#{race_result[49]},#{race_result[50]},#{race_result[51]},#{race_result[52]},#{race_result[53]},\n" csv.write "\n" end #//File.open end #//found_races.each do |race_result| puts "" puts @guidances[:tocsv] end #//unless found_races.size == 0 end end #----ここからコードの実行----- infoCollector = RaceinfoCollector.new infoManager = RaceInfoManager.new #crawllerを実行してレース結果を入手する #crawlで入手したレース結果をinfoManagerに取り込む result_collections = infoCollector.crawl_results infoManager.race_results = result_collections while true #クラブ名とマッチするレースを検索する #見つけた結果を表示してCSVファイルをつくる found_races = infoManager.find_race infoManager.display_race_results(found_races) infoManager.write_csv(found_races) puts "" puts "検索をつづける場合は 1 を入力" puts "検索を終了する場合は 2 を入力" print "半角(1or2): " reply = gets.chomp.to_i if reply != 1 exit end end
実行結果
結果をターミナルに表示させる
女子舵手なしクォドルプル、男子エイトともに優勝している明治大学で検索を試してみる。
まず適当なディレクトリをつくり、その中にRubyコードのファイルを保存する。
ターミナルでコードファイルが保存されているディレクトリに移動しておく。
コードを実行する。
ruby rowingfan_ver1_1.rb
確認表示が出るので1
か2
を入力する。
---------- レース(レガッタ)情報をクローリングします。 つづける場合は 1 を入力 中止する場合は 2 を入力 ---------- 半角(1or2):
ボートクラブ名の入力を求められるので検索したいボートクラブ名を入力する。
今回は明治大学
と入力。
半角(1or2): 1 クローリング中です。 2秒おまちください。 ---------- ボートクラブ別にレース結果を検索します。 ボートクラブの名称を入力してください。 名称:
検索結果がターミナルに表示されCSVファイルが種目ごとにつくられる。
ボートクラブ別にレース結果を検索します。 ボートクラブの名称を入力してください。 名称: 明治大学 ---------- 8レースを表示します。 ---------- 2017全日本軽量級選手権 RaceNo 39 女子舵手なしクォドルプル 予選C組 05/26 13:48 1着 明治大学 07:12.79 →Semi-Final 2着 立命館大学 07:22.76 3着 早稲田大学 07:26.72 ---------- 2017全日本軽量級選手権 RaceNo 111 女子舵手なしクォドルプル 準決B組 05/28 10:52 1着 仙台大学 07:01.51 →Final A 2着 明治大学 07:01.76 →Final A 3着 富山国際大学 07:04.14 →Final B 4着 日本体育大学A 07:15.03 →Final B ---------- 2017全日本軽量級選手権 RaceNo 129 女子舵手なしクォドルプル 決勝 05/28 15:40 1着 明治大学 07:07.20 2着 法政大学A 07:10.52 3着 仙台大学 07:12.14 4着 立命館大学 07:17.54 ---------- 2017全日本軽量級選手権 RaceNo 45 男子エイト 予選A組 05/26 14:36 1着 一橋大学A 06:17.28 →Semi-Final 2着 明治大学B 06:26.61 3着 早稲田大学 06:26.93 4着 一橋大学C 06:45.94 ---------- 2017全日本軽量級選手権 RaceNo 46 男子エイト 予選B組 05/26 14:44 1着 明治大学A 06:04.48 →Semi-Final 2着 東京大学 06:22.25 3着 日本大学 06:25.30 4着 成蹊大学 06:27.95 ---------- 2017全日本軽量級選手権 RaceNo 91 男子エイト 敗復B組 05/27 15:10 1着 東京大学 06:26.79 →Semi-Final 2着 早稲田大学 06:29.00 →Semi-Final 3着 明治大学B 06:29.45 4着 一橋大学C 06:48.27 5着 慶應義塾大学 06:54.51 ---------- 2017全日本軽量級選手権 RaceNo 114 男子エイト 準決A組 05/28 11:16 1着 明治大学A 06:08.44 →Final A 2着 東京大学 06:13.42 →Final A 3着 日本大学 06:16.40 →Final B 4着 一橋大学B 06:24.66 →Final B ---------- 2017全日本軽量級選手権 RaceNo 133 男子エイト 決勝 05/28 16:30 1着 明治大学A 06:07.50 2着 仙台大学 06:12.84 3着 一橋大学A 06:18.60 4着 東京大学 06:29.16 ---------- CSVファイルをつくりました。
CSVファイルを表計算アプリで開く
検索結果をターミナルに表示したあと種目ごとにCSVファイルがつくられる。
文字エンコーディングはShift-JISに設定してある。
ファイル名の書式は次の通り。
年_大会名(記号)_種目名(記号).csv
CSVファイルを表計算アプリで開いたところは次の通り。
2017_lightweight_w4x.csv
感想
オブジェクト指向型でコードを書くとメンテナンスがしやすくなる。
拡張もしやすい。データベースやTwitterと連動させればWebアプリ版ローイングファンになる。
今回のコードはRuby2.5.1 Windows環境では動かなかった。getsで取得する日本語処理のしかたがわからなかった。
解決したらコードを修正する予定。
そろそろWindowsPCに仮想環境をつくりたい。