Goでブラウザ
業務を自動化する
@shuntaka
takahashi shunichi
@shuntaka_jp
普段:インフラ・サーバーサイド(AWS,C#,Python)
早速ですが.....
ブラウザで行う定型業務ありませんか?
私の日常的なブラウザ定型業務例
・着席した席をスプレッドシートに入力(弊社フリーアドレス)
・勤怠管理
・本番サーバーのアクセス申請
・領収書のとりまとめ
日常的なブラウザ業務がもたらす弊害
・プログラマ美徳三原則(怠惰・傲慢・短気)に反する
・地味な面倒さ故、後回しにして忘れる
・知的創造業務に充てるべき時間が減る
そうだ、自動化しよう
Agouti (https://github.com/sclevine/agouti.git)
アグーチと発音する
ここから、agoutiの話です
知ってる方は、ごめんなさい🙇
agoutiの話
〜基礎的な使い方〜
使い方(ドライバーの作成、起動)
// driverを定義
var driver *agouti.WebDriver
�// ブラウザにchromeを指定して起動(自動操作したいブラウザを指定)�driver = agouti.ChromeDriver(agouti.Browser("chrome"))�if err := driver.Start(); err != nil {� log.Fatalf("Faild to start driver %v\n", err)�}
// 関数終了時、ブラウザ終了�defer driver.Stop()
続き(ページ遷移)
// 先程のdriverから、ページを取得
page, err := driver.NewPage()�if err != nil {� log.Fatalf("Faild to return page %v\n", err)�}
�// Googleに遷移�if err := page.Navigate("https://www.google.com/"); err != nil {� log.Fatalf("Faild to open page %v\n", err)�}
headlessモード(ブラウザ非表示モード)
// non-headless
driver = agouti.ChromeDriver(agouti.Browser("chrome"))
// headless
driver = agouti.ChromeDriver(� agouti.Browser("chrome"),� agouti.ChromeOptions(� "args",� []string{"--headless", "--disable-gpu"}),�)
agoutiの話
〜実際に自動化してみる〜
私の日常的なブラウザ定型業務
・着席した席をスプレッドシートに入力(弊社フリーアドレス)
・勤怠管理
・本番サーバーのアクセス申請
・領収書のとりまとめ
着席した席をスプレッドシートに入力
以下のようなフロー
Gsuite
ログイン処理
入力フォーム
遷移(席入力)
Gsuiteページ遷移
入力フォーム
入力処理
反映したスプレッドシート画面に遷移
着席した席をスプレッドシートに入力
以下のようなフロー
入力フォーム
遷移(席入力)
Gsuiteページ遷移
反映したスプレッドシート画面に遷移
Gsuite
ログイン処理
入力フォーム
入力処理
フォーム処理・ログイン処理方法
// ログイン画面遷移�if err := page.Navigate("https://login..."); err != nil {� log.Fatalf("driver停止エラー:%v", err)�}
// ユーザー名入力・ボタン押下�page.FindByClass("input-text").Fill(conf.Gsuite.User)�page.FindByClass("input-button").Click()�
// パスワード入力・ボタン押下
page.Find("#password").Fill(conf.Gsuite.Password)�page.FindByClass("input-button").Click()
指定したタグに、値を入力
指定したタグをクリック
タグを指定
タグの指定方法は複数存在
困ったらDevTool(chrome)からCopy selector
タグの指定が思うように
行かない場合等...
私の日常的なブラウザ定型業務
・着席した席をスプレッドシートに入力(弊社フリーアドレス)
・勤怠管理
・本番サーバーのアクセス申請
・領収書のとりまとめ
→ 同じ要領で解決
私の日常的なブラウザ定型業務
・着席した席をスプレッドシートに入力(弊社フリーアドレス)
・勤怠管理
・本番サーバーのアクセス申請
・領収書のとりまとめ
本番サーバーのアクセス申請
毎朝月曜日、5日分申請を行う
・申請日時
・申請者
・作業内容(テンプレで良い)
goroutineを用いて、5日分並列処理で申請を作成
をフォームに記載・申請
入力フォーム
遷移
入力処理・申請
(1日分)
パラレル実行(フォーム入力・申請)
wg := &sync.WaitGroup{}�filDay := time.Now()
// 1週間分の申請をパラレルに実行�for i := 0; i < 5; i++ {� go func(day string) {� wg.Add(1)
Fillday(day)� wg.Done()� }(filDay.Format("20060102"))
filDay = filDay.AddDate(0, 0, 1) // 日付を加算�}�wg.Wait()
※イメージ
日付(引数指定)のサーバー利用申請してくれる関数
私の日常的なブラウザ定型業務
・着席した席をスプレッドシートに入力(弊社フリーアドレス)
・勤怠管理
・本番サーバーのアクセス申請
・領収書のとりまとめ
領収書のとりまとめ
・半年に1回あるイベント
・ブラウザで、月毎の領収書画面をそれぞれ印刷(計6枚)
↪ホッチキス止め
↪担当者に提出
こう自動化する・・
結合
印刷
🙍
提出
自動化領域
ペーパーレス化
1月分
2月分
3月分
...
行う処理
・ページ遷移(agouti)
・タグの指定、操作(agouti)
・スクリーンショット機能(agouti)
・画像合成(image)
スクリーンショット機能
// 領収書のポップアップ表示されるボタンタグを押下�page.Find("#contentInner > div.table-history > p > a").Click()
// ポップアップにウィンドウを遷移�page.NextWindow()
// スクリーンショットを取得�page.Screenshot(imgfolder + "/" + baseobj.Start + ".png")
// 元のウィンドウに戻る
page.NextWindow()
保存するパス・ファイル名を指定
取得した画像数を元に、*RGBAを生成
合成後の画像サイズ
// 取得した画像枚数を元に
// 合成する画像イメージのa,b点を算出
a := image.Point{a.X, a.Y}
b := image.Point{b.X, b.Y}
// *RGBAインスタンスを生成
rgba := image.NewRGBA(a, b)
rgba
x
y
a
b
rgbaに、スクショした画像をDrawしていく
合成後の画像サイズ
draw.Draw(
rgba,
Rectangle{p1,p2},
1月の領収書画像(Image型),
image.Point{0, 0},
draw.Src
)
rgba
1月の領収書画像
p2
rgba
x
y
p1
同じ要領で画像をDrawしていく
合成後の画像サイズ
1月の領収書画像
draw.Draw(
rgba,
Rectangle{p1,p2},
2月の領収書画像(Image型),
image.Point{0, 0},
draw.Src
)
rgba
2月の領収書画像
p2
x
y
p1
最後に画像エンコード処理を実行
合成後の画像サイズ
5月の領収書画像
out, _ := os.Create("out.png")�// エンコード
png.Encode(out, rgba)
/*
(0,0)-(670,654) 201701.png
(670,0)-(1340,654) 201702.png
(0,654)-(670,1308) 201803.png
…
*/
rgba
6月の領収書画像
3月の領収書画像
4月の領収書画像
1月の領収書画像
2月の領収書画像
x
y
Goでブラウザ業務を自動化してみて
agoutiに関して
・ブラウザの基本操作が、agoutiで直感的に書ける
・ヘッドレスモードは、
操作内容が分からないので、配布時はデフォルトオフ
・クローリング・スクレイピング用途でも使えそう
Goの得意分野が生きる
・ワンバイナリ = 配布が簡単
・クロスコンパイル = より多くの人(win,Mac)に配布可能
改善点
・chromedriverを静的リンクしたい(一緒に配布したくない)
寄せられた苦情(おまけ)
・config.jsonってファイルが開けないんだけど(営業)
・win32で動かないんだけど(総務)
→配布先のITリテラシーを考慮したい人生だった。
ご清聴頂きありがとうございました!
Gopher道場 #3開催して頂きありがとうございました!
@tenntennさん、メンターさんありがとうございました!