wide and deep

Docker コンテナ環境で VPN クライアントを立てて通信を経由させる方法

はじめに

Docker コンテナを複数使っていると、「特定コンテナの外部通信だけ VPN 経由にしたい」というケースはよくあります。
例えば以下のような用途です。

今回は VPN Gategluetun(OpenVPN) を利用して、

  • VPN クライアントをコンテナ化
  • 任意のコンテナ通信を gluetun 経由に変更
  • docker-compose だけで構成できる

という構成を紹介します。

サンプルリポジトリはこちら:

github.com

構成イメージ

network_mode: service:gluetun を指定すると、該当コンテナは gluetun を経由して外部通信します。

使用コンテナ


必要な設定ファイル

  • VPN Gate の OpenVPN 設定ファイル (.ovpn)
    gluetun が読み込む VPN 接続設定ファイル
    VPN Gate からダウンロード
    • 高スコアで Ping が低いサーバーを選ぶ
    • .ovpn 内に IP が埋め込まれているものを使用
      • DDNSアドレスのものはgluetunが対応していないためNG

docker-compose

抜粋しています。フルバージョンはgithubリポジトリを参照してください。

以下のような設定のみで gluetun を VPN クライアントとして動作させることができます。

services:
  gluetun:
    image: qmcgaw/gluetun:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./gluetun:/gluetun
    environment:
      # VPNサービスプロバイダー設定
      - VPN_SERVICE_PROVIDER=custom
      - VPN_TYPE=openvpn
      # OpenVPN設定ファイルのパス
      - OPENVPN_CUSTOM_CONFIG=/gluetun/openvpn/myvpn.ovpn
      # 使用する暗号スイートの指定
      - OPENVPN_CIPHERS=AES-128-CBC

利用する側のコンテナは以下のように network_mode: "service:gluetun" を指定します。
また、このとき portsexpose は空にする必要があります。
network_modeで他サービスのネットワークを共有するため、個別サービスのポート公開設定は無効になるためです。

  # seleniumサービスをgluetun経由にするための設定
  selenium:
    network_mode: "service:gluetun"
    ports: !reset []
    expose: !reset []

実行例

今回は、VPN 経由で Selenium コンテナにアクセスし、ブラウザから外向き IP を取得する例を示します。

gluetunのcomposeファイルを個別に切り出しているため、
-f オプションで指定しなければVPNを利用しない構成になります。

$ tree
.
├── docker-compose.yml
├── docker-compose.gluetun.yml
├── app/
├── selenium/
└── gluetun/
    └── openvpn
        └── myvpn.ovpn

-f オプションで指定する docker-compose ファイルは、
後から指定したファイルの設定項目が優先される形でマージされます。
そのため、VPN 接続用の設定ファイルを最後に指定するように注意してください。

VPNを利用しない接続で外向き IP の確認

$ docker compose -f docker-compose.yml up --build
...
app-1       | [Selenium] Your IP address is: 203.0.113.10
app-1       | [Requests] Your IP address is: 203.0.113.10
# 203.0.113.10 は例です

Selenium経由・requests経由ともにホストのグローバルIPアドレスが表示されます。
VPNを使わない場合、全ての通信は通常のネットワーク経由となります。

VPN 経由で外向き IP の確認

$ docker compose -f docker-compose.yml -f docker-compose.gluetun.yml up --build
...
app-1       | [Selenium] Your IP address is: 219.100.37.170
app-1       | [Requests] Your IP address is: 219.100.37.170
# 219.100.37.170 は例です

Selenium経由・requests経由ともにVPNで割り当てられたグローバルIPアドレスが表示されます。
VPNを使うことで、通信経路がVPNトンネルに切り替わることを確認できます。

おわりに

Docker だけで VPN 経由の通信を制御できると、
スクレイピング、自動テスト、アクセス制限回避の検証が非常に楽になります。

gluetunVPN Gate は無料で使える組み合わせとして非常に強力です。

サンプルリポジトリ
https://github.com/catdance124/sample_docker-vpn

我が家の家計管理法:マネーフローで整えるシンプル管理術

はじめに

家計管理を始めるときに大切なのは、まず「全体の流れ」をざっくりと捉えることです。
数字や計算ではなく、お金がどこから入り、どこに出ていくかをイメージするだけでOKです。

