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