Humanity

Edit the world by your favorite way

Puppeteer で楽天銀行の入出金明細を保存

以前楽天銀行ワンタイムパスワードを突破しつつ明細履歴を TSV ファイルに保存するスクリプトを書いた。 ワンタイムパスワードのメールを受け取って本文をパースするために IMAP にログインしてメールが来るまで待つ、みたいな事をしている。

tyru.hatenablog.com

前のは id:motemen さんのスクリプトを参考にさせてもらいながら書いたので Protractor + WebDriver 製だった。

ただ最近はなんやかんや (Headless Chrome + Chrome Debugging Procotol) で ChromeGUI のない環境でも動かす事ができて、 さらにヘッドレス Chrome を操作する Puppeteer というライブラリもある。 しかもこれは Google 自身が開発しているのでサードパーティのライブラリよりも大分信頼できる。

Puppeteer はフラグ1つ変えるだけでヘッドレスで動作するかどうかを切り替える事ができるので、 開発中は非ヘッドレスでやりながら、うまくいったらヘッドレスにして Linux サーバで cron で動かすとかができてだいぶ便利 (のはず)。 ちなみに自分の環境は Windows 10 で元々 Chrome を入れてたので使わなかったけど、 Puppeteer 自体に Chrome をダウンロードしてくる機能とかもあるらしい。

ということで冒頭のスクリプトを Puppeteer で書き直してみた。 Node v8.10.0 で動作確認済みです。

github.com

Puppeteer の API

詳しくはこの辺を見てください。

スクリプトの解説

すみません、力尽きました。

すでに色んなブログとかで Puppeteer の解説されてるので省略。

あとでやるかも

楽天 e-NAVI (楽天カードの明細取得) とかもスクレイピングしたかったんだけど、そもそも e-NAVI は CSV で明細を取得できるのでログインしてそれをダウンロードすればええやん、 と思ったら CSV 取得を Fetch API でやろうとしてうまくいかなかった (HTML が返ってきてしまう)。 ちなみにダウンロードの URL はこれ。ログインしてれば CSV がダウンロードされるはず。

https://www.rakuten-card.co.jp/e-navi/members/statement/index.xhtml?downloadAsCsv=1

誰か pull request 送ってください。

参考エントリ

tech.vasily.jp

AppVeyor で Go のバージョンを固定する方法

基本的に Go アプリのテストは以下を参考にしたら概ねうまく言ってたんだけど、 (最新じゃなく)バージョン固定してる場合に問題となったため書いておく。

blog.markvincze.com

起こった問題

最近 AppVeyor の Go のバージョンが上がって Go 1.10 をデフォルトで使うようになった。 上記記事の設定だと GOROOT の定義が抜けてるために以下のようなエラーが出るようになった。

compile: version "go1.10" does not match go tool version "go1.9.4"

対応策

GOROOT 設定すればいいだけ。 今開発してる Volt というツールでは Go 1.9 でテストしたかったので以下のように指定した。 他のバージョンまたはアーキテクチャでテストしたい場合は C:\go19 じゃなく別のパスにする必要がある

volt/.appveyor.yml at feabe7feeecf2bddda135cc7e43cc188ed781d81 · vim-volt/volt · GitHub

差分は見る必要ないと思うけど一応こんな対応をした。

[AppVeyor] Fix go version mismatch by tyru · Pull Request #204 · vim-volt/volt · GitHub

解説

Vim プラグインのパッケージマネージャなので Vim のインストールとか色々書いてあるけど、 以下の記述だけ参考にすればいい。

  • GOPATH の設定
  • GOROOT の設定
  • PATH の設定
    • set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH%

後は go build やら go test するだけ。 go build はビルドが通る事を確認するためのもの。 go test はテストするためのもの。 詳しくは上の設定見てください。

どうでもいいけどビルドが通ることを確認するなら Build configuration | AppVeyor を使った方がいい気がする。 まぁそれは後で。

参考リンク

ロガー用 vital モジュールを作った (ただし Vim 8 専用)

まだ vim-jp/vital.vim には PR してないけど十分使える (使えてる)。

https://github.com/tyru/nesk.vim/blob/0775e7d6fb3502ce52b64911d84b19cde32825aa/autoload/vital/__nesk__/Nesk/Log.vim https://github.com/tyru/nesk.vim/tree/0775e7d6fb3502ce52b64911d84b19cde32825aa/autoload/vital/__nesk__/Nesk/Log

ちなみに使ってるのはここら辺。

