ディエンドっす。

 

とりあえず、複数レイヤ作って、全件登録してみました。

住所の間違いは手動で訂正(結局、間違っているのは35店舗でした)。

下記のような感じになってます。

とりあえず、みなさんから見えるのかテスト。

 

ういっす。ディエンドっす。 

 

なんとかお店のリストを作ることに成功しました。

↓こんな感じ。

 

ということで意気揚々と、Googleマイマップに読み込ませてみたのですが・・・・・

まぁ、次から次へと問題が発生するものですなぁ(;´Д`)

解決せねばならない問題は下記のとおり。

  1. お店は2378件あるが、マイマップの1レイヤは2000件までしか登録できない
  2. 遊べるお店に登録されている住所が、(少なくとも)Googleマップでは表示できないお店が47件ある
それぞれについて対策を考えてみよう。
 

1レイヤの登録上限は2000件

1つのマップに複数レイヤ配置できるので、データを2〜3のレイヤに分ければ、登録可能になるはず。
どうせレイヤ分けするなら、意味のある分け方にしたいところ。
例えば、地域ごと(東北、北陸、関東・・・・)に分けるとか、筐体の個数ごとに分けるとか。
とりあえずは、都道府県を3つのグループに分けて3レイヤってのが簡単そう。
 

Googleマップで表示出来ない住所がある

こっちが面倒なのです。単純に住所が間違っているものもあれば、建物の名前が間違っているものもあり。
2378件中47件なので、変換する辞書みたいなものを作っておいて、データ取得時に一括修正するのが良いのかなぁ。
本来ならバソダイに連絡して直してもらうのが正攻法なのかもしれないけど、、、、
 
 
まずは、レイヤ分けから着手することにします。
 
つづく。
 

ディエンドっす。

 

前回、Google Apps Scriptの実行時間制限に引っかかってしまい、修正が必要になっていましたが、なんとか、超力技で修正完了しました\(^o^)/

 

本来、時間制限を回避するには、トリガーってのを使うのが一般的のようなのですが、ひとまず、分割起動できるように修正し、手動で再起動するようにしてみました。

こうしておけば、トリガーを使うときも簡単に修正できるはず。

 

んで、全都道府県の筐体をリストアップすることに成功!

2021/08/26時点で、全国に2378筐体設置されているようです。

 

いまのところ店名と住所のみリストアップしていますが、筐体数とかもリスト出来ると便利かな。これについては今後の課題ということで。。。。

この後、このリストをつかって地図に登録ってな流れなのですが、

本日はここまで。

 

現在のソースコードを載せておきます。

最初は startScraping関数をキックする。

中断後の再開はresumeScraping関数をキックする。

ってな使い方です。(何度かresumeしていると全都道府県完了するはず)

 

//
// 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));
}