見出し画像

コミュニケーションデザインと入金通知botの実装

ライター

取引先や振込の件数が多くなればなるほど、入金確認の件数は膨大になり、確認先の銀行システムも増える一方、経理業務の圧迫は免れない状況に追い込まれていく...

本記事では、コミュニケーションデザインから観た入金通知業務について解説する。

株式会社シクミヤでは、本記事のような業務の自動化に多く取り組んでおりますので、お困りのことがあればお気軽にご相談ください。

1. 設計思想

1-1. 入金通知のコミュニケーションと課題

経理業務のひとつとして、入金確認および通知の作業がある。各銀行システムにて取引先等からの振り込みが行われているかを確認し、それらを振り分けて、債権の消し込みや顧客への着金報告、商品の発送など、各窓口への通知を行う。それらの通知をもって各機能の作業(=仕事)が開始することとなる。

経理は銀行への入金確認と、各機能の作業開始のトリガーとなる通知とを媒介するハブとしての役割を担っており、一連の作業およびコミュニケーションは経理担当者が人力で行っている。

画像28

入金の件数が肥大化するほど、確認漏れや金額の誤り、通知漏れ、通知先の誤りなど、人為的ミスも起こりやすくなる。また、銀行システム毎にインターフェースは異なるため、入金情報を見つけ出すことにも労力を要する。

そのような混沌とした状況下に置かれれば各窓口への通知においても遅延が発生するようになる。通知が遅延することで、作業開始が計画よりも後ろ倒しとなり、各機能を有する組織やその先のユーザー、関連するステークホルダーなど、各所への多大な影響を及ぼすこととなるだろう。

これでは経理担当者にとって精神的にも時間的にも負荷がかかってしまうばかりだ。

もしも、入金確認のインターフェースが統一され、通知が自動で行われるようになったら——新しい取引銀行が増える度に覚えていた画面や書式から解放され、一通一通を丁寧に作成していた大量の通知作業から解放されたらどうだろう。

1-2. 入金通知のコミュニケーションを最適化する

「コミュニケーションが最適化された状態」とは、ルールに従い義務的に行われる作業時間が減り、組織課題に向き合う時間がより多く取れている状態のことである。

入金に係る経理業務において、入金についてのお知らせを他の担当者に伝えるときには、ルール化された義務的なコミュニケーションが行われている。

画像30

この時のコミュニケーションは、以下の2種類の機能と性質を持っており、これらは組織全体の業務を円滑に進めるために、ルール化された義務的なものになる。

・「事実を伝える」:入金内容を伝える
・「通知する」:担当者の注意を引く

組織運営において、このルールに従い義務的に行われる活動に多くの時間が割かれている。その状況で、組織における問題提起や課題解決など、組織課題に向き合う時間を十分に取ることができるだろうか。

このルールベースの義務的なコミュニケーションに着目し、それをbotによって自動化する、というのが今回の設計思想である。

機械に任せられる領域と人間にしかできない領域との棲み分けを行うことで、後者に割ける時間を増やすことが実現できる。botによって入金の常時監視が行われ、人為的ミスは解消され、経理が担うコミュニケーションが最適化される。

画像31

botを活用するのは、従前に担っている「人」から「botへ」とコミュニケーションを移譲し、義務的に行われるコミュニケーションから人を解放することが目的だ。ルールが明確で定型化されているほど、botの適応範囲は広がり適応性が高くなる。botはルールベースのフロー自動化に向いている。

尚、自動化の対象としているのはあくまでも「コミュニケーション」であり「作業」ではない。ミクロの視点で見れば作業の連続とも思えるが、マクロの視点で捉えると、入金の確認から通知、更にその先の担当者の作業へと繋ぐひとつのコミュニケーション体系であることがわかる。

1-3. 入金通知自動化の概要とそれにより達成すること

自動化の概要としては、クラウド会計ソフトから入金取引情報のみを抽出、コミュニケーションツールへ通知するというフローをbotによって自動化するというものである。

・クラウド会計ソフトを利用するメリット