この記事では、我が家(家族構成:夫婦)で実践している、その具体的な方法と仕組みをご紹介します。
段階を踏んで仕組みを構築していき、最後に我が家での例を紹介する流れを取ります。

マネーフローで整えるシンプル管理術のステップ

マネーフローの書き方

まず、下の図のように「収入 → 支出/貯蓄・投資」と分けることから家計の見える化は始まります。

我が家ではGoogleスライドを使って、入金口座→各費用・貯蓄の流れを矢印でつないでいます。
紙に書いても構いませんが、スライドにしておくと修正や家族間での共有が容易です。

本記事での描画例は以下スライドで公開しています。適宜コピーしてご利用ください。
docs.google.com


先に凡例について整理しておきます。

基本的には箱を線でつないでいくような構成を取ります。箱の色やその枠線で役割を表し、
それをつなぐ線にも種別を設けています。実際に後ほど出てくるため意味があるんだなくらいで認識しておいてください。
各家庭で必要なカテゴリが出てきたら追加してみると良いでしょう。

支出の構成を把握

先ほどのシンプルすぎるフローのうち、支出の細分化を行ってみましょう。
その結果は下図のようになりました。

大きく分けて、各個人の支出・毎月の変動費・固定費となりました。
このうち、各個人の支出は本記事の主題から外し、変動費・固定費について主に扱います。

変動費と固定費の仕分け

家計を整理するには、支出を 固定費 と 変動費 に分けるのが基本です。簡単に例を上げると下記のようになります。

  • 変動費  : 食費、日用品、交通費、レジャー費、etc.
  • 固定費(月): 家賃(住宅ローン)、保険、通信費、水道光熱費、サブスク、etc.
  • 固定費(年): 固定資産税、火災保険、ふるさと納税自動車税、サブスク、etc.


#↓↓ここからの内容はマネーフローの整理という意味では具体的すぎる内容のため、一度読み飛ばしてもOKです。↓↓
ここで、ポイントは 水道光熱費 です。
水道光熱費は月ごとに変動するものですが、過去1年分の平均額を算出し、予算を組んで固定費に組み込む ようにしています。

以下はサンプルデータから計算した 光熱費の1年の移動平均およびそれを少し上回る予算を決定するためのグラフです。
このデータを用いて実際に入金する額を決定します。

計算用に使ったサンプルデータの入力例は以下を参照してください。
docs.google.com

#↑↑ここまで↑↑

口座とクレジットカードの適切な分割管理

次に重要なのは「お金の置き場所を分けること」です。
お金の置き場所自体を役割別に分けておくと、流れを自然に整理できます。

以下のように口座とカードを分けてみます。

  • 給与受け取り口座
  • 生活費(変動費)引き落とし口座
    • 生活費決済用カード
  • 固定費(月) 引き落とし口座
    • 固定費(月) 決済用カード
  • 住宅ローン引き落とし口座
  • 固定費(年) 引き落とし口座
    • 固定費(年) 決済用カード
  • 貯金専用口座
  • つみたて投資用預金口座
    • つみたて投資用カード

これをフローに起こすと、下図のようになります。
各口座の役割も付記しています。

利用口座・利用カードについて

基本的に利用口座は何でもいいと思いますが、各口座への振り分けを実施するハブとなる口座は、住信SBIネット銀行が以下の点でおすすめです。

  • 毎月の定額振込処理を自動化できる
  • 毎月の無料振込回数が5~10回
  • 各口座内で目的別口座を持てる
  • 提携するNEOBANKが豊富にある

「毎月の定額振込処理を自動化できる」というメリットを活かし、先程の図で口座をつなぐ線が点線(自動)になっていることがわかると思います。

固定費(月) 引き落とし口座について

この口座は、毎月入金される額は一定である一方、引き落とし額は毎月異なります(光熱費の平均額を入金しているため)。
そのため、この目的以外の引き落としを同居させると管理しにくくなってしまうため、独立させることが望ましいです。

固定費(年) 引き落とし口座について

この口座は、1年間トータルで見たとき入金額・引き落とし額がほぼ等しくなるようにします。
そのため、この目的以外の引き落としを同居させると管理しにくくなってしまうため、独立させることが望ましいです。

この口座への入金は、年間必要額を月割りで毎月入金してもいいですし、特定のタイミングで一括入金してもいいです。


我が家の管理フロー例

我が家のマネーフローは下図のような感じです。
実際に運用しているものの一部を修正して公開しています。

