【GAS】位置情報を送ると近場の深夜営業レストランを教えてくれるLINEbot

##追記
こちらでスクレイピングしていた食べログのページが、提供を終了してしまったため動作しなくなりました。残念。

##概要
終業時間が遅く、夕食が深夜０時を過ぎることが多い。レストランの選択肢が少ない時間帯のため、いつも同じ食事になってしまい味気ない・・・。

GAS、LINEmessagingAPI、WEBスクレイピング、食べログを組み合わせて、LINEに位置情報を投げたら近場の深夜営業レストランを返すLINEBotを作ってみた。

##続編あり
後日、返信をテンプレートメッセージにして表示する情報量を増やした刷新版を作成。そちらについての記事がこちら。コードも全文記載。
Qiita記事：[【GAS】LINEbotでテンプレートメッセージを活用する](https://qiita.com/YasumiYasumi/items/fee6f43bef7f96fb7b22)

##実行結果
作成したLINEアカウントとお友達になり、メッセージで位置情報を送信（左）。食べログの検索結果から最大５件、周辺の深夜営業レストランを教えてくれる（右）。
![Screenshot_20201211-031754-01-01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/4b00b8e1-70a9-f350-d89a-cfafee903051.jpeg)
お店が無い地域を送信すると、こんな感じ。
![Screenshot_20201211-033946-01.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/7bb83449-8217-62aa-a183-e73145434a8e.jpeg)

##下ごしらえ
使用したサービスを列挙する。ありがたいことに全て無料(人´Д`)
###LINEアカウント作成＆GAS紐づけ
・LINEデベロッパーズでアカウントを作成
・APIトークンを発行
・GASをWEBアプリとして導入
・Webhookを有効にする。

GASでLINEBotを作る方法を紹介しているサイトは多く、ここまではだいたい同じ。長くなるので割愛<(_ _)>

###食べログ検索URL
現在位置の周辺でジャンルや予算などを指定して検索する方法がある。今回は「1km圏内」＆「始発まで営業」を条件に検索する。

URLは、緯度経度を元に次のように作ることができる。

```
http://m.tabelog.com/mobile_gps/RC////A/0/6/l/rstlstgps/?narrow=1&RdoCosTp=1&LstCos=0&LstCosT=0&lat={緯度}&lon={経度}&SrtT=trend&LstSitu=&sw=&img_on=1
```

検索結果は【会員店舗優先】順になるみたい(´・ω・`)

###ライブラリ
GASを使って食べログの検索結果をスクレイピングするのに「TextPicker」というライブラリを使用する。長い文字列から必要部分を切り出すのにとっても便利！

プロジェクトID：Ms1_ywyxDUyXZlf1HE1E2_ydpxDUCDjPE

###LINEアカウントのアイコン
いらすとやさんの「終電を逃したサラリーマン」を使用。
![syuden_businessman.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/708687/921acb72-39f2-b01b-415f-1d54eee2a987.png)

##コード
###メッセージを送ったら動く関数
```javascript
function doPost(event) {

  //APIトークンとメッセージ送信用のURL
  var TOKEN = '××××××××××××××××××××';
  var reply = "https://api.line.me/v2/bot/message/reply";

  //メッセージを取得
  var json = JSON.parse(event.postData.contents);
  var replyToken = json.events[0].replyToken;
  var type = json.events[0].message.type;
  
  //メッセージタイプ判定
  if(type==='location'){
    
    //緯度経度を取得
    var lat = json.events[0].message.latitude;
    var lng = json.events[0].message.longitude;
    
    //緯度経度からレストラン検索
    var storeURL = storeSearch(lat,lng);
    
    //メッセージ作成
    var botMessage = storeURL;
    var payload = JSON.stringify({
        "replyToken": replyToken,
        "messages": [{
          "type": "text",
          "text": botMessage
        }]
    });
  
    //メッセージ送信
    UrlFetchApp.fetch(reply, {
        "headers": {
          "Content-Type": "application/json; charset=UTF-8",
          "Authorization": "Bearer " + TOKEN,
        },
        "method": "post",
        "payload": payload
    });
    
  }else{
  
    //メッセージ作成
    var botMessage = '位置情報を送ってね\n近くの始発までやってるレストランを調べるよ';
    var payload = JSON.stringify({
        "replyToken": replyToken,
        "messages": [{
          "type": "text",
          "text": botMessage
        }]
    });
  
    //メッセージ送信
    UrlFetchApp.fetch(reply, {
        "headers": {
          "Content-Type": "application/json; charset=UTF-8",
          "Authorization": "Bearer " + TOKEN,
        },
        "method": "post",
        "payload": payload
    });
  }
}
```

LINEにメッセージが送信された場合、返信用のトークンとメッセージのタイプを取得。

メッセージタイプが位置情報だった場合、その緯度経度を食べログ検索用の関数`storeSearch()`へ送り、返ってきた検索結果をLINEで返信する。

メッセージタイプが位置情報でない場合は、「位置情報を送ってね ～～～」のメッセージが返信される。
###storeSearch()
```javascript
function storeSearch(lat,lng) {

  //食べログ 1km圏内 始発まで営業 絞り込み検索URL生成
  var storeURL = 'http://m.tabelog.com/mobile_gps/RC////A/0/6/l/rstlstgps/?narrow=1&RdoCosTp=1&LstCos=0&LstCosT=0&lat=' + lat + '&lon=' + lng + '&SrtT=trend&LstSitu=&sw=&img_on=1';
  var data = UrlFetchApp.fetch(searchURL).getContentText();
  
  var stores = '';
  var storeURL = '';
  
  //ソースをTextPickerへ
  TextPicker.open(data);
  
  //該当するお店がない場合
  if(TextPicker.pickUp('指定の条件に','見つかりませんでした')==='該当するお店は'){
    return '指定の条件に該当するお店は見つかりませんでした。';
  }
  
  //ヒット件数取得 5件以上の場合は5に丸める
  var num = TextPicker.pickUp('</span>/全','件');
  num = Number(num);
  if(num>5){num=5;}
  
  TextPicker.skipTo('</span>/全');
  
  //件数分のレストランURL取得
  for(i=0; i<num; i++){
  
    var URL = TextPicker.pickUp('http://m.tabelog.com/','">');
    TextPicker.skipTo(URL);
    
    //クーポンページと中間リンクをコンティニュー
    if(URL.slice(-5)==='="red'){i--; continue;}
    if(URL==='billing_mobile/register_mymenu?msgid=6'){i--; continue;}
    
    storeURL += 'http://m.tabelog.com/' + URL + ' ';
  }
  
  return storeURL;
}
```

位置情報を元に食べログ検索を行い、検索結果ページを取得。検索結果ページをTextPickerに投げて、最大５件まで食べログ検索結果のレストランURLをスクレイピングし、得た情報をreturnしている。

###スクレイピング部分補足
`TextPicker.open(data);`でページソースを丸っとTextPickerへ渡す。

`TextPicker.pickUp('</span>/全','件');`でソース内の`</span>/全`と`件`の間を切り取っている。

```html
<hr size="1" style="width:100%; height:1px; margin:1px 0; padding:0; color:#ffffff; background:#ffffff; border:0px solid #ffffff;" />
<span style="color:#ff0000;">1～10件</span>/全163件
<hr size="2" style="width:100%; height:2px; margin:1px 0; padding:0; color:#ffffff; background:#ffffff; border:0px solid #ffffff;" />
```

`TextPicker.skipTo('</span>/全');`で`</span>/全`までスキップし、それ以前の記述をスクレイピングの対象から外している。

あとのソースは次のような記述が連続しているので、`TextPicker.picUp`でレストランのURLを取得、`TextPicker.skipTo`で取得したURLまでをスキップ、という操作をfor文で繰り返している。

```html
<a href="http://m.tabelog.com/tokyo/A1307/A130701/13250430/">ZEUS GARDEN</a>
</span>
<span style="color:#800000;"></span><br />
<span style="font-size:xx-small;">
<span style="color:#666666;">(六本木､六本木一丁目､麻布十番/ダイニングバー､タイ料理)</span>
<br /><span style="color:#ffaa00;">★★★</span><span style="color:#999999;">☆☆</span><span style="color:#ff0000;">3.07</span><br /><span style="color:#ff00ff;"></span><span style="color:#000000;">口ｺﾐ:</span>4件<br /><span style="color:#ff0000;"></span>夜:\3,000～\3,999<br /><span style="color:#ff0000;"></span>昼:-<br />
<span style="color:#ff0000;">○:空席あり</span>(本日夜18-20時)
<br /><span style="color:#ff5555;"><span style="color:#000000;"></span>現在地から829m</span>
</span>
```

途中でクーポンページやその他のリンクが挟まっていることがあるので、if文で回避。その分の繰り返しカウントを無かったことにしている。

検索結果がゼロの場合は`指定の条件に該当するお店は見つかりませんでした。`と言う記述があるので、ソースからその記述を探しreturnしている。

URLだけ取得しておけば、LINEが勝手にリンクを作ってくれるので便利。
検索条件のURLを変えれば応用も効きそう^^

###おしまい