1 | // Amazonの注文履歴をTSV形式で出力するスクリプト |
1 | // Amazonの注文履歴をTSV形式で出力するスクリプト |
2 | // |
2 | // |
3 | // 2015-01-01 時点での DOM 構造に対応, GoogleCrome, Opera でテスト済。 |
3 | // 2015-01-01 時点での DOM 構造に対応, GoogleCrome, Opera でテスト済。 |
4 | // formatEntry関数を書き換えれば自由な書式で出力できます。 |
4 | // formatEntry関数を書き換えれば自由な書式で出力できます。 |
5 | // |
5 | // |
6 | // 参考: |
6 | // 参考: |
7 | // - Amazonの注文履歴をCSV形式にして出力するスクリプト |
7 | // - Amazonの注文履歴をCSV形式にして出力するスクリプト |
8 | // https://gist.github.com/arcatdmz/8500521 |
8 | // https://gist.github.com/arcatdmz/8500521 |
9 | // - Amazon で使った金額の合計を出す奴 (2014 年バージョン) |
9 | // - Amazon で使った金額の合計を出す奴 (2014 年バージョン) |
10 | // https://gist.github.com/polamjag/866a8af775c44b3c1a6d |
10 | // https://gist.github.com/polamjag/866a8af775c44b3c1a6d |
11 | |
11 | |
12 | (function () { |
12 | (function () { |
13 | |
13 | |
14 | // 各注文履歴をTSVフォーマットにして返す |
14 | // 各注文履歴をTSVフォーマットにして返す |
15 | var datePattern = new RegExp("(\\d{4})年(\\d{1,2})月(\\d{1,2})日"); |
15 | var datePattern = new RegExp("(\\d{4})年(\\d{1,2})月(\\d{1,2})日"); |
16 | function formatEntry(entry) { |
16 | function formatEntry(entry) { |
17 | console.log(entry); |
17 | console.log(entry); |
18 | entry.date.match(datePattern); |
18 | entry.date.match(datePattern); |
19 | var year = RegExp.$1; |
19 | var year = RegExp.$1; |
20 | var month = RegExp.$2; if (month.length <= 1) month = "0" + month; |
20 | var month = RegExp.$2; if (month.length <= 1) month = "0" + month; |
21 | var day = RegExp.$3; if (day.length <= 1) day = "0" + day; |
21 | var day = RegExp.$3; if (day.length <= 1) day = "0" + day; |
22 | var date = "" + year + "/" + month + "/" + day; |
22 | var date = "" + year + "/" + month + "/" + day; |
23 | var arr = [date, entry.name, entry.author, entry.url]; |
23 | var arr = [date, entry.name, entry.price, entry.url]; |
24 | return arr.join('\t') + "\n"; |
24 | return arr.join('\t') + "\n"; |
25 | } |
25 | } |
26 | |
26 | |
27 | function popup(content) { |
27 | function popup(content) { |
28 | var generator = window.open('', 'name', 'height=250,width=700'); |
28 | var generator = window.open('', 'name', 'height=250,width=700'); |
29 | generator.document.write('<html><head><title>Amazon to TSV</title>'); |
29 | generator.document.write('<html><head><title>Amazon to TSV</title>'); |
30 | generator.document.write('</head><body>'); |
30 | generator.document.write('</head><body>'); |
31 | generator.document.write('<pre>'); |
31 | generator.document.write('<pre>'); |
32 | generator.document.write(content); |
32 | generator.document.write(content); |
33 | generator.document.write('</pre>'); |
33 | generator.document.write('</pre>'); |
34 | generator.document.write('</body></html>'); |
34 | generator.document.write('</body></html>'); |
35 | generator.document.close(); |
35 | generator.document.close(); |
36 | return generator; |
36 | return generator; |
37 | } |
37 | } |
38 | |
38 | |
39 | var itemDelimiter = " / "; |
39 | var itemDelimiter = " / "; |
40 | var total = {}; |
40 | var total = {}; |
41 | var year = '2014'; |
41 | var year = 2016; |
42 | var nyear: number = 2014; |
| |
43 | var all = false; |
42 | var all = false; |
44 | |
43 | |
45 | function init(num : number) { |
44 | function init(num : number) { |
46 | if (typeof num !== 'number') { |
45 | if (typeof num !== 'number') { |
47 | var num = 0; |
46 | var num = 0; |
48 | $('<div/>').css({ |
47 | $('<div/>').css({ |
49 | position: 'fixed', |
48 | position: 'fixed', |
50 | left: 0, |
49 | left: 0, |
51 | top: 0, |
50 | top: 0, |
52 | width: '100%', |
51 | width: '100%', |
53 | height: '100%', |
52 | height: '100%', |
54 | zIndex: 1000, |
53 | zIndex: 1000, |
55 | backgroundColor: 'rgba(0,0,0,.7)', |
54 | backgroundColor: 'rgba(0,0,0,.7)', |
56 | color: '#fff', |
55 | color: '#fff', |
57 | fontSize: 30, |
56 | fontSize: 30, |
58 | textAlign: 'center', |
57 | textAlign: 'center', |
59 | paddingTop: '15em' |
58 | paddingTop: '15em' |
60 | }).attr('id', '___overlay').text('Amazonいくら使った?').appendTo('body'); |
59 | }).attr('id', '___overlay').text('Amazonいくら使った?').appendTo('body'); |
61 | year = window.prompt('何年分の注文を集計しますか?\n - 半角数字4桁で入力してください\n - 全期間を集計する場合は「all」と入力します', year); |
60 | var inp = window.prompt('何年分の注文を集計しますか?\n - 半角数字4桁で入力してください\n - 全期間を集計する場合は「all」と入力します', year.toString()); |
62 | if (year === 'all') { |
61 | if (inp === 'all') { |
63 | all = true; |
62 | all = true; |
64 | year = jQuery('div.top-controls select option:last').val().match(/[0-9]/g).join(''); |
63 | year = jQuery('div.top-controls select option:last').val().match(/[0-9]/g).join(''); |
65 | } else if (!/^[0-9]{4}$/.test(year)) { |
64 | } else if (!/^[0-9]{4}$/.test(inp)) { |
66 | alert('正しい数値を入力してください'); |
65 | alert('正しい数値を入力してください'); |
67 | $('#___overlay').remove(); |
66 | $('#___overlay').remove(); |
68 | return false; |
67 | return false; |
69 | } |
68 | } |
70 | nyear = Number(year); |
69 | else { |
| |
70 | year = Number(inp); |
| |
71 | } |
71 | } |
72 | } |
72 | // 第二引数を true にすると各商品とかエラーを逐一表示する |
73 | // 第二引数を true にすると各商品とかエラーを逐一表示する |
73 | var progress = load(num, false); |
74 | var progress = load(num, false); |
74 | $('#___overlay').text(nyear + '年の集計中… / ' + (num + 1) + 'ページ目'); |
75 | $('#___overlay').text(year.toString() + '年の集計中… / ' + (num + 1) + 'ページ目'); |
75 | progress.done(function (results) { |
76 | progress.done(function (results) { |
76 | if (typeof total[nyear] === 'undefined') { |
77 | if (typeof total[year] === 'undefined') { |
77 | total[nyear] = results; |
78 | total[year] = results; |
78 | } else { |
79 | } else { |
79 | total[nyear] = total[nyear].concat(results); |
80 | total[year] = total[year].concat(results); |
80 | } |
81 | } |
81 | init(num + 1); |
82 | init(num + 1); |
82 | }).fail(function () { |
83 | }).fail(function () { |
83 | if (all && new Date().getFullYear() > nyear) { |
84 | if (all && new Date().getFullYear() > year) { |
84 | nyear++; |
85 | year++; |
85 | init(0); |
86 | init(0); |
86 | } else { |
87 | } else { |
87 | var _total = 0; |
88 | var _total = 0; |
88 | var _content = ""; |
89 | var _content = ""; |
89 | jQuery.each(total, function (nyear, results) { |
90 | jQuery.each(total, function (year, results) { |
90 | var yen = 0; |
91 | var yen = 0; |
91 | jQuery.each(results, function () { |
92 | jQuery.each(results, function () { |
92 | yen += this.price; |
93 | yen += this.price; |
93 | $.each(this.items, function (i, item) { |
94 | $.each(this.items, function (i, item) { |
94 | _content += formatEntry(item); |
95 | _content += formatEntry(item); |
95 | }); |
96 | }); |
96 | }); |
97 | }); |
97 | _total += yen; |
98 | _total += yen; |
98 | }); |
99 | }); |
99 | // result |
100 | // result |
100 | $('#___overlay').remove(); |
101 | $('#___overlay').remove(); |
101 | alert('合計 ' + _total + ' 円'); |
102 | alert('合計 ' + _total + ' 円'); |
102 | popup(_content); |
103 | popup(_content); |
103 | console.log('合計 ' + _total + ' 円'); |
104 | console.log('合計 ' + _total + ' 円'); |
104 | } |
105 | } |
105 | }); |
106 | }); |
106 | } |
107 | } |
107 | |
108 | |
108 | function load(num: number, verbose: boolean) { |
109 | function parsePage(data: string, results: any[], lnks: any[], verbose: boolean) { |
109 | var df = jQuery.Deferred(); |
| |
110 | var page = get(num, verbose); |
| |
111 | page.done(function (data : string) { |
| |
112 | var dom = jQuery.parseHTML(data); |
110 | var dom = jQuery.parseHTML(data); |
113 | var results = []; |
| |
114 | |
111 | |
115 | jQuery(dom).find('div.order').each(function () { |
112 | jQuery(dom).find('div.order').each(function () { |
116 | var box = jQuery(this); |
113 | var box = jQuery(this); |
| |
114 | var emphasis = jQuery(box.find("a.a-link-emphasis")); |
| |
115 | if (emphasis.length > 0) { |
| |
116 | var path = emphasis.attr('href').trim().replace('/ref=/', ''); |
| |
117 | var lnk = 'https://www.amazon.co.jp' + path; |
| |
118 | lnks.push(lnk); |
| |
119 | } else { |
117 | var dateText = jQuery(box.find('div.order-info span.value')[0]).text().trim(); |
120 | var dateText = jQuery(box.find('div.order-info span.value')[0]).text().trim(); |
118 | |
| |
119 | var items = []; |
121 | var items = []; |
120 | var item = {}; |
122 | var item = {}; |
121 | var pubarr = box.find("div.a-row > span.a-size-small"); |
123 | var pubarr = box.find("div.a-row > span.a-size-small"); |
122 | box.find("div.a-row > a.a-link-normal").each(function (j) { |
124 | box.find("div.a-row > a.a-link-normal").each(function (j) { |
123 | item = {}; |
125 | item = {}; |
124 | item['name'] = $(this).text().trim(); |
126 | item['name'] = $(this).text().trim(); |
125 | item['path'] = $(this).attr('href').trim(); |
127 | item['path'] = $(this).attr('href').trim().replace(/ref=.*/, ''); |
126 | item['url'] = 'https://www.amazon.co.jp' + item['path']; |
128 | item['url'] = 'https://www.amazon.co.jp' + item['path']; |
127 | item['date'] = dateText; |
129 | item['date'] = dateText; |
128 | item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, ''); |
130 | item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, '').replace(/ +/g, ' '); |
129 | item['price'] = $(this).parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().find(".order-info").find(".a-span2").find(".a-size-base").find(".value").text().trim(); |
131 | item['price'] = $(this).parent().parent().find("span.a-color-price").text().trim(); |
130 | items.push(item); |
132 | items.push(item); |
131 | }); |
133 | }); |
132 | |
134 | |
133 | var priceText = jQuery(box.find('div.order-info span.value')[1]).text(); |
135 | var priceText = jQuery(box.find('div.order-info span.value')[1]).text(); |
134 | var price = Number(priceText.match(/[0-9]/g).join('')); |
136 | var price = 0; |
| |
137 | if (priceText.match(/[0-9]/g) != null) { |
| |
138 | price = Number(priceText.match(/[0-9]/g).join('')); |
| |
139 | } |
| |
140 | |
| |
141 | if (verbose) |
| |
142 | console.log(item, price); |
| |
143 | results.push({ 'date': dateText, 'items': items, 'price': price }); |
| |
144 | } |
| |
145 | }); |
| |
146 | } |
| |
147 | |
| |
148 | function parsePage2(data: string, results: any[], verbose: boolean) { |
| |
149 | var dom = jQuery.parseHTML(data); |
| |
150 | |
| |
151 | var dateText = jQuery(dom).find('span.order-date-invoice-item').first().text().trim(); |
| |
152 | var items = []; |
| |
153 | var item = {}; |
| |
154 | jQuery(dom).find('div.shipment-is-delivered').each(function () { |
| |
155 | var box = jQuery(this); |
| |
156 | var pubarr = box.find("div.a-spacing-base"); |
| |
157 | box.find("div.a-row > a.a-link-normal").each(function (j) { |
| |
158 | item = {}; |
| |
159 | item['name'] = $(this).text().trim(); |
| |
160 | //item['path'] = $(this).attr('href').trim(); |
| |
161 | item['path'] = $(this).attr('href').trim().replace(/ref=.*/, ''); |
| |
162 | item['url'] = 'https://www.amazon.co.jp' + item['path']; |
| |
163 | item['date'] = dateText; |
| |
164 | item['author'] = $(pubarr[j * 2]).text().trim().replace(/(\n)/g, '').replace(/ +/g, ' '); |
| |
165 | item['price'] = $(this).parent().parent().find("span.a-color-price").text().trim(); |
| |
166 | items.push(item); |
| |
167 | }); |
| |
168 | }); |
| |
169 | |
| |
170 | var priceText = jQuery(dom).find('div.a-first div.a-fixed-right-grid-inner div.a-span-last span.a-text-bold').text(); |
| |
171 | var price = 0; |
| |
172 | if (priceText.match(/[0-9]/g) != null) { |
| |
173 | price = Number(priceText.match(/[0-9]/g).join('')); |
| |
174 | } |
135 | |
175 | |
136 | if (verbose) console.log(item, price); |
176 | if (verbose) |
| |
177 | console.log(item, price); |
137 | results.push({ 'date': dateText, 'items': items, 'price': price }); |
178 | results.push({ 'date': dateText, 'items': items, 'price': price }); |
| |
179 | } |
| |
180 | |
| |
181 | function load(num: number, verbose: boolean) { |
| |
182 | var df = jQuery.Deferred(); |
| |
183 | var results = []; |
| |
184 | var lnks = []; |
| |
185 | var page = get(num, verbose); |
| |
186 | page.done(function (data: string) { |
| |
187 | parsePage(data, results, lnks, verbose); |
| |
188 | var dfunc = function (url) { |
| |
189 | var df2 = jQuery.Deferred(); |
| |
190 | get_url(url, verbose) |
| |
191 | .done(function (data: string) { |
| |
192 | parsePage2(data, results, verbose); |
| |
193 | df2.resolve(); |
| |
194 | }); |
| |
195 | return df2.promise(); |
| |
196 | }; |
| |
197 | if (lnks.length > 0) { |
| |
198 | var dlist = []; |
| |
199 | for (var url of lnks) { |
| |
200 | dlist.push(dfunc(url)); |
| |
201 | } |
| |
202 | $('#___overlay').text(year.toString() + '年の集計中… / ' + (num + 1) + 'ページ目(サブ' + lnks.length + ')'); |
| |
203 | |
| |
204 | var dwhen = jQuery.when.apply(null, dlist); |
| |
205 | dwhen.done(function () { |
| |
206 | if (results.length <= 0) { df.reject(); } |
| |
207 | else { df.resolve(results); } |
| |
208 | }) |
| |
209 | .fail(function(){ |
| |
210 | $('#___overlay').text(year.toString() + '年の集計中… / ' + (num + 1) + 'ページ目(サブ 失敗)'); |
| |
211 | setTimeout(() => { |
| |
212 | if (verbose) console.log("fail"); |
| |
213 | }, 500); |
138 | }); |
214 | }); |
139 | |
215 | |
140 | if (results.length <= 0) df.reject(); |
216 | } else { |
141 | else df.resolve(results); |
217 | if (results.length <= 0) { df.reject(); } |
| |
218 | else { df.resolve(results); } |
| |
219 | } |
142 | }); |
220 | }); |
| |
221 | |
143 | return df.promise(); |
222 | return df.promise(); |
144 | } |
223 | } |
145 | |
224 | |
146 | function get(num: number, verbose: boolean) { |
225 | function get(num: number, verbose: boolean) { |
| |
226 | var url = 'https://www.amazon.co.jp/gp/css/order-history?digitalOrders=1&unifiedOrders=1&orderFilter=year-' + year.toString() + '&startIndex=' + num * 10; |
| |
227 | return get_url(url, verbose); |
| |
228 | } |
| |
229 | |
| |
230 | function get_url(strUrl: string, verbose: boolean) { |
147 | var df = jQuery.Deferred(); |
231 | var df = jQuery.Deferred(); |
148 | jQuery.ajax({ |
232 | jQuery.ajax({ |
149 | url: 'https://www.amazon.co.jp/gp/css/order-history?digitalOrders=1&unifiedOrders=1&orderFilter=year-' + year + '&startIndex=' + num * 10, |
233 | url: strUrl, |
150 | beforeSend: function (xhr) { |
234 | beforeSend: function (xhr) { |
151 | //xhr.setRequestHeader('X-Requested-With', { toString: function () { return ''; } }); |
| |
152 | function toString(): string{ return ''; } |
235 | function toString(): string { return ''; } |
153 | xhr.setRequestHeader('X-Requested-With', 'toString' ); |
236 | xhr.setRequestHeader('X-Requested-With', 'toString'); |
154 | //xhr.setRequestHeader('X-Requested-With', '');//NG |
| |
155 | }, |
237 | }, |
156 | success: function (data) { |
238 | success: function (data) { |
157 | df.resolve(data); |
239 | df.resolve(data); |
158 | } |
240 | } |
159 | }) |
241 | }) |
160 | .fail(function (jqXHR, msg) { |
242 | .fail(function (jqXHR, msg) { |
161 | if (verbose) console.log("fail", msg); |
243 | if (verbose) console.log("fail", msg); |
162 | }); |
244 | }); |
163 | return df.promise(); |
245 | return df.promise(); |
164 | } |
246 | } |
165 | |
247 | |
166 | if (typeof jQuery !== 'function') { |
248 | if (typeof jQuery !== 'function') { |
167 | var d = document; |
249 | var d = document; |
168 | var s = d.createElement('script'); |
250 | var s = d.createElement('script'); |
169 | s.src = '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'; |
251 | s.src = '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'; |
170 | s.onload = e => { init(null); } |
252 | s.onload = e => { init(null); } |
171 | d.body.appendChild(s); |
253 | d.body.appendChild(s); |
172 | } else { |
254 | } else { |
173 | init(null); |
255 | init(null); |
174 | } |
256 | } |
175 | })(); |
257 | })(); |