高知に行きたくても行けないので勝手にやりました
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/
最後に
ここ最近毎年高知に行っていて、好きな土地の一つでもあります
早く高知に行けるようになって欲しいです
また、「あしたの分も買うちょくきね。~飲食券先買い応援プロジェクト~「参加店舗」」と私はまったくの無関係です
問題があるようでしたら即刻削除致します