通常、銀行ではセキュリティ上の観点より、取引情報を取得するためのAPIを公開していない。その代替手段として、各銀行口座への入金取引が行われた際の入金通知のサービスは提供されているが、その通知方法やメール本文の書式は各々で異なっている。これらのサービスを利用していない、あるいは提供されていないサービスについては、担当者が能動的に各データソースにアクセスして膨大な取引データから1件1件、入金確認を行わなければならない。

これらの煩雑さはクラウド会計ソフトが解決してくれる。クラウド会計ソフトでは各銀行とアグリゲーションして自身のサービスに全ての取引情報を連携・集約している。ひとつのデータソース、ひとつのインタフェースで完結させられるのがメリットである。

・コミュニケーションツールと組み合わせた自動化フロー

入金確認に対してはクラウド会計ソフトが有効であるが、入金通知に対しては利用頻度が多くリアルタイム性の高いコミュニケーションツールを用いることが有効である。これらのツールとbotを組み合わせて自動化のフローを構築する。

画像33

この自動化フローによって受動的かつスマートな入金確認を実現することができる。担当者が各口座の入金確認を行うのはひとつのデータソースへのアクセスで良くなり、入金確認に対する時間や手間を大幅に削減できる

また、フローを拡張すれば、各窓口への通知の振り分けも自動化でき、更にスプレッドシートに蓄積された取引履歴を各機能へ展開することも可能になるため、情報伝達の幅が向上し、正確性とリアルタイム性が向上する。

2. 実装設計

前述の設計思想を実装するため、実装設計に落とし込んでいく。

・本記事で取り上げるサービスと技術要素

今回は、データソースとなるクラウド会計ソフトにMoneyForwardクラウド会計(以下、MF)を、コミュニケーションツールにSlackを、取引情報の蓄積先としてGoogleスプレッドシート(以下、スプレッドシート)を例として解説する。

画像33

これらの処理にはGoogle Apps Script(以下、GAS)を用いてフローを構築する。

自動化-入金通知bot

2-1. 実装する機能

MFでは各種銀行やカード会社とのデータ連携を行っており、一連の取引がMF上に集約され、一元管理が行える。また、SaaSコミュニケーションツールであるSlackでは、Slackbotの機能により外部のシステムやサービスからSlackチャンネルあるいはユーザーへとプッシュ通知が行える。

これらの特性を活かして、以下の機能をGASで実装する。

■ 実装する機能
1. MFのスクレイピング 
2. MFから取引情報および口座情報を抽出
3. スプレッドシートに取引情報・口座情報を記入
4. 新着の入金取引をSlackへ通知

また、これらの機能を以下のフローに組み込んで実現する。

自動化-入金通知bot_MF

GASで作成するインターフェースは以下の3つのfunctionである。それぞれにトリガーを設定し、定期実行を行う。

1) 入金通知: main()
 明細情報を取得し、新着の入金情報をSlackへ通知する処理。
 ⏱ トリガー:時間主導型:分ベースのタイマー:5分おき

2) 一括取得: refresh()
 明細情報の同期処理。
 ⏱ トリガー:時間主導型:分ベースのタイマー:10分おき

3) マスタ同期: syncMaster()
 口座マスタの同期処理。
 ⏱ トリガー:時間主導型:時間ベースのタイマー:1時間おき

次より、実装する機能の詳細を説明する。

2-1-1. MFのスクレイピング 

MFのようなSaaSから情報を取得するためにはWebAPI、iPaaS、Webスクレイピングの3通りの方法がある。執筆時点ではMFはWebAPIの一般公開を行っておらず、WebAPIおよびiPaaSでの取得は不可能である。そのため、MFからの情報取得はGASからのWebスクレイピング(以下、スクレイピング)によって行う。

2-1-2. MFから取引情報および口座情報を抽出

スクレイピングによって明細一覧画面から情報を抽出する。メニューの「データ連携」>「明細一覧」の「閲覧」から明細一覧画面が閲覧できる。

画像6

