2019/09/10
WorkatoでRubyを利用する大野 智之Tomoyuki Ohno

Workato(ワーカート)には、Slackとの親和性が抜群のWorkbotという機能があります。
このWorkbotとRubyコネクタを組み合わせて、Slackから「地域のオススメなランチ情報」を入手するボットを作ってみたいと思います。
Workatoは対象ソフトウェアで接続するコネクタと設定し、トリガー(条件)とアクション(実行)を設定するだけで業務を自動化できる優れたツールですが、これだけでは実現できないプロセスや処理があった場合は『Rubyコネクタ』を使うと便利です。
Rubyコネクタを利用するにはRuby言語を理解する必要はありますが、これにより複雑な処理に対応することができます。
今回は、Rubyコネクタを利用してHTMLスクレイピングを行ってみました。
Rubyコネクタは、前述の通りWorkato上でRubyのコードを実行するためのコネクタです。コードの実行は、外部のサービス(アプリケーション)を利用することなく、Workato内で処理が完結します。
ただし、利用可能なメソッド・プロパティは制限があり、Rubyに標準で用意されたメソッド・プロパティと、下記URLに記載のメソッド・プロパティに限られます。また、外部ライブラリは利用出来ません。
https://docs.workato.com/developing-connectors/sdk/methods.html
また、標準のメソッドであっても、レシーバーを直接書き換えるものは利用できません。(例えば、hoge.gsub!やhoge.pushなど)
色々と制限はありますが、応用の幅が広いコネクタですので、覚えておいて損はありません。
WorkatoはAPIを備える様々なアプリケーション同士を組み合わせることで、様々なオートメーションを実現することが出来ますが、外部サービスの中にはAPIが提供されていないものも存在します。
このようなサービスからデータを取得してWorkatoで処理する方法の1つとして、Webスクレイピングを行う方法が挙げられます。
WorkatoでWebスクレイピングを行う方法には、Parsehub(https://www.parsehub.com/)があります。ParsehubコネクタもWorkatoに標準で用意されていますので、通常はこれを利用するのがベターでしょう。
しかし、今回は敢えてRubyコネクタでWebスクレイピングにチャレンジしてみます。
RubyでWebスクレイピングを行う場合、Nokogiriがよく利用されます。しかし、WorkatoのRubyコネクタでは、Nokogiriを含めて外部ライブラリを利用することができません。
このため、標準メソッド・プロパティおよびWorkatoが対応するメソッド・プロパティの範囲でコーディングを行う必要があります。
Rubyコネクタを利用したWebスクレイピングを行う際のレシピは、次のとおりとなります。
このレシピでは、リックソフト本社の近くにある、東京サンケイビルの敷地内で平日昼間に展開される「ネオ屋台村」のランチスケジュールを取得して、Slackの特定のチャンネルへ投稿します。
ただし、上記ランチスケジュールにはAPIやRSSなどの構造化されたデータを取得する手段が存在しないため、Webスクレイピングで必要な値を取得していきます。
上記レシピの各タスクでは、以下のような処理が実装されています。
| Trigger | SlackのWorkbotに対して、「otemachi lunch sankei」というメッセージ(コマンド)を送信すると、実行結果がSlack上に返されます。 | 
|---|---|
| Actions | SlackのWorkbotに対して、「otemachi lunch sankei」というメッセージ(コマンド)を送信すると、実行結果がSlack上に返されます。 | 
| 「ネオ屋台村」のランチスケジュールのHTMLを取得します。 | |
| Rubyコードを実行します。 | |
| ループ用の変数を初期化します。(Rubyの実行結果より出力された配列の値を、For eachループで0,1,2,3・・・といった形で順番に取得するため) | |
| Rubyの実行結果をFor eachループします。 | |
| Rubyの実行結果より1件取得(指定された配列のインデックスより取得)し、Slackにその結果を返します。 | |
| ループ用の変数をインクリメント(+1)します。 | 
このうち、Rubyのコードは次のようになっています。
前述の通りNokogiri等の外部ライブラリが利用できないため、特定の文字列が出現したら処理を行うという実装となります。
# @param input [Hash] input hash supplied in the recipe step
# @return value returned in the last line
# Eg: Code for returning country code for an IP address
# {
#   country_code: get("http://freegeoip.net/json/" + input["ip_address"])
# }
  
output = ""
flg = false
  
s = input['html_body']
res = []
flg_a = false
flg_b = false
flg_c = false
shop_name = ""
car_img_url = ""
food_img_url = ""
description = ""
  
cnt = 0
  
s.split("\n").each do | line |
        tmp = line.strip
  
        # Check Today's shops block.
        if tmp.include?('<li class="active"><!-- date -->') then
                flg_a = true
                next
        end
  
        if tmp.include?("</li><!-- date -->") then
                flg_a = false
        end
  
        if flg_a == false then
                next
        end
  
  
        # Fetch Today's shops.
        if tmp == "<li><!-- cnt_archive_lists_col02 li -->" then
                shop_name = ""
                car_img_url = ""
                food_img_url =""
                description = ""
                flg_b = true
                next
        end
  
        if tmp.include?("cnt_modalbox") then
                flg_b = false
        end
  
  
        if flg_b == false
                next
        end
  
        # Exclude a tag
        if tmp.include?("<a ") || tmp.include?("</a>") then
                next
        end
  
        # Get shop name
        if tmp.include?("<h4") then
                shop_name = tmp.gsub('<h4 class="cnt_archive_lists_ttl"><span class="link_icon link_icon_arrow">','')
                shop_name = shop_name.gsub('</span></h4>','')
                shop_name = shop_name.strip
        end
  
        # Get car_photo_img_url
        if tmp.include?("cnt_archive_lists_img") then
                car_img_url = tmp.gsub('<span class="cnt_archive_lists_img resize_img"><img src="','')
                car_img_url = car_img_url.gsub('" alt=""></span>','')
                car_img_url = car_img_url.strip
        end
  
        # Get food_photo_img_url
        if tmp.include?("cnt_archive_lists_thumb") then
                food_img_url = tmp.gsub('<span class="cnt_archive_lists_thumb resize_img"><img src="','')
                food_img_url = food_img_url.gsub('" alt=""></span>','')
                food_img_url = food_img_url.strip
        end
  
        # Get description
        if tmp.include?("cnt_archive_lists_txt") then
                flg_c = true
                next
        end
  
        if flg_c == true
                description = tmp.gsub('<p>','')
                description = description.gsub('</p>','')
                description = description.gsub('</div>','')
                description = description.strip
                flg_c = false
            
                res[cnt] = {
                        "shop_name" => shop_name,
                        "car_img_url" => car_img_url,
                        "food_img_url" => food_img_url,
                        "description" => description
                }
                cnt = cnt + 1
  
                next
        end
end
            
{ result: res }
SlackのWorkbotで「otemachi lunch sankei」と入力すると、レシピが実行されます。
実行されると、以下のように当日のランチがSlackに投稿されます。
今回は、WorkatoのRubyコネクタの利用例としてWebスクレイピングを行ってみました。
このような使い方が適切かどうかはありますが、Rubyコネクタとは何か、どのように利用できるのかがお分かりいただけると思います。
また、iPaaS製品の中でもWorkatoは柔軟性が高く、かつ簡単に使える製品であることもご理解いただけると思います。
iPaaS製品の導入を検討の際は、Workatoを是非ご検討ください。
製品についてはこちらをご覧ください。
アトラシアン社ではサポート範囲外となっているサードパーティ製のアドオンをリックソフトのRS標準サポートではサポートします。
リックソフトのRS標準サポートは開発元が提供するサポート以上の価値があります。
ツールを導入しただけでは成功とはいえません。利用者が効果を感じていただくことが大切です。独自で制作した各種ガイドブックはツール活用を促進します。
リックソフトからライセンス購入を頂いたお客様にはガイドブックを無料進呈いたします。
ツール操作の研修だけでなく「ウォータフォール型開発」「アジャイル型開発」のシミュレーション研修も提供。
日本随一の生産性向上にも効果のある研修サービスです。
リックソフトからライセンス購入を頂いたお客様には無料招待や割引特典がございます。