はじめに
よくあるPythonでLineBotを作ってみたってやつを自分でもやってみたくて作りました。
仕組みは簡単で、クックパッドで検索をする作業をLineBotで出来るようにしただけです。
※いわゆるスクレイピングという技術を使うわけですが自己責任でお願いします。
あんまりこういう記事では見かけない
定期的にメッセージを送信してくれる方法も解説します。
準備
まずはPython + LineBot + Herokuの環境を作成します。
私は以下の記事を参考にしました。
https://qiita.com/shimajiri/items/cf7ccf69d184fdb2fb26
https://developers.line.biz/ja/docs/messaging-api/building-sample-bot-with-heroku/
デプロイするファイル構成
以下を全て同じディレクトリに配置します。
ファイル名 | 説明 |
---|---|
main.py | ソースコード |
runtime.txt | Pythonのバージョンを記載 |
requirements.txt | インストールするライブラリの記載 |
Procfile | プログラムの実行方法を定義 |
実装
web: python main.py
Flask==1.0.2
line-bot-sdk==1.8.0
beautifulsoup4==4.7.1
urllib3==1.24.1
soupsieve==1.6.1
python-3.9.0
from flask import Flask, request, abort
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage, LocationMessage
)
from linebot.exceptions import LineBotApiError
from linebot.models import (
CarouselColumn, CarouselTemplate, FollowEvent,
LocationMessage, MessageEvent, TemplateSendMessage,
TextMessage, TextSendMessage, UnfollowEvent, URITemplateAction
)
import os
import requests
from bs4 import BeautifulSoup
import re
app = Flask(__name__)
YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"]
YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"]
DATABASE_URL = os.environ['DATABASE_URL']
line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(YOUR_CHANNEL_SECRET)
#WebhookからURLにイベントが送られるようにする
@app.route("/callback", methods=['POST'])
def callback():
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
#テキストメッセージが送られてきた場合
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
#テキスト保持
text = event.message.text
#料理リスト
list = []
#クックパッドで検索
url = "https://cookpad.com/search/" + text + "?order="
#検索結果取得
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
#レシピタイトル
title = soup.find_all(class_="recipe-title")
#レシピ画像
image = soup.find_all(class_="recipe-image")
#レシピ内容
memo = soup.find_all(class_="recipe_description")
#5つのレシピを料理リストに追加
i=0
for each in title[:5]:
result_dict = {
"thumbnail_image_url": image[i].find('img').get('src'),
"title": title[i].get_text(),
"text": memo[i].get_text().strip('\n'),
"actions": {
"label": "料理を見る",
"uri": "https://cookpad.com" + each.get('href')
}
}
list.append(result_dict)
i+=1
#カルーセルに渡す形に変換
columns = [
CarouselColumn(
thumbnail_image_url=column["thumbnail_image_url"],
title=column["title"],
text=column["text"],
actions=[
URITemplateAction(
label=column["actions"]["label"],
uri=column["actions"]["uri"],
)
]
)
for column in list
]
#メッセージ作成
messages = TemplateSendMessage(alt_text="料理について提案しました。",template=CarouselTemplate(columns=columns))
#送信する
try:
line_bot_api.reply_message(event.reply_token, messages=messages)
except Exception as e:
line_bot_api.reply_message(event.reply_token, messages=str(e))
if __name__ == "__main__":
port = int(os.getenv("PORT", 5000))
app.run(host="0.0.0.0", port=port)
##完成イメージ
ここまでで一旦以下のようにキーワードを送信すると料理を5つおすすめしてくれるbotが
出来上がりました。
材料入れたら料理提案してくれるbot作ってみたhttps://t.co/Ft7yGclsgO#現役エンジニアと繋がりたい #駆け出しエンジニアと繋がりたい #プログラミング #Python #linebot #heroku pic.twitter.com/QKQl0FTXia
— そると (@jsalt_0525) February 5, 2021
##定期実行機能追加
料理を提案してくれるbotは出来ましたが、こちらから材料を送信しないと提案してくれませんので
定期的に自動で料理を提案してくれる機能も追加してみましょう!
##定期実行の仕組み
定期実行をするためにはHerokuのschedulerという機能を追加します。
また、定期で実行するPythonのファイルを上記で作成したファイルたちと同じディレクトリに作成します。
まずは以下の記事を参考に同じく定期実行する scheduler.py
を作成してみましょう
https://qiita.com/eito_2/items/b313077e3860ee6a71b7
##DB用意
定期実行の仕組みがわかったところであと一つ必要な準備があります。
最初に作ったbotでは自分からbotに材料をテキストメッセージとして送ってあげることで
差出人の情報(ユーザーID)を取得し返信することが出来ましたが、
botの方から送信するためには宛先(ユーザーID)を保持していなければなりません。
ということでHerokuの機能であるpostgresを追加します。
私は以下の記事を参考に追加しました。
https://qiita.com/q_masa/items/6eb28f772157382aeac8
##実装2
まずはDBに接続するためにpsycopg2というライブラリを追加します。
Flask==1.0.2
line-bot-sdk==1.8.0
beautifulsoup4==4.7.1
urllib3==1.24.1
soupsieve==1.6.1
psycopg2
次にmain.pyに友達追加時にDBにユーザーIDを保持する処理を追加します。
ここでは<Host><Database><User>****<Password>に
それぞれHerokuのpostgresのページにあるDatabase Credentialsに記載の情報を入れます。
#~ 省略 ~
import psycopg2
#接続情報取得
def get_connection():
dsn = "host=<Host> port=5432 dbname=<Database> user=<User> password=<Password>"
return psycopg2.connect(dsn)
#友達追加時のイベント
@handler.add(FollowEvent)
def handle_follow(event):
#友達追加時DBにユーザーIDを登録する
with get_connection() as conn:
with conn.cursor() as cur:
try:
profile = line_bot_api.get_profile(event.source.user_id)
s = 'INSERT INTO Users VALUES (%s)' % ("'" + str(profile.user_id) + "'")
cur.execute(s)
conn.commit()
except:
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='exception')
)
#~ 省略 ~
これで友達追加時にDBにユーザーIDが追加されます。
もしちゃんと追加されたか確認したい場合はターミナルに
heroku pg:psql -a <アプリケーション名>
を入力すればSQLを実行できるようになるので確認してみてください。
詳しい使い方はここでは割愛します。
最後に scheduler.py
を修正して定期的に料理を送ってくれるようにします。
from flask import Flask, request, abort
import os
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)
from linebot.models import (
CarouselColumn, CarouselTemplate, FollowEvent,
LocationMessage, MessageEvent, TemplateSendMessage,
TextMessage, TextSendMessage, UnfollowEvent, URITemplateAction
)
import psycopg2
import requests
from bs4 import BeautifulSoup
import re
app = Flask(__name__)
LINE_CHANNEL_ACCESS_TOKEN = "<botのChannel access token>"
line_bot_api = LineBotApi(LINE_CHANNEL_ACCESS_TOKEN)
#接続情報取得
def get_connection():
dsn = "host=<Host> port=5432 dbname=<Database> user=<User> password=<Password>"
return psycopg2.connect(dsn)
def main():
#料理リスト
list = []
#クックパッドで検索
url = "https://cookpad.com/search/" + "豚肉" + "?order="
#検索結果取得
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
#レシピタイトル
title = soup.find_all(class_="recipe-title")
#レシピ画像
image = soup.find_all(class_="recipe-image")
#レシピ内容
memo = soup.find_all(class_="recipe_description")
#5つのレシピを料理リストに追加
i=0
for each in title[:5]:
result_dict = {
"thumbnail_image_url": image[i].find('img').get('src'),
"title": title[i].get_text(),
"text": memo[i].get_text().strip('\n'),
"actions": {
"label": "料理を見る",
"uri": "https://cookpad.com" + each.get('href')
}
}
list.append(result_dict)
i+=1
#カルーセルに渡す形に変換
columns = [
CarouselColumn(
thumbnail_image_url=column["thumbnail_image_url"],
title=column["title"],
text=column["text"],
actions=[
URITemplateAction(
label=column["actions"]["label"],
uri=column["actions"]["uri"],
)
]
)
for column in list
]
#メッセージ作成
messages = TemplateSendMessage(alt_text="料理について提案しました。",template=CarouselTemplate(columns=columns))
#DBに登録されている友達分送信する
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM Users")
for each in cur:
line_bot_api.push_message(each[0], messages=messages)
if __name__ == "__main__":
main()
##完成イメージ2
ここまでで以下のようにHerokuのschedulerで指定した時間に"豚肉"料理が送信されてくればOKです!
定期的に料理提案してくれるbot作ってみたhttps://t.co/7u7j4MOZrl…#現役エンジニアと繋がりたい #駆け出しエンジニアと繋がりたい #プログラミング #Python #linebot #heroku pic.twitter.com/CCZsM67Y7K
— そると (@jsalt_0525) February 5, 2021
今回は材料に固定で"豚肉"を入れていますが
材料を何種類かDBに持っといて定期的にランダムで材料を選んで送信してくれるようにしたりとか
過去にこっちから送った材料をDBに保持しておいて勝手に提案してくれたりとか
色々発展させればもっと楽しくなりそうですね!
##まとめ
今回はPython + LineBot + Herokuで料理を提案してくれるbotの作成を行いましたが
料理の提案以外にもいろんなことが出来そうですね。
基盤さえ出来ていれば後は乗せていけばいいだけなので
自分が好きなことにどんどん応用していって知見を広げていきましょう!
##ソースコード
https://github.com/beniho/CuisineBot