・連携している口座情報
連携しているサービスの一覧が明細一覧画面ソースコード中のJavaScript内に記載されてるため、以下の項目を抽出する。1時間おきに更新する。

■ 取得項目
連携サービス名、口座名

・口座毎の取引情報
明細一覧画面に連携サービス単位で取引の明細が表示されるため、以下の項目を取得する。5分間おきに取得する。

■ 取得項目
日付、内容、金額、連携サービス名、口座名

また、上記の取引情報はホーム画面の「一括再取得」にて同期する必要がある。10分間おきに同期する。

画像7

2-1-3. スプレッドシートに取引情報・口座情報を記入

スプレッドシートを作成し、以下のシートを作成する。

・口座マスタシート
2.の「連携している口座情報」を蓄積するシート。口座の別名、通知ON/OFF、可読性を上げるためのSlack通知時のアイコン(絵文字)の欄を追加し、カスタマイズを可能にしている。

■ 各列の設計
A列:連携サービス名:文字列
B列:口座名:文字列
C列:別名:文字列
D列:アイコン:文字列(Slackに登録されている絵文字のキー)
E列:通知設定:文字列、入力規則=リストを直接指定(OFF,ON)

画像3

・明細シート
2.の「口座毎の取引情報」を蓄積するシート。通知有無と重複通知を避けるためにSlack通知日時を設けている。
金額がプラスの数値は入金通知対象とし、マイナスの数値は対象外とする。Slackへ通知されるとSlack通知日時に日付時刻が記録される。

■ 各列の設計
A列:日付:日付
B列:内容:文字列
C列:金額:文字列
D列:連携サービス名:文字列
E列:口座名:文字列
F列:Slack通知日時:日時

画像4

2-1-4. 新着の入金取引をSlackへ通知

3.の新着入金行をSlackへ通知する。Slackのワークスペースに新たにSlackbotを作成し、特定のチャンネルへの投稿を可能にする。

通知本文には「口座名」「入金日」「金額」「内容」を記載する。アイコンは口座毎に設定されたものが表示される。bot名にタイムスタンプを付加しているが、これはbot名が同じ投稿が連投されると、2つ目以降の投稿にはアイコンが表示されなくなるためである。

画像5

■ 投稿の書式
アイコン:{口座マスタ.アイコン}
bot名:{bot名} タイムスタンプ(YYYY-MM-DD HH:mm:ss.SSS)
メッセージ:{口座マスタ.別名}の口座に新しく入金がありました!
※別名を設定していない場合は{口座マスタ.連携サービス名+口座名}
入金日:{明細.日付}
金額:{明細.金額}
内容:{明細.内容}


3. 実装方法

具体的な実装方法について。MFへのスクレイピング、スプレッドシート操作およびSlackへの通知は全てGASによって行う。GASでコーディングをする前にスプレッドシートとSlackbotを作成しておく。

3-1. スプレッドシートの作成

2-1-3.のシート設計に従い、スプレッドシートを作成する。スプレッドシートのIDはGASからスプレッドシートを操作するために使用するため、控えておくこと。

💡 スプレッドシートIDの取得方法
1) スプレッドシートを開く
2) URLからスクリプトIDを取得
//docs.google.com/spreadsheets/d/{スプレッドシートID}/

3-2. Slackbotの作成

入金通知先のSlackワークスペースに、以下の手順にてSlackbotを作成する。

1) 新しいSlackアプリケーションの作成
slack apiのApplicationsページから「Create New App」を押下して新しいアプリケーションを作成する。


画像8

アプリケーション名を入力し、アプリケーションを追加するワークスペースを選択する。

画像9

2) Webhookの設定

SlackがGASからメッセージを受け取るための入り口となるWebhookの設定をする。

メニューの「Incoming Webhooks」からActivate Incoming Webhooksを「On」にする。

画像10

画像12

メニューの「App Home」からApp Display Nameの「Edit」を押下する。

画像13

bot名、botのユーザ名を入力し「Add」を押下する。

画像14

