2017-09-24

【Python】Slack Botの作り方まとめ - Webスクレイピング・定期実行の合わせ技 -

#背景


ちはっす。DAIです。
会社の同僚と自宅でよく作業会をやっています。音楽聞いて、料理食べて、あとはただコツコツとみんなでそれぞれが勉強するだけなのですが、とてもQOLが高いです。

#やりたいこと


チケットを譲ってくれる人を見つけられるサイトがあるそうです。ここの最新情報を毎日自動でSlackで更新するようにします。


おけぴチケット救済サービス(定価以下限定チケット掲示板)

ミュージカルや演劇、劇団四季、宝塚歌劇、クラシック、コンサート、ライブ等の空席救済を目的とした観劇鑑賞生活応援サイトです



そのサイトでは、自分の見たいタイトルの舞台の名前を入れると、そのチケットを渡してくれる人を探すことができるそうなのですが、毎日検索して確認するのが面倒だそうなので、これを毎日プログラムが取得して、まとめたデータをSlackに通知してくれたら便利じゃね?ってはなしになり、作ってみました。




処理の流れとしては


  1. サイトにSeleniumにアクセス
  2. Seleniumで検索語句を入力
  3. Seleniumで検索結果を取得
  4. それぞれの新着情報を見やすく一つのレポートにまとめる
  5. Slackで通知
  6. これを毎日自動化する


といった感じです。なのでこの技術を使えると、実質すべてのWeb上のデータをSlackで通知させられると思うので、便利です


#TODO


  1. Cloud9上でSelenium・自動化プログラムのセットアップ
  2. SlackのWebhook登録を行う
  3. SlackのURLを取得
  4. コーディング
  5. 完成コード
  6. HerokuでDeployして自動化する

# Cloud9上でSeleniumのセットアップ


Cloud9上でSeleniumを使う方法に関しては、こちらの記事にまとめました。

【Python】Seleniumの定期処理をHerokuから実行するための環境構築方法 - Cloud9編-

一回Cloud9上でSeleniumの環境構築をする方法をまとめておきたいと思います。だれでも5分以内に同じ動作で、Cloud9上でSeleniumが利用できる環境をセットアップできることを目指します。


# Slackに登録してWebhookの登録を行う


まず最初に、Slackに登録します。


Where work happens

Slack is where work flows. It's where the people you need, the information you share, and the tools you use come together to get things done.

SlackでIDが登録されると、最初にデフォルトのチャットルームが作成されます。今度は、チャンネルを作成してください。そして、チャンネルを選択し、Add Incoming WebHooks integrationを押します。こんな感じで、WebhookのURLを取得してください。このURLに存在する部屋に、Slackでポストすることができるようになります。詳しくは下記のURLがとてもわかりやすいので、ご参考に。

SlackのWebhook URL取得手順 - Qiita

SlackのWebhook URLを取得するまでの手順を示す。 ChatにWebサービスの結果を通知するChatOps的なことをしたい。 通知先のChatツールとしてSlackを使用する Download Apps | Slack


# コーディング

さて、ここまで来ればあとは実装です。前半ではSeleniumでWebスクレイピングを行い、情報を抜き取ります。後半では、抜き取ったデータを加工して、Slackに投げます。


とりあえず、完成コードをおいておきます。コードを書いていた当時、なぜか日本語変換ができないままコードを書いていたので、コメントは英語ですすいません。


from selenium import webdriver
import time
import requests # for slack  
import json # for slack 

#Phanttomjs Driverを入手
browser = webdriver.PhantomJS()  # DO NOT FORGET to set path

#TODO set search query

query = "ワンピース"  # set search query here
url = "http://okepi.net/top.aspx"

#TODO access to targeted URL

print("Successfully accessed to the targeted page! title:{}".format(browser.title))

#TODO insert query into #txtTopSearch

queryField = browser.find_element_by_xpath("//*[@id='txtTopSearch']") # This x-path locates in search form
queryField.send_keys(query) #Insert query message into search form


#TODO submit and load page

