【Python】Money Forwardをスクレイピングして得た情報をLINEに通知する

技術メモ

現在ルームシェアをしており、電気代など毎月の公共料金をルームメイトのいるLINEグループに通知するプログラムを作成しました。

公共料金の明細をLINEボット通知

今回のプログラムを実行すると以下のような通知がLINEに届きます。

MoneyFowardの明細の情報を取得してLINEボットで通知しています。

※電気のメーターが二つある特殊な家のため、電気代の項目が二つあります。

※水道代は2ヶ月に1回の請求のため、ここでは0円になっています。

※ガス代に関しては月末時点でMoneyFowardに明細が反映されていなかったため、0円になっています。

スクレイピング実装

必要に応じて以下をインストールします。(2021/11/8 更新)

$ brew install chromedriver
$ brew install oath-toolkit
$ pip3 install python-dotenv
$ pip3 install line-bot-sdk
$ pip3 install selenium
$ pip3 install webdriver-manager //追加

main.pyの実装

main.pyファイルを作成し、スクレイピング処理を実装します。(2021/11/8 更新)

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager #追加
from time import sleep
from linebot import LineBotApi
from linebot.models import TextSendMessage
from dotenv import load_dotenv
import os
import subprocess
import re
import datetime

# Load .env file
load_dotenv()

# Get environment variables
CHANNEL_ACCESS_TOKEN = os.environ['CHANNEL_ACCESS_TOKEN']
DESTINATION_LINE_ID = os.environ['DESTINATION_LINE_ID']
EMAIL = os.environ['EMAIL']
PASSWORD = os.environ['PASSWORD']
TWO_STEP_AUTHENTICATION_SETTING_CODE = os.environ['TWO_STEP_AUTHENTICATION_SETTING_CODE']

line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)


def get_monthly_bills():

    # Define browser
    options = webdriver.ChromeOptions()
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    options.add_argument("--hide-scrollbars")
    options.add_argument("--single-process")
    options.add_argument("--ignore-certificate-errors")
    options.add_argument("--window-size=880x996")
    options.add_argument("--no-sandbox")
    options.add_argument("--homedir=/tmp")
    options.add_argument(
        f'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36')

    browser = webdriver.Chrome(
        ChromeDriverManager().install(), #追加
        options=options
    )

    print('start login')

    # Jump to email sing-in page
    url = 'https://id.moneyforward.com/sign_in/email'
    browser.get(url)
    sleep(3)

    # Enter email
    elem_loginMethod = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div/div[1]/section/form/div[2]/div/input')
    elem_loginMethod.send_keys(EMAIL)

    # Jump to password input page
    elem_login = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div/div[1]/section/form/div[2]/div/div[3]/input')
    elem_login.click()
    sleep(3)

    # Enter password
    elem_password = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div/div[1]/section/form/div[2]/div/input[2]')
    elem_password.send_keys(PASSWORD)

    # Jump to tow-step-authentication page
    elem_login = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div/div[1]/section/form/div[2]/div/div[3]/input')
    elem_login.click()
    sleep(3)

    # Receive auth code for tow-step-authentication
    two_step_authentication = ['oathtool', '--totp',
                               '--base32', TWO_STEP_AUTHENTICATION_SETTING_CODE]
    auth_code = re.findall(
        r'\d+', subprocess.check_output(two_step_authentication).decode('utf-8'))

    # Enter auth code
    elem_auth_number = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/section/div[1]/section/form/div[2]/div/div[1]/input')
    elem_auth_number.send_keys(auth_code[0])

    # Jump to already-logged-in servise list page by Money Foward ID
    elem_auth = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/section/div[1]/section/form/div[2]/div/div[2]/input')
    elem_auth.click()
    sleep(3)

    # Jump to Money Forward ME account select page
    elem_money_forward = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div[1]/div/ul/li/a')
    elem_money_forward.click()
    sleep(3)

    # Jump to Money Forward ME Top page
    elem_choose_account = browser.find_element_by_xpath(
        '/html/body/main/div/div/div/div/div[1]/section/form/div[2]/div/div[2]/input')
    elem_choose_account.click()
    sleep(3)

    # Jump to Money Forward ME household expenses page
    elem_household_expenses = browser.find_element_by_xpath(
        '//*[@id="header-container"]/header/div[2]/ul/li[2]/a')
    elem_household_expenses.click()

    print('start getting bills')

    last_month = datetime.datetime.now().month - 1
    text = f'{last_month}月の公共料金\n'
    bills = [{'title': '電気代: ', 'service': '楽天でんき'},
             {'title': 'ガス代: ', 'service': '楽天ガス'},
             {'title': '水道代: ', 'service': '水道利用'},
             {'title': '通信費: ', 'service': '楽天ブロードバンド'}]

    # Create text content to send to LINE
    text = calc_bills(browser, bills, text)
    messages = TextSendMessage(text=text)
    # Push massages to LINE
    line_bot_api.push_message(DESTINATION_LINE_ID, messages=messages)

    print('everything done')

    browser.close()


def calc_bills(browser, bills, text):
    total = 0
    for bill in bills:
        span_tags = browser.find_elements_by_xpath(
            f'//span[contains(text(), "{bill["service"]}")]')
        # Add only available bills to text
        if not span_tags:
            text += f'\n{bill["title"]}0円'
        else:
            for span_tag in span_tags:
                price = span_tag.find_element_by_xpath('../../../td[4]')
                print(price.text)
                text += f'\n{bill["title"]}{price.text[1:]}円'
                total += int(price.text[1:].replace(',', ''))

    # Get comment depending on how much money you have spent on utitlities
    comment = get_comment(total)

    total = f'\n\n合計: {total}円\n{comment}'
    text += total
    print(text)
    return text