https://github.com/tyru/nesk.vim/blob/9d0422c3ac064183d64a137116ab305786c99248/autoload/vital/__nesk__/Nesk.vim#L21-L36 https://github.com/tyru/nesk.vim/blob/9d0422c3ac064183d64a137116ab305786c99248/autoload/vital/__nesk__/Nesk.vim#L395-L420

注意点

  • Vim 7.4 には対応してない (Vim 8 専用)
  • Log.XXX を実装する場合
    • logger.flush() を読んだ時点で出力する
    • logger.log(level, msg) を読んだ時点では出力しない
    • Log.File で1行ずつ writefile() したらめっちゃ遅かったので flush 前提の API とした
  • 遅い場合は {autoflush: 0} を指定する
    • デフォルトでは毎行 flush する
  • それでも遅い、特に Log.File が遅い場合は {file_redir: 1, autoflush: 0} を指定する
    • writefile() での追記が遅い ので :redir を使える場合は指定すると速くなる
    • デフォルトじゃないのは :redir はネストして使用した場合にエラーとなるため、 プラグイン:redir を使用している可能性があるのでデフォルトは writefile() の追記で実装している

出力先

  • Log.File: ファイル出力ロガー
  • Log.Echomsg: :echomsg 出力ロガー
  • Log.Nop: 何も出力しないロガー

Log.Noplogger.log(msg) を呼ぶのに if で分岐せずに済むようにするため。 レベルによらず全く出力させたくない場合はこの出力先にする。

上記のモジュールのオブジェクトは new() で生成できる。

  • Log.new({'output': 'File'})
    • file_path (String)
      • 例: '/path/to/logfile'
    • file_format (Funcref)
      • {options -> {level,msg -> ...}} のような Funcref を指定する。
      • 例 (デフォルト値): {options -> {level,msg -> printf('[%s] %s', get(options.levels[0], level, '?'), msg)}}
    • file_redir (Number | Bool)
      • もし true 値が指定されたら :redir を使ってファイル末尾に追記する。
      • デフォルトは writefile() で追記する。
  • Log.new({'output': 'Echomsg'})
    • echomsg_hl
      • :echohl の引数をリストで渡す。それぞれの要素の位置は options.levels と対応している。
      • デフォルト値: []
      • 例: ['None', 'WarningMsg', 'ErrorMsg']
    • echomsg_format
      • File の file_format と同じ
      • デフォルト値は file_format と違ってタイムスタンプも出力される
        • {options -> {level,msg -> printf('[%s] %s %s', get(options.levels[0], level, '?'), strftime('%Y-%m-%d %H:%M'), msg)}}
    • echomsg_nomsg
      • もし true 値が指定されたら :echo を使って出力する。
      • デフォルトは :echomsg で出力する。
  • Log.new({'output': 'Nop'})

Log.File.new(path)Log.Echomsg.new() としないのは切り替えをより楽にするため。 例えばデバッグのために Log.EchomsgLog.File に切り替えたいとする。 そうすると Log.File.new(path) の場合

  1. s:V.import() の引数 (モジュール名)
  2. Log.new() の引数

の両方を変えなければいけない。

Logger API

  • logger.set_level(level Number)

  • logger.log(level Number, msg String | Funcref)

  • logger.flush()
    • ImplLogger.flush() を呼び出す。

デフォルトではそれぞれのレベルに対応した以下のメソッドとラベルが追加される。 レベルに対応したメソッドは Logger.new(options Dictionary) の引数 options.levels で指定できる。

  • logger.info(msg String | Funcref)
  • logger.warn(msg String | Funcref)
  • logger.error(msg String | Funcref)

  • logger.INFO (0)

  • logger.WARN (1)
  • logger.ERROR (2)

ラベルは既存メソッド名(Logger.log)と被らなければ何でもいい。 [['INFO', 'info'], ['WARN', 'warn'], ['ERROR', 'error']] のような配列を渡せば メソッドとそれに対応するラベル定数を生成してくれる (ちなみにこの値は levels のデフォルト値)。 この場合それぞれのレベルは INFO < WARN < ERROR となる。 Dictionary ではなく List で指定するのはこのため。

出力先モジュールの API

出力先のモジュールは Log.new() によって裏で import() され new() される。 切替可能にするため以下のメソッドを実装しなければならない。以下がその API

  • implLogger.log(level Number, msg String)
  • implLogger.flush()

TODO

  • Vim 7.4 対応
  • vim-jp/vital.vim に PR する