LoginSignup
0
1

More than 3 years have passed since last update.

日報Go

Last updated at Posted at 2021-03-17

きっかけ

「リモートワークで勤怠入力が必須になってしまった...」
「いちいちブラウザ開いて入力したくない....」
ということでGolangの勉強がてらに作りました:fist:

上司に許可とって、きちんとやっている内容出していればOKとのことだったので自動化してみた感じです。:tea:
簡単な内容ですが、同僚には受けがよかったです...w

間違いなどありましたらご指摘していただけると幸いです。

ソース

前提

  • Golang ver1.15
    • goquery
    • Agouti
  • ChromeDriver ver88.0.4324.96
  • zsh
  • macOS ver10.15.7

注意

スクレイピングは脅威にもなります。
サイトごとに行ってよいか注意しながら行いましょう。
待ち処理などを適宜入れてください。

前準備

goファイルが走るように設定します。

% cd go_chatwork_notice/
% go mod init
% mkdir tmp/
% vi config.ini
### 記述 ###
[web]
# for Basic('user:password@')
url = https://user:password@example.com

[login]
username = your nice username
password = your nice password

[chatwork]
cwToken = your nice token
cwURL   = https://xxxxxxxxxxx
cw2Me   = xxxxxxxxxx

やったこと

全部説明すると長いので掻い摘みます:bow:
下記はmain.goの内容です。

■構造体

既定の値はconfig.iniを参照します。
値を呼ぶ時は、Scraping.UserNameのように使用します。

main.go
// 定数
const (
    // TimeLayout 出力時間のフォーマット
    TimeLayout string = "2006-01-02 15:04:05"
    // DateLayout 日付フォーマット
    DateLayout string = "2006/01/02"
)

// ScrapingList スクレイピング用のstruct
type ScrapingList struct {
    URL           string
    UserName      string
    Password      string
    ChatworkToken string
    ChatworkURL   string
    Chatwork2Me   string
}
func init() {
    config, err := ini.Load("config.ini")
    if err != nil {
        log.Fatalf("config.iniファイルの読み込みに失敗しました:%v", err)
    }
    Scraping = ScrapingList{
        URL:           config.Section("web").Key("url").MustString(""),
        UserName:      config.Section("login").Key("username").MustString(""),
        Password:      config.Section("login").Key("password").MustString(""),
        ChatworkToken: config.Section("chatwork").Key("cwToken").MustString(""),
        ChatworkURL:   config.Section("chatwork").Key("cwURL").MustString(""),
        Chatwork2Me:   config.Section("chatwork").Key("cw2Me").MustString(""),
    }
}

■API

Chatworkに結果を自身のチャットルームに通知しています。

main.go
func ChatWorkMessagePost(message string) {
    values := url.Values{}
    values.Add("body", message)

    req, err := http.NewRequest(
        "POST",
        Scraping.ChatworkURL,
        strings.NewReader(values.Encode()),
    )
    if err != nil {
        log.Fatalf("リクエスト失敗しました。:%v", err)
    }

    // コンテントタイプを設定
    req.Header.Set("X-ChatWorkToken", Scraping.ChatworkToken)
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    // メッセージ投稿
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatalf("メッセージ送信失敗しました。:%v", err)
    }
    defer resp.Body.Close()

    // メッセージ投稿結果表示
    if resp.StatusCode == http.StatusOK {
        fmt.Println("メッセージ投稿に成功しました。")
    } else {
        fmt.Println("メッセージ投稿に失敗しました。")
    }

    fmt.Println("メッセージ投稿ステータスコード=[",
        resp.StatusCode,
        "]レスポンス内容=[",
        resp.Status,
        "]",
    )
}

■ChromeDriver(Agouti)

ヘッドレスモードで起動しています。
ChromeOptionsで追加して快適に行うことができます。

main.go
driver := agouti.ChromeDriver(
    agouti.ChromeOptions("args", []string{
        "--headless",
        "--window-size=1280,800",
    }),
    agouti.Debug,
)

err := driver.Start()
    if err != nil {
        errLog("Chromeドライバ起動に失敗しました。")
        log.Fatalln(err)
    }
defer driver.Stop()

下記では、値の入力とクリックを行います。
HTMLの要素の取得に関してはFindByXPathFindByNameなどを使って自由に行ってください。

main.go
// ログイン処理
page.FindByXPath(`//*[@id="username"]`).Fill(Scraping.UserName)
page.FindByXPath(`//*[@id="password"]`).Fill(Scraping.Password)
page.FindByName(`login`).Click()

■Goquery

テキストの取得を行い判定をしています。
流れとしては、

  • chromedriverでHTMLを取得
  • HTMLをstringに変換
  • goqueryでHTMLを解析して要素のテキスト取得

を行っています。

main.go
// HTMLを取得する
getAll, err := page.HTML()
if err != nil {
    errLog("遷移後のHTML(一覧)取得失敗しました。")
    log.Fatalln(err)
}
readerGetAll := strings.NewReader(getAll)
contentDom, _ := goquery.NewDocumentFromReader(readerGetAll)
seeText := contentDom.Find("#header > h1 > span.current-project").Text()
if seeText != "xxxxxx" {
    message := "ログインに失敗しました。"
    errLog(message)
    log.Fatalln(message)
}
// 行数取得
rawCnt := len(contentDom.Find("#content > form:nth-child(5) > div > table > tbody > tr").Nodes)

■OS

ディレクトリの存在判定と作成し、スクリーンショット撮り保存します。

main.go
var dir = "./tmp/" + time.Now().Format(strings.Replace(DateLayout, "/", "-", -1))

// ディレクトリ作成
_, err = os.Stat(dir)
if err != nil {
    if os.IsNotExist(err) {
        fmt.Println("ディレクトリ作成します...")
        err = os.Mkdir(dir, 0755)
        if err != nil {
            fmt.Println("ディレクトリ作成失敗しました...")
            panic(err)
        }
    }
}

// スクリーンショットをとる
page.Screenshot(dir + "/input_" + time.Now().Format(TimeLayout) + ".png")

おまけ

いちいち起動したくないので、自分はcronで自動化しています。
やっていることは、

  • 平日のみ起動
  • 30分の揺らぎをもたす

人間らしく時間が毎日変わるので、個人的には満足してます。

cron
% cd
% wget https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv
% crontab -e
# 記述する内容
00 18 * * 1-5 bash -c "sleep $((RANDOM \% 1800))s"; grep `date "+\%Y/\%-m/\%-d"`, syukujitsu.csv > /dev/null || cd ~/Desktop/go_kintai; bash -l -c 'go run ~/go_chatwork/main.go'
# mailコマンドで起動したか確認できます
% mail
No mail for username

今後の展開

  • GoogleCalender連携
  • ラズパイサーバで起動させる
    • macがスリープだと、cronが走らず日報を書いてくれないので...

自動でカレンダーを見て、リモートワークの日は出勤/退勤時のメッセージ送信などをやってみようかなと思います。
これは流石に怒られるかもしれないので聞いてみます笑

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1