def get_comment(total):

    comment = ''

    if(total > 50000):
        comment += '富豪。'
    elif(total > 40000):
        comment += '贅沢してんじゃないの!?節約しよ!'
    elif(total > 30000):
        comment += 'もうちょっと出費抑えたき!'
    elif(total > 20000):
        comment += 'ぼちぼちの出費やね'
    elif(total > 10000):
        comment += '4桁円まで削ろう'
    else:
        comment += '前人未到の域、節約の神。'

    return comment


get_monthly_bills()

.envファイルの設定

.envファイルに環境変数を定義します。

CHANNEL_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DESTINATION_LINE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EMAIL=xxxxxxxxxxxxxxxxxxx
PASSWORD=xxxxxxxxxxxxxxxxxxxxx
TWO_STEP_AUTHENTICATION_SETTING_CODE='xxxx xxxx xxxx xxxx xxxx'
  • CHANNEL_ACCESS_TOKEN: LINE API アクセストークン ※1
  • DESTINATION_LINE_ID: LINEメッセージ送信先のgroupIdまたは個人のuserId ※1
  • EMAIL: MoneyForward ID ログイン時のメールアドレス
  • PASSWORD: MoneyForward ID ログイン時のパスワード
  • TWO_STEP_AUTHENTICATION_SETTING_CODE: 2段階認証突破のために設定が必要な20桁の英字 ※2

※1 LINEボットの作成、groupIdやuserId取得の方法が分からない方は以下の記事を参照すると良いかもしれません。

LINE BOTを作ってMessaging API でグループにメッセージを送る

※2 以下のステップでニ段階認証を突破するのに必要な20桁の英字を取得します。

1. MoneyFoward ME にログインする。

2. トップ画面の「設定」をクリックする。

2. アカウント設定 >> ログイン設定の「こちら」のリンクをクリックする。

3. 「二段階認証の設定」をクリックする。

4. 「有効にする」をクリックする。(既に有効になっている方は、一度無効にする必要があります)

5. 「コードを入力して設定」の欄に記載されている20字の英字をメモしておく。(画面の指示に従い、実際に認証アプリをダウンロードして二段階認証の設定を完了する。)

6. 5.でメモした20桁の英字を.envファイルの以下の環境変数の値にコピペする。

TWO_STEP_AUTHENTICATION_SETTING_CODE='xxxx xxxx xxxx xxxx xxxx'

以上の設定を行い、ターミナル上で python3 main.pyを実行するとプログラムが走ります。

取得金額はターミナル上でも確認できるので、LINEに通知する必要がない場合は以下のソースコードをコメントアウトしていただければと思います。

line_bot_api.push_message(DESTINATION_LINE_ID, messages=messages)

ソースコードのアレンジ

以下のソースコードを修正することでスクレイピング対象の明細を変更できます。

    last_month = datetime.datetime.now().month - 1
    text = f'{last_month}月の公共料金\n'
    bills = [{'title': '電気代: ', 'service': '楽天でんき'},
             {'title': 'ガス代: ', 'service': '楽天ガス'},
             {'title': '水道代: ', 'service': '水道利用'},
             {'title': '通信費: ', 'service': '楽天ブロードバンド'}]

text変数、titleプロパティに設定している文字列はLINE上で表示するテキストの内容です。好きなように変更できます。

seriveプロパティに設定しているのはMoneyFoward上の明細の内容列の値です。

私は楽天でんきを使用しているので、serviceプロパティに「楽天でんき」を設定しています。明細の内容列の値に「楽天でんき」という文字列が含まれる明細の金額を取得するようになっています。

使用しているインフラサービスが異なれば、ソースコード上のserviceプロパティの値も変更する必要があります。

LINEボットに送信するコメントも以下のメソッドの中身を修正することで自由に変えられます。

def get_comment(total):

    comment = ''

    if(total > 50000):
        comment += '富豪。'
    elif(total > 40000):
        comment += '贅沢してんじゃないの!?節約しよ!'
    elif(total > 30000):
        comment += 'もうちょっと出費抑えたき!'
    elif(total > 20000):
        comment += 'ぼちぼちの出費やね'
    elif(total > 10000):
        comment += '4桁円まで削ろう'
    else:
        comment += '前人未到の域、節約の神。'

    return comment

あとがき

pythonはほとんど触ったことないので大変でしたが、スクレイピングで取得した情報をLINEで通知するとこまで実装できて良かったです。

今後の展望

  • 例外処理の追加
  • AWSにホスティングして、月末のLINE通知を自動化する
  • LINE上からスクレイピングする明細の月と取得項目を設定できるようにする

現時点では手動でプログラムを実行している(しかも月末を狙って実行せねばならない)ので、少し手間がかかります。早くAWSにホスティングして作業を自動化したいところ。というかそれができないとあまり意味ない。。。

何はともあれ、今回の記事が少しでもどなたかの参考になれば幸いです。

参考URL

XPathって何? 取得方法など分かりやすく解説 – WinActor

Python+Seleniumで2段階認証(6桁のパスコード)を突破する全手順

【日記】Seleniumである特定の文字列の近くにある要素を取得する

PythonでLINE Bot APIを使ってプッシュ通知を実装する

AWS FargateでSeleniumを使い定期的にチェックする環境を作ってみた

seleniumを使用しようとしたら、「”chromedriver”は開発元を検証できないため開けません。」と言われた

コメント

  1. […] 二段階認証をMoneyFoward ME 側で設定して下さい。こちらのページが参考になります。 […]

タイトルとURLをコピーしました