Puppeteer で楽天銀行の入出金明細を保存
以前楽天銀行でワンタイムパスワードを突破しつつ明細履歴を TSV ファイルに保存するスクリプトを書いた。 ワンタイムパスワードのメールを受け取って本文をパースするために IMAP にログインしてメールが来るまで待つ、みたいな事をしている。
前のは id:motemen さんのスクリプトを参考にさせてもらいながら書いたので Protractor + WebDriver 製だった。
ただ最近はなんやかんや (Headless Chrome + Chrome Debugging Procotol) で Chrome を GUI のない環境でも動かす事ができて、 さらにヘッドレス Chrome を操作する Puppeteer というライブラリもある。 しかもこれは Google 自身が開発しているのでサードパーティのライブラリよりも大分信頼できる。
Puppeteer はフラグ1つ変えるだけでヘッドレスで動作するかどうかを切り替える事ができるので、 開発中は非ヘッドレスでやりながら、うまくいったらヘッドレスにして Linux サーバで cron で動かすとかができてだいぶ便利 (のはず)。 ちなみに自分の環境は Windows 10 で元々 Chrome を入れてたので使わなかったけど、 Puppeteer 自体に Chrome をダウンロードしてくる機能とかもあるらしい。
ということで冒頭のスクリプトを Puppeteer で書き直してみた。 Node v8.10.0 で動作確認済みです。
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 送ってください。
参考エントリ
AppVeyor で Go のバージョンを固定する方法
基本的に Go アプリのテストは以下を参考にしたら概ねうまく言ってたんだけど、 (最新じゃなく)バージョン固定してる場合に問題となったため書いておく。
起こった問題
最近 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.Nop
は logger.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()
で追記する。
- もし true 値が指定されたら
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)}}
- File の
echomsg_nomsg
- もし true 値が指定されたら
:echo
を使って出力する。 - デフォルトは
:echomsg
で出力する。
- もし true 値が指定されたら
Log.new({'output': 'Nop'})
Log.File.new(path)
や Log.Echomsg.new()
としないのは切り替えをより楽にするため。
例えばデバッグのために Log.Echomsg
を Log.File
に切り替えたいとする。
そうすると Log.File.new(path)
の場合
s:V.import()
の引数 (モジュール名)Log.new()
の引数
の両方を変えなければいけない。
Logger API
logger.set_level(level Number)
logger.log(level Number, msg String | Funcref)
ImplLogger.log()
を呼び出す。- 第二引数に 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()