宮島弥山山頂から |
久しぶりに宮島の弥山に行ってきました。暑い夏の登山は結構疲れます。
さて,前回はローカルPCファイルからGoogleドライブへの一括アップロードでしたが,今回はGoogleドライブからローカルPCへの一括ダウンロードになります。
ブックマークレットの検証で暫く時間をとられましたが,GASのダウンロードについて,ある程度知見を得ましたので投稿します。
業務の自動化に関するシステムを作成していると,どうしてもGoogleドライブとローカルPC間で多数のファイルのやり取りが発生する場合があります。
Googleドライブからダウンロードする簡単な方法としては,GoogleドライブファイルのダウンロードURLを取得してwindow.open関数を利用する方法が考えられます。
しかし,window.open関数ではポップアップブロック問題があるため,どうしても1ファイル毎に手動によるダウンロード指示が入ります。
これでは,一括してファイルをダウンロードしたい場合に具合が悪く自動化に適しません。
そこで,今回は前回と同様にBase64エンコードを利用したやり方で,複数ファイルを一括してダウンロードしてみたいと思います。
今回のGASスクリプトのあらまし
今回作成するスクリプトの外観図は以下のようになります。
GASダウンロード外観図 |
今回はスプレッドシート上に,フォルダID・MIMEタイプ・ファイル名・ファイルID・ダウンロードURL等を表示し,ダウンロードしたいファイルを指定できるようにしました。
スクリプトで全てコード化してもいいのですが,スプレッドシートを利用することで,該当するフォルダを変えるのも簡単ですし,ダウンロードファイルを選択することも可能になります。
なお,今回はスプレッドシートやドキュメントをダウンロードの対象にしていません。これらのファイルは,Blobオブジェクトを取得した時点でPDFに自動的に変換されますので,今回はそのままの状態でダウンロードできるファイルに限定しました。
但し,ドライブに格納されているPDFファイルは対象にしています。
シーケンスフローについて
今回のシーケンスフローは以下のとおりです。
①起動(GASスクリプト起動)
スプレッドシートにダウンロードしたいファイルが属するフォルダIDとMIMEタイプを入力して,ファイル検索ボタンを押下します。これによりダウンロード対象ファイルをスプレッドシート上に表示するためのGASスクリプトが起動します。
②表示(スプレッドシートを表示する)
指定されたフォルダから,ダウンロード対象ファイル抽出して,スプレッドシート上に表示します。
スプレッドシートのイメージは,以下のとおりです。
スプレッドシートのイメージ図 |
③ダウンロードファイル選択(スプレッドシート)
ダウンロードしたいファイルをチェックします。
④ダウンロード実行(GASスクリプト起動)
ダウンロード実行ボタンを押すことで,該当するGASスクリプトを起動します。これにより,ダウンロードするファイルの存在を確認します。
ダウンロード実行ボタンを押すことで,該当するGASスクリプトを起動します。これにより,ダウンロードするファイルの存在を確認します。
⑤モーダルダイアログの表示(HTML側)
ダウンロードするファイルが存在すれば,簡易ブラウザであるモーダルダイアログを表示します。
モーダルダイアログのイメージは次のとおりです。
モーダルダイアログ図 |
⑥ダウンロード開始(GASスクリプト起動)
モーダルダイアログのダウンロード開始ボタンを押下することで,「google.script.run」関数
を使ってダウンロードファイル情報を収集するスクリプトを起動します。
⑦ダウンロード情報の取得(HTML側)
「google.script.run」関数
のリターン値からダウンロードファイル情報群を取得します。
⑧ダウンロードの実行(HTML側)と後処理(GAS側)
ダウンロードファイル情報群から,HTML側のa要素を使ってローカルPCに一括ダウンロードします。
また,ダウンロード終了時にgoogle.script.run.関数
を使ってGASスクリプトを呼びだし,スプレッドシート上のダウンロードファイル指定を解除します。
この流れに従って,コードを作成します。
スプレッドシートを表示するGASスクリプト
まずは,スプレッドシートを表示するGASスクリプト(①②)のコーディングです。このスクリプトはスプレッドシートの「ファイル検索ボタン」に割り付けます。
main.gs
function main() {
//スプレッドシートから検索するフォルダーIDと検索するMIMETYPEを読み込む
//Activeシートオブジェクト指定
var sheet = SpreadsheetApp.getActiveSheet(); //スプレッドシートクラス指定
//スプレッドシートからフォルダーIDを読み込む
var folder_id = sheet.getRange(2,3).getValue();
console.log(folder_id);
//エラーメッセージ欄をクリアする。
sheet.getRange(2,4).setValue(" ");
sheet.getRange(4,4).setValue(" ");
//フォルダーIDからフォルダーオブジェクトを取得する
var folder = getfolderobj(folder_id);
if(folder == "err"){
sheet.getRange(2,4).setValue("フォルダーIDがありません。再度実行して下さい。");
console.log("フォルダーIDがありません。"); //エラー表示
return;
}
console.log(folder.getName()); //現在のドライブ
console.log(folder.getId()); //現在のドライブ
//スプレッドシートからMIMEタイプを読み込む
var mime_type = sheet.getRange(4,3).getValue();
if (mime_type == ""){
sheet.getRange(4,4).setValue("MIMEタイプがありません。指定して下さい。");
console.log("MIMEタイプがありません。"); //エラー表示
return;
}
//MIMEタイプを配列に分解する
var mime_types = mime_type.split(/,/g);
console.log(mime_types.length);
//検索条件文字列を初期化する
var serch_cond = "";
//MIME_TYPEから検索条件文字列を編集する。
for(let i=0; i < mime_types.length; i++){
switch(mime_types[i]){
case "text/plain":
case "text/csv":
case "text/html":
case "application/pdf":
case "image/jpeg":
case "image/png":
if ( i == 0 ){
serch_cond = serch_cond + 'mimeType = "' + mime_types[i] + '"';
}
else{
serch_cond = serch_cond + ' or mimeType = "' + mime_types[i] + '"';
}
continue;
default:
sheet.getRange(4,4).setValue("指定できないMIMEタイプがあります。ご確認下さい。");
console.log("使えないMIMEタイプがあります。"); //エラー表示
return;
}
}
console.log("serch_cond=" + serch_cond);
let row_count = sheet.getLastRow() -7; //HEADER7行分を除く
let col_count = sheet.getLastColumn() -1; //利用カラムの最大数
if(row_count > 0){
//8行目から最終行までをクリア
sheet.getRange(8, 1, row_count, col_count+1).clear({contentsOnly: true, skipFilteredRows: true});
}
let row = 8;
let counter = 1;
//該当フォルダー下の該当するファイルのコレクションを取得する
const files = folder.searchFiles(serch_cond);
//console.log(files);
while (files.hasNext()){ //コレクションファイルが存在する間繰り返す
var file =files.next(); //コレクションファイルを読む
console.log(file.getName()); //ファイル名を出力する
//ファイル名,ファイルID,ファイルURLを編集する
let col = 2;
while (col < 7){ //配列の数だけ繰り返す
sheet.getRange(row,col).setBorder(true,true,true,true,true,true); //セル上下左右罫線書く
sheet.getRange(row,col).setVerticalAlignment('middle'); //セル垂直中央に設定
switch(col){
case 2: //項番を編集する
sheet.getRange(row,col).setHorizontalAlignment('center'); //セル水平中央に設定
sheet.getRange(row,col).setValue(counter);
break;
case 3: //ファイル名を編集する
sheet.getRange(row,col).setValue(file.getName());
break;
case 4: //ファイルIDを編集する
sheet.getRange(row,col).setWrap(true); //セル内折り返し設定
sheet.getRange(row,col).setValue(file.getId()); //ファイルIDを編集する
break;
case 5: //ファイルURLを編集する
sheet.getRange(row,col).setWrap(true); //セル内折り返し設定
sheet.getRange(row,col).setValue(file.getDownloadUrl()); //ファイルDWLURLを編集する
break;
case 6:
sheet.getRange(row,col).setHorizontalAlignment('center'); //セル水平中央に設定
sheet.getRange(row,col).insertCheckboxes();
}
col++;
}
row++;
counter++;
}
SpreadsheetApp.flush();
return;
}
function getfolderobj(folder_id) {
try {
return DriveApp.getFolderById(folder_id);
}
catch(e) {
return "err";
}
}
【補足】
このスクリプトは,単に与えらたフォルダーID下にあるファイルを,MIMEタイプに従って検索し,スプレッドシートに表示しています。
表示項目としては,ファイル毎に項番,ファイル名,ファイルID,ダウンロードURL,チェックボックスとなっています。ファイルダウンロードURLなどは必要ないのですが,学習のために取得表示しています。
所々に「console.log」というデバッグ用の記述がありますが,無視してください。
ダウンロードファイルの存在確認とモーダルダイアログの表示
次に,スプレッドシートのダウンロード実行ボタンを押下することでダウンロードファイルの存在確認とモーダルダイアログを表示します。
GASスクリプトとHTMLスクリプト(④⑤)のコードは以下のとおりです。
dwl_make.gs
function dwl_perform(){
// ダウンロードファイルの存在を確認する
var files = dwl_exist();
if(files[0][0] == ""){
Browser.msgBox("ダウンロードファイルが指定されていません");
return;
}
//htmlを起動する。
var output = HtmlService.createHtmlOutputFromFile("index");
SpreadsheetApp.getUi().showModalDialog(output, 'ダウンロードファイル指示');
}
function dwl_exist(){
//データ配列の初期化
var files = [["","","","",""]];
//Activeシートオブジェクト指定
var sheet = SpreadsheetApp.getActiveSheet(); //スプレッドシートクラス指定
//ダウンロードファイルが指示されているか,確認する。
let row_count = sheet.getLastRow() -7; //HEADER7行分を除く
let col_count = sheet.getLastColumn() -1; //利用カラムの最大数
if(row_count < 1){
Browser.msgBox("ダウンロードすべきファイルがありません,再度検索実行後指示して下さい");
return;
}
//ダウンロード指示されたファイルを探す
let i = 0;
var dwl_filename = "";
var dwl_fileid = "";
var dwl_fileurl = "";
let row = 8;
//ダウンロードファイル名,ダウンロードURL,zダウンロードIDを取得する
while(row < (row_count + 8)){
if(sheet.getRange(row, 6).getValue() == true){ //ダウンロード指示有を確認
dwl_filename = sheet.getRange(row,3).getValue(); //ファイル名を退避
dwl_fileid = sheet.getRange(row,4).getValue(); //ファイルIDを退避
dwl_fileurl = sheet.getRange(row,5).getValue(); //ファイルURLを退避
files[i] = [dwl_filename, dwl_fileid, dwl_fileurl, row, ""];
file_obj = DriveApp.getFileById(dwl_fileid);
blob_obj = file_obj.getBlob();
files[i][4] = `data:${blob_obj.getContentType()};base64,${Utilities.base64Encode(blob_obj.getBytes())}`;
console.log(files[i][4]);
i = i + 1;
}
row = row + 1;
}
return files;
}
【補足】
このスクリプトは,ダウンロードファイルの存在を確認し,存在すればモーダルダイアログを表示しています。
dwl_exist関数は,スプレッドシート内を検索してダウンロード指示されたファイルの情報を「files」という2次元配列に格納しています。
この中で最も注意すべきは,「files」の各行毎(ファイル毎)にファイルの中身をBase64エンコードしたデータURL形式データとして配列内に格納している点です。
データ形式は以下のようになります。
data:${blob_obj.getContentType()};base64,${Utilities.base64Encode(blob_obj.getBytes())}
この式では,$マークを使って変数を文字列として扱っていますので,「`」(バッククォート)で囲むことに注意してください。
このスクリプトをスプレッドシートの「ダウンロード実行ボタン」に割り付けます。
次に,モーダルダイアログを表示するコードを作ります。
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
#drop {
height: 50px;
border: #D4D4D4;
border-style: dashed;
border-width: 3px;
padding: 10px;
text-align: center;
}
#click {
text-align: right;
}
#result {
text-align: center;
}
</style>
</head>
<body>
<div>
<div id="drop">
<p>チェックしたファイルをダウンロードします
<button id="Dwl_button" type="button">ダウンロード開始</button>
</p>
</div>
<hr />
<div id="click">
<button id="click_1" type="button" >実行</button>
<button id="click_2" type="button" >終了</button>
</div>
<div>
<pre id="result"></pre>
</div>
</div>
</body>
</html>
【補足】
モーダルダイアログのHTMLについては,イベントドリブン型でjavascriptが記述できるようにしています。
ダウンロードファイル情報の取得
次に,モーダルダイアログの「ダウンロード開始ボタン」を押下することで,ダウンロードファイル情報を取得するjavascriptとGASスクリプト(⑥⑦)コードを作ります。
GASとの連携には,「google.script.run」関数を使って任意のGAS関数を起動します。
なお,このスクリプトには日付を組み立てる関数「getToday()」とスクリプトを終了する関数「spread_syuryo()」を加えています。
このjavascriptをindex.htmlの</html>の前に挿入します。
index.html
<script>
//退避エリアを初期値する
var dwl_files = [["","","","",""]];
window.addEventListener('DOMContentLoaded', function() {
document.getElementById("Dwl_button").addEventListener('click', function(e){
//引数エリアを初期化する。
var files = [["","","","",""]];
//ダウンロードファイルの情報を取得する
google.script.run
.withSuccessHandler(function(files){
dwl_files = files; //引数エリアを退避する。
//alert("files= " + files[0][0]);
if(files[0][0] === ""){
var msg = "日付=" + getToday() + " ダウンロードファイルはありません。";
msg = msg + "\n\n" + "終了ボタンを押して下さい。";
} else {
var msg = "日付=" + getToday() + " ファイル名=" + files[0][0];
msg = msg + "\n\n" + "ダウンロードします。実行ボタンを押して下さい";
}
document.getElementById('result').textContent = msg;
})
.withFailureHandler(function(err) { //ファイル情報取得に失敗した場合の処理
alert("ダウンロードが失敗しました。\n\nエラーメッセージ=" + err.message);
}).dwl_exist(files);
},true);
//*** 次の関数をココに入れる。 ***
});
function getToday(){
let today = new Date();
today.setDate(today.getDate());
const yyyy = today.getFullYear();
const mm = ("0"+(today.getMonth()+1)).slice(-2);
const dd = ("0"+today.getDate()).slice(-2);
const result = yyyy+'-'+mm+'-'+dd;
return result;
}
function spread_syuryo(){
// スプレッドシートで、アップロードUIを自動的に閉じる
google.script.host.close();
}
</script>
【補足】
このスクリプトは,イベントドリブン型のスクリプト関数になっています。「window.addEventListener('DOMContentLoaded',
function()」でページが開くのを待って,「ダウンロード開始ボタン」がクリックされると起動します。
「google.script.run」関数を使って,GAS側の「dwl_exist()」関数を呼びだして,ダウンロードファイル情報を「files」という2次元配列で受け取っています。
ダウンロードするファイルがあれば,その1番目をモーダルダイアログに表示して,実行ボタンを押すように促します。
「dwl_exist()」関数の起動に失敗した場合は,エラーメッセージを出力します。
ダウンロードの実行とスプレッドシートの後処理
ダウンロードの実行(HTML側)(⑧)とスプレッドシートの後処理(GAS側)のjavascriptとGASスクリプトのコードを作ります。
モーダルダイアログの「実行ボタン」を押すことで動作します。また,「終了ボタン」を押すとスクリプト終了関数が動作します。
以下に,HTML側のjavascriptのコードを記載します。このコードは,前述の「document.getElementById("Dwl_button").addEventListener('click',
function(e)」の次の関数として設定します。
index.html
document.getElementById("click_1").addEventListener('click', async function(e){
// 退避エリアから引数を戻す
files = dwl_files;
if(files[0][0] === ""){
alert("ダウンロードファイルがありません。ダウンロード開始ボタンを押して下さい。")
return;
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms || 1000));
for( let i = 0; i < files.length; i++){
var msg = "日付=" + getToday() + " ファイル名=" + files[i][0];
msg = msg + "\n\n" + "ダウンロードします。実行ボタンを押して下さい";
document.getElementById('result').textContent = msg;
let a = document.createElement("a");
document.body.appendChild(a);
a.download = files[i][0];
a.href = files[i][4];
a.click();
var ms = 3000;
//alert("時間待ち" + ms.toString());
await sleep(ms);
//alert("秒経過=" + ms.toString());
}
google.script.run
.withSuccessHandler(function(files){
dwl_files = files; //引数エリアを退避する。
var msg = "日付=" + getToday() + " ダウンロードは終了しました。";
msg = msg + "\n\n" + "終了ボタンを押して下さい。";
document.getElementById('result').textContent = msg;
})
.withFailureHandler(function(err) { //ファイル情報取得に失敗した場合の処理
alert("ダウンロード終了処理が失敗しました。\n\nエラーメッセージ=" + err.message);
}).dwlfile_syuryo(files);
},true);
document.getElementById("click_2").addEventListener('click', function(e){
//alert("終了ボタンが押されました");
spread_syuryo()
},true);
【補足】
「document.getElementById("click_1").addEventListener('click', async
function(e)」の関数は,「await関数」を使っているため,async宣言をしていることにご注意ください。
また,GAS側より受け取った二次元配列「files」を利用して,body要素にa要素を追加し,
downloadを繰り返しています。
download先のローカルPCフォルダは,ブラウザのダウンロード設定項目で指定できます。ブラウザからダウンロード許可を求められた場合は,許可してください。
a要素のhref属性に,Base64エンコードで作成したデータURLを設定しています。なお,download間隔をある程度保つため,3秒間隔で処理しています。
sleep関数は,「const sleep = ms => new Promise(resolve =>
setTimeout(resolve, ms || 1000));」で定義しています。
なお,終了ボタンが押された場合は,「document.getElementById("click_2").addEventListener('click',
function(e)」が動作します。
次に,「google.script.run」関数で呼ばれるGASの関数「dwlfile_syuryo(files)」のコードを以下に示します。
dwl_make.gs
function dwlfile_syuryo(files){
//Activeシートオブジェクト指定
var sheet = SpreadsheetApp.getActiveSheet(); //スプレッドシートクラス指定
console.log("ファイル名 = " + files[0][0]);
console.log("ファイルID = " + files[0][1]);
console.log("ファイルURL= " + files[0][2]);
console.log("row= " + files[0][3]);
console.log("ファイルDate =" + files[0][4]);
//ダウンロード処理済みのファイルがある時は,チェックを消す。
for(let i = 0; i < files.length; i++){
if(files != undefined && files[i][0] != ""){
sheet.getRange(files[i][3],6,1,1).clear({contentsOnly: true, skipFilteredRows: true});
sheet.getRange(files[i][3],7,1,1).clear({contentsOnly: true, skipFilteredRows: true});
sheet.getRange(files[i][3],6,1,1).setHorizontalAlignment('center'); //セル水平中央に設定
sheet.getRange(files[i][3],6,1,1).insertCheckboxes();
}
}
files = [["","","","",""]];
return files;
}
【補足】
この関数では,スプレッドシート上のダウンロードファイル指定をクリアしています。
まとめ
今回は,前回のアップロードの反対に複数ファイルを一括でダウンロードする方法を学習しました。
a要素を使ってダウンロードし,実用的であるとは思います。
なお,ダウンロードURLが取得出来るので,HTTPリクエストのUrlfetchAppクラスでも可能ではないかと思いますが,今後の研究課題とします。
今回は,時間調整としてsetTimeout関数を利用しています。Promiseを使った時間待ちでは,async宣言関数の中だけ,時間待ちすることができるようです。この点は,注意して使う必要があると感じました。
まだまだ,わからないことが多いですが,一応,ローカルPCとGoogleドライブ間連携の目途が立ちました。
少し,長くなりましたが,以上です。
それでは,楽しいITリテラシーライフをお過ごしください。
(ご注意)情報の正確性を期していますが,実施される場合には自己責任でお願いします。
【追伸】
最後に,ダウンロードURLを使って,UrlfetchApp.fetch()関数からGoogleドライブ内のファイルを読み取ろうと試みましたが,ログイン画面がリターンされ読み取ることができませんした。
過去情報では,ScriptApp.getOAuthToken()でTokenを得て,UrlfetchApp.fetch()のオプションに指定すれば出来るようでしたが,効果ありませんでした。
知識が足りないのかもしれませんが,調査に時間がかかりますので断念することにしました。UrlfetchApp.fetch()を利用しなくても,DriveApp.getFileById(”ファイルID”).getBlob()でファイルを直接取得することができますので,特に問題ないと考えます。
0 件のコメント:
コメントを投稿