1
0

More than 1 year has passed since last update.

Playwrightで楽天カードのCSVを自動で取得するクローラーを作った

Last updated at Posted at 2023-03-31

はじめに

クレジットカードの利用明細はPDFやCSVでダウンロードすることができますが、これらを手動で取得するのは手間がかかります。
うまく改造すればfreeeへの自動連携なども考えられます。
ビジネスカードのAPI非対応の履歴も取れますので、色々と利用できそうです。
そこで、Playwrightを使って自動化することで、手動での取得作業を自動化することができます。

以下は取得したcsvから月の支払金額をLine通知するサンプルです
https://github.com/natsumeaurlia/rakuten-csv

準備

適当な作業ディレクトリを作成。
必要なパッケージのインストール方法
以下のコマンドで、必要なパッケージをインストールします。

npm init playwright@latest
npm install playwright dotenv

ドライバーのダウンロードと設定方法

Playwrightでは、実行するブラウザに応じたドライバーを必要とします。本クローラーでは、Chromiumを利用するため、以下のコマンドでChromiumのドライバーをダウンロードします。

npx playwright install

dotenvを使った環境変数の設定方法

セキュリティ上の理由から、本クローラーではIDとパスワードを環境変数として設定します。.envファイルをプロジェクトのルートディレクトリに作成し、以下のように設定してください。

.env
ID=<楽天カードのログインID>
PASS=<楽天カードのログインパスワード>

起動処理

main.ts,tsconfig.jsonを作成してください。

tsconfig.jsonはこのような形です。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",                           
    "module": "commonjs",                         
    "strict": true,                               
    "esModuleInterop": true,                      
    "skipLibCheck": true,                         
    "forceConsistentCasingInFileNames": true      
  }
}

main.tsはこのような雛形になります。

main.ts
import * as dotenv from 'dotenv';

dotenv.config();

(async () => {
    try {

    } catch (err) {
        console.error(err);
    }
})();

まずはenvからIDとPASSがあるか確認します
tryブロックの中に追記してください

const ID = process.env.ID;
const PASS = process.env.PASS;
if (!ID || !PASS) throw new Error("ID or PASS is not set.");

これでID,PASSがなかったら実行されません。

次に以下のように追記してください。


const downloadDir = path.join(__dirname, 'storage');
const browser = await chromium.launch({
  headless: true,
  downloadsPath: downloadDir,
  args: [
    '--incognito',
    '--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
  ]
});
const context = await browser.newContext({
  acceptDownloads: true,
});
const page = await context.newPage();

csvの保存ディレクトリを選択。
chromium起動オプションで、シークレットモード、agent偽装、headless、ダウンロード許可状態で起動します。

ログイン処理

ログイン画面に移動し、3秒待機。
ログイン情報を入力欄に記載しログインボタンを押します。
少しスリープを入れてしっかり画面が開くようにします。


await page.goto("https://www.rakuten-card.co.jp/e-navi/index.xhtml");
await page.waitForTimeout(3000);

await page.fill('input[name="u"]', ID);
await page.fill('input[name="p"]', PASS);

await Promise.all([
  page.click('input[id="loginButton"]'),
  page.waitForLoadState('networkidle'),
]);

await page.waitForTimeout(2000);

明細取得処理

利用明細をcsvにする関数は下記のように表せます。
unixtimeなどをつけてcsv名の重複を防いでいます


// 利用明細CSVのダウンロード
const downloadFunc = async () => {
  const downloadPromise = page.waitForEvent('download', {timeout: 10000});
  await page.click('.stmt-c-btn-dl.stmt-csv-btn');
  const download = await downloadPromise;
  const unixtime = Math.floor(new Date().getTime() / 1000);
  await download.saveAs(path.join(downloadDir, unixtime + 'rakuten-card.csv'));
})

これを利用してダウンロードは行います。

それでは利用明細に移動します。
移動したら、楽天カードの利用可能なものを取得します。

main.ts
// 利用明細画面に遷移する
await Promise.all([
  page.goto('https://www.rakuten-card.co.jp/e- navi/members/statement/index.xhtml?l-id=enavi_all_glonavi_statement'),
  page.waitForLoadState('networkidle'),
]);

const availableOptionsTexts = await page.evaluate(() => {
      const cardSelect = <HTMLInputElement>document.querySelector('.stmt-head-regist-card__select__box');
      return Array.from(cardSelect.querySelectorAll('option'))
            .filter((option) => !option.innerText.includes('利用不可'))
            .filter((option) => option.selected === false)
            .map((option) => option.innerText);
        });

あとはダウンロードしましょう

main.ts

// 初期の利用可能なカードの利用明細をダウンロード
        await downloadFunc();

        // その他利用可能なカードの利用明細をダウンロード
        for (const option of availableOptionsTexts) {
            // カードを選択
            await page.locator('.stmt-head-regist-card__select__box').selectOption(option);
            await page.waitForLoadState('networkidle');
            await downloadFunc();
        }

await browser.close();

最終コード

main.ts
import { chromium } from 'playwright';
import * as path from 'path';
import * as dotenv from 'dotenv';

dotenv.config();

(async () => {
    try {
        const ID = process.env.ID;
        const PASS = process.env.PASS;
        if (!ID || !PASS) throw new Error("ID or PASS is not set.");

        // storageフォルダに保存
        const downloadDir = path.join(__dirname, 'storage');
        const browser = await chromium.launch({
            headless: true,
            downloadsPath: downloadDir,
            args: [
                '--incognito',
                '--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
            ]
        });
        const context = await browser.newContext({
            acceptDownloads: true,
        });
        const page = await context.newPage();

        await page.goto("https://www.rakuten-card.co.jp/e-navi/index.xhtml");
        await page.waitForTimeout(3000);

        await page.fill('input[name="u"]', ID);
        await page.fill('input[name="p"]', PASS);

        await Promise.all([
            page.click('input[id="loginButton"]'),
            page.waitForLoadState('networkidle'),
        ]);

        await page.waitForTimeout(2000);

        // 利用明細画面に遷移する
        await Promise.all([
            page.goto('https://www.rakuten-card.co.jp/e-navi/members/statement/index.xhtml?l-id=enavi_all_glonavi_statement'),
            page.waitForLoadState('networkidle'),
        ]);

        const availableOptionsTexts = await page.evaluate(() => {
            const cardSelect = <HTMLInputElement>document.querySelector('.stmt-head-regist-card__select__box');
            return Array.from(cardSelect.querySelectorAll('option'))
                .filter((option) => !option.innerText.includes('利用不可'))
                .filter((option) => option.selected === false)
                .map((option) => option.innerText);
        });

        // 利用明細CSVのダウンロード
        const downloadFunc = async () => {
            const downloadPromise = page.waitForEvent('download', {timeout: 10000});
            await page.click('.stmt-c-btn-dl.stmt-csv-btn');
            const download = await downloadPromise;
            // Wait for the download process to complete
            console.log(await download.path());
            // Save downloaded file somewhere
            const unixtime = Math.floor(new Date().getTime() / 1000);
            await download.saveAs(path.join(downloadDir, unixtime + 'rakuten-card.csv'));
        }

        // 初期の利用可能なカードの利用明細をダウンロード
        await downloadFunc();

        // その他利用可能なカードの利用明細をダウンロード
        for (const option of availableOptionsTexts) {
            // カードを選択
            await page.locator('.stmt-head-regist-card__select__box').selectOption(option);
            await page.waitForLoadState('networkidle');
            await downloadFunc();
        }

        await browser.close();

    } catch (err) {
        console.error(err);
    }
})();

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0