LoginSignup
1
1

More than 3 years have passed since last update.

高知の「あしたの分も買うちょくきね。~飲食券先買い応援プロジェクト~」の参加店舗を勝手にgoogle map上のマーカーでわかるようにした

Last updated at Posted at 2020-05-23

高知に行きたくても行けないので勝手にやりました
https://script.google.com/macros/s/AKfycbzRJCvOuWC5uMFiqhl5soiLHnWbkGE5P-GAkxFmVT08u_3E2hRi/exec

あしたの分も買うちょくきね。~飲食券先買い応援プロジェクト~「参加店舗」
https://www.kochinews.co.jp/norikoeyo/asukau_eatery/
この店舗一覧をgoogle map上のマーカーでわかるようにしました

構成

  • フロントはgoogle app script
  • フロントからAPI Gateway+LambdaのAPIを呼び店舗情報を取得
  • 店舗データはDynamoDBに保存

という完全サーバレスです

まずは店舗一覧のデータ作成

https://www.kochinews.co.jp/norikoeyo/asukau_eatery/
このサイトをスクレイピングするlambdaを書きます(python)
座標取得するため、googleのAPIキーも事前に取得しておきました


import requests
import re
import googlemaps
import time
import boto3

def lambda_handler(event, context):
    dynamo_db = boto3.resource('dynamodb')
    table = dynamo_db.Table('kouchi_shop')

    gmaps = googlemaps.Client(key=API_KEY)

    response = requests.get('https://www.kochinews.co.jp/norikoeyo/asukau_eatery/')
    response.encoding = response.apparent_encoding

    text = response.text.replace('\n', '')
    regex = r'<b>(\d+)(.*?)<\/b>\s*<br>(.*?)<br>'
    result = re.findall(regex, text)

    if result:
        for data in result:
            if not data[0]:
                continue
            number = data[0]
            shop_name = data[1].strip()
            href_result = re.findall(r'<a href=\"(.*?)\"', data[2])
            shop_address = re.sub(r'<a.*<\/a>', '', data[2]).strip()
            url = href_result[0] if href_result else ''
            # 座標取得
            geo_result = gmaps.geocode(shop_address)

            item = {
                'id': int(number),
                'name': shop_name,
                'address': shop_address,
                'url': url,
                'lat': str(geo_result[0]["geometry"]["location"]["lat"]),
                'lng': str(geo_result[0]["geometry"]["location"]["lng"])
            }
            print(item)
            table.put_item(Item=item)
            # 念のため連続でリクエストしないようにsleepを入れておく
            time.sleep(0.5)

    return {
        'statusCode': 200
    }

適当に一回実行してDynamoDBに店舗データを保存させます

beautifulsoupとかは使ってません(DOMの構造上あんまり意味なかったので)
ライブラリのインストールについてはこちらを参考にさせていただきました
https://tech.bita.jp/article/38

店舗一覧を取得するAPIを作る

これもLambdaで書いて、トリガーをAPI Gatewayにします


import json
import boto3
from decimal import Decimal

def lambda_handler(event, context):
    dynamo_db = boto3.resource('dynamodb')
    table = dynamo_db.Table('kouchi_shop')

    return {
        'statusCode': 200,
        'body': json.dumps(table.scan(), default=decimal_default_proc)
    }

def decimal_default_proc(obj):
    if isinstance(obj, Decimal):
        return str(obj)
    raise TypeError

特に絞り込みとかしないのでscan()で全件取得してるだけです
bodyは連想配列のままだとAPIリクエストしたときに{"message":"Internal Server Error"}が出るので必ずjson.dumpsが必要でした(これでちょっと嵌った)

あと、jsonに小数点があるとjsonエンコードでエラーになるのでdecimal_default_procで文字列にキャストしてます(letとlngが小数点)
https://qiita.com/ekzemplaro/items/5fa8900212252ab554a3
こちらを参考にさせていただきました

フロント作成

google app scriptでhtmlを作成し、店舗一覧取得APIからマーカーを設置します

<script>
  var marker = [];
  var info_window = [];

  function init_map () {
    // 初期表示の中心
    var map = new google.maps.Map(document.getElementById('map'), {
      center: {
           lat: 33.561067,
           lng: 133.531465
      },
      zoom: 15
    });

    get_shop(map)
  }

  function marker_event(i) {
    marker[i].addListener('click', function() {
      info_window[i].open(map, marker[i]);
    });
  }

  function set_marker(items, map) {
    for (var i = 0; i < items.length; i++) {        
      // マーカーの設置
      marker[i] = new google.maps.Marker({
        position: {
          lat: parseFloat(items[i]['lat']),
          lng: parseFloat(items[i]['lng'])
        },
        map: map
      });

      // マーカーをクリックした際のポップアップ
      var text = '[' + String(items[i]['id']) + '] ' + items[i]['name'] + '<br>' + items[i]['address'];
      if (items[i]['url']) {
        text += '<br><a href="' + items[i]['url'] + '" target="_blank">' + items[i]['url'] + '</a>';
      }
      info_window[i] = new google.maps.InfoWindow({
        content: text
      });
      marker_event(i);
    }
  }

  function get_shop(map) {
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {
      if (req.readyState == 4 && req.status === 200) {
        const obj = JSON.parse(req.responseText);
        set_marker(obj['Items'], map)
      }
    }
    req.open('GET', '店舗一覧取得API');
    req.send();
  }
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=API_KEY&callback=init_map"></script>

フロント作成については以下を参考にさせていただきました
https://tonari-it.com/gas-button-event-javascript/
https://www.tam-tam.co.jp/tipsnote/javascript/post7755.html
https://webdesignday.jp/inspiration/technique/css/4213/

最後に

ここ最近毎年高知に行っていて、好きな土地の一つでもあります
早く高知に行けるようになって欲しいです

また、「あしたの分も買うちょくきね。~飲食券先買い応援プロジェクト~「参加店舗」」と私はまったくの無関係です
問題があるようでしたら即刻削除致します

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1