※投稿先のチャンネルをSlackワークスペースに作成しておく。(作成権限がない場合は管理者に依頼するなどして作成する)

Addボタン押下後、投稿先の選択と許可を求められるため、投稿先のチャンネルを選択し「許可する」を押下する。

💡 チャンネル名がプルダウン内に表示されないことがあるが、チャンネル名の一部を入力すると候補に表示される。

画像15

「Incoming Webhooks」に戻り、Webhook URLs for Your WorkspaceにWebhookが追加される。

画像15

3) OAuthの設定
メニューの「OAuth & Permissions」を選択し、アクセストークンを表示する。アクセストークンはGASからSlackへ通知する際に使用するため、控えておくこと。

画像16

チャンネルに投稿する権限を付与するために、Bot Token ScopesとUser Token Scopesを設定する。

画像17

■ Bot Token Scopes
chat:write
 投稿先へ投稿するための権限。
chat:write.customize
 botのユーザ名やアイコンをカスタマイズするための権限。

■ User Token Scopes
chat:write
 ユーザデータへアクセスし、ユーザに代わって投稿するための権限。

画像18

設定後、以下のようなメッセージが表示される。「reinstall your app」を押下し、botの権限を許可する。

画像19

画像20

メニューの「Basic Information」からbotが正常に作成できたことを確認して完了。

画像21

3-3. GASのコーディング

GASの新しいスクリプトを作成し、今回のフローを実装していく。フロー図とインタフェースを再掲する。

自動化-入金通知bot_MF

1) 入金通知: main()
 明細情報を取得し、新着の入金情報をSlackへ通知する処理。

2) 一括取得: refresh()
 明細情報の同期処理。

3) マスタ同期: syncMaster()
 口座マスタの同期処理。

各処理ではMFのスクレイピングやSlackへの通知を行うが、それらの処理中に送出されるリクエストの詳細については参考情報として後述する。

3-3-1. 入金通知処理: main()

明細行のデータから新着の入金情報を抽出し、Slackへ通知を行う。

1) MFから明細一覧のHTMLを取得
明細一覧のHTMLから明細行のデータを抽出する。

■ リクエストフロー
(1) ログインリクエスト
(2) 明細データ取得リクエスト

明細一覧の「検索」ボタンを押下すると非同期通信で明細行のデータを取得しに行っており、その通信を分析して明細行のデータを抽出する。(明細一覧取得リクエストの内容)

画像27

画像28

「$("tbody#js-acts-table-tbody").append("」と「");」の間が明細の一行分のHTMLになっているため、全ての行に共通している前後の文字列を置換、HTML部分のみを切り出し、各列の項目値を取得する。

// 一部抜粋
   let result = StringUtil.replaceAll(response.getContentText(), '\\', '')
   result = StringUtil.replaceAll(result, '$("tbody#js-acts-table-tbody").append("', '')
   result = StringUtil.replaceAll(result, '");', '')
   return result

// 使用部品
class StringUtil {
  static replaceAll(val, target, replaceTo) {
   return val.split(target).join(replaceTo)
  }
}

2) 口座マスタシートを取得
通知用に口座マスタシートの設定情報を取得する。

3) 新着の入金情報をSlackに通知し、明細シートに記録
1)のデータから新着の入金情報(明細シートに存在しないかつ金額がプラスかつ通知設定がON)を取得し、Slackへ通知する。通知した明細行にはSlack通知日時をセットする。

Slackへの通知は前段で取得したアクセストークンとともにリクエストを送出する。

[POST] https://slack.com/api/chat.postMessage
リクエストヘッダー
 ・なし
リクエストパラメータ
 ・token: アクセストークン(Bot User OAuth Access Token)
 ・payload: {
channel: SlackのチャンネルID,
icon_emoji: 絵文字コード,
blocks: メッセージ本文,
username: 入金通知bot YYYY-MM-DD HH:mm:ss.SSS
}

payload.blocksはSlackが提供しているBlock Kit Builderを活用すると楽に構築ができる。

画像22

3-3-2. 一括取得処理: refresh()

MFの連携データを同期する。

