技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

ゴールドポイントカードプラスが、家計簿アプリに連携出来ないので CSV 連携する

はじめに

ことの始まりはこの Tweet

クレジットカード嫌いな人は良く「いくら使ったかわからない」という言い方をするが、要するにこの手の人は財布内の残量を見て使い方を考えるタイプな訳です。
逆に言えば、次の補充タイミングまでという短期的な視点でしか計画を立てれないので、財布にあればあっただけ期間丁度で使い切ってしまう。

もちろんこの思考でもお金を貯める事は不可能ではありません。
給料日に「毎月財布に入れるお金は給料のN%」といった割り切りをして、残りを銀行に入れ続ける方法です。
家賃や光熱費、ローンを上回る差し引き分を銀行に入れておけば、貯めること自体は不可能ではありません。

しかし、これは別の言い方をすれば「使えるだけ使ってしまう」ということを意味します。
「使えるだけ使う」のスタンスの場合、「何にいくら使った?」「無駄な出費はない?」が把握できなくなります。
もし使い方を計画できるなら、上記以上に貯める事が出来ますよね?

ならば、財布だけでなく、全収支を集計して俯瞰的に確認できる状況を作るのが最も正しいのです。

つまり 家計簿アプリ かなにかで家計管理するのが貯蓄には正しいわけです。

クレジットカードのススメ

ここではクレジットカードで買い物ををする利点を挙げていきます。

家計簿がつけやすい

クレジットカードでの購入をした場合、クレジットカード会社でその利用履歴が全て残ります。
つまり… 買い物が全てレジットカードなら、クレジットカード会社の履歴=家計簿 になるわけです。

少なくとも支出はこれで集計できる訳ですから、これほど楽なものはありません。

カードによってはポイントがつく

カードによってはポイントがつくものがあり、大抵の場合そのポイントは 1 ポイント = 1 円で使えます。
例えば

  • 楽天カード:カードでの購入金額の 1% と、使い方次第で 10% 以上までレートが上がっていきます。このポイントは楽天での決済や、楽天証券での投資で利用出来ます。
  • ゴールドポイントカードプラス:利用金額の 1% と、ヨドバシカメラでの購入金額の 10% がポイントとしてもらえます。これはヨドバシカメラでの購入時に決済で利用出来ます。
  • 三井住友VISA:Vポイントというものが手に入ります。ナンバーレスでは 0.5% で、年間更新料ありの上位カード*1 なら1%がポイントとしてもらえます。このポイントは提携ショップや、クレジット支払いの金額への充当に充てられます。

こんな感じです。
仮に、日用品・雑貨・食品の一部 などで 3 万円を使ってるとして、これが「ゴールドポイントカードプラス」「ヨドバシ」で買った場合、毎月 3000 円のポイントがつきますよね?
これは 2L のほうじ茶ペットボトル で換算すると 12 本セットがタダになって、追加でそこそこポイントがあるという状況に…

家計簿アプリのススメ

家計簿をつけるにあたって、一番面倒なのが「家計簿の入力」これにつきます。
とは言え、現金決済をしないようにすれば、カード会社の利用履歴で支出が分かります。
では、銀行の残高は?投資している場合、証券会社の収支は?といろいろあると思います。

これを全部連携して一括でやってくれるのが家計簿アプリです。
たとえば以下のようなものがあります。