今まで紹介してきた枠組みの上に乗っていますが、以下の点が特筆すべき点だと思います。

  1. 収入源が複数存在する
  2. 生活費引き落とし口座を住信SBIネット銀行NEOBANKにし、目的別口座で一部のお金をプールしている
  3. 生活費には毎月固定の予算を充てており、それを溢れた分は追加入金する運用を取っている(赤線の矢印)
  4. 賞与から固定費(年) の予算を計上している



このように「どの支出がどの口座から出ていくか」を明確にすると、お金の流れが一目で分かるようになりますが、
我が家の状況に特化してかなりチューニングしているため、ここまでやらなくてもOKです。

おわりに

家計管理を整えるには、まず「見える化」から始めるのが一番の近道です。

たったこれだけでも、お金の流れが整理され、家計がかなり扱いやすくなります。

大切なのは、最初から完璧を目指さないこと。
シンプルな図から始め、必要に応じて少しずつ枝葉を足していけば十分です。

この記事で紹介した仕組みは、我が家の事情にあわせてチューニングしたものですが、基本の考え方はどの家庭でも応用できます。
ぜひご自身の家計に置き換えて、まずは「自分なりのマネーフロー」を描いてみてください。

docs.google.com
docs.google.com

Anker Eufy SoloCam S340(監視カメラ)をパイプに取り付ける

はじめに

自宅の玄関(屋外)に監視カメラを設置しようと思いました。
取り付けられる場所は、雨が流れるパイプ(縦樋という名称らしいです)くらいなので、そこに取り付けることを考えます。

Eufy SoloCam S340 という監視カメラを利用します。せっかくなので写真を交えてメモしておきます。

Eufy SoloCam S340の購入を検討している方は参考にしてみてください。



―――――――
(前回はTapoC410KITを取り付けました)
catdance124.hatenablog.jp


先に成果物


道具・材料

Eufy SoloCam S340 内容物

パッケージ表面


内容物

実際の内容物です

大まかなサイズ

iPhone14と比較すると、大きくないことがわかります。

気になっていた箇所のメモ

カメラ

type-C給電ポートは背面に用意されています。

ここはパッキンで防水仕様になっていますが、ソーラーパネルによる給電を利用すると開けっ放しになります(プラグ側に防水パッキンがあります)。

カメラの固定部品です。

カメラ上部と側部に固定部品を取り付けることができます。
私はパイプに取り付けたかったので、カメラ側部の固定位置に取り付けます。

ソーラーパネル

パネル背面の穴に可動金具を取り付けます。

向きはある程度自由に動かせます。

カメラ・ソーラーパネルのセットアップ

カメラ上部にソーラーパネルを取り付けることもできますが、
今回は3mのtype-C延長ケーブルを利用します。

延長ケーブルをつけて接続したところ


ポール取付金具への取り付け

ディスプレイのVESA規格のような金具にカメラ土台を固定します。

付属のボルト、ナット、ワッシャー(大)、ワッシャー(小) を使用し、固定します。
取り付けは↓の順でしています。
  ボルト頭
  ワッシャー(小)
  カメラの固定部品
  ポール取付金具
  ワッシャー(大)
  ナット

ポール取付金具に開いている穴にうまく取り付けることができました

取付金具への取り付け後

ソーラーパネルの設置

日当たりの良い位置に設置します。
今回は自宅にあった結束バンドで簡易的に取り付けています。

監視カメラの設置

ポールへの取り付け(再掲)

Tapo C410 KIT(監視カメラ)をパイプに取り付ける

はじめに

自宅の2階部分に監視カメラを設置しようと思いました。
取り付けられる場所は、雨が流れるパイプ(縦樋という名称らしいです)くらいなので、そこに取り付けることを考えます。

Tapo C410 KIT という監視カメラを利用しますが、ネットの海にレビューがあまり転がっていないので
写真を交えてメモしておきます。

Tapo C410 KITの購入を検討している方は参考にしてみてください。

先に成果物


道具・材料

Tapo C410 KIT 内容物

パッケージ表面・背面



パッケージ側面(仕様)

パッケージ側面(内容物)


実際の内容物です

大まかなサイズ

500mlのペットボトルと比較すると、小ささがわかります。

説明書

カメラ

カメラのセットアップはアプリを入れて手順通りにやれば特に難しいことはないです。


