【Javascript】カートの中身を自動的にCSV出力するスクリプトで会計処理を楽にする

#作るに至った経緯
会計処理の際に購入予定のもの情報を一つ一つExcelにコピーして貼り付ける作業が毎回手作業なので、どうにか時間短縮できないかなと思い、自動化するスクリプトを作ることにしました。NodeやSeleniumだと既存のブラウザにアタッチできないのでjavascriptを使いました。

#今回やったこと
モノタロウの通販サイトにログインし、カートに入っている全ての商品の情報をCSVファイルに自動でまとめてくれるスクリプトを作りました。
ちなみに秋月電子は
https://tlt.gurigoro.net/entry/2018/01/13/200510 この拡張機能を使ってまとめるとよいです。
できるだけ有りものは利用しましょう。
#環境
Windows10 Home
CPU: Intel Core i5 7200U
tampermonkey
javascript
Google Chrome
#本題
##前提
モノタロウの通販サイトで既に買いたい商品がカートに入っていて、ログインが済んでいる。カートのページにいる。
##プログラムの流れ
モノタロウの商品カートから情報を取得
取得した情報の配列を作る
配列の整形
配列からCSVに変換する
CSVファイルをダウンロードする

##ブラウザでCSVをダウンロードする方法
Google chrome上のjavascriptでCSVファイルをダウンロードする方法を学びました。まずは[こちら](https://blog.mudatobunka.org/entry/2017/04/23/135753)のサイトを参考に情報を取得した前提でCSVをダウンロードしてみます。ちなみにこのCSVファイルはwindowsでないと文字化けします。詳しくは[javascript で作成したCSVファイルをエクセルで表示可能にする](https://qiita.com/bump_of_kiharu/items/f41beec668e1f3ea675e#%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89--bom--%E6%8B%A1%E5%BC%B5%E5%AD%90-%E3%81%AE%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%83%86%E3%82%B9%E3%83%88%E7%B5%90%E6%9E%9C)を参考にしてください。
##tampermonkeyの導入
まずはchrome拡張 [tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo)を入れます。登録したページに行くとjavascriptを実行してくれる拡張機能です。
詳細は[こちらの記事](https://qiita.com/sagami1991/items/794168cf14ed198d6372)が参考になります。
##カートの中身の情報をスクレイピングする
tampermonkeyという拡張機能を使って製品名などの情報を取得し、csvに出力します。残念ながら今の私の技能ではtampermonkeyとspredsheetを連携させることは無理でした。

要素の取得はCSSセレクタを手打ちで入力していきました。コーディングのポイントは、そのタグやクラスの名前が固有のものだったらそのまま書く、固有でなかったら子要素を利用して絞り込みます。`item_content`など、親要素の名前が同じ場合がありますが、子要素のクラス名が独特なものなので直接指定しています。入れ子にするかは状況をみて使い分けましょう。

tampermonkeyでは`(function() {})()`の`{}`の中にコードを書いて実行します。
・@matchは実行するページのURLを書きます。ここに書いたページに遷移すると実行されるので、カートに入るボタンを押したら実行されることになります。

```MonotaroCart_to_csv.js
// ==UserScript==
// @name         カートの中身CSV出力くん
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://www.monotaro.com/monotaroMain.py?func=monotaro.basket.showListServlet.ShowListServlet&RtnPage=%2Fg%2F04470570%2F%3Fes%3D1
// @grant        none
// ==/UserScript==

(function() {
let product_name = [];
let oneset_num = [];
let price = [];
let buy_num = [];
let url = [];
let subtotal = [];
const Store = 'モノタロウ';
let heder_label = ['用途', '種別', '製品名', '購入場所', '内容量', '単価', '購入数量', 'URL', '小計', '合計'];

const ProductInfo = () => {
    let elm = document.querySelectorAll('.product_td');//商品ブロックの要素を取得
    let price_elm = document.querySelectorAll('td.price_td');//金額ブロックの要素を取得
    for (let i = 0; i < elm.length; i++) {
        product_name[i] = elm[i].querySelector('h4>a').innerText;//製品名を取得
        //oneset_num[i] = elm[i].querySelector('li>#text').innerText;//内容量を取得
        url[i] = elm[i].querySelector('h4>a').href;//urlを取得
    }
    for (let i = 0; i < price_elm.length; i++) {
        price[i] = price_elm[i].querySelector('td.item_content').innerText;//値段
        buy_num[i] = price_elm[i].querySelector('input.button_items_quantity.imemode_inactive').value;//購入数量
        subtotal[i] = price_elm[i].querySelector('em').innerText;//小計

    }
}



class CSV {
    constructor(data, keys = false) {
        this.ARRAY = Symbol('ARRAY');
        this.OBJECT = Symbol('OBJECT');

        this.data = data;

        if (CSV.isArray(data)) {
            if (0 == data.length) {
                this.dataType = this.ARRAY
            } else if (CSV.isObject(data[0])) {
                this.dataType = this.OBJECT
            } else if (CSV.isArray(data[0])) {
                this.dataType = this.ARRAY
            } else {
                throw Error('Error: 未対応のデータ型です')
            }
        } else {
            throw Error('Error: 未対応のデータ型です')
        }

        this.keys = keys
    }

    toString() {
        if (this.dataType === this.ARRAY) {
            return this.data.map((record) => (
                record.map((field) => (
                    CSV.prepare(field)
                )).join(',')
            )).join('\n')
        } else if (this.dataType === this.OBJECT) {
            const keys = this.keys || Array.from(this.extractKeys(this.data))

            const arrayData = this.data.map((record) => (
                keys.map((key) => record[key])
            ))

            console.log([].concat([keys], arrayData))

            return [].concat([keys], arrayData).map((record) => (
                record.map((field) => (
                    CSV.prepare(field)
                )).join(',')
            )).join('\n')
        }
    }

    save(filename = 'data.csv') {
        if (!filename.match(/\.csv$/i)) { filename = filename + '.csv' }

        console.info('filename:', filename);
        console.table(this.data);

        const csvStr = this.toString();

        const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
        const blob = new Blob([bom, csvStr], { 'type': 'text/csv' });
        const url = window.URL || window.webkitURL;
        const blobURL = url.createObjectURL(blob);

        let a = document.createElement('a');
        a.download = decodeURI(filename);
        a.href = blobURL;
        a.type = 'text/csv';

        a.click();
    }

    extractKeys(data) {
        return new Set([].concat(...this.data.map((record) => Object.keys(record))));
    }

    static prepare(field) {
        return '"' + ('' + field).replace(/"/g, '""') + '"';
    }

    static isObject(obj) {
        return '[object Object]' === Object.prototype.toString.call(obj);
    }

    static isArray(obj) {
        return '[object Array]' === Object.prototype.toString.call(obj);
    }
}


const MakeList = () => {
    let csvfile = [heder_label,];
    for (let i = 0; i < product_name.length; i++) {
        csvfile.push([, , product_name[i], Store, oneset_num[i], price[i], buy_num[i], url[i], subtotal[i]]);
    }

    return csvfile;
}
//console.log(MakeList());
ProductInfo();
(new CSV(MakeList())).save('buylist.csv');//csvオブジェクトの作成 引数にはファイル名を指定
})();
```
新しいスクリプトをtampermonkeyに書いて有効にしたら[モノタロウのカート](https://www.monotaro.com/monotaroMain.py?func=monotaro.basket.showListServlet.ShowListServlet)に移動します。すると、以下のようなテーブルでcsvファイルが作成され、CSVファイルがダウンロードできます。
![buylist.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/616291/5060705d-d528-2974-5b04-3b7c71e03c45.png)

#最後に
seleniumだと環境構築が面倒ですが、tampermonkeyは拡張機能のインストールだけで動くので非常に簡単でいいですね。
今回でjavascriptを使ったスクレイピングはできるようになったので、今後は別のECサイトの分もつくりたいです。

参考
https://qiita.com/sagami1991/items/794168cf14ed198d6372
https://blog.mudatobunka.org/entry/2017/04/23/135753