tkrkt
10/13/2018 - 7:47 AM

Amazonで買ったもの集計

Amazonで買ったもの集計

// 使い方: 
//   1. 全部コピーする (右上の Raw をクリックした先でやるのが楽)
//   2. Amazon の注文履歴ページ ( https://www.amazon.co.jp/gp/css/order-history/ ) を開く
//   3. F12 または 右クリ→要素の検証 とかで出てくる開発者ツールのコンソール (JavaScript REPL) にペースト
//   4. エンターで実行
//
// format: 
// type outputJSON = Array<{
//   product: string; // ファイル名
//   href: string;    // 商品ページURL
//   asin: string;    // ASIN
//   date: string;    // 購入日 "YYYY年MM月DD日"
//   price: number;   // 金額
// }>;

(function() {
  const inputYear = () => {
    return +window.prompt('何年分の注文を集計しますか?', '2018');
  };
  
  const fetchDoc = async (year, page = 0) => {
    const res = await fetch(`https://www.amazon.co.jp/gp/css/order-history?digitalOrders=1&unifiedOrders=1&orderFilter=year-${year}&startIndex=${page * 10}`, {
      headers: {
        'X-Requested-With': ''
      }
    });
    const text = await res.text();
    const doc = document.implementation.createHTMLDocument("");
    doc.documentElement.innerHTML = text;
    return doc;
  };
  
  const getText = (container, query) => {
    const element = container.querySelector(query);
    if (element) {
      return element.textContent.trim();
    } else {
      return '';
    }
  };
  
  const getAttr = (container, query, attr) => {
    const element = container.querySelector(query);
    if (element) {
      return element[attr] || '';
    } else {
      return '';
    }    
  };
  
  const extractItems = (doc) => {
    const result = [];
    Array.from(doc.querySelectorAll('div.order')).forEach(container => {
      const date = getText(container, 'div.order-info span.value');
      Array.from(container.querySelectorAll('.a-fixed-left-grid-col.a-col-right')).map(item => {
        const product = getText(item, 'a.a-link-normal[href^="/gp/product/"]');
        const href = getAttr(item, 'a.a-link-normal[href^="/gp/product/"]', 'href');
        const group = /\/gp\/product\/([^\/]+)\//.exec(href);
        const asin = (group && group[1]) ? group[1] : '';
        const price = +(getText(item, 'span.a-color-price') || getText(container, '.a-span2 .a-color-secondary.value')).replace(/[¥\s,]/g, '') || 0;
        result.push({product, asin, href, date, price});
      });
    });
    return result;
  };
  
  const hasNext = (doc) => {
    const next = doc.querySelector('.pagination-full ul.a-pagination li:last-child');
    return next && !next.classList.contains('a-disabled');
  };
 
  const extractAllItems = async (year) => {
    let page = 0;
    let items = [];
    while (true) {
      console.log(`${year}年のデータ取得中... ${page+1}ページ目`);
      // sleep
      await new Promise(resolve => setTimeout(resolve, Math.random() * 2000));

      const doc = await fetchDoc(year, page);
      items = [...items, ...extractItems(doc)];
      if (hasNext(doc)) {
        page++;
      } else {
        break;
      }
    }
    return items;
  };
  
  const convertItemsToJSON = (items) => {
    return JSON.stringify(items, null, 2);
  };
  
  const convertItemsToCSV = (items, forExcel = false) => {
    const lines = items.map(item => {
      return `"${item.product.replace(/"/g, '\"')}",${item.date},${item.price}`;
    });
    return `${forExcel ? 'sep=,\n' : ''}produce,date,price\n${lines.join('\n')}`;
  };
  
  const downloadFile = (name, text) => {
    const a = document.createElement('a');
    a.download = name;
    a.href = URL.createObjectURL(new Blob([text], {type: 'text/plain'}));
    a.click();
  };

  const main = async () => {
    const year = inputYear();
    if (Number.isNaN(year) || !year) return;
    
    const items = await extractAllItems(year);
    console.log('取得完了', items);
    console.log('合計金額', items.reduce((sum, i) => sum + i.price, 0));
    downloadFile(`amazon-${year}.json`, convertItemsToJSON(items));
  };
  
  main();
}());