ソーラーパネル

色々書いてありますが、Tapo C410 KIT はオプション3にあたります。それとカメラの接続を読めばOKです。



気になっていた箇所のメモ

カメラ

microSDカードスロット と type-C給電ポートは背面に用意されています。

ここはパッキンで防水仕様になっています。microSDカードスロット は閉じますが、type-Cポートはソーラーパネルを利用すると開けっ放しになります(別の防水パーツがあります)。

カメラの固定金具は取り外せます。
カメラ本体には固定位置が2箇所あります。
私はカメラをかなり下に向けたかったので、カメラ側部の固定位置に取り付けます。

ソーラーパネル

土台の凸にソーラーパネル背面の凹を差し込み固定できます。

向きはある程度自由に動かせます。

カメラ・ソーラーパネルのセットアップ

土台にカメラを固定します。

ソーラーパネルから伸びているtype-Cをカメラに差し込みます。
差し込むときは付属の防水アタッチメントを使用します。

アタッチメントをつけてtype-Cを差し込んだところ

ポール取付金具への取り付け

ディスプレイのVESA規格のような金具にカメラ土台を固定します。

付属のボルト、ナット、ワッシャー(大)、ワッシャー(小) を使用し、固定します。
取り付けは↓の順でしています。
  ボルト頭
  ワッシャー(小)
  カメラ土台
  ポール取付金具
  ワッシャー(大)
  ナット

また、固定は本来3箇所で行いたかったのですが、カメラ土台下部の穴が小さかったために2箇所で妥協しています。


取付金具への取り付け後


ポールへの取り付け(再掲)

テープライトと100均製品で5m長のコーブ照明を作成

はじめに

天井に光を当てるタイプの間接照明をコーブ照明と呼ぶらしいです。
通常、折り上げ天井を用意する必要がありますが、
テープライトと100均の製品を使って5m長のコーブ照明を作ってみました。
ネットの海を漁ったところ、似たような作り方が見当たらなかったので、
使用した道具・材料と工程をメモしておきます。

先に成果物


道具・材料

