はじめに
GitHub Actionsで処理を自動化する話です。
Go言語でスクレイピングして結果をSlackに通知します。
参考にさせていただいた記事
- Go で Slack Bot を作る (2020年3月版)
- https://qiita.com/frozenbonito/items/cf75dadce12ef9a048e9
- Goとgoqueryでスクレイピング
- https://qiita.com/Yaruki00/items/b50e346551690b158a79
準備
GitHubとSlackのアカウントが必要です。
Slackはフリープランでスペースを作成し、Botから通知を送るためのチャンネルを作成しておきます。GitHubにはプライベートのリポジトリを作成しておきます(DDoS攻撃などに悪用されないため非公開にする)
流れ
以下の順番で作成を進めます。
- 対象ページから必要な情報を取得する処理の作成
- Slackアプリの作成とトークンの取得
- SlackにPOSTする処理の作成
- GitHub Actionsで処理を定時実行できるようにする
対象ページから必要な情報を取得する処理の作成
Go言語でスクレイピングする際の定番パッケージである(ように思われる)PuerkitoBio/goquery
を使用します。スクレイピングするからには有用でなおかつ毎日更新される情報が欲しいので、Yahoo!ショッピングの日替わりクーポンを取得してみます。https://topics.shopping.yahoo.co.jp/campaign/cate_coupon/index.html このURLがスクレイピングの対象です。
サンプル https://github.com/PuerkitoBio/goquery#examples を参考にしつつtitleタグの中身だけ取得する処理を作成します。
package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
func main() {
// Request the HTML page.
res, err := http.Get("https://topics.shopping.yahoo.co.jp/campaign/cate_coupon/index.html")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
title := doc.Find("title").Text()
fmt.Println(title)
}
ビルドして実行するとタイトルの取得結果を確認することができます。
$ go mod init
$ go build
$ ./goquery_test
【今日のクーポン】メガネ、くもり止めカテゴリで使える30%OFFクーポン
Slackアプリの作成とトークンの取得
SlackにメッセージをPOSTする処理を作成する前にSlackアプリ(Bot)を作成し、トークンを取得しておく必要があります。
-
https://api.slack.com/apps にアクセスして
Create New App
ボタンを押下 -
Bot Token Scopes
にchat:write
を追加(Add an OAuth Scopeをクリックしてリストから選択)
-
左カラムのSettings配下にある
Install App
をクリック
7.ワークスペースにアプリのインストールが完了するとトークン(OAuth Access Token)が表示されるのでメモしておく。
8.メッセージを受信したいチャンネルに作成したアプリを追加しておく。ショートカットを開いてinvite
と入力、表示されたリストからこのチャンネルにアプリを追加する
を選択する。
SlackにPOSTする処理の作成
続いてSlackにPOSTする処理を作成します。Qiitaの多くの記事にも書かれているslack-go/slack
パッケージを使います。examples以下を探したところ単純にメッセージを送信するサンプルを見つけることができました。https://github.com/slack-go/slack/tree/master/examples/messages こちらをベースに作成します。
2箇所書き換えが必要です。
YOUR_TOKEN_HERE
: メモしておいたOAuth Access Token
に書き換え
CHANNEL_ID
: メッセージを受信したいチャンネル名かチャンネルIDを指定
package main
import (
"fmt"
"github.com/slack-go/slack"
)
func main() {
// YOUR_TOKEN_HEREをメモしておいたトークンに置き換える
api := slack.New("YOUR_TOKEN_HERE")
attachment := slack.Attachment{
Pretext: "some pretext",
Text: "some text",
// Uncomment the following part to send a field too
/*
Fields: []slack.AttachmentField{
slack.AttachmentField{
Title: "a",
Value: "no",
},
},
*/
}
channelID, timestamp, err := api.PostMessage(
"CHANNEL_ID", // メッセージを送信したいチャンネルを指定する
slack.MsgOptionText("Some text", false),
slack.MsgOptionAttachments(attachment),
slack.MsgOptionAsUser(true), // Add this if you want that the bot would post message as a user, otherwise it will send response using the default slackbot
)
if err != nil {
fmt.Printf("%s\n", err)
return
}
fmt.Printf("Message successfully sent to channel %s at %s\n", channelID, timestamp)
}
ビルドして実行するとSlackにメッセージが送信されます。
$ go mod init
$ go build
$ ./slack_bot
Message successfully sent to channel XXXXXXXXXXXXX at 1613796836.000500
not_in_channel
とエラーが出力された場合にはチャンネルにアプリを追加して下さい。
GitHub Actionsで処理を定時実行できるようにする
まずは動作確認用のプライベートリポジトリを作成しておく。Hello, World
を出力するだけの処理を作りgo.mod
も含めてリポジトリに追加しておく。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Actionsタブをクリックするとそのリポジトリに適したワークフローテンプレートがサジェストされます。サジェストされたGo言語用のテンプレートをベースにしつつワークフローを作成します。cronによる定期実行だけではなく手動で実行するためworkflow_dispatch
を登録しておくと確認が楽になります。schedule
イベントはcron形式で記述できますがUTC時間なので注意が必要です。
詳しくはコチラ https://docs.github.com/ja/actions/reference/events-that-trigger-workflows
作成したワークフローファイルは、リポジトリの.github/workflows
ディレクトリに登録します。ワークフローを手動実行するか設定した時刻になれば結果を確認できるはずです。ワークフローの手動実行方法 https://docs.github.com/ja/actions/managing-workflow-runs/manually-running-a-workflow
name: Go
on:
workflow_dispatch:
schedule:
- cron: '5 15 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Build
run: |
mkdir /home/runner/work/dist
go build -v -o /home/runner/work/dist/myapp ./...
- name: RunMyApp
run: /home/runner/work/dist/myapp
まとめ+α
仕上げとしてスクレイピング+Slack送信の処理を作成します。さらにトークンはシークレットから取得するようにしておきます。シークレットはリポジトリのSettings
タブのsecrets
から登録する。下に掲載したサンプル(main.go)ではトークンをSLACK_TOKEN
にしています。
package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/PuerkitoBio/goquery"
"github.com/slack-go/slack"
)
func mustGetenv(k string) string {
v := os.Getenv(k)
if v == "" {
log.Panic("env not set.")
}
return v
}
var token string
func init() {
token = mustGetenv("SLACK_TOKEN")
}
func main() {
// Request the HTML page.
res, err := http.Get("https://topics.shopping.yahoo.co.jp/campaign/cate_coupon/index.html")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
title := doc.Find("title").Text()
api := slack.New(token)
attachment := slack.Attachment{
Text: "https://topics.shopping.yahoo.co.jp/campaign/cate_coupon/",
}
channelID, timestamp, err := api.PostMessage(
"CHANNEL_ID",
slack.MsgOptionText(title, false),
slack.MsgOptionAttachments(attachment),
slack.MsgOptionAsUser(true),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Message successfully sent to channel %s at %s\n", channelID, timestamp)
}
ワークフローにもシークレットを取得してセットする処理を追加しておきます。(追加した前後だけ抜粋)
name: Go
env:
SLACK_TOKEN: ${{secrets.SLACK_TOKEN}}
on:
workflow_dispatch:
schedule:
- cron: '5 15 * * *'
処理や設定に問題がなければ以下のように毎日通知が届くようになります。
最後に
GitHub Actionsで何か自動化してみたかったのでスクレイピングしてSlackに通知をしてみましたが、スクレイピングする場合にはルールとマナーを守りましょう。