//
// START関数:最初はこの関数を実行する
//
var procDesc;
function startScraping() {
procDesc = new ProcessDescriptor();
procDesc.start();
// スクレイピングの準備
const sheet = prepareScraping();
// スクレイピング開始
processScraping(sheet);
}
//
// 店舗リスト作成メイン処理
// 引数:sheet = 出力するシート
//
function processScraping(sheet) {
// 47都道府県分繰り返す
const NUM_OF_PREFECTURE = 47;
for (let pref = procDesc.getPrefIndex(); pref <= NUM_OF_PREFECTURE; pref++) {
outputOnePrefecture(sheet, pref);
// タイムアウトチェック
// 本当は、1ページ処理する毎にチェックすべきなのだが、処理が複雑になるので、
// 暫定的に1都道府県毎のチェックとする。
if (procDesc.isTimeout()) {
Logger.log("TIMEOUT");
procDesc.suspend(pref+1);
break;
}
}
}
//
// RESUME関数:一度中断した後の再開はこの関数から実行する
//
function resumeScraping() {
procDesc = new ProcessDescriptor();
procDesc.resume();
const sheet = getOutputSheet();
processScraping(sheet);
}
//
// Process Descriptor Class
// 処理の進み具合、状態を管理するためのクラス
//
var PROC_STATE = {
NONE : 0,
RUN : 1,
SUSPEND : 2,
STOP : 3
}
class ProcessDescriptor {
constructor() {
const TIMEOUT_VAL = 4*60*1000; //ひとまず4分にしておく
this.state = PROC_STATE.NONE;
this.pref = this.page = 0;
this.timer = new ScriptTimer(TIMEOUT_VAL);
this.props = PropertiesService.getScriptProperties();
}
start() {
this.state = PROC_STATE.RUN;
this.timer.start();
this.pref = 1; this.page = 1;
this.props.setProperty("pref", this.pref);
}
isTimeout() {
return ((this.state == PROC_STATE.RUN) && this.timer.isExpired());
}
suspend(pref) {
this.state = PROC_STATE.SUSPEND;
this.pref = pref;
this.props.setProperty("pref", this.pref);
}
resume() {
this.state = PROC_STATE.RUN;
this.timer.start();
this.pref = Number.parseInt(this.props.getProperty("pref"));
}
getPrefIndex() {
return this.pref;
}
}
//
// Script Timeoutを測るクラス
//
class ScriptTimer {
constructor(expierTime) {
this.expireTime = expierTime;
this.startTime = 0;
}
start() {
this.startTime = new Date();
}
isExpired() {
let endTime = new Date();
return ((endTime - this.startTime) >= this.expireTime);
}
}
//
// スクレイピングの準備(スプレッドシートの初期化など)
//
function prepareScraping() {
const sheet = getOutputSheet();
sheet.clearContents(); // 出力シートをクリア
const title = [["店名", "住所"]];
sheet.getRange(getOutputRange(sheet.getLastRow()+1)).setValues(title); //タイトルを設定
return sheet;
}
//
// 出力するシートを取得
//
function getOutputSheet() {
const TARGET_SHEET_NAME = 'シート2';
const spreadSheet = SpreadsheetApp.openById("spreadsheet id");
const sheet = spreadSheet.getSheetByName(TARGET_SHEET_NAME)
return sheet;
}
//
// 出力するスプレッドシートのRANGE文字列取得
//
function getOutputRange(row) {
const OUTPUT_RANGE_FORMAT = "A%d:B%d";
return Utilities.formatString(OUTPUT_RANGE_FORMAT, row, row);
}
//
// 1つの都道府県の店舗データを出力する
// 引数:sheet = 出力シート
// prefIndex = 都道府県番号
//
function outputOnePrefecture(sheet, prefIndex) {
const URL_FORMAT = "https://www.ganbarizing.com/shop/list.php?pref=%02d&p=%d";
let page = 1;
do {
let url = Utilities.formatString(URL_FORMAT, prefIndex, page);
let res = UrlFetchApp.fetch(url).getContentText();
let $ = Cheerio.load(res);
let $data = $('table.resultTb tbody tr td');
//
// 終了判定:HITする店舗がなくなったら終了
//
let isEmpty = ($($data[0]).text() == "検索条件にHITする店舗がありません。");
if (isEmpty) break;
//
// 店舗がある場合は、シートへ出力する
//
let shop = ["", ""];
$data.each(function(index, element){
if (index % 3 == 0) { // 店舗名
shop[0] = $(element).text().slice(0, -3);
} else if (index % 3 == 1) { // 住所
shop[1] = $(element).text();
sheet.getRange(getOutputRange(sheet.getLastRow()+1)).setValues([shop]);
}
})
Utilities.sleep(2000); //連続でアクセスしたら迷惑なので、2秒間隔にする
} while(++page <= 30); //暫定処理。最悪30ページ分ループしたら終了。もし30ページ以上の都道府県が出てきたら再考要
Logger.log(Utilities.formatString("process:%02d, num_page=%d", prefIndex, page-1));
}
ディエンドっす。
とりあえず、複数レイヤ作って、全件登録してみました。
住所の間違いは手動で訂正(結局、間違っているのは35店舗でした)。
下記のような感じになってます。
とりあえず、みなさんから見えるのかテスト。