マスキングテープ(ダイソー

幅はできるだけ太めがいいです。
塗装用マスキングテープがおすすめです。
マスキングテープ(塗装用、白、30mm×12m)jp.daisonet.com

配線カバー(ダイソー

今回は壁の色に合わせて白を使っています。
口径は11mmと16mmがあり、今回は11mmを使います。
今回は切らずに使用していますが、必要に応じて切断してください。
配線カバー(2本、50cm、口径11mm用、ホワイト)jp.daisonet.com

テープライト(switchbot)

switchbot製を使います。調光調色なのである程度自分好みの照明にできます。
今回は切らずに使用していますが、必要に応じて切断してください。

作業工程

はじめに

位置関係がややこしいので、先に完成品と図で整理します。

テープライトを天井に向け照らすことをゴールとし、
そのために配線カバーを利用しています。
下記では接着面①~③を取り付ける際のポイントを述べます。

取り付け予定位置にマスキングテープを貼り付ける(接着面①)

取り付け予定位置は天井から離れすぎていない、目線より上の箇所がいいでしょう。

マスキングテープの上に配線カバーを貼り付ける(接着面②)

マスキングテープの中心よりやや下部に貼り付けましょう。
上部に貼り付けてしまうとマステごと剥がれやすくなると思います。

配線カバーの上にテープライトを貼り付ける(接着面③)

テープライトの幅と配線カバーの奥行きはほぼ同じなので、ぴったり貼ることを心がけましょう。

点灯させる


後処理

テープライトの給電ケーブル部分も同じ配線カバーで隠しちゃいましょう。

おわりに

5m長の間接照明を4000円ほどで作ることができました。
switchbot製なのでAlexaも対応しており、音声操作が可能で便利です。
テープライトで間接照明を作りたいけど遮蔽物がない…という方は参考にしてみてください。

フレキシスポットの脚部にかなでものの天板を取り付けた

はじめに

フレキシスポットの脚にかなでものの天板を取り付けた話です。
使用した道具・材料と工程をメモしておきます。

以前はフレキシスポット純正の天板に木ネジで脚部を固定していましたが、
天板をかなでもの製の広いものに交換したいと思い、
ついでに鬼目ナットとボルトで固定するように施工しました。

先にbefore/after

before: D x W = 60cm x 120cm

after: D x W = 75cm x 140cm

参考にしたサイト

下記サイトをとても参考にさせていただきました。ありがとうございます。
ritalog0317.com


道具・材料

天板

かなでもの製の「ラバーウッドアッシュ天板」にしました。
THE BOARD / ラバーウッド アッシュkanademono.design

サイズを1cm単位で指定でき、最大サイズはD x W = 80cm x 180cm です。
サイズをいくつにしても値段が変わらないので、大きいものほどお得だと感じました。

今回は面取りオプションR5をつけて、36,200円でした。


鬼目ナット

天板と脚部の固定に利用します。
脚部固定には全部で12個必要なので、下記で15個購入しました。
(リモコンパネルは移動を考えて両面テープで取り付けています)
サイズはM4で長さ10mmです。

ボルト

天板と脚部の固定に利用します。
M4サイズの8mm, 12mm を利用しました。
下記の商品はたくさん入っていて、個別で買うよりは安くつくと思います。

電動ドライバ

天板への穴あけ、およびネジやボルトの固定に利用します。
多分規格が統一されているので、どれを買っても同じだと思います。

電動ドライバ用ドリルアタッチメント

印をつけるのに1mmのドリルを、
下穴を開けるのに6mmのドリルを利用します。

今回は時間がなかったのでセットを下記で買いましたが、
最近はダイソーで1本ずつ売っているみたいです。

木工用ドリル刃6.0mm6角ビット付 | 【公式】DAISO(ダイソー)ネットストア
チタンコーティング鉄工ドリル刃2.0mm6角ビット付 | 【公式】DAISO(ダイソー)ネットストア


作業工程

準備

届いた天板を開封し、以前の天板から脚部を取り外します。
2つを並べた写真を撮りました。

天板に脚部をあて印をつける

取り付けたい位置に脚部を置き、固定穴の箇所にペンで印をつけ、中心に1mmのドリルで穴を開けておきます。(あとで下穴を開けるときの補助になります)

ここで、深く穴を開けてしまわないように、ドリルには1cmの部分がわかるようにマスキングテープで印をつけておきます。

ちなみに天板は若干ツルツルした面があるので、そちらが表になるように気をつけます。

天板に下穴を開ける

今度は印をつけた箇所に、6mmのドリルで下穴を開けます。
ペンで印をつけた幅を超えないように、なんとか頑張ります。
ここでも、深く穴を開けてしまわないように、ドリルには1cmの部分がわかるようにマスキングテープで印をつけておきます。

木くずが大量に出るので、掃除機を近くに配置しておいたほうがいいでしょう。

下穴に鬼目ナットを埋め込む

綿棒などを使って下穴に木工用ボンドを軽く塗布し、
六角のドライバで鬼目ナットを下穴に埋め込みます。
埋め込み加減は、穴に鬼目ナットがちょうど埋まるくらいです。


脚部を天板に固定する

下処理が済んだ天板に、脚部を乗せます。
脚部の固定穴から鬼目ナットが見える(と思う)ので、
ワッシャー挟んでボルトで固定します。(購入したボルトの頭が小さかったので、ワッシャーを噛ませないと抜けてしまいました。。ワッシャーがついててラッキーでした)


ひっくり返す

脚部の電源パーツを固定し、根性で机をひっくり返しておしまいです。
天板が広くなって満足しました!

おわりに

今回はフレキシスポットの脚にかなでものの天板を取り付けた話でした。
フレキシスポットの天板もいいのですが、サイズ展開は固定されていることがネックです。
自分好みの天板を取り付けたいときの参考にしてください。

複数アカウントのMoneyForwardから資産情報を取得して結合する方法で連携上限4件の縛りを突破する【docker, python, selenium, gspread】

はじめに

表題のことをやりたいです

2022/12/7 から無料会員アカウントでの連携上限数が10->4になるらしいです。

自分の連携数Xが 4 < X < 10 であり、総資産推移くらいしか見ないことから、
連携を複数アカウントに分散させ収集し結合すればいいじゃんと考えました。

過去に(無料連携数が10の頃に)1つのMoneyForwardアカウントから資産推移情報を持ってきてGoogleスプレッドシートに吐き出すところまで作っていたので、複数アカウントに対応することが今回のゴールになります。

catdance124.hatenablog.jp

catdance124.hatenablog.jp

↑ の記事を書いた頃からリポジトリ内容はだいぶ変わっており、いつの間にかdockerで動かすようになっています。
(前まで仮想ディスプレイとか設定が面倒でしたが、全部docker内でやることで何も考えなくてよくなりました…)

リポジトリはこちらです。

github.com

規約確認

複数アカウント利用が利用規約に抵触しないかが心配だったので、サポートに問い合わせてみました。

  • 問い合わせ内容(記録が残っていないのでざっくり)

    • 1個人による複数アカウントの利用は容認されているか
    • 容認されない場合、どのように本人確認を実施し同一人物ではないと判定するのか
  • 回答(原文ママ

    弊社サービスは、ご登録のメールアドレスでアカウントの管理を行っております。
    このため、ご登録メールアドレス以外の「氏名、電話番号、住所」等の個人情報は
    お預かりしておりません。
    ご登録メールアドレスとパスワードにてログイン可能な仕組みのため、
    確認されるアカウントを変更する際に、都度ログアウトのうえで、再ログインしていただく
    形にはなりますが、複数アカウントをご利用いただくことは可能でございます。

個人で複数アカウントを使っていいよとの回答なので、今回の開発内容は現時点で問題なさそうです。

構成

全体こんな感じです。 簡単な図

実行後、ファイル構成はこんな感じになります。

├── README.md
├── Dockerfile
├── docker-compose.yml
├── make_env.sh
├── requirements.txt
├── download
├── log
│   └── log
├── csv
│   ├── all_history_with_profit_and_loss.csv    ...    アップロード用に過去の資産推移情報と統合したもの
│   ├── concat
│   │   ├── all_history_with_profit_and_loss.csv    ...    アカウント1~3の資産推移情報を集約したもの
│   │   ├── portfolio_det_depo.csv
│   │   ├── portfolio_det_eq.csv
│   │   ├── portfolio_det_mf.csv
│   │   └── portfolio_det_pns.csv
│   ├── <アカウント1>
│   │   ├── all_history.csv
│   │   ├── all_history_with_profit_and_loss.csv    ...    アカウント1の資産推移情報
│   │   ├── history
│   │   │   └── this_month.csv
│   │   └── portfolio
│   │       ├── portfolio_det_eq.csv
│   │       └── portfolio_det_pns.csv
│   ├── <アカウント2>
│   │   ├── all_history.csv
│   │   ├── all_history_with_profit_and_loss.csv    ...    アカウント2の資産推移情報
│   │   ├── history
│   │   │   └── this_month.csv
│   │   └── portfolio
│   │       └── portfolio_det_depo.csv
│   └── <アカウント3>
│       ├── all_history.csv
│       ├── all_history_with_profit_and_loss.csv    ...    アカウント3の資産推移情報
│       ├── history
│       │   └── this_month.csv
│       └── portfolio
│           └── portfolio_det_mf.csv
└── src
    ├── client_secret.json
    ├── config.ini
    ├── download_history.py
    ├── export_gspread.py
    ├── mf2gs.py
    └── my_logging.py

(portfolio_**.csvが分かれているのは自分の例です)
src/config.ini で↓のように書いています。

[MONEYFORWARD]
Email = [
    "example1@hoge.com",
    "example2@hoge.com",
    "example2@hoge.com"
    ]
Password = [
    "password1",
    "password2",
    "password3"
    ]

[SPREAD_SHEET]
Key = 1_*****
Worksheet_name = 資産推移データ(自動入力)

[asset_depo]
id = portfolio_det_depo
column_name =
sheet_name = _預金・現金・暗号資産

[asset_eq]
id = portfolio_det_eq
column_name = 損益_株式(現物)
sheet_name = _株式(現物)

[asset_mf]
id = portfolio_det_mf
column_name = 損益_投資信託
sheet_name = _投資信託

[asset_pns]
id = portfolio_det_pns
column_name = 損益_年金
sheet_name = _年金

前からの変更点

アカウントごとに収集してくるようにしました。
ここで、用語は下記のとおりです。

  • 各月の資産推移 ... 名前の通り、各資産カテゴリごとの日別推移です。アカウントに登録して以降の情報をMoneyForwardは保持しています。
  • 各assetの資産内訳(損益) ... 各資産ごとの詳細情報です。MoneyForwardは損益情報を当日分しか保持していません。
    • そのため、資産推移と資産内訳(損益)を結合することで日ごとの損益情報をローカルcsvに保持します。

↓でcsv/<アカウント>/*.csvを収集・作成。

    # download each files
    for email, password in zip(emails, passwords):
        mf = Moneyforward(email=email, password=password)
        try:
            mf.login()
            mf.download_history()  #    ...    各月の資産推移を取得し結合する
            for asset_id in [asset['id'] for asset in assets]:
                mf.get_valuation_profit_and_loss(asset_id)  #    ...    各assetの資産内訳(損益)を取得する
            mf.calc_profit_and_loss(assets)  #    ...    資産推移と資産内訳(損益)を結合
        finally:
            mf.close()

↓で csv/<アカウントn>/*.csvからcsv/concat/*.csv を作成します。
*[!concat]と指定しないと結合したものを再計上しちゃうので気をつけないといけません(1敗)
結合方針は下記のとおりです。

  • 資産推移 ... sumを取る
  • 資産内訳(損益) ... axis=0で結合する
def concat_files(assets: list) -> Path:
    """
    複数アカウントから取得された資産推移と資産内訳(損益)を結合する
    Parameters
    ----------
    assets : list of dict
        各assetのidを含む辞書のリスト
    Returns
    -------
    output_path : Path
        各アカウント、各月、各assetの資産内訳(損益)を結合したcsvのパス
    """
    concat_csv_dir = root_csv_dir / "concat"
    concat_csv_dir.mkdir(exist_ok=True, parents=True)
    ## asset files
    for asset in assets:
        df_list = []
        for asset_csv_path in root_csv_dir.glob(f"*/portfolio/{asset['id']}.csv"):
            df = pd.read_csv(asset_csv_path, encoding="utf-8", sep=',')
            df_list.append(df)
        df_concat = pd.concat(df_list)
        df_concat.to_csv(concat_csv_dir / f"{asset['id']}.csv", encoding="utf-8", index=False)
    ## history files
    output_path = concat_csv_dir / "all_history_with_profit_and_loss.csv"
    df_concat = None
    for csv_path in root_csv_dir.glob(f"*[!concat]/all_history_with_profit_and_loss.csv"):
        df = pd.read_csv(csv_path, encoding="utf-8", sep=',')
        df.set_index('日付', inplace=True)
        df_concat = df_concat.add(df, fill_value=0) if df_concat is not None else df
    df_concat.sort_index(inplace=True, ascending=False)
    df_concat.to_csv(output_path, encoding="utf-8")
    return output_path

↓ でアップロード用の過去の情報と統合します。
なぜこんな処理があるかというと、今回の運用の都合上です。

  • MoneyForwardはアカウントに登録して以降の資産情報しか保持しない
  • 今回複数アカウントに分けて運用するため、新規アカウント登録が発生
  • 各アカウントは過去情報を持っていないため、収集・結合とは別に統合が必要

統合処理を挟むことで、前時代(無料連携数が10の頃)に収集した情報を無駄にしなくて済みました。

pd.mergeするときhow='outer'にしていますが、同じキーのレコードを持つ場合に第1引数のdataframeが優先される仕様を今回はじめて知りました(1敗)

    # concat each files
    new_all_history_wpl_csv_path = concat_files(assets)
    new_all_history_wpl = pd.read_csv(new_all_history_wpl_csv_path, encoding="utf-8", sep=',')

    # generate result csv
    all_history_wpl_csv_path = root_csv_dir / "all_history_with_profit_and_loss.csv"
    current_all_history_wpl = pd.read_csv(all_history_wpl_csv_path, encoding="utf-8", sep=',') if all_history_wpl_csv_path.exists() else new_all_history_wpl
    df_merged = pd.merge(new_all_history_wpl, current_all_history_wpl, how='outer')
    df_merged.drop_duplicates(subset='日付', inplace=True)
    df_merged.set_index('日付', inplace=True)
    df_merged.sort_index(inplace=True, axis='columns')
    df_merged.sort_index(inplace=True, ascending=False)
    df_merged.to_csv(all_history_wpl_csv_path, encoding="utf-8")

おわりに

MoneyForwardは便利ですが、これで資産推移グラフを見られるのでMoneyForwardに課金する必要がなくなりました

月500yenというのもチリツモなので...

あと、ソースに関数レベルでコメントをつけるようにしましたが、これがあれば解説書かなくていいじゃんと気づきました

github.com