Webコンテンツ(HTML)の並列ダウンロードを行うAppleScriptです。
Webコンテンツからデータを抽出する際に、あらかじめ一括でHTMLを並列ダウンロードしておいて、ダウンロードずみのデータを一括処理すると処理時間を大幅に短縮できます。「戦場の絆」Wikiから機体データの表をすべて取得するのに、順次HTMLを取得して抽出していた場合には3〜4分程度かかっていたものを、並列ダウンロードしたのちにデータ処理するように変更すれば、これが十数秒程度にまで高速化できます。
既存のプログラムを修正してHTMLのダウンロード用に仕立ててみました。並列キュー(未処理タスク数)へのアクセスはAppleScriptからはできなかった(実行すると結果が返ってこなかった)のですが、そこに手をつけずに並列処理の完了状態を検出しています。
ダウンロードが完了したデータはlist(配列)に入るので、リクエスト数と完了リクエスト数をループ内でチェックし、完了アイテム数がリクエスト数と同じになれば終了、条件を満たさない場合でも指定のタイムアウト時間を超えたらエラー終了という処理を行なっています。
問題点は、スクリプトエディタ上では実行できるもののScript Debugger上では実行できないことです(結果が返ってきません)。AppleScriptアプレットに書き出して実行してみたところ、結果が返ってきません。ただし、Script Menuからの実行は行えます(macOS 10.12.6、10.13.6、10.14.6で同様)。XcodeのAppleScript Appのプロジェクト内で実行することはできました。
このように、ランタイム環境に実行状況が左右される点にご注意ください。ただし、そうしたマイナス面を補ってあまりあるほどダウンロードは高速です。90ファイル強のダウンロードを数秒で完了(マシン、ネットワーク速度、ダウンロード先サーバーの負荷状況、キャッシュ状態などに左右される、参考値です)するため、ダウンロード後に各HTMLからのデータ抽出も高速に行えます。
AppleScript名:htmlの並列ダウンロード処理 |
— Created 2019-09-13 by Takaaki Naganoya — 2019 Piyomaru Software — – オリジナル: HTTP からファイルをダウンロードして、ローカルに保存する方法(shintarou_horiのブログ) — http://shintarou-hori.hatenablog.com/entry/2014/03/15/193604 use AppleScript version "2.4" use scripting additions use framework "Foundation" property |NSURL| : a reference to current application’s |NSURL| property NSData : a reference to current application’s NSData property NSString : a reference to current application’s NSString property NSOperationQueue : a reference to current application’s NSOperationQueue property NSInvocationOperation : a reference to current application’s NSInvocationOperation property NSUTF8StringEncoding : a reference to current application’s NSUTF8StringEncoding property aTimer : missing value script spd property mList : {} property resList : {} end script set (mList of spd) to {} set (resList of spd) to {} set baseURL to "https://w.atwiki.jp/senjounokizuna/pages/" set numList to {25, 1594, 890, 70, 1669, 82, 1717, 997, 1080, 1614, 1712, 159, 1311, 1694, 1752, 1263} set urlList to {} repeat with i in numList set the end of urlList to (baseURL & i as string) & ".html" end repeat set aLen to length of urlList set paramObj to {urls:urlList, timeOutSec:60} –set (resList of spd) to downloadHTMLConcurrently(urlList, 60) of me my performSelectorOnMainThread:"downloadHTMLConcurrently:" withObject:(paramObj) waitUntilDone:true return length of (mList of spd) on downloadHTMLConcurrently:paramObj set urlList to (urls of paramObj) as list set timeOutS to (timeOutSec of paramObj) as real set aLen to length of urlList repeat with i in urlList set j to contents of i set aURL to (|NSURL|’s URLWithString:j) set aQueue to NSOperationQueue’s new() set aOperation to (NSInvocationOperation’s alloc()’s initWithTarget:me selector:"execDL:" object:aURL) (aQueue’s addOperation:aOperation) end repeat –Check Completion set compF to false set loopTimes to (timeOutS * 10) repeat loopTimes times if length of (mList of spd) = aLen then set compF to true exit repeat end if delay 0.1 end repeat if compF = false then error "Concurrent download timed out" end downloadHTMLConcurrently: on execDL:theURL set receiveData to NSData’s alloc()’s initWithContentsOfURL:theURL if receiveData = missing value then return end if set aFileName to theURL’s |lastPathComponent|() my performSelectorOnMainThread:"saveData:" withObject:{receiveData, aFileName} waitUntilDone:false end execDL: on saveData:theDataArray copy theDataArray to {theData, saveFileName} set aCon to NSString’s alloc()’s initWithData:theData encoding:NSUTF8StringEncoding set the end of (mList of spd) to {fileName:saveFileName as string, contentsData:aCon as string} end saveData: |