【Selenium+Python+CentOS7/Win10】MoneyForwardから資産推移CSVを自動取得
はじめに
表題のことをやりたかったです.
moneyforwardサブスク入ってないと資産推移グラフ見れないのが悔しいからseleniumでログインしてからcsv抜いてくるとこまで作った、あとはプロットすればいい感じになりそう📈
— kinosi (@catdance124) September 26, 2021
このツイートをしたときはWindowsで動かせていたのですが,レンタルサーバのCentOS7上で定期的に取得したかったので,対応しました.
本記事はそれらの説明です.
将来的には,このデータをいい感じにプロットして閲覧できるWEBページを作りたいと思います.
(もしくは画像化して配信)
成果物はこちら ↓.
github.com
環境構築について
seleniumとpandasをインストールしたり,ここは簡単
pip install selenium, pandas
web driverまわり
Windowsの場合はここからバイナリを落として来てそのまま使えます.かんたん.
https://sites.google.com/chromium.org/driver/
CentOS7でのweb driverを使用したアクセスが面倒だったのでメモを残します.
なぜCentOSでは面倒なのかというと,CLIから,つまりDisplayがないとMoneyForwardはアクセスを弾いちゃうらしいです.
Javascriptによるレンダリングがされていると,クライアントによってはレンダリングを実施しない場合があるらしいです.
そのため,chrome本体/chrome driver/仮想ディスプレイを入れる必要があります.
このあたりは下記記事を参考にさせていただきました.
CentOS7でSelenium+Pythonを動かすまで - Qiita
CentOS7とSeleniumとPythonとChromeで定期実行処理を作ってみた - Qiita
Chromeのインストール
# vim /etc/yum.repos.d/google.chrome.repo [google-chrome] name=google-chrome baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch enabled=1 gpgcheck=1 gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
# yum update # yum -y install google-chrome-stable # google-chrome --version Google Chrome 94.0.4606.61 # yum -y install ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts # google-chrome --headless --no-sandbox --dump-dom https://www.google.com/
Chromeドライバのインストール(バージョンをChromeに合わせる形で)
# cd /usr/local/bin # wget https://chromedriver.storage.googleapis.com/94.0.4606.61/chromedriver_linux64.zip # unzip chromedriver_linux64.zip # chmod 755 chromedriver # rm chromedriver_linux64.zip
仮想ディスプレイインストール&設定
# yum install xorg-x11-server-Xvfb
# vim /usr/lib/systemd/system/Xvfb.service [Unit] Description=Virtual Framebuffer X server for X Version 11 [Service] Type=simple EnvironmentFile=-/etc/sysconfig/Xvfb ExecStart=/usr/bin/Xvfb $OPTION ExecReload=/bin/kill -HUP ${MAINPID} [Install] WantedBy=multi-user.target
# vim /etc/sysconfig/Xvfb # Xvfb Enviroment File OPTION=":1 -screen 0 1366x768x24"
# systemctl enable Xvfb # systemctl start Xvfb
# export DISPLAY=localhost:1.0;
開発の流れ
GUIではどうやってCSVをダウンロードするかを確認
↓
seleniumでの記述に落とし込む
↓
落としてきたcsvをいい感じに整形
開発
GUIではどうやってCSVをダウンロードするかを確認
- URLアクセス
- [メールアドレスでログイン]を押す
- 遷移した先でメールアドレスを入力しsubmit
- 遷移した先でパスワードを入力しsubmit
- ログイン完了
- [資産推移]ページに移動(URLアクセス)
- ページ下部リンクから当月CSVをダウンロード
- 各月のページに飛び,CSVをダウンロード
こんな感じですかね.
勘のいい人は7,8の画像左下のリンクを見て,CSV取得はURL決め打ちでできるとわかったかと思います.
seleniumでの記述に落とし込む
細かい実装はリポジトリを見てもらうとして,上の記述をコードで追っていきます.
ログイン
ここの処理を実装します.
1. URLアクセス 2. [メールアドレスでログイン]を押す 3. 遷移した先でメールアドレスを入力しsubmit 4. 遷移した先でパスワードを入力しsubmit 5. ログイン完了
def login(self, email, password): login_url = "https://moneyforward.com/sign_in" self.driver.get(login_url) self.driver.find_element_by_link_text("メールアドレスでログイン").click() elem = self.driver.find_element_by_name("mfid_user[email]") elem.clear() elem.send_keys(email) elem.submit() elem = self.driver.find_element_by_name("mfid_user[password]") elem.clear() elem.send_keys(password) elem.submit()
ここはHTMLソースを見ながら要素のinnerHTMLやname, classなどが使えないかを見ながら,流れ通り実装していきます.
資産推移CSVダウンロード
6. [資産推移]ページに移動(URLアクセス) 7. ページ下部リンクから当月CSVをダウンロード 8. 各月のページに飛び,CSVをダウンロード
def download_history(self): # 6. [資産推移]ページに移動(URLアクセス) history_url = "https://moneyforward.com/bs/history" self.driver.get(history_url) elems = self.driver.find_elements_by_xpath('//*[@id="bs-history"]/*/table/tbody/tr/td/a') # download previous month csv # 8. 各月のページに飛び,CSVをダウンロード for elem in elems: href = elem.get_attribute("href") if "monthly" in href: month = re.search(r'\d{4}-\d{2}-\d{2}', href).group() save_path = Path(self.csv_dir/f"{month}.csv") if not save_path.exists(): month_csv = f"https://moneyforward.com/bs/history/list/{month}/monthly/csv" self.driver.get(month_csv) self._rename_latest_file(save_path) # download this month csv # 7. ページ下部リンクから当月CSVをダウンロード this_month_csv = "https://moneyforward.com/bs/history/csv" save_path = Path(self.csv_dir/"this_month.csv") if save_path.exists(): save_path.unlink() self.driver.get(this_month_csv) self._rename_latest_file(save_path)
大体は流れ通りなのですが,各月CSVをダイレクトにURLからダウンロードする場合,どの月のCSVが存在するかを判定しなければいけません.
そこは,資産推移ページに存在する各月のリンクhrefから情報を取得しています.href = elem.get_attribute("href")
のところ
あとはダウンロードしたファイルの名前が日本語だったりで扱いづらいので,YYYY-MM-dd.csvにリネームしたりしています.
seleniumでは名前を付けて保存ができないので,一度ダウンロードしてからself._rename_latest_file(save_path)
のところで最新日時ファイルをリネームするという処理をしています.
def _rename_latest_file(self, new_path): time.sleep(2) csv_list = self.csv_dir.glob('*[!all].csv') latest_csv = max(csv_list, key=lambda p: p.stat().st_ctime) latest_csv.rename(new_path)
落としてきたCSVをまとめる
各月で別のCSVになっているので,pandasでまとめます.
def _concat_csv(self): csv_list = sorted(self.csv_dir.glob('*[!all].csv')) df_list = [] for csv_path in csv_list: df = pd.read_csv(csv_path, encoding="shift-jis", sep=',') df_list.append(df) df_concat = pd.concat(df_list) df_concat.drop_duplicates(subset='日付', inplace=True) df_concat.set_index('日付', inplace=True) df_concat.sort_index(inplace=True) df_concat.fillna(0, inplace=True) df_concat.to_csv(Path(self.csv_dir/'all.csv'), encoding="shift-jis")
all.csv以外を読んで,concatして,日付でソート・重複削除後 all.csvとして保存するって感じです.
おわりに
MoneyForwardで資産推移グラフを見るためだけにお金を払いたくないというモチベーションのみで,着想からここまで3日でできました.
データを使用してプロットするプログラムは別リポジトリで作業しようと思っています.完走できるといいなあ
github.com
.