■ リクエストフロー
(1) ログインリクエスト
(2) 一括取得リクエスト

ホーム画面の「一括再取得」のリクエストは非同期通信にて行われる。通信内容を分析し、リクエスト処理を実装する。

画像24

画像25

3-3-3. マスタ同期処理: syncMaster()

MFから連携サービスと口座の情報を取得し、口座マスタシートへ反映させる。

■ リクエストフロー
(1) ログインリクエスト
(2) 明細一覧画面リクエスト

明細一覧のHTMLソース内、windows.embeddedVariablesに代入している値から連携サービス名と口座名を抽出する。ソースからは文字列で抽出するが、JSON形式の文字列であるため、JSON.parse()でオブジェクト値に変換して処理を行う。

画像26

window.embeddedVariableの構造は以下のようになっている。(必要な部分のみを抜粋)

{
  'actsSearchForm': {
    'accountAndSubAccount': {
      'accounts': [
        {
          'label': '連携サービス名',
          'subAccounts': [
            {'label': '口座名'},
            {'label': '口座名'},
            {'label': '口座名'}
          ]
        },
        {
          'label': '連携サービス名',
          'subAccounts': [
            {'label': '口座名'},
            {'label': '口座名'},
            {'label': '口座名'}
          ]
        },
      ]
    }
  }
}

連携サービス名(account)と口座名(subAccount)を抽出するGASのコードを一部抜粋する。

 const text = elem['children'][0]['data']
 if (text.indexOf('window.embeddedVariables = ') > -1) {
   const accountObj = JSON.parse(text.substring(text.indexOf('{'), text.lastIndexOf('}')+1))
   const accounts = accountObj['actsSearchForm']['accountAndSubAccount']['accounts']
   for (const account of accounts) {
     if (account['label'] != '全て') {
       const accountName = account['label']
       
       let subAccountList = []
       
       const subAccounts = account['subAccounts']
       for (const subAccount of subAccounts) {
         if (subAccount['label'] != '全て') {
           const subAccountName = subAccount['label']
           subAccountList.push(subAccountName)
         }
       }
       
       accountNameList.push({
         'account': accountName,
         'subAccount': subAccountList
       })
     }
   }

参考. MFのスクレイピング処理

MFをスクレイピングして情報を抽出する目的は先述の通りである。ブラウザにて抽出先の画面や通信を確認・分析し、スクレイピングの処理を実装する。

MFについては、以下のリクエストを処理する。

■ リクエスト
・ ログインリクエスト

 MFのログイン画面からID/パスワード認証を行い、ホーム画面へと遷移する。

・ 一括取得リクエスト
 MFのホーム画面上にある「一括取得」のリクエストを送出する。

・ 明細一覧画面リクエスト
 明細一覧画面を開くリクエストを送出する。レスポンス本文に含まれるJavaScript内から連携サービス名と口座名の一覧を抽出する。

・ 明細データ取得リクエスト
 明細データの検索リクエストを送出する。検索リクエストは明細一覧画面から非同期通信にて送出されている。レスポンス本文に含まれるJavaScript内から明細一覧行を生成するためのHTMLを抽出する。
❗️ 各リクエストと処理フローは執筆時点のものであり、今後サービスの仕様変更により変わる場合あり

・ ログインリクエスト

MFにログインするにはリダイレクトを含めた複数のリクエストを連続で送出する必要がある。

GASスクレイピングでのログインについては以下の記事を参考にしていただきたい。概略としては以下の通り。
・Webブラウザでログインし、行われている通信内容を解析する
・解析した通信内容からGASでの通信を実装する

1) セッション新規作成

[GET] https://biz.moneyforward.com/session/new
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・なし
レスポンスボディ
 ・HTML内のa要素href属性値

上記書式の各リクエストは以下のGASのコードに置き換えて実装する。

