やったこと

  • Chrome Devtools Protocolを用いて、HeadlessChromeでファイルダウンロードをする。

実際のコード

  • 例としてMoneyForwardにログインして、家計簿のcsvをダウンロードするコードを書いてみる。
package main

import (
    "context"
    "log"
    "time"
    "os"

    "github.com/chromedp/chromedp"
    "github.com/chromedp/chromedp/runner"
    "github.com/chromedp/cdproto/cdp"
    "github.com/chromedp/cdproto/page"
)

func main() {
    var err error

    // create context
    ctxt, cancel := context.WithCancel(context.Background())
    defer cancel()

    // create chrome instance with headless mode
    chrome := "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
    c, err := chromedp.New(ctxt, chromedp.WithRunnerOptions(runner.HeadlessPathPort(chrome, 9992)))
    if err != nil {
        log.Fatal(err)
    }

    host := "https://moneyforward.com"
    user := os.Getenv("MF_USER")
    pass := os.Getenv("MF_PASS")

    // run task
    err = c.Run(ctxt, doScrape(host, user, pass))
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Scrape Done.")

    // shutdown chrome
    err = c.Shutdown(ctxt)
    if err != nil {
        log.Println("down-err")
        log.Fatal(err)
    }
    log.Println("Chrome Shutdown.")
}

func doScrape(host, user, pass string) chromedp.Tasks {
    return chromedp.Tasks{
        // configure to download behavior
        chromedp.ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
            err := page.SetDownloadBehavior("allow").WithDownloadPath("/tmp").Do(ctxt, h)
            if err != nil {
                return err
            }
            return nil
        }),
        chromedp.Navigate(host + "/users/sign_in"),
        chromedp.SendKeys("//input[@id='sign_in_session_service_email']", user),
        chromedp.SendKeys("//input[@id='sign_in_session_service_password']", pass),
        chromedp.Click("//input[@id='login-btn-sumit']"),
        // login
        chromedp.WaitVisible(".logo-image", chromedp.ByQuery),
        // move to cashflow page
        chromedp.Navigate(host + "/cf"),
        // download file
        chromedp.Click("//*[@id='js-dl-area']"),
        chromedp.Click("//*[@id='js-csv-dl']"),
    }
}

モチベーション

  • URIを持たないファイルリソースや認証の先にあるダウンロードリンクからファイルを取得したかった。
  • Golangにした理由はコマンドラインツールにしたかったため。
    • 取得したファイルを標準出力にはけば、あとはパイプでつないで編集→別ツールにインポート、みたいなワークフローを想定。

所感

  • Chrome Devtools Protocolおもしろい!ブラウザで出来ることのAPIが豊富に用意されているので、Golangとの組み合わせはブラウザ操作をガシガシ自動化していくよいツールに思えた。
  • chromedpとcdprotoの設計がかなり抽象レイヤを挟んでいて、読んでいて面白い反面、少々冗長に思えた。私が読み切れていないだけかもしれない。

参照リンク

  • Headless Chromeでファイルをダウンロードする
    • 「chrome headless ファイルダウンロード」でtopでヒットする情報。こちらでdownloadBehaviorの存在を知ることができました。ありがとうございます。
  • chromedp
    • golangでchrome devtool protocolを扱うためのフロント
  • cdproto
    • chromedpから参照されるライブラリ群
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.