URL、フォルダ名を書き換えれば好きに使える。
連番がゼロ埋めの固定桁の場合は createUrl(i) をカスタマイズする。
連番を1から始め、HTTPステータス404 Not Foundが返ってくるまでファイルをダウンロードする。
なお、本スクリプトを実行すると、Internet Explorerに履歴が残る。
AsyncDownload.js
//フォルダ名、URLを書き換えて実行 var folderName = 'MyDownloadFolder'; var baseUrl = 'https://aaaa/bbbb/cccc/6.jpg'; //URLのファイル名の数字部を '#' に変換。 baseUrl = baseUrl.replace(/([^\/]*)\d+([^\/]*)\.(jpg|png)$/i, '$1#$2.$3'); //例)http://aaa/xxx/123.jpg --> http://aaa/xxx/#.jpg //連番からURLを作成する関数 (カスタマイズ用) function createUrl(i) { /* 簡単な処理の例 return 'http://xxxx/xxxx/' + i + '.jpg' return baseUrl + i + '.jpg' ゼロ埋めする例 (URLが http://xxxx/xxxx/b0041.jpg のような場合) var seq = ('0000' + i).slice(-4); //例: 23 を '0023' に編集 return 'http://xxxx/xxxx/b' + seq + '.jpg' */ return baseUrl.replace('#', i); } //連番の最大値。ただし HTTP Status 404 Not Found が返ってきた場合、 //この値を更新し後続のHTTP要求を停止させている var endIndex=999; //詳細ログを出力する場合、@debug_log に true を設定する /*@cc_on @*/ /*@set @debug_log = false @*/ var maxThreads = 35; var timeoutMsec = 90 * 1000; var waitTimeMsec = 100; var requestedMaxIndex = 0; //requestDownload 関数を呼び出した最大のインデックスを格納 var arrayLength = endIndex + 1; //配列はインデックス 1 の要素から使用する var active = fillValues(new Array(arrayLength), false); var httpStatus = fillValues(new Array(arrayLength), 0); var xhr = new Array(arrayLength); var info = new Array(arrayLength); var fso = new ActiveXObject('Scripting.FileSystemObject'); var logFilePath = WScript.ScriptFullName.replace(/\.[^.]+$/, '.log'); var errLogFilePath = WScript.ScriptFullName.replace(/\.[^.]+$/, '.Error.log'); var prcsStartTime = void 0; function main() { deleteFile(logFilePath); deleteFile(errLogFilePath); /*@if(@debug_log) putLog('START'); @end @*/ //ダウロード用のフォルダを作成(カレントフォルダ内) var folderPath = createFolder(downloadFolderName); prcsStartTime = new Date(); //HTTP要求の発行 requestAll(folderPath); //HTTPステータス404が返される前、先んじて発行されたHTTP要求を取り消す cancelVainRequests(); function cancelVainRequests() { var i; for(i = endIndex + 1; i <= requestedMaxIndex; i++) { if(active[i]) { abortWork(i); } } } //すべてのスレッドが完了するまで待つ waitForCompletion(); var i; for(i = 0; i < 3; i++) { retryDownload(); //すべてのスレッドが完了するまで待つ waitForCompletion(); } reportSummary(); } function requestAll(folderPath) { //URLからファイル名を抜き出すための正規表現オブジェクト var reFileName = /[^\/]+$/; var i; for(i = 1; i <= endIndex; i++) { var url = createUrl(i); var fileName = url.match(reFileName)[0]; var filePath = fso.BuildPath(folderPath, fileName); endIndex = getNewEndIndex(); while(countActiveThread() >= maxThreads) { waitAnyone(); endIndex = getNewEndIndex(); if(i > endIndex) { break; } } if(i > endIndex) { break; } info[i] = new ProcessInfo(url, filePath); requestDownload(url, filePath, i); } /*@if(@debug_log) putLog('**** All Url requested. ****'); @end @*/ } function retryDownload() { var i; for(i = 1; i <= endIndex; i++) { if(info[i] != null) { if(!fso.FileExists(info[i].filePath)) { requestDownload(info[i].url, info[i].filePath, i); } } } } function requestDownload(url, filePath, index) { if(index > requestedMaxIndex) { requestedMaxIndex = index; //この関数を呼び出した最大のインデックスを格納 } //HTTP通信を行うためのオブジェクト var http = getHttpObject(); xhr[index] = http; active[index] = true; info[index].startTime = new Date(); //コールバックを登録する http.onreadystatechange = function () { if (http.readyState == 4) { try { httpStatus[index] = http.status; if (http.status == 200) { var adTypeBinary = 1, adSaveCreateOverWrite = 2; //バイナリファイルを保存するためのオブジェクト var st = new ActiveXObject('Adodb.Stream'); //保存する st.Type = adTypeBinary; st.Open(); st.Write(http.responseBody); //書き込み st.Savetofile(filePath, adSaveCreateOverWrite); //保存 st.Close(); } } finally { active[index] = false; /*@if(@debug_log) endTime[index] = new Date(); varsInfoAtEnd[index] = varsToString(); @end @*/ } } //サブスレッドで動くのはこのコールバックのみ // //注意 //サブスレッドでは競合の恐れがあるグローバル変数やグローバルオブジェクトを変更するような関数、 //putLogやputLog2などのログ関数などをコールしないこと }; var t = timeoutMsec; http.setTimeouts(t, t, t, t); try { http.open('GET', url, true); //http.setRequestHeader('Content-type', 'image/jpeg'); //http.setRequestHeader('Content-type', 'image/png'); /*@if(@debug_log) putLog3(index, 'send', url); @end @*/ http.send(null); //送信 } catch(e){ handleError(e, index) } } function getHttpObject() { var i; for(i = 1; i <= requestedMaxIndex; i++) { if(!active[i] && xhr[i] != null) { var http = xhr[i]; xhr[i] = null; try { xhr[i].abort(); } catch(e){} return http; } } return new ActiveXObject('Msxml2.ServerXMLHTTP'); } function deleteFile(filePath) { try { if(fso.FileExists(filePath)) { fso.DeleteFile(filePath, true); } } catch(e) { WScript.Echo(createErrorMessage(e) + '\n\n' + quote(filePath) + ' を削除できません'); WScript.Quit(getErrorCode(e)); } } function createFolder(folderName) { var basePath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), folderName); var folderPath = basePath; var i = 1; while(fso.FolderExists(folderPath) || fso.FileExists(folderPath)) { i++; folderPath = basePath + ' (' + i + ')'; } fso.CreateFolder(folderPath); return folderPath; } function toHex(n) { return (n >>> 0).toString(16).toUpperCase() } function fillValues(array, value) { var i; for(i = 0; i < array.length; i++) { array[i] = value; } return array; } function ProcessInfo(url, filePath) { this.url = url; this.filePath = filePath; } function getNewEndIndex() { var i; for(i = 1; i <= requestedMaxIndex && i < endIndex; i++) { if(httpStatus[i] == 404) { /*@if(@debug_log) putLog('[endIndex] will be updated by ' + i); @end @*/ return i - 1; } } return endIndex; } function countActiveThread() { var count = 0; var i; for(i = 1; i <= requestedMaxIndex; i++) { if(active[i]) { count++; } } return count; } function waitForCompletion() { do { waitAll(); /*@if(@debug_log) putLog('**** waitForCompletion Loop ****'); @end @*/ } while(countActiveThread() != 0) } function waitAnyone() { wait(true); } function waitAll() { wait(false); } function wait(waitOne) { var i; for(i = 1; i <= requestedMaxIndex; i++) { if(active[i] && xhr[i] != null) { try { /*@if(@debug_log) putLog2(i, 'waitForResponse(' + timeoutMsec + 'msec) START'); @end @*/ xhr[i].waitForResponse(waitTimeMsec); if(waitOne) { return; } } catch(e) { handleError(e, i); } finally { /*@if(@debug_log) putLog2(i, 'waitForResponse(' + timeoutMsec + 'msec) END '); @end @*/ } } } } //ErrorCode var WININET_E_TIMEOUT = 0x80072EE2; //The operation timed out. var WININET_E_INVALID_URL = 0x80072EE5; //The URL is invalid. var WININET_E_UNRECOGNIZED_SCHEME = 0x80072EE6; //The URL does not use a recognized protocol. var WININET_E_NAME_NOT_RESOLVED = 0x80072EE7; //The server name or address could not be resolved. var WININET_E_PROTOCOL_NOT_FOUND = 0x80072EE8; //A protocol with the required capabilities was not found. function handleError(e, i) { var errMsg = createErrorMessage(e); switch(e.number >>> 0) { case WININET_E_INVALID_URL: case WININET_E_UNRECOGNIZED_SCHEME: case WININET_E_NAME_NOT_RESOLVED: case WININET_E_PROTOCOL_NOT_FOUND: WScript.Echo(quote(info[i].url) + '\n\n' + errMsg); WScript.Quit(getErrorCode(e)); break; case WININET_E_TIMEOUT: /*@if(@debug_log) if (i > 0) { putLog2(i, 'Timeout'); } @end @*/ break; } try { putLog2(i, quote(info[i].url) + ' ' + errMsg); } catch(e) {} abortWork(i); /*@if(@debug_log) putLog3(i, 'FAILED', info[i].url); @end @*/ } function createErrorMessage(e) { var description = e.description.replace(/\s+$/g, ''); switch(e.number >>> 0) { case WININET_E_UNRECOGNIZED_SCHEME: description = 'The URL does not use a recognized protocol'; } return 'Error: ' + description + ' (0x' + toHex(e.number) + ')'; } function getErrorCode(e) { return e.number & 0xFFFF; } function abortWork(i) { try { if(xhr[i]) { try { xhr[i].abort(); /*@if(@debug_log) abortTime[i] = new Date(); @end @*/ } catch (e) {} } } finally { active[i] = false; /*@if(@debug_log) putLog3(i, 'Abort', info[i].url); @end @*/ } } function reportSummary() { var tsErrLog = void 0; var errorReporter = function (msg) { try { var ForWriting = 2, Unicode = -1; tsErrLog = tsErrLog || fso.OpenTextFile(errLogFilePath, ForWriting, true, Unicode); tsErrLog.WriteLine(msg); } catch(e){} } var summary = checkResult(errorReporter); tsErrLog && tsErrLog.Close(); /*@if(@debug_log) emptyLine(); putLog('File Count = ' + summary.fileCount + ' / ' + summary.total + ', Error Download = ' + summary.errorCount); @end @*/ var prcsEndTime = new Date(); var elapsedTime = Math.round(((prcsEndTime - prcsStartTime) / 1000)*10)/10; /*@if(@debug_log) putLog('END (Elapsed Time:' + elapsedTime + 'sec)'); writeLogFile(logFilePath); @end @*/ WScript.Echo('File Count = ' + summary.fileCount + ' / ' + summary.total + (summary.errorCount? ('\n\nError Download = ' + summary.errorCount) : '') + '\n\nElapsed Time = ' + elapsedTime + 's' ); } function checkResult(errLogFunc) { var ret = new Object(); ret.fileCount = 0; ret.errorCount = 0; var i; for(i = 1; i <= requestedMaxIndex; i++) { if(info[i] != null) { if (i == 1 || i <= endIndex) { if(fso.FileExists(info[i].filePath)) { ret.fileCount++; } else { if(typeof(errLogFunc) === 'function') { var errMsg = 'Download Error: ' + info[i].url; errLogFunc && errLogFunc(errMsg); } ret.errorCount++; } } /*@if(@debug_log) if(endTime[i]) { if(httpStatus[i] != 200) { storeLog(endTime[i], false, i, logMsg2(i, 'Returned HTTP Status: ' + httpStatus[i]) ); } var label = fso.FileExists(info[i].filePath) ? 'done' : 'FAILED'; storeLog(endTime[i], false, i, logMsg2(i, logMsg4(label, info[i].url, varsInfoAtEnd[i]))); } @end @*/ } } ret.total = ret.fileCount + ret.errorCount; return ret; } /*@if(@debug_log) var endTime = new Array(arrayLength); var abortTime = new Array(arrayLength); var varsInfoAtEnd = new Array(arrayLength); var logBuf = new Array(); function LogLine(timestamp, fromMainThread, index, logSeqno, message) { this.timestamp = timestamp; this.message = message; this.sortKey1 = timestamp.getTime(); this.sortKey2 = fromMainThread? logSeqno : 0; this.sortKey3 = index; this.sortKey4 = logSeqno; //ログ出力順 //まず、タイムスタンプで並び替える //タイムスタンプが同じなら //サブスレッドのログを先、メインスレッドのログは後 //さらにメインスレッドのログは、ログ登録順 //サブスレッドは、インデックス、ログ登録順 } function storeLog(timestamp, fromMainThread, index, message) { var logSeqno = logBuf.length + 1; var logLine = new LogLine(timestamp, fromMainThread, index, logSeqno, message); logBuf.push(logLine); } function writeLogFile(logFilePath) { sortLogLines(); var ForWriting = 2, Unicode = -1; var ts = fso.OpenTextFile(logFilePath, ForWriting, true, Unicode); var i; for(i = 0; i < logBuf.length; i++) { var s = logBuf[i].message; if(s == null) { ts.WriteLine(); } else { s = dateToString(logBuf[i].timestamp) + ' ' + s; ts.WriteLine(s); } } ts.Close(); } function sortLogLines() { function compareFunc(line1, line2) { return sign(line1.sortKey1 - line2.sortKey1 || line1.sortKey2 - line2.sortKey2 || line1.sortKey3 - line2.sortKey3 || line1.sortKey4 - line2.sortKey4); //参考 //JScript.NET(WSH JScriptではない)では比較関数が -1, 0, 1 の //いずれかの固定値を返さないと 0x80000000以上の値が正しくソートされない } logBuf.sort(compareFunc); } function sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; } @end @*/ function putLog(str) { putLog2(0, str); } function putLog2(index, str) { /*@if(@debug_log) var fromMainThread = true; storeLog(new Date(), fromMainThread, index, logMsg2(index, str)); @else if (str != null){ str = dateToString(new Date()) + ' ' + logMsg2(index, str); } try { var ForAppending = 8, Unicode = -1; var ts = fso.OpenTextFile(logFilePath, ForAppending, true, Unicode); if(str == null) { ts.WriteLine(); } else { ts.WriteLine(str); } ts.Close(); } catch(e){} @end @*/ } function putLog3(index, label, url) { putLog2(index, logMsg3(label, url)); } function logMsg2(index, str) { if (str && index > 0) { return '(' + index + ') ' + str; } else { return str; } } function logMsg3(label, url) { return logMsg4(label, url, varsToString()); } function logMsg4(label, url, varsInfo) { return (label + ' ').slice(0, 8) + quote(url) + ' ' + varsInfo; } function varsToString() { return '[Thread Count=' + padStart(countActiveThread(), ' ', 3) + ', endIndex=' + padStart(endIndex, ' ', 4) + ', requestedMaxIndex=' + padStart(requestedMaxIndex, ' ', 4) + ']'; } function emptyLine() { putLog(void 0); } function quote(s) { return (s == null) ? '' : '\'' + s + '\''; } function padStart(value, padString, targetLength) { var str = String(value); return ('xxxxxxxxxx'.replace(/x/g, padString) + str).slice(-targetLength); } function padZero(value, targetLength) { return padStart(value, '0', targetLength); } function dateToString(dt) { var s = ''; s += dt.getFullYear(); s += '/' + padZero(dt.getMonth() + 1, 2); s += '/' + padZero(dt.getDate(), 2); s += ' ' + padZero(dt.getHours(), 2); s += ':' + padZero(dt.getMinutes(), 2); s += ':' + padZero(dt.getSeconds(), 2); s += '.' + padZero(dt.getMilliseconds(), 3); return s; } main();