Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

discordでランチの店を教えてくれるbotをHerokuで動かす

今日お昼どこ行こうか...ってみなさん悩みますよね。

ということでdiscordのbotを作りました。
動作はこんな感じです。

無題.png

エリアとジャンルを指定して食べログのランキング上位5件内のURLを
ランダムで返します。

一式ここに置いてます
https://github.com/sato-ken/discord-lunch-bot

作り方

1. Bot作成

まず以下ページを参考にBotを作成します。
何か話しかけてメッセージが返ってくればOKです。

2. 食べログスクレイピング

こちらのURLを参考に食べログをスクレイピングすることにしました。

参考にした記事では新宿のラーメン屋をスクレイピングしてきますが、以下改良します。

①エリアを選べるように
②ラーメンだけではなく他のジャンルも
③ランキングのページをスクレイピング
④店名ではなくURLを取得するように

3.改良

①エリアを選べるように
食べログのページに行きURLを貼り付けます。
会社の近くということで神保町と御茶ノ水のURLをコードにベタ書いちゃってますが、
増やしていくならファイルに外だししたほうがいいですね。。。

tabelog.py
jinbo_url = "https://tabelog.com/tokyo/A1310/A131003/rstLst/"
ocha_url  = "https://tabelog.com/tokyo/A1310/A131002/R2080/rstLst/"

②ラーメンだけではなく他のジャンルも
食べログのURLの仕組みを眺めると以下がわかります。

"https://tabelog.com/tokyo/A1310/A131003/rstLst/" ←神保町エリア
"https://tabelog.com/tokyo/A1310/A131003/rstLst/soba/" ←神保町のそば
"https://tabelog.com/tokyo/A1310/A131003/rstLst/curry/" ←神保町のカレー

無題1.png

エリアの後ろにジャンルを指定するようです。
discordのメッセージでジャンルを指定させてcsvファイルで変換するようにしました。

list.csv
和食,washoku
日本料理,japanese
寿司,sushi
魚介,seafood
そば,soba
うどん,udon
以下、省略

③ランキングのページをスクレイピング
美味しいものが食べたいということでランキング上位の店を出すことにします。
また食べログのURLの仕組みを眺めます。

"https://tabelog.com/tokyo/A1310/A131003/rstLst/curry/" ←神保町のカレー
"https://tabelog.com/tokyo/A1310/A131003/rstLst/curry/?SrtT=rt&Srt=D&sort_mode=1" ←神保町のカレーがランキング形式で上位20件が表示。

エリアとジャンルを指定したURLの後ろに"?SrtT=rt&Srt=D&sort_mode=1"をくっつければOKです。

④店名ではなくURLを取得するように
①~③の手順で生成したURLをスクレイピングしてお店のURLを取得します。
printでスクレイピングしたものを眺めてみてsoup_a.get("href")でランキング20位までのお店のURLが取れました。

最終的に

こんなソースになりました。

bot.py
import discord
import random
import tabelog
client = discord.Client()

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_message(message):
    if  message.content.startswith("ランチ"):
        if client.user != message.author:
            say = message.content
            words = say.split("、")
            lunch = tabelog.get_shop_list(words[1], words[2])
            await client.send_message(message.channel, lunch)

client.run("TOKEN")

上位5件に入ってる店のURLを返します。

tabelog.py
from bs4 import BeautifulSoup
import requests
import csv
import random

jinbo_url = "https://tabelog.com/tokyo/A1310/A131003/rstLst/"
ocha_url  = "https://tabelog.com/tokyo/A1310/A131002/R2080/rstLst/"
list_usl  = "?SrtT=rt&Srt=D&sort_mode=1"

def read_csv(f_name):
    csv_file = open("list.csv", "r", encoding="utf_8", errors="", newline="" )
    f = csv.reader(csv_file, delimiter=",", doublequote=True, lineterminator="\n", quotechar='"', skipinitialspace=True)
    return f

def make_url(csv_list, town, food):
    req_url = ""
    for i in csv_list:
        if i[0] == food:
            req_url = town + i[1] + "/" +list_usl
    return req_url

def scrape_list(list_url):
    """
    一覧ページのパーシング
    """
    shop_list = []
    r = requests.get(list_url)
    if r.status_code != requests.codes.ok:
        return False
    soup = BeautifulSoup(r.content, 'lxml')
    soup_a_list = soup.find_all("a", class_="list-rst__rst-name-target")
    if len(soup_a_list) == 0:
        return False
    for soup_a in soup_a_list:
        item_url = soup_a.get("href")
        shop_list.append(item_url)
    return shop_list

def get_shop_list(erea, food):
    data = read_csv('list.csv')
    tabelogu_url = ""
    if erea == "神保町":
        tabelogu_url = make_url(data, jinbo_url, food)
    elif erea == "御茶ノ水":
        tabelogu_url = make_url(data, ocha_url, food)
    rank_list = scrape_list(tabelogu_url)
    return rank_list[random.randint(0,4)]
list.csv
和食,washoku
日本料理,japanese
寿司,sushi
魚介,seafood
そば,soba
うどん,udon
うなぎ,unagi
焼き鳥,yakitori
とんかつ,tonkatsu
串揚げ,kushiage
天ぷら,tempura
お好み焼き,okonomiyaki
もんじゃ焼き,monjya
しゃぶしゃぶ,syabusyabu
沖縄料理,okinawafood
洋食,yoshoku
フレンチ,french
イタリアン,italian
スペイン料理,spain
パスタ,pasta
ピザ,pizza
ステーキ,staek
ハンバーグ,hamburgersteak
ハンバーガー,hamburger
中華,chinese
餃子,gyouza
韓国料理,korea
タイ料理,thai
ラーメン,ramen
カレー,curry
焼肉,yakinuku
ホルモン,horumon
鍋,nabe
もつ鍋,motsu
居酒屋,izakaya
バイキング,viking
カフェ,cafe
パン,pan
スイーツ,sweets
ケーキ,cake
バー,bar