submitButton = browser.find_element_by_xpath('//*[@id="btnTopSearch"]')
submitButton.click()
browser.implicitly_wait(5) # seconds
print("Submit completed. now at:{}".format(browser.title))

#TODO retrieve ticket data

tables = browser.find_elements_by_class_name("tbl_j") #
print("Number of table:{}".format(len(tables)))

#TODO insert data that you want from http://okepi.net/all.aspx?key=performance&value=%83%8F%83%93%83s%81[%83X

contents = [] #Stores each content later

for table in tables:
    try:
        print("#######################")
        title = table.find_element_by_class_name("goleft") #without this, title retrieves web-element object
        print("title:{}".format(title.text)) 
        titleURL = title.find_element_by_css_selector("a").get_attribute("href")
        print("titleURL:{} ".format(titleURL))
        datetime = table.find_element_by_class_name("pdatetime").text
        print("datetime:{} ".format(datetime))
        price_place = table.find_element_by_class_name("price").text
        print("price_place:{} ".format(price_place))
        
        #TODO make summary of each data
        
        content = title.text + "\n" + datetime + "\n" + price_place + "\n" + titleURL + "\n" + "###################\n"
        print(content)
        contents.append(content) # insert contents so slack can post only once
        
    except Exception as e: #error message appears when something goes wrong
        print(e)
        continue

#TODO create summary for slack
summary = "==============" + query + "の新着情報!================\n" #
for content in contents:
    summary = summary + content    
print(summary) # This summary is sent to slack finally

#TODO post summary on slack 

slackURL = "[your slack app URL]" # https://api.slack.com/custom-integrations
requests.post(slackURL, data = json.dumps({
    'text': summary, # text that you will post
    'username': u'Musical Reminder', # username (whatever name you want is fine)
    'icon_emoji': u':ghost:', # profile emoji
    'link_names': 1, # enables mention
}))

で、これを実行するとこんな感じの結果となります。



さて、コードの説明をします。


from selenium import webdriver
import time
import requests # for slack
import json # for slack 

seleniumは、仮想のウェブブラウザを起動して、人が操作ができるようなモジュールです。timeは時間を操作するモジュールで、requestsはslackにデータを送るためのモジュール、jsonは、データをJSON形式に変換できるモジュールです。


#Phanttomjs Driverを入手
browser = webdriver.PhantomJS()  # DO NOT FORGET to set path
#TODO set search query
query = "ワンピース"  # set search query here
url = "http://okepi.net/top.aspx"
#TODO access to targeted URL
browser.get(url)
print("Successfully accessed to the targeted page! title:{}".format(browser.title))
#TODO insert query into #txtTopSearch
queryField = browser.find_element_by_xpath("//*[@id='txtTopSearch']") # This x-path locates in search form
queryField.send_keys(query) #Insert query message into search form

webdriver.PhantomJSはSeleniumを使えるようにします。
browser.get(url)はurlにアクセスすることができるようなメソッドですね。

queryField = browser.find_element_by_xpath("//*[@id='txtTopSearch']")
これは、HTMLをX-Pathを取得します。このパスは、実際HTMLでいうところの検索フォームを指定しています。

queryField.send_keys(query)
さきほど指定した検索語句を、そのフォームの中に入れることができます。

#TODO submit and load page
submitButton = browser.find_element_by_xpath('//*[@id="btnTopSearch"]')
submitButton.click()
print("Loading...")
browser.implicitly_wait(5) # seconds
print("Submit completed. now at:{}".format(browser.title))
#TODO retrieve ticket data
tables = browser.find_elements_by_class_name("tbl_j") #<table class = "tbl_j">
print("Retrieved tables data")
print("Number of table:{}".format(len(tables)))

submitButton = browser.find_element_by_xpath('//*[@id="btnTopSearch"]')
submitButton.click()
次に、HTMLから検索ボタンがあるところをX-pathで取得しsubmitButtonに代入します。そして、そのボタンをclickします。こうすることで、指定した語句で検索された結果のページが表示されます。