// リクエストヘッダー
const headers = {
  'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
// リクエストパラメータ
const payload = {
  // 'key': 'value'
}
const options = {
  'method': 'get', // HTTPメソッド
  'headers': headers,
  'payload': payload,
  'followRedirects': false // 自動リダイレクトはOFFにしておく
}
// リクエストURL
const url = 'https://biz.moneyforward.com/session/new'

// リクエスト送出
const response = UrlFetchApp.fetch(url, options)

// レスポンスヘッダーを取得
const responseHeaders = response.getAllHeaders()
// const headerValue = responseHeaders['Header-Name']

// レスポンスボディを取得
const responseBody = response.getContentText()
// console.log(responseBody)

// HTML内のa要素href属性値を抽出
const nextLink = responseBody.match(/href=".*?"/g)[0].replace('href="', '').replace('"', '')
// console.log(nextLink)

2) リダイレクト1

[GET] 1)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
 ・Location
レスポンスボディ
・LocationのURLに含まれる「client_id」パラメータ

3) リダイレクト2

[GET] 2)で取得したLocationのURL
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・なし
レスポンスボディ
 ・HTML内のa要素href属性値

4) リダイレクト3

[GET] 3)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・なし
レスポンスボディ
 ・HTML内のa要素href属性値

5) リダイレクト4

[GET] 4)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: last_used_application
レスポンスボディ
 ・HTML内のa要素href属性値

6) ログインページ1

[GET] 5)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 5)で取得したlast_used_application
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _mfid_session
レスポンスボディ
 ・CSRFトークン(meta[name=csrf-token]セレクタで取得したmeta要素のcontent属性値)

7) ログインページ2

[POST] https://id.moneyforward.com/sign_in/email
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 5)で取得したlast_used_application
 ・Cookie: 6)で取得した_mfid_session
 ・Accept: 'ext/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
リクエストパラメータ
 ・authenticity_token: 6)で取得したCSRFトークン
 ・_method: 'post'
 ・client_id: 2)で取得したclient_id
 ・redirect_uri: 'https://biz.moneyforward.com:443/auth/mfid/callback'
 ・response_type: 'code'
 ・scope: 'openid email'
 ・state: 5)で取得したhref属性値に含まれるstateパラメータ
 ・nonce: 5)で取得したhref属性値に含まれるnonceパラメータ
 ・mfid_user[email]: MFのログインID
レスポンスヘッダー
 ・Cookie: _mfid_session
レスポンスボディ
 ・HTML内のa要素href属性値

8) ログインページ3

[GET] 7)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 6)で取得した_mfid_session
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _mfid_session
レスポンスボディ
 ・CSRFトークン

9) ログイン

[POST] https://id.moneyforward.com/sign_in
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 8)で取得した_mfid_session
 ・Cookie: 5)で取得したlast_used_application
 ・Accept: 'ext/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
リクエストパラメータ
 ・authenticity_token: 8)で取得したCSRFトークン
 ・_method: 'post'
 ・client_id: 2)で取得したclient_id
 ・redirect_uri: 'https://biz.moneyforward.com:443/auth/mfid/callback'
 ・response_type: 'code'
 ・scope: 'openid email'
 ・state: 7)で取得したhref属性値に含まれるstateパラメータ
 ・nonce: 7)で取得したhref属性値に含まれるnonceパラメータ
 ・mfid_user[email]: MFのログインID
 ・mfid_user[password]: MFのログインパスワード
レスポンスヘッダー
 ・Cookie: sih
 ・Cookie: _mfid_session
レスポンスボディ
 ・HTML内のa要素href属性値

10) 認証

[GET] 9)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 9)で取得したsih
 ・Cookie: 9)で取得した_mfid_session
 ・Cookie: 5)で取得したlast_used_application
 ・Accept: 'ext/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _mfid_session
レスポンスボディ
 ・HTML内のa要素href属性値

11) 認証コールバック

[GET] 10)で取得したhref属性値
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 2)で取得した_ca_bweb_session
 ・Cookie: 10)で取得した_mfid_session
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
 ・Location
レスポンスボディ
 ・なし

12) ホーム表示

