URL、フォルダ名を書き換えれば好きに使える。
連番がゼロ埋めの固定桁の場合は createUrl(i) をカスタマイズする。
連番を1から始め、HTTPステータス404 Not Foundが返ってくるまでファイルをダウンロードする。
なお、本スクリプトを実行すると、Internet Explorerに履歴が残る。
AsyncDownload.js
var folderName = 'MyDownloadFolder';
var baseUrl = 'https://aaaa/bbbb/cccc/6.jpg';
baseUrl = baseUrl.replace(/([^\/]*)\d+([^\/]*)\.(jpg|png)$/i, '$1#$2.$3');
function createUrl(i) {
return baseUrl.replace('#', i);
}
var endIndex=999;
var maxThreads = 35;
var timeoutMsec = 90 * 1000;
var waitTimeMsec = 100;
var requestedMaxIndex = 0;
var arrayLength = endIndex + 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);
var folderPath = createFolder(downloadFolderName);
prcsStartTime = new Date();
requestAll(folderPath);
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) {
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);
}
}
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;
}
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;
}
}
};
var t = timeoutMsec;
http.setTimeouts(t, t, t, t);
try {
http.open('GET', url, true);
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) {
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();
} 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 {
xhr[i].waitForResponse(waitTimeMsec);
if(waitOne) {
return;
}
} catch(e) {
handleError(e, i);
} finally {
}
}
}
}
var WININET_E_TIMEOUT = 0x80072EE2;
var WININET_E_INVALID_URL = 0x80072EE5;
var WININET_E_UNRECOGNIZED_SCHEME = 0x80072EE6;
var WININET_E_NAME_NOT_RESOLVED = 0x80072EE7;
var WININET_E_PROTOCOL_NOT_FOUND = 0x80072EE8;
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:
break;
}
try {
putLog2(i, quote(info[i].url) + ' ' + errMsg);
} catch(e) {}
abortWork(i);
}
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();
} catch (e) {}
}
} finally {
active[i] = false;
}
}
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();
var prcsEndTime = new Date();
var elapsedTime = Math.round(((prcsEndTime - prcsStartTime) / 1000)*10)/10;
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++;
}
}
}
}
ret.total = ret.fileCount + ret.errorCount;
return ret;
}
function putLog(str) {
putLog2(0, str);
}
function putLog2(index, str) {
}
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();