さらに、HTMLでクラスの名前が"tbl_j"を取得することで、合致するすべてのクラスの要素を取得することができます。その後取得したデータ数を表示していますね。

contents = []

for table in tables:
    try:
        print("#######################")
        title = table.find_element_by_class_name("goleft") #without this, title retrieves web-element object
        print("title:{}".format(title.text))
        titleURL = title.find_element_by_css_selector("a").get_attribute("href")
        print("titleURL:{} ".format(titleURL))
        datetime = table.find_element_by_class_name("pdatetime").text
        print("datetime:{} ".format(datetime))
        price_place = table.find_element_by_class_name("price").text
        print("price_place:{} ".format(price_place))
     
        #TODO make summary of each data
     
        content = title.text + "\n" + datetime + "\n" + price_place + "\n" + titleURL + "\n" + "###################\n"
        print(content)
        contents.append(content) # insert contents so slack can post only once
     
    except Exception as e:
        print(e)
        continue

次。その取得した複数のテーブルをfor文で回しています。そして、それぞれのチケット情報のタイトル、講演日付、チケットの値段、URLを取得し、それぞれのデータを結合して、contentの中に代入します。最後に、最初に空でつくったcontentsの中に代入します。こうすることで、それぞれの情報をまとめたcontentをcontentsの配列を作成します。


summary = "==============" + query + "の新着情報!================\n"
for content in contents:
    summary = summary + content
 
print(summary)

最後に、一つ一つ取得したデータを、レポート用にまとめます。summaryの中に、それぞれのcontentを作成することで、まとめたレポートを作成することができます。


slackURL = "your slack url" # https://api.slack.com/custom-integrations
requests.post(slackURL, data = json.dumps({
    'text': summary, # 投稿するテキスト
    'username': u'Musical Reminder', # 投稿のユーザー名
    'icon_emoji': u':ghost:', # 投稿のプロフィール画像に入れる絵文字
    'link_names': 1, # メンションを有効にする
}))

そして、Webhook URLで取得したURLを代入します。で、投稿するテキストをまとめたデータを、textに入れて、usernameを適当な名前をつけます。そこから下はどうでもいいので解説は無視しますね。


そんなこんなで実行すると、Slackではこんな感じで出力されます!



# 自動化

自動化に関しては、少しセットアップが必要になるので、以下の記事を読んでやってみてください!

【Python】Seleniumの定期処理をHerokuから実行するための環境構築方法 - Cloud9編-

一回Cloud9上でSeleniumの環境構築をする方法をまとめておきたいと思います。だれでも5分以内に同じ動作で、Cloud9上でSeleniumが利用できる環境をセットアップできることを目指します。


【Pythonで定期処理】 Cloud9を利用して、Seleniumでherokuから定期実行する

Python Cloud9を利用して、定期処理をherokuから行う Cloud9というIDEを利用して、PythonからSeleniumを利用し、あるサイトでいいねを自動化するプログラムを作った。 これを定期実行を行いたい。検索してみると、crontabを使えば定期実行ができるようだが、cloud9上では実行できないらしい。 ほかの代替案を考えたとき、 heroku ...

$ heroku addons:create scheduler:standard
$ heroku addons:open scheduler #URLをひらいてコードを設定


参考

【Python】Cloud9でSeleniumのセットアップを5分で行うためのチュートリアル
http://review-of-my-life.blogspot.jp/2017/09/selenium-5-minutes-setup-on-cloud9.html

【Python】Seleniumの定期処理をHerokuから実行するための環境構築方法 - Cloud9編-

一回Cloud9上でSeleniumの環境構築をする方法をまとめておきたいと思います。だれでも5分以内に同じ動作で、Cloud9上でSeleniumが利用できる環境をセットアップできることを目指します。


Python データ分析入門マニュアルに戻る



注目の投稿

 PythonのTweepyを利用して、Twitter APIを利用している。 その中で、ハマったポイントをメモしておく。 まず、Searchに関して。 Twitter検索は、クライアントアプリ側では、全期間の検索が可能になっている。 一方で、APIを利用する際は、過去1週間しか...