ただし、最近マネーフォワードMEなどは、無料利用で連携できる先が 4 つまで制限されたみたいですね。
「クレジットカード会社」「銀行」「証券会社」くらいなら良いですが、クレジットカードが2つあるとか、証券会社も2つ、銀行も…となると流石に有料で使わざるを得ませんね(汗

自分は Zaim (+有料プラン)を使ってます。

すると、全体は映せませんがこんな感じで、いつ何にいくら使った等の詳細が出せます。

…エンタメに使いすぎがわかる…では何に使ってるの?

11 月 9 日?

…あー(汗
欲望にまかせて衝動買いしちゃったやつか…

みたいに把握できる訳です。

家計簿アプリ連携の出来ない「ゴールドポイントカード・プラス」

段々本題に入ってきました。

私はヨドバシでお米などの食材、家電、飲み物などを購入しています。
そのため、「ゴールドポイントカード・プラス」をよく使うわけですが、なんとこのカード 家計簿アプリに対応されていない *2 という困ったちゃんです(汗

これでは家計簿アプリの効果半減ですね…

他に連携する方法

が、Zaim など一部の家計簿アプリは、CSV で登録することが出来ます。
そして幸いなことに、ゴールドポイントカードプラスもCSVでダウンロードできるんですね。

CSVをダウンロードしたら、Zaim 等でも「ファイルの入出力」から

「一般的なCSVファイルをアップロードする」を使ってアップロードすれば連携出来ます。

連携前に加工しよう

ただし、ヨドバシのゴールドポイントカードのCSVですと、中身がこんな感じで、「カテゴリ」(食費、雑費、エンタメ、医療費、通信費などの種別)がつけられないのと、「支払元」(ゴールドポイントカード・プラスからの支払いですが、それを指定することが出来ない)が空欄になってしまいます。

アップロードのテストを行った図

これでは分析も面倒になってしまいますね…ということでプログラムを書いてこれを加工してしまいましょいう。

プログラムで加工する

Python のインストール

説明するのがめんどくさいので、Youtube リンク。
まぁ初回だけなので…

youtu.be

仕様

やりたいことをまとめます

  1. カテゴリ、サブカテゴリは欲しい(支払先が決まれば、カテゴリは大体決まるはず)
  2. 「ゴールドポイントカード・プラス」(支払い元)の列を追加する
  3. ことのついでなので、項目の並び順を Zaim の並び順にしよう

こんなところでしょうか

実際のコード

実行する場合、一度だけ以下のコマンドが必要

> pip install glob2
> pip install csv
import csv
import glob2
import re

files = glob2.glob('./*.csv')
pattern = '\\.[\\\\\\/]\\d+\\.csv'

CATEGORY_MAP = {
    'So−net': ['通信', '通信'],
    'AmazonDownloads': ['エンタメ', '書籍'],
    'あみあみ': ['エンタメ', 'その他'],
    'DMM': ['エンタメ', 'その他'],
    'マクドナルド': ['食費', '食費'],
    'ワイヤレスゲート': ['通信費', '通信費'],
    'AMAZON.CO.JP': ['エンタメ', 'その他'],
    'UNHCR協会': ['その他', 'その他'],
    'セブン−イレブン': ['食費', '食費'],
    '寿屋秋葉原館': ['エンタメ', 'その他'],
    'コトブキヤオンラインショップ': ['エンタメ', 'その他'],
    'モバイルSuica': ['その他', '現金の引出'],
    'STEAM': ['エンタメ', 'その他'],
    'STEAMGAMES': ['エンタメ', 'その他'],
    'ツルハドラッグ': ['日用雑貨', '消耗品'],
    'JR東日本みどりの窓口': ['交通', '電車']
}

def to_category(name):
    values = name.replace(' ', '').replace(' ', '')
    for key in CATEGORY_MAP.keys():
        if key in values:
            return CATEGORY_MAP[key]
    return ['その他', 'その他']

def set_parameter(row1, row2):
    print('The first line is:')
    print(row1)
    is_skip = input('Is skip line 1 (yes/no): ').lower() == 'yes'
    
    print('The seccond line is:')
    print(row2)
    date_index = int(input('Input datetime col index(0 start): '))
    shop_index = int(input('Input shop name col index(0 start): '))
    amount_index = int(input('Input amount col index(0 start): '))
    return {
        'is_skip': is_skip,
        'date': date_index,
        'shop': shop_index,
        'amount': amount_index
    }

for file in files:

    if re.match(pattern, file) == None:
        continue

    read_data = []
    with open(file, encoding='shift-jis', mode='r') as rp:
        reader = csv.reader(rp)
        for row in reader:
            read_data.append(row)

    setting = set_parameter(read_data[0], read_data[1])

    write_data = []
    for line_no in range(len(read_data)):
        if setting['is_skip'] and line_no == 0:
            continue
        row = read_data[line_no]
        category = to_category(row[setting['shop']])
        write_data.append([
            row[setting['date']],
            category[0],
            category[1],
            row[setting['shop']],
            'ゴールドポイントカードプラス',
            row[setting['amount']]
        ])
    with open('result.csv', encoding='shift-jis', mode='w') as wp:
        writer = csv.writer(wp)
        writer.writerows(write_data)

使ってみる

動作仕様は以下の仕様です。

  • 実行した瞬間に同じフォルダの CSV を開いて、設定方法を質問してくる
  • CATEGORY_MAP の設定に従い、店舗名が一致するとカテゴリ/サブカテゴリを設定する(未登録は「その他・その他」で登録する)
  • 最終的に result.csv として結果を返す

論より run ですね

いくつか実行前に質問が飛んできます。

  • Is skip line 1 (yes/no): no : CSVの先頭行をスキップするかどうか→ no を選択
  • Input datetime col index(0 start): 0 : 日付カラムは何列目? → 0 と回答(先頭を 0 として考える)
  • Input shop name col index(0 start): 1 : 店名カラムは何列目? → 1 と回答
  • Input amount col index(0 start): 6 : 金額カラムは何列目? → 6 と回答

(ゴールドポイントカード・プラスは過去ログ CSV だと1行目が余計なデータなので、その対応が必要なのと、列も違うので変更できるようにしています。)

すると result.csv が作成されてこんな感じに

これを Zaim にアップロードする

すると

てな具合。

説明

使うライブラリの読み込み

#!/bin/python
import csv
import glob2
import re

スクリプト実行ディレクトリ内の csv ファイルの列挙

files = glob2.glob('./*.csv')

ファイル名の正規表現パターン。意味は今のディレクトリ内にある「./[1文字以上の数字].csv」という意味。
ゴールドポイントカードプラスの csv202211.csv みたいな名前であるため、これに一致させる。

pattern = '\\.[\\\\\\/]\\d+\\.csv'

カテゴリの指定設定。「ショップ名:['カテゴリ', 'カテゴリの内訳']」の設定。

CATEGORY_MAP = {
    'So−net': ['通信', '通信'],
    'AmazonDownloads': ['エンタメ', '書籍'],
    'あみあみ': ['エンタメ', 'その他'],
    # 中略
}

因みにカテゴリは「支出カテゴリ」から参照出来ます。

店名からカテゴリ設定を返す関数の作成

def to_category(name):
    values = name.replace(' ', '').replace(' ', '')
    for key in CATEGORY_MAP.keys():
        if key in values:
            return CATEGORY_MAP[key]
    return ['その他', 'その他']

起動時の設定読み込み処理を行う関数の作成
print が画面表示の関数で、input は画面からの入力を行う関数。

input は画面の入力を文字列型で返して来るので、int(...) で数字型に変更しています。

def set_parameter(row1, row2):
    print('The first line is:')
    print(row1)
    is_skip = input('Is skip line 1 (yes/no): ').lower() == 'yes'
    
    print('The seccond line is:')
    print(row2)
    date_index = int(input('Input datetime col index(0 start): '))
    shop_index = int(input('Input shop name col index(0 start): '))
    amount_index = int(input('Input amount col index(0 start): '))
    return {
        'is_skip': is_skip,
        'date': date_index,
        'shop': shop_index,
        'amount': amount_index
    }

後はそれを使って処理

# ファイルを全部ループする
for file in files:

    # ファイルを正規表現でフィルタ
    if re.match(pattern, file) == None:
        continue

    # CSV の内容を一旦 read_data に格納する
    read_data = []
    with open(file, encoding='shift-jis', mode='r') as rp:
        reader = csv.reader(rp)
        for row in reader:
            read_data.append(row)

    # 1 行目を飛ばす、処理に必要な列番号の設定等を実行
    setting = set_parameter(read_data[0], read_data[1])

    # 書き込む内容を write_data 変数に格納
    write_data = []
    for line_no in range(len(read_data)):
    
        # 1 行目は設定次第で読み飛ばす
        if setting['is_skip'] and line_no == 0:
            continue

        # 処理対象業を row に格納
        row = read_data[line_no]

        # ショップ名からカテゴリ情報を取得
        category = to_category(row[setting['shop']])
        
        # 保存時の CSV 行を作成
        write_data.append([
            row[setting['date']],   # 先頭は日付項目
            category[0],            # 2列目はカテゴリ
            category[1],            # 3列目はカテゴリ内訳
            row[setting['shop']],   # 4列目でショップ名
            'ゴールドポイントカードプラス',   # 5列目に支払い元設定
            row[setting['amount']]  # 最後は支払い金額
        ])

    # write_data を CSV に書き込んでフィニッシュ
    with open('result.csv', encoding='shift-jis', mode='w') as wp:
        writer = csv.writer(wp)
        writer.writerows(write_data)

*1:「修行」と呼ばれる年間 100 万の決済をクレジットカード利用があれば、来年以降は年間更新料タダになる

*2:他のカード会社と異なり、独自のセキュリティを儲けているため、連携が出来ない