概要
都道府県名と駅名を入力すると、その周辺(半径1km以内)のお店を検索するアプリを作ってみました!
(HotpepperAPIを使用しているのでHotpepperに載っているお店限定ではありますが・・)
検索される内容としては、店名、住所、ホットペッパーのリンク、飲み放題について、予算、開店日です。とりあえず検索して予算とか飲み放題とかが希望にあってたらお店のリンクに飛ぶって感じの使い方がいいかなと思います。
表示されるお店は検索条件に合っている中からランダムで3つ選択されます。そのため、「3つ微妙!」と感じたらすぐ戻ってもう一回検索押してもらう、そんなお店ガチャみたいな使い方を想定しています。
作ろうと思ったきっかけ
皆さん飲んでいて、2件目に困った経験がありませんか?
1件目は幹事なり誰かが予約して行くことが多いと思います。しかし、2件目までお店が決まっていることはあまりないかと思います。
正直2件目なんてもう酔ってるし、酒が飲めればどこでもいい!
なので手っ取り早く、開いてて、飲み放題があって、安い近くのお店を検索できれば、、、という思いで作りました。
apiの使い方とかはこちらの記事を参考にさせて頂きました。
コード
バージョン
conda 22.11.1
python 3.9.12
flask 2.0.3
ディレクトリ構造
flask
├── app.py
├── static
| ├── css
| | ├── style_main.css
| | └── style_search.css
| └── images
└── templates
├── templates.html
└── search.html
app.py
from flask import Flask, render_template, request
import json
import requests
import urllib.parse
import pandas as pd
import os
import random
TEMPLATE_DIR = os.path.abspath('./templates')
STATIC_DIR = os.path.abspath('./static')
app = Flask(__name__, template_folder=TEMPLATE_DIR, static_folder=STATIC_DIR)
def RailAPI(station, pref):
station_url =urllib.parse.quote(station)
pref_url =urllib.parse.quote(pref)
api='http://express.heartrails.com/api/json?method=getStations&name={station_name}&prefecture={pref_name}'
url=api.format(station_name=station_url, pref_name=pref_url)
response=requests.get(url)
result_list = json.loads(response.text)['response']['station']
lng=result_list[0]['x']
lat=result_list[0]['y']
return lat, lng
def HotpepperAPI(lat, lng, free_drink, code):
api_key="" #APIキーを入力
if code:
api = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?" \
"key={key}&lat={lat}&lng={lng}&budget={code}&free_drink={free_drink}&range=3&count=200&order=1&format=json"
else:
api = "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?" \
"key={key}&lat={lat}&lng={lng}&free_drink={free_drink}&range=3&count=200&order=1&format=json"
url=api.format(key=api_key,lat=lat, lng=lng, code=code, free_drink=free_drink)
response = requests.get(url)
result_list = json.loads(response.text)['results']['shop']
shop_datas=[]
for shop_data in result_list:
shop_datas.append([shop_data["name"],shop_data["address"],shop_data["urls"]['pc'],shop_data["free_drink"], shop_data["budget"]['average'],shop_data["open"]])
shop_datas = random.sample(shop_datas, 3)
return shop_datas
@app.route('/', methods=["GET"])
def main_page():
return render_template("templates.html")
@app.route("/results", methods=["POST"])
def result_page():
pref = request.form["pref"]
station = request.form["station"]
#飲み放題設定
drink = request.form.get("drink")
if drink == "ari":
free_drink = 1
elif drink == "nasi":
free_drink = 0
#予算設定
yosan = request.form.get("yosan")
if yosan == "yosan0":
code = None
elif yosan == "B011":
code = "B011"
elif yosan == "B001":
code = "B001"
elif yosan == "B002":
code = "B002"
elif yosan == "B003":
code = "B003"
elif yosan == "B008":
code = "B008"
elif yosan == "B004":
code = "B004"
lat_st, lng_st = RailAPI(station, pref)
shop_data = HotpepperAPI(lat_st, lng_st, free_drink, code)
shop1 = shop_data[0]
shop2 = shop_data[1]
shop3 = shop_data[2]
return render_template("search.html", shop1=shop1, shop2=shop2, shop3=shop3)
if __name__ == "__main__":
app.run(port=8888)
templates.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Tyoe" content="text/javascript" />
<title>2軒目検索アプリ</title>
<meta name="Description" content="2軒目の居酒屋を探してるならこれ!2軒目をランダムに検索" />
<meta name="Keywords" content="居酒屋,2軒目,検索" />
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/style_main.css') }}">
<link href="../static/css/style_main.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- ヘッダー(サイト名やサイトの内容を示す部分) -->
<div id="header">
<div id="logo">
<ul>
<li>2軒目テキトーに検索!</li>
</ul>
</div>
</div>
<!-- ここからメインの内容 -->
<div id="container">
<div id="home">
<img src="../static/images/izakaya-image.jpg" width="838" height="500" alt="image" />
<h1>都道府県名と駅名を入力してください</h1>
<form action="/results" method="POST" enctype="multipart/form-data">
<div>
<label for="pref">都道府県名:</label>
<input type="text" id="pref" name="pref" placeholder="○○県" class="form1" required>
<label for="pref">駅名:</label>
<input type="text" id="station" name="station" placeholder="「駅」は必要ありません" class="form2" required>
</div>
<div>
<label for="pref">飲み放題</label>
<select name="drink">
<option value="ari">あり</option>
<option value="nasi">どちらでも</option>
</select>
<label for="pref">予算</label>
<select name="yosan">
<option value="yosan0">特になし</option>
<option value="B011">1001~1500円</option>
<option value="B001">1501~2000円</option>
<option value="B002">2001~3000円</option>
<option value="B003">3001~4000円</option>
<option value="B008">4001~5000円</option>
<option value="B004">5001~7000円</option>
</select>
</div>
<button type="submit">検索</button>
</form>
<h3>
<span>※都道府県名と駅名は正式名称(漢字など)で入力してください。(例:あいち✕、愛知県〇)</span>
</h3>
<h2>概要</h2>
<p>HotpepperAPIを使用して、駅から半径1km以内の条件に合うお店を検索してランダムに3つ表示します。</p>
</div>
<!-- ページの下部分 -->
<div id="footer">
<p>Copyright(C) © 2023 YuuN </p>
</div>
search.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta http-equiv="Content-Script-Tyoe" content="text/javascript" />
<title>ここにキーワードを含むページのタイトルを記入</title>
<meta name="Description" content="ここにキーワードを含むページの説明文を記入" />
<meta name="Keywords" content="キーワード,キーワード,キーワード" />
<!--
<link rel="icon" href="https://saetl.net/favicon.ico" type="image/x-icon" />
<link rel="Shortcut Icon" type="img/x-icon" href="https://saetl.net/favicon.ico" />
-->
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='css/style_search.css') }}">
<link href="../static/css/style_search.css" rel="stylesheet" type="text/css" />
<!--
<link href="css/lightbox.css" rel="stylesheet" type="text/css" />
-->
</head>
<body>
<!-- ヘッダー(サイト名やサイトの内容を示す部分) -->
<div id="header">
<div id="logo">
<ul>
<li>検索結果</li>
</ul>
<!-- / #logo --></div>
</div>
<!-- ここからメインの内容 -->
<div id="container">
<div id="category1">
<h2>オススメ1</h2>
<table class="ta2">
<tr>
<th colspan="2">{{ shop1.0 }}</th>
</tr>
<tr>
<td>住所</td>
<td>{{ shop1.1 }}</td>
</tr>
<tr>
<td>店のページ</td>
<td><a href="{{shop1.2}}">{{ shop1.2 }}</a></td>
</tr>
<tr>
<td>飲み放題</td>
<td>{{ shop1.3 }}</td>
</tr>
<tr>
<td>予算</td>
<td>{{ shop1.4 }}</td>
</tr>
<tr>
<td>開店日</td>
<td>{{ shop1.5 }}</td>
</tr>
</table>
</div>
<div id="category2">
<h2>オススメ2</h2>
<table class="ta2">
<tr>
<th colspan="2">{{ shop2.0 }}</th>
</tr>
<tr>
<td>住所</td>
<td>{{ shop2.1 }}</td>
</tr>
<tr>
<td>店のページ</td>
<td><a href="{{shop2.2}}">{{ shop2.2 }}</a></td>
</tr>
<tr>
<td>飲み放題</td>
<td>{{ shop2.3 }}</td>
</tr>
<tr>
<td>予算</td>
<td>{{ shop2.4 }}</td>
</tr>
<tr>
<td>開店日</td>
<td>{{ shop2.5 }}</td>
</tr>
</table>
</div>
<div id="category3">
<h2>オススメ3</h2>
<table class="ta2">
<tr>
<th colspan="2">{{ shop3.0 }}</th>
</tr>
<tr>
<td>住所</td>
<td>{{ shop3.1 }}</td>
</tr>
<tr>
<td>店のページ</td>
<td><a href="{{shop3.2}}">{{ shop3.2 }}</a></td>
</tr>
<tr>
<td>飲み放題</td>
<td>{{ shop3.3 }}</td>
</tr>
<tr>
<td>予算</td>
<td>{{ shop3.4 }}</td>
</tr>
<tr>
<td>開店日</td>
<td>{{ shop3.5 }}</td>
</tr>
</table>
</div>
</div>
<!-- ページの下部分 -->
<div id="footer">
<p>Copyright(C) © 2023 YuuN </p>
</div>
<!-- JavaScript設定 -->
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <!--googleのCDN(ネットワーク経由でコンテンツを提供するサービス)よりjqueryをロード-->
<script type="text/javascript" src="js/jQuery.ScrollTo.js"></script> <!--ページ内スクロール-->
<script type="text/javascript" src="js/lightbox.js"></script> <!--画像拡大-->
<script type="text/javascript">
$(function() {
$(".ta2 tr:even").addClass("even");});
</script>
<!-- テーブル1行ごと背景色変更 -->
</body>
</html>
style_main.css
/* ページ全体のスタイル */
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.5;
background-color: #261818;
color: #ffffff;
}
/* リンクのスタイル */
a {
color: #007ACC;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* ヘッダーのスタイル */
#header {
background-color: #ffffff;
border-bottom: 1px solid #ccc;
padding: 20px;
}
#logo {
font-size: 24px;
font-weight: bold;
color: #000000;
}
/* メインコンテンツのスタイル */
#container {
width: 100%;
margin: 0 auto;
padding: 0px;
}
#home {
text-align: center;
position: relative;
margin-top: 50px;
height: 800px;
}
#home img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: auto;
z-index: -1; /* メインの内容より下に表示されるようにする */
}
h1 {
font-size: 36px;
margin-top: 50px;
margin-bottom: 30px;
color: #ffffff;
}
form {
display: inline-block;
text-align: left;
}
label {
display: block;
margin-bottom: 10px;
}
input[type="text"],
select {
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
width: 250px;
margin-bottom: 10px;
}
select {
margin-left: 20px;
}
button[type="submit"] {
padding: 10px 20px;
font-size: 16px;
border: none;
background-color: #007ACC;
color: #fff;
border-radius: 5px;
cursor: pointer;
}
button[type="submit"]:hover {
background-color: #005DA7;
}
h2 {
font-size: 30px;
margin-top: 50px;
margin-bottom: 30px;
color: #ffffff;
}
p {
font-size: 20px;
font-weight: bold;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li {
margin-bottom: 10px;
}
h3 {
font-size:25px;
margin-top: 30px;
margin-bottom: 30px;
color: #ffffff;
}
span {
color: #FF0000;
}
/* フッターのスタイル */
#footer {
background-color: #fff;
border-top: 1px solid #ccc;
padding: 20px;
text-align: center;
font-size: 14px;
color: #000000;
}
style_search.css
/* 全体のスタイル */
body {
font-family: Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
/* ヘッダー */
#header {
background-color: #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
position: fixed;
top: 0;
width: 100%;
z-index: 999;
}
#logo {
float: left;
font-size: 24px;
font-weight: bold;
margin: 10px 20px;
text-transform: uppercase;
}
/* メインコンテンツ */
#container {
margin-top: 80px;
padding: 20px;
}
.ta2 {
border-collapse: collapse;
width: 100%;
}
.ta2 th {
background-color: #2b2b2b;
color: #fff;
font-weight: normal;
padding: 10px;
text-align: left;
vertical-align: middle;
}
.ta2 td {
background-color: #f5f5f5;
padding: 10px;
vertical-align: middle;
}
.ta2 td:first-child {
font-weight: bold;
width: 25%;
}
/* カテゴリー */
#category1, #category2, #category3 {
margin-bottom: 40px;
}
h2 {
border-bottom: 2px solid #2b2b2b;
font-size: 24px;
margin: 10 0 20px;
padding-bottom: 10px;
}
/* フッター */
#footer {
background-color: #2b2b2b;
color: #fff;
padding: 10px 20px;
text-align: center;
}
感想
flaskで簡単なアプリを作ってみたいと思って作ったアプリでしたが、apiの仕組みとかhtmlの変数の受け渡しとか色々勉強になりました。pythonしか触ってこなかったので、htmlとcssにかなり苦戦しました、、、(見よう見まねで、コードもかなりつたないです)
レスポンシブデザインとかまったく考慮してないので、このアプリの現在の実用性はほぼ皆無です。(笑)
なので、これをそのままLINEbotにしようかなと考えています。