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();
}());