tag:crieit.net,2005:https://crieit.net/tags/GoogleDrive/feed
「GoogleDrive」の記事 - Crieit
Crieitでタグ「GoogleDrive」に投稿された最近の記事
2023-12-17T10:55:37+09:00
https://crieit.net/tags/GoogleDrive/feed
tag:crieit.net,2005:PublicArticle/18679
2023-12-17T10:50:09+09:00
2023-12-17T10:55:37+09:00
https://crieit.net/posts/avoid-processing-limit-on-gas
【GAS】処理時間の制限を回避する
<p>こんにちは、しきゆらです。<br />
急な出社が続いてあまり記事を書けていない今日この頃です。<br />
今回は、以前投稿したGASでファイル一覧を取得するコードを載せましたが、実際の環境で処理回したら処理時間の制限に引っかかったので、回避するように直したのでその旨をメモしておきます。<br />
前回の記事はこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/12/avoid_processing_limit_on_gas/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>改めて、GASの制限については以下を参照いただければと思います。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/guides/services/quotas?hl=ja#current_limitations">Google サービスの割り当て | Apps Script | Google for Developers https://developers.google.com/apps-script/guides/services/quotas?hl=ja#current_limitations</a></p>
<p>上記を見ると、スクリプトの制限としては1回の処理で6分までとなっています。 今回は、ここを回避していきます。</p>
<h2 id="回避の方針"><a href="#%E5%9B%9E%E9%81%BF%E3%81%AE%E6%96%B9%E9%87%9D">回避の方針</a></h2>
<p>処理時間の制限としては、処理時間が一定時間を超えたらそこでいったん処理を止める、というだけ。<br />
ただし、それだけだと処理しきれなかった部分が出てくるので、そこもケアしてあげましょう。</p>
<p>ざっくり手順としては以下の通り。</p>
<ul>
<li>定期的に処理時間の確認し、一定時間を超えていたら処理を止める</li>
<li>処理途中のデータを保存する</li>
<li>処理を続きから実行するためのトリガーを定義する</li>
<li>一時停止した場合に前回の処理から再開する</li>
</ul>
<p>それぞれ見ていきましょう。</p>
<p>なお、スクリプトのよって内容が変わると思いますが、ここでの例として前回のファイル・フォルダの一覧を取得する、というものに対してコードを書いていきます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>ファイル・フォルダ一覧を取得するコードはこんな感じ。</p>
<pre><code class="javascript">function allFiles(){
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
const files = []; // ファイルオブジェクトを保持する配列
const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
folders.push(...allFilesBy(folder));
}
}
return files;
}
function allFilesBy(folder) {
const files = []; // 対象となるフォルダにあるファイルオブジェクトを保持する配列
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
return files;
}
</code></pre>
<h2 id="処理時間を回避するコード"><a href="#%E5%87%A6%E7%90%86%E6%99%82%E9%96%93%E3%82%92%E5%9B%9E%E9%81%BF%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89">処理時間を回避するコード</a></h2>
<h3 id="処理時間の確認"><a href="#%E5%87%A6%E7%90%86%E6%99%82%E9%96%93%E3%81%AE%E7%A2%BA%E8%AA%8D">処理時間の確認</a></h3>
<p>処理時間を取得する機能はなさそうなので、単純に実行タイミングで<code>Date</code>オブジェクトを生成しておき、定期的に現在時刻との差分を求める形で簡易的に確認します。</p>
<p>// 処理の開始時点の日付・時刻を取得する</p>
<pre><code class="javascript">const startDate = new Date();
...
</code></pre>
<p>後は、定期的に処理の中で<code>startDate</code>と現在時刻との差分を求めて処理時間を計算しましょう。</p>
<p><code>Date</code>オブジェクトの差分だったり、<code>Date.getTime()</code>を使っても同じように取得できます。 なお、値はミリ秒なので注意が必要です。</p>
<pre><code class="javascript">...
// ミリ秒で計測する場合
const processingMilliSec = (new Date() - startDate);
// 秒に直す場合
const processingSec = (new Date() - startDate) / 1000;
...
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime">Date.prototype.getTime() - JavaScript | MDN https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime</a></p>
<p>雑に関数にしておきます。 一定時間(ここでは300秒(= 5分)が経過したら処理を止めるようにしています。</p>
<pre><code class="javascript">function hoge() {
// 処理の開始時点の日付・時刻を取得する
const startDate = new Date();
// いろいろな処理
if (limitCheck(startDate)) return ;
}
function limitCheck(startDate: Date): boolean => {
const processingSec = (new Date().getTime() - startDate.getTime()) / 1000;
return processingSec >= 300; // 処理時間が300秒より大きい場合はtrueを返す
}
</code></pre>
<p>これにて、一定時間が経過した時点で処理を止める機能は完了です。</p>
<h3 id="処理途中のデータを保存する"><a href="#%E5%87%A6%E7%90%86%E9%80%94%E4%B8%AD%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B">処理途中のデータを保存する</a></h3>
<p>処理を止めるだけでは、次に実行したときに再開することはできません。 処理を止めたときに、次回実行時に処理途中から再開できるように</p>
<p>前述の通り、前回のファイル・フォルダ一覧を取得するコードを例として書いていきます。 前回の記事はこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/">【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する | しきゆらの備忘録 https://shikiyura.com/2023/11/how_to_get_file_list_in_google_drive/</a></p>
<p>データを保持する先は、以前に紹介したPropertiesServiceを使います。 PropertiesServiceに関しては、過去記事を書いているのでこちらもご覧ください。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://shikiyura.com/2021/07/how_to_use_properties_service_at_gas/">【GAS】パスワードやトークンをコードに直書きしない方法 | しきゆらの備忘録 https://shikiyura.com/2021/07/how_to_use_properties_service_at_gas/</a></p>
<p>ファイル・フォルダの一覧を取得する場合、保持しなければいけないのは主に以下の3点ですね。</p>
<ul>
<li>取得したファイル・フォルダのリスト</li>
<li>処理途中のファイル・フォルダイテレータ</li>
<li>参照予定のフォルダリスト</li>
</ul>
<p>この3点の中で、最初の「取得したファイル・フォルダのリスト」については、おそらく取得した後で最終的にはスプレッドシートにまとめたりするはずなので、あえて<code>PropertiesService</code>に置いておかなくてもよいかもしれません。</p>
<p>一方で、「処理途中のファイル・フォルダイテレータ」や「参照予定のフォルダリスト」は処理途中で終了しなければいけない場合は保持しておかなければ続きから再開ができません。<br />
ということで、この2点を保持する形を作っていきます。</p>
<p>イメージ図を置いておきます。<br />
赤フォルダ配下にあるフォルダを取得するとき、<code>getFolders()</code>で青フォルダたち<code>FolderIterator</code>として取得できます。</p>
<p><a href="https://crieit.now.sh/upload_images/b3b529b8f6a5073642d0882136913d91657e55096d865.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b3b529b8f6a5073642d0882136913d91657e55096d865.png?mw=700" alt="image" /></a></p>
<p>青フォルダたちを<code>FolderIterator</code>で取得している間に下図の線の位置で時間切れとなった場合、残り2つのフォルダは次回に回さないといけません。<br />
合わせて、青フォルダ1の配下にある紫フォルダたちを取得する<code>FolderIterator</code>も取得済みなので、こいつらも次回処理するときに見る必要がありますよね。</p>
<p><a href="https://crieit.now.sh/upload_images/c6c8e59753fab560266a366bc25aeb18657e55155a660.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c6c8e59753fab560266a366bc25aeb18657e55155a660.png?mw=700" alt="image" /></a></p>
<p>ということで上記3点のうち、青フォルダの<code>FolderIterator</code>が「処理途中のファイル・フォルダイテレータ」、紫フォルダの<code>FolderIterator</code>が「参照予定のフォルダリスト」となります。<br />
では、それぞれの保持の仕方を見ていきます。</p>
<h3 id="イテレータの保持"><a href="#%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%BF%E3%81%AE%E4%BF%9D%E6%8C%81">イテレータの保持</a></h3>
<p><code>FolderIterator</code>、<code>FileIterator</code>のどちらも<code>getContinuationToken()</code>メソッドが定義されており、イテレータ処理で時間がかかる場合に途中から再開することができるようになっています。<br />
再開するには、DriveAppクラスに定義されている<code>continueFolderIterator</code>、<code>continueFileIterator</code>にトークンを渡せばよい。</p>
<pre><code class="javascript">// トークンの取得
const iteratorToken = fileIterator.getContinuationToken();
// イテレータの再開
const resumeIterator = DriveApp.continueFileIterator(iteratorToken);
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja#getContinuationToken%28%29">Class FolderIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja#getContinuationToken()</a></p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/file-iterator?hl=ja#getContinuationToken%28%29">Class FileIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/file-iterator?hl=ja#getContinuationToken()</a></p>
<p>このトークンをPropertiesServiceなどで保持しておけば再開できますね。</p>
<h3 id="参照予定のフォルダリストの保持"><a href="#%E5%8F%82%E7%85%A7%E4%BA%88%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E4%BF%9D%E6%8C%81">参照予定のフォルダリストの保持</a></h3>
<p>前述の通り、フォルダやファイル一覧についてはPropertiesServiceを使って保持します。 なお、参照予定のフォルダ一覧はFolderIteratorが複数ある形なので、これらも上記の通りトークンに変換して保持します。</p>
<pre><code class="javascript">const subFolders = []; // 参照予定のFolderIteratorリストを保持する配列
...
// スクリプトプロパティを取得
const scriptProperty = PropertiesService.getScriptProperties();
// 参照予定のFolderIteratorをトークンに変換する
const subFolderTokens = subFolders.map(subFolder => subFolder.getContinuationToken());
// 参照予定のリストをJSONに変換して保持
scriptProperty.setProperty("folders", JSON.stringify(subFolderTokens));
</code></pre>
<p>処理途中のイテレータも同じく<code>getContinuationToken</code>メソッドでトークンを取得できるので 取得しつつ<code>subFolders</code>の先頭に置いておけば再開できそうですね。</p>
<p>ここまでで、一時停止のためのデータ保持が完了です。</p>
<h3 id="処理の続きを実行するトリガーを定義する"><a href="#%E5%87%A6%E7%90%86%E3%81%AE%E7%B6%9A%E3%81%8D%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%83%88%E3%83%AA%E3%82%AC%E3%83%BC%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%99%E3%82%8B">処理の続きを実行するトリガーを定義する</a></h3>
<p>GASにはトリガーという機能があり、特定の時間やタイミングなどになったら処理を始める、ということを指定できます。<br />
トリガークラスについてはこの辺を参照ください。</p>
<p>トリガーの作成は<code>ScriptAppのnewTrigger()</code>で作成できます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/script/script-app?hl=ja#getProjectTriggers%28%29">Class ScriptApp | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/script/script-app?hl=ja#getProjectTriggers()</a></p>
<p>トリガー作成時に、トリガー起動時に呼び出す関数名を文字列で指定します。 ここでは、トリガーとして1分後に起動するトリガーを作成します。</p>
<pre><code class="javascript">ScriptApp.newTrigger(functionName).timeBased().after(1000 * 60).create();
</code></pre>
<p><code>newTrigger()</code>が返してくるのは<code>TriggerBuilder</code>クラスになっています。 詳しくはこちら。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/script/trigger-builder?hl=ja">Class TriggerBuilder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/script/trigger-builder?hl=ja</a></p>
<p><code>timeBased()</code>はトリガーの種類として時間を基準として動くタイプとして指定しています。 <code>after()</code>でトリガーが起動する時間をミリ秒で指定しています。 <code>create()</code>で指定したトリガーを作っているだけ。見ればわかる感じですね。</p>
<h3 id="一時停止した場合に前回の処理から再開する"><a href="#%E4%B8%80%E6%99%82%E5%81%9C%E6%AD%A2%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88%E3%81%AB%E5%89%8D%E5%9B%9E%E3%81%AE%E5%87%A6%E7%90%86%E3%81%8B%E3%82%89%E5%86%8D%E9%96%8B%E3%81%99%E3%82%8B">一時停止した場合に前回の処理から再開する</a></h3>
<p>前回の処理がある場合は、前回の処理から再開してあげる必要があります。</p>
<p>先ほど<code>PropertiesService</code>に保持したデータですが、ここから取得してできるかどうかで判別したり、関数の引数の有無で判別したり、方法はいくつかありそうですがここでは<code>PropertiesService</code>にデータがあるかどうかで判別してみます。</p>
<pre><code class="javascript">// スクリプトプロパティを取得
const scriptProperty = PropertiesService.getScriptProperties();
// 前回の処理からの続きとなるデータを取得
const resumeData = scriptProperty.getProperty("folders");
if(resumeData === null) {
// 前回のデータがないので、ターゲットとなるフォルダを取得
} else {
// 前回のデータがあるので、再開する
}
</code></pre>
<p>再開する場合は、トークンを使ってイテレータの続きを取得します。 上記サンプルのelse部分は以下のような感じになります。</p>
<pre><code class="javascript">const tokenJson = JSON.parse(resumeData);
const subFolders = tokenJson.map(token => DriveApp.continueFolderIterator(token));
</code></pre>
<p>あとは、このトークンから復元したイテレータをひとつづつ取り出して処理を進めればよいですね。</p>
<p>まとめてコードを載せると以下のような感じ。</p>
<pre><code class="javascript">// 処理時間の制限を超えたかどうかのフラグ
let limitFlag = false;
function allFiles(){
const functionName = "allFiles";
const startDate = new Date();
let subFolders = []; // 配下にあるサブフォルダを保持する配列
let files = [];
// 中断データを取得する
const resume = getResume(functionName);
if (resume === null) {
// 中断データがない場合は、初期フォルダから処理を開始する
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
subFolders.push(targetDir.getFolders());
files.push(...Array.from(allFilesBy(targetDir, startDate)));
} else {
// 中断データがある場合は、トークンからFolderIteratorに変換する
const tokensJson = JSON.parse(resume);
subFolders = tokensJson.map(token => DriveApp.continueFolderIterator(token));
}
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
files.push(...allFilesBy(folder));
if(limitFlag || checkLimit(startDate)) break;
}
// 一定の処理時間を超えた場合、処理途中のFolderIteratorをトークンに変換して保存する
if(limitFlag || checkLimit(startDate)){
const tokens = [subFolder.getContinuationToken()];
const subFolderTokens = subFolders.map(subFolder => subFolder.getContinuationToken());
setResume(functionName, JSON.stringify(tokens.concat(subFolderTokens)));
break;
}
}
// 今回処理した結果を返す
return files;
}
function allFilesBy(folder) {
const files = []; // 対象となるフォルダにあるファイルオブジェクトを保持する配列
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
return files;
}
function checkLimit(startDate) {
const processingSec = (new Date().getTime() - startDate.getTime()) / 1000;
limitFlag = processingSec >= 300
return limitFlag;
}
// 中断データを取得する
function getResume(functionName) {
const scriptProperty = PropertiesService.getScriptProperties();
const properties = scriptProperty.getProperty(functionName);
scriptProperty.deleteProperty(functionName);
return properties;
}
// 中断データを保持し、トリガーを設定する
function setResume(functionName, data) {
const scriptProperty = PropertiesService.getScriptProperties();
scriptProperty.setProperty(functionName, data);
setTrigger(functionName);
}
// トリガーを設定する
function setTrigger(functionName) {
let triggers = ScriptApp.getProjectTriggers();
for(let trigger of triggers) {
if(trigger.getHandlerFunction() === functionName){
ScriptApp.deleteTrigger(trigger);
}
}
ScriptApp.newTrigger(functionName).timeBased().after(1000 * 60).create();
}
</code></pre>
<p>これで、処理時間の制限に引っかかるような長時間の処理が必要な場合、これを回避して処理させることができるようになりました。<br />
処理時間の制限があるのでGASを使うのはきびしいな、というような場合に参考にしていただければ幸いです。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、GASの処理時間の制限を回避するため、以下の項目を組み合わせて回避してみました。</p>
<ul>
<li>処理時間の計測</li>
<li>処理途中のデータをPropertiesServiceへ保存</li>
<li>処理再開のためのトリガー設定</li>
<li>前回の処理途中からの再開</li>
</ul>
<p>私の場合は、とあるフォルダ配下にあるファイルたちのオーナー一覧を取得してほしい、というお題が来たので上記のようなコードを書いていました。 RubyなどからAPI経由でデータ取得するよりも、GAS上で書く方がシンプルでしたが、ざっと書いたところ処理時間の制限に阻まれたのでリファレンス等を読みつつ変更した結果が今回の時期の内容になります。</p>
<p>GASでコードを書いた方がシンプルだが、処理にどの程度時間がかかるかわからない というような場合でもGASを使って処理させることができるようになるので、参考になればうれしいです。</p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら
tag:crieit.net,2005:PublicArticle/18641
2023-11-04T21:30:36+09:00
2023-11-04T21:30:36+09:00
https://crieit.net/posts/how-to-get-file-list-in-google-drive
【GAS】Googleドライブにあるフォルダ・ファイルの一覧を取得する
<p>こんにちは、しきゆらです。<br />
今回は、GASでGoogleドライブにあるファイル・フォルダの一覧を取得する方法をメモしておきます。</p>
<h2 id="ファイル・フォルダの一覧を取得するコード"><a href="#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%BB%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%82%B3%E3%83%BC%E3%83%89">ファイル・フォルダの一覧を取得するコード</a></h2>
<h3 id="すべてのフォルダを取得する"><a href="#%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">すべてのフォルダを取得する</a></h3>
<p>すべてのフォルダを取得する場合、以下だけです。</p>
<pre><code class="JS">const folders = DriveApp.getFolders();
</code></pre>
<p><code>DriverApp.getFolders()</code>でGoogleドライブにあるフォルダのイテレータを取得できます。<br />
ドライブ内にあるすべてのファイルが欲しければ、このイテレータ内でファイル一覧を取得していけば可能ですね。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/drive-app?hl=ja#getfolders">Class DriveApp | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/drive-app?hl=ja#getfolders</a></p>
<p>なお、<code>Folder</code>クラスにも直下にあるフォルダ一覧を取得できる<code>getFolders()</code>メソッドが生えているので、同じように使えます。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfolders">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfolders</a></p>
<h3 id="フォルダ直下にあるファイル一覧を取得する"><a href="#%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E7%9B%B4%E4%B8%8B%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">フォルダ直下にあるファイル一覧を取得する</a></h3>
<p>上記のような<code>FolderIterator</code>から<code>Folder</code>クラスのインスタンスを取得します。<br />
<code>Folder</code>クラスには、直下にあるファイルのイテレータを取得するメソッドがあるのでこれを使います。</p>
<pre><code>const folder = folders.next(); // FolderIteratorからFolderオブジェクトを取得
const fileIterator = folder.getFiles();
const files = []; // ファイルオブジェクトを格納する配列
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
</code></pre>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfiles">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja#getfiles</a></p>
<p>これらを組み合わせれば、特定のフォルダ配下のすべてのファイル一覧を取得することができますね。 やってみましょう。</p>
<h2 id="特定のフォルダ配下にあるすべてのファイルを取得する"><a href="#%E7%89%B9%E5%AE%9A%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E9%85%8D%E4%B8%8B%E3%81%AB%E3%81%82%E3%82%8B%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">特定のフォルダ配下にあるすべてのファイルを取得する</a></h2>
<h3 id="ターゲットとなるフォルダを取得する"><a href="#%E3%82%BF%E3%83%BC%E3%82%B2%E3%83%83%E3%83%88%E3%81%A8%E3%81%AA%E3%82%8B%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">ターゲットとなるフォルダを取得する</a></h3>
<p>まずは、GASからターゲットとなるフォルダを取得します。</p>
<pre><code>// https://drive.google.com/drive/u/0/folders/xxxxxxのxxxxxxx部分
const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
</code></pre>
<p><code>targetDir</code>はGASの<code>Folder</code>クラスのインスタンスが入っています。</p>
<p>Folderクラスについては以下を参照。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder?hl=ja">Class Folder | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder?hl=ja</a></p>
<h3 id="フォルダ一覧を取得する"><a href="#%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E4%B8%80%E8%A6%A7%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">フォルダ一覧を取得する</a></h3>
<p>前述の通り、<code>Folder</code>クラスには配下にあるフォルダ一覧を取得するメソッドが定義されているのでそれを使います。</p>
<pre><code>const folderIterator = targetDir.getFolders(); // Folderのイテレータオブジェクトが返ってくる
while (folderIterator.hasNext()) { // 参照していないFolderがあればループする
folders.push(folderIterator.next()); // .next()でFolderを取得
}
</code></pre>
<p><code>FolderIterator</code>クラスは以下を参照。<br />
よくあるイテレータで、<code>hasNext()</code>で次の要素があるかどうかを確認しつつ、次の要素があれば<code>next()</code>で取得する形です。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja">Class FolderIterator | Apps Script | Google for Developers https://developers.google.com/apps-script/reference/drive/folder-iterator?hl=ja</a></p>
<p>ただし、ここで取得できるのは<code>targetDir</code>直下にあるフォルダのみです。<br />
<code>targetDir</code>配下すべてを取得するにはちょっと工夫してあげる必要がります。</p>
<pre><code>const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolder.push(folder.getFolders()); // フォルダ一覧を取得し、次に参照する
}
}
</code></pre>
<p>配列に破壊的に追加しつつ<code>for</code>でループ処理するので、だいぶお行儀がよくない印象ですがいい方法が思いつきませんでした。<br />
<code>forEach</code>を使うと、追加された要素を参照してくれなかったので<code>for</code>で回しています。</p>
<p>ファイルの一覧を取得するコードの全体像はこんな感じ。<br />
最終的にはfilesの配列にファイルオブジェクトが集まるので、ここに対してあれこれすればよい。<br />
3重ループになっているので、ファイル取得部分を関数に分けるなどすればもう少し見やすくなるかなと思います。</p>
<pre><code>const folderId= "xxxxxxxxxx";
const targetDir = DriveApp.getFolderById(folderId);
const files = []; // ファイルオブジェクトを保持する配列
const subFolders = [targetDir.getFolders()];
for(let i = 0; i < subFolders.length; i++) {
const subFolder = subFolders[i];
while(subFolder.hasNext()) {
const folder = subFolder.next();
subFolders.push(folder.getFolders());
const fileIterator = folder.getFiles();
while(fileIterator.hasNext()) {
files.push(fileIterator.next());
}
}
}
</code></pre>
<p>フォルダの一覧の場合は、<code>fileIterator</code>のループが不要で、<code>subFolder.next()</code>を配列に詰めればOK。<br />
もっとこう書けばええやん、というご指摘等あればコメントいただければ嬉しいです。</p>
<h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2>
<p>今回は、GASでGoogleドライブにあるとあるフォルダ配下にあるすべてのファイルを取得する方法をメモしました。<br />
GASは結構機能が用意されているので、Googleサービスに対してやろうと思ったことは気軽に実装できそうな印象です。</p>
<p>ただし、GASには実行時間の制限などもあるので、この辺に気をつけて使いましょう。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/apps-script/guides/services/quotas?hl=ja">Google サービスの割り当て | Apps Script | Google for Developers https://developers.google.com/apps-script/guides/services/quotas?hl=ja</a></p>
<p>今回は、ここまで。</p>
<p>おわり</p>
しきゆら