Herokuにデプロイ

Herokuで動かしてみることにしました。
使うの初めてだったのでまず登録してCLIをインストールしました。

image.png

こちらの手順を参考にデプロイを行います。
https://www.slideshare.net/dcubeio/python-heroku-slack-bot

以下の3つの設定ファイルを用意します。

Procfile

worker: python bot.py
runtime.txt
python-3.6.4
requirements.txt
discord.py==0.16.12
beautifulsoup4==4.6.0
requests==2.18.4
lxml==4.1.1

Herokuにログインしてアプリを作成します。

>heroku login
>heroku create lunchlist
>git init
>heroku git:remote -a lunchlist
>git add .
>git commit -am "first commit"

deploy実行

>git push heroku master
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 2.26 KiB | 210.00 KiB/s, done.
Total 8 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Python app detected
remote:  !     The latest version of Python 3 is python-3.6.4 (you are using python-3.6.3, which is unsupported).
remote:  !     We recommend upgrading by specifying the latest version (python-3.6.4).
remote:        Learn More: https://devcenter.heroku.com/articles/python-runtimes
remote: -----> Installing python-3.6.3
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote:        Collecting discord.py==0.16.12 (from -r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requirements.txt (lin
e 1))
remote:          Downloading discord.py-0.16.12.tar.gz (414kB)
remote:        Collecting beautifulsoup4==4.6.0 (from -r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requirements.txt (l
ine 2))
remote:          Downloading beautifulsoup4-4.6.0-py3-none-any.whl (86kB)
remote:        Collecting requests==2.18.4 (from -r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requirements.txt (line 3
))
remote:          Downloading requests-2.18.4-py2.py3-none-any.whl (88kB)
remote:        Collecting lxml==4.1.1 (from -r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requirements.txt (line 4))
remote:          Downloading lxml-4.1.1-cp36-cp36m-manylinux1_x86_64.whl (5.6MB)
remote:        Collecting aiohttp<1.1.0,>=1.0.0 (from discord.py==0.16.12->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f1
3/requirements.txt (line 1))
remote:          Downloading aiohttp-1.0.5.tar.gz (499kB)
remote:        Collecting websockets<4.0,>=3.1 (from discord.py==0.16.12->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13
/requirements.txt (line 1))
remote:          Downloading websockets-3.4-cp36-cp36m-manylinux1_x86_64.whl (54kB)
remote:        Collecting idna<2.7,>=2.5 (from requests==2.18.4->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requirem
ents.txt (line 3))
remote:          Downloading idna-2.6-py2.py3-none-any.whl (56kB)
remote:        Collecting chardet<3.1.0,>=3.0.2 (from requests==2.18.4->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/r
equirements.txt (line 3))
remote:          Downloading chardet-3.0.4-py2.py3-none-any.whl (133kB)
remote:        Collecting urllib3<1.23,>=1.21.1 (from requests==2.18.4->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/r
equirements.txt (line 3))
remote:          Downloading urllib3-1.22-py2.py3-none-any.whl (132kB)
remote:        Collecting certifi>=2017.4.17 (from requests==2.18.4->-r /tmp/build_2929c25e13c07a309b65a5e5e97c7f13/requ
irements.txt (line 3))
remote:          Downloading certifi-2018.1.18-py2.py3-none-any.whl (151kB)
remote:        Collecting multidict>=2.0 (from aiohttp<1.1.0,>=1.0.0->discord.py==0.16.12->-r /tmp/build_2929c25e13c07a3
09b65a5e5e97c7f13/requirements.txt (line 1))
remote:          Downloading multidict-4.1.0-cp36-cp36m-manylinux1_x86_64.whl (482kB)
remote:        Collecting async_timeout (from aiohttp<1.1.0,>=1.0.0->discord.py==0.16.12->-r /tmp/build_2929c25e13c07a30
9b65a5e5e97c7f13/requirements.txt (line 1))
remote:          Downloading async_timeout-2.0.0-py3-none-any.whl
remote:        Installing collected packages: chardet, multidict, async-timeout, aiohttp, websockets, discord.py, beauti
fulsoup4, idna, urllib3, certifi, requests, lxml
remote:          Running setup.py install for aiohttp: started
remote:            Running setup.py install for aiohttp: finished with status 'done'
remote:          Running setup.py install for discord.py: started
remote:            Running setup.py install for discord.py: finished with status 'done'
remote:        Successfully installed aiohttp-1.0.5 async-timeout-2.0.0 beautifulsoup4-4.6.0 certifi-2018.1.18 chardet-3
.0.4 discord.py-0.16.12 idna-2.6 lxml-4.1.1 multidict-4.1.0 requests-2.18.4 urllib3-1.22 websockets-3.4
remote:
remote: -----> Discovering process types
remote:
remote: -----> Compressing...
remote:        Done: 49.1M
remote: -----> Launching...
remote:        Released v3
remote:        https://lunchlist.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/lunchlist.git
 * [new branch]      master -> master

>

動きました。
※設定ファイルミスって最初上手く動かなかったのはナイショ

>heroku ps
Free dyno hours quota remaining this month: 550h 0m (100%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping

=== worker (Free): python boss_bot.py (1)
worker.1: up 2018/02/24 22:51:38 +0900 (~ 2h ago)


>
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
20
Help us understand the problem. What are the problem?