[GET] https://biz.moneyforward.com/home
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: 11)で取得した_ca_bweb_session
リクエストパラメータ
 ・cti: 11)のLocationから取得した「cti」パラメータ
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
レスポンスボディ
 ・なし
❗️ 最終的に取得できた「cti」「_ca_bweb_session」の値はログイン後のスクレイピングで使用するため保管しておくこと。

・一括取得リクエスト

ホーム画面内の「一括取得」ボタンを押下した時のリクエストを送出する。

1) ホーム表示

[GET] https://biz.moneyforward.com/home
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: ログインで取得した「_ca_bweb_session」
リクエストパラメータ
 ・cti: ログインで取得した「cti」
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
レスポンスボディ
 ・CSRFトークン

2) 一括取得

[POST] https://biz.moneyforward.com/aggregation_queues/bulk_create
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: ログインで取得した「_ca_bweb_session」
 ・Accept: '*/*;q=0.5, text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
 ・X-CSRF-Token: 1)で取得したCSRFトークン
 ・X-Requested-With: 'XMLHttpRequest'
リクエストパラメータ
 ・called_by: 'home_show'
 ・cti: ログインで取得した「cti」
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
レスポンスボディ
 ・なし

・明細一覧画面リクエスト

明細一覧画面を開いた時のリクエストを送出する。連携サービス名と口座名を抽出する。

1) 明細一覧表示

[GET] https://biz.moneyforward.com/accounts/trans_list
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: ログインで取得した「_ca_bweb_session」
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・Cookie: _ca_bweb_session
レスポンスボディ
 ・script要素に含まれる「window.embeddedVariables」に代入しているオブジェクト値から「連携サービス名」と「口座名」を抽出
 actsSearchForm
  └ accountAndSubAccount
   └ accounts[i]
    └ label - 連携サービス名
    └ subAccounts[j]
     └ label - 口座名

・ 明細データ取得リクエスト

明細一覧画面で検索した時のリクエストを送出する。条件に一致する明細データtable要素のHTMLを抽出する。

1) 明細一覧表示

[GET] https://biz.moneyforward.com/accounts/trans_list
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: ログインで取得した「_ca_bweb_session」
リクエストパラメータ
 ・なし
レスポンスヘッダー
 ・なし
レスポンスボディ
 ・CSRFトークン

2) 明細一覧取得

毎日実行するため、当日分の明細のみを取得する。

[POST] https://biz.moneyforward.com/accounts/trans_list_ajax
リクエストヘッダー
 ・User-Agent: 最新Chromeのエージェント値
 ・Cookie: ログインで取得した「_ca_bweb_session」
 ・X-CSRF-Token: 1)で取得したCSRFトークン
 ・X-Requested-With: 'XMLHttpRequest'
 ・Accept: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01'
 ・Content-Type: 'application/x-www-form-urlencoded; charset=UTF-8'
リクエストパラメータ
 ・acts[recognized_at_from]: 今日(YYYY/MM/DD)
 ・acts[recognized_at_to]: 今日(YYYY/MM/DD)
 ・acts[within_term]: false
 ・acts[content]: ''
 ・acts[account_id_hash]: ''
 ・acts[sub_account_id_hash]: ''
 ・acts[status]: ''
 ・acts[limit]: 101
 ・acts[value_min]: ''
 ・acts[value_max]: ''
 ・acts[offset]: 0
 ・acts[order]: 'desc'
レスポンスヘッダー
 ・なし
レスポンスボディ
 ・「$("tbody#js-acts-table-tbody").append("」と「");」の間に挟まれた文字列

ソースコードの紹介

今回のソースコードは以下のGitHubに公開している。シクミヤ独自開発のライブラリ(Slib)を利用しているため、細かな実装については省略されているが、本記事に記述した内容の実装をブラックボックス化しているだけであるため、参考にしていただければ幸いである。


弊社は業務効率化・自動化など、仕組みで解決するお手伝いをさせていただいております。お仕事のご依頼はコチラ↓までお願いいたします。

note: Visionary Base編集部
Twitter: @visionary_base

この記事が気に入ったらサポートをしてみませんか?