Go言語でHTMLをスクレイピングして電車の遅延情報を教えてくれるSlack-botをつくる

GoとSlackのbotを組み合わせて勉強をしてみたかったので関東の電車運行状況を取得するbotをつくってみました。
本当は勝手につぶやく方式にしたかったのですがとりあえずこの時点で記事にします。
（そのうち改造するかも。）

# Slack Appの作成

こちらのサイトを参照し作成しました。
[GolangでSlack Interactive Messageを使ったBotを書く](https://tech.mercari.com/entry/2017/05/23/095500)

# botの作成
## スクレイピングするパッケージのインストール
goqueryをインストールします。
jQuery的な操作が出来るので経験のあるWebエンジニアには割とすんなり理解できます。

```bash:
$ go get github.com/PuerkitoBio/goquery
```

## 情報を取得するWebサイト
こちらがないと始まりません。
今回はYahoo!路線情報さんから取得します。

https://transit.yahoo.co.jp/traininfo/area/4/

## 実装
では実際にコーディングしてアプリをつくります。
「運行状況」というキーワードに反応して上記サイトから運行状況を返すbotです。

```golang:slack_bot.go
package main

import (
    "log"
    "os"
    "net/url"
    "github.com/nlopes/slack"
    "github.com/PuerkitoBio/goquery"
)

// HTMLをスクレイピングして運行状況のテキストを取得する
func getRailwayText() string {
    _url := "https://transit.yahoo.co.jp/traininfo/area/4/"

    doc, err := goquery.NewDocument(_url)
    if err != nil {
        panic(err)
    }

    u := url.URL{}
    u.Scheme = doc.Url.Scheme
    u.Host = doc.Url.Host

    var message string
    // まずはテーブル要素を取得する
    doc.Find("div.trouble > table > tbody").Each(func(_ int, s *goquery.Selection) {
        // 次にtrを掘っていく
        s.Children().Each(func(idx int, ss *goquery.Selection) {
            // idx==0は見出し列なので飛ばす
            if (idx > 0) {
                railway := ss.Children().Find("a").Text()
                status := ss.Children().Find("span.colTrouble").Text()
                detail := ss.Children().Next().Next().Text()
                message = message + status + " @ " + railway + " (" + detail + ")\n"
            }
        })
    })
    return message
}

func run(api *slack.Client) int {
    rtm := api.NewRTM()
    go rtm.ManageConnection()

    for {
        select {
        case msg := <-rtm.IncomingEvents:
            switch ev := msg.Data.(type) {
            case *slack.HelloEvent:
                log.Print("Hello Event")

            case *slack.MessageEvent:
                switch ev.Text {
                case "運行状況":
                    message := getRailwayText()
                    rtm.SendMessage(rtm.NewOutgoingMessage(message, ev.Channel))
                case "help":
                    rtm.SendMessage(rtm.NewOutgoingMessage("Usage: 運行状況　関東圏の電車の運行状況を表示します。\n       他の機能？ないよ。", ev.Channel))
                }

            case *slack.InvalidAuthEvent:
                log.Print("Invalid credentials")
                return 1

            }
        }
    }
}

func main() {
    api := slack.New("YOUR TOKEN")
    os.Exit(run(api))
}

```
トークンはSlack APIページの左メニュー[OAuth & Permissions]の[Bot User OAuth Access Token]にあります。
![スクリーンショット 2019-05-03 11.23.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35884/42e41d01-c80c-8361-753c-1a5e4ca76f1d.png)

## 実行
ではローカルのターミナル上で動かしてみます。

```bash
$ go run slack_bot.go
2019/05/03 11:03:27 Hello Event

```
Hello Eventが表示されれば成功です。
実際につくったbotに対してメッセージを送ってみます。
![スクリーンショット 2019-05-03 11.31.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35884/5fdaa255-d176-4fd1-0939-a9f8f0d1b2dd.png)

ちゃんと返ってきました！

# 終わりに
goqueryは欲しいオブジェクトの検索条件を指定するだけで勝手にとってきてくれるので非常に便利でした。
複数のサイトから情報を取得する場合であってもGoお得意の並行処理で効率的に処理したりできそうですね。
slack-botも拡張性が高く、工夫次第で様々なところで効率化ができそうです。

# 参考
[Goとgoqueryでスクレイピング](https://qiita.com/Yaruki00/items/b50e346551690b158a79)