Edited at

[python] LINEで画像を送ると商品検索するbotを作ってみた

More than 1 year has passed since last update.


:star: 概要


  • LINEから画像が送信されるとMessagingAPIを使ってwebhookで画像を受け取る

  • docomoの画像認識APIで画像に写っている商品を分析し、その結果をカルーセルで表示する

  • docomoの画像認識APIで認識できる商品は、食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトのみ


:star: 実際に作ったもの


  • 下のボタン、もしくは@sga5626fでID検索すると友達に追加できます


  • 画像を送るとこんな感じで分析結果を返してくれます


:warning: 2018/7/12 追記


  • docomoの画像認識APIのうち、オブジェクト認識APIは提供終了となりました

  • よって、このbotはもう使えません(泣)


:star: 事前に準備すること


  • LINEのbotを用意する

  • webhookを受け取るアプリを作る

  • Herokuにデプロイする

  • LINEとアプリを連携する

上記については前に書いた記事 [python] LINEの文字起こし君を作ってみた と同じですので割愛します


:pencil: docomoAPIのAPIキーを取得する



  • # docomo Developer support に登録します

  • APIの新規利用申請をします。必要事項を入力して、画像認識のAPIの利用申請をしましょう


  • 利用申請が完了すると、APIキーが発行されます


:pencil: docomoAPIを使って画像から商品を検索する



  • # 画像認識API リファレンス に沿ってリクエストを送ります


  • candidatesがある場合は整形してlistの出力データを作ります


  • categoryによって返ってくるデータが若干違う(例: bookの場合はmakerやbrandがない等)ので注意してください

  • LINEのmessagingAPIで画像URLを送る場合、httpsしか受け付けないのでhttpsの場合のみimageUrlを使うようにしています


docomo.py


import requests

import settings

API_KEY = settings.API_KEY

endpoint = 'https://api.apigw.smt.docomo.ne.jp/imageRecognition/v1/recognize'

def search_product(image=None):
if image is None:
return '必要な情報が足りません'

params = {
'APIKEY': API_KEY,
'recog': 'product-all',
'numOfCandidates': 5,
}

headers = {"Content-Type": "application/octet-stream"}

response = requests.post(
endpoint,
headers=headers,
params=params,
data=image,
)

status = response.status_code
data = response.json()

print(data)

if status != 200 and status != 204:

if status == 403:
error = '今月に使用できる回数を超過しました'

else:
error = 'エラーが発生しました'

print(status, data)
return error

candidates = data.get('candidates')

if not candidates:
return '商品が見つかりませんでした'

results = []
for content in candidates:
content['detail']['maker'] = \
content['detail'].get('maker') or \
content['detail'].get('publisher', '')

content['detail']['brand'] = \
content['detail'].get('brand') or \
content['detail'].get('author', '')

imageUrl = 'https://i.vimeocdn.com/video/443809727.jpg?mw=700&mh=394'

if content['imageUrl'].startswith('https'):
imageUrl = content['imageUrl']

result = {
"thumbnail_image_url": imageUrl,
"title": content['detail']['itemName'][:40],
"text": "{maker}: {brand}\n発売日: {releaseDate}"
.format(**content['detail']),
"actions": {
"label": "商品ページを見る",
"uri": content['sites'][0]['url']
}
}
results.append(result)

print(results)

return results



:pencil: LINEでカルーセルテンプレートで返信する


result = search_product(image)

if isinstance(result, str):
messages = [
TextSendMessage(text=result),
TextSendMessage(text='食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトを検索できるよ!')
]

elif isinstance(result, list):

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 result
]

messages = TemplateSendMessage(
alt_text='template',
template=CarouselTemplate(columns=columns),
)


  • これをindex.pyに組み込むと、こんな感じになります


index.py


import os
from io import BytesIO

from flask import Flask, abort, request
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (CarouselColumn, CarouselTemplate, ImageMessage,
MessageEvent, TemplateSendMessage, TextMessage,
TextSendMessage, URITemplateAction)

import settings
from docomo import search_product

app = Flask(__name__)

line_bot_api = LineBotApi(settings.YOUR_CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(settings.YOUR_CHANNEL_SECRET)

@app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']

# get request body as text
body = request.get_data(as_text=True)
# print("body:", body)

# handle webhook body
try:
handler.handle(body, signature)

except InvalidSignatureError as e:
print("InvalidSignatureError:", e)
abort(400)

return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
# print("handle_message:", event)
text = event.message.text

messages = [
TextSendMessage(text=text),
TextSendMessage(text='画像を送ってみてね!'),
]

reply_message(event, messages)

@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
# print("handle_image:", event)

message_id = event.message.id
message_content = line_bot_api.get_message_content(message_id)

image = BytesIO(message_content.content)

try:
result = search_product(image)

if isinstance(result, str):
messages = [
TextSendMessage(text=result),
TextSendMessage(text='食品パッケージ/書籍/CD/DVD/ゲームソフト/PCソフトを検索できるよ!')
]

elif isinstance(result, list):

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 result
]

messages = TemplateSendMessage(
alt_text='template',
template=CarouselTemplate(columns=columns),
)

reply_message(event, messages)

except Exception as e:
print("error:", e)
reply_message(event, TextSendMessage(text='エラーが発生しました'))

def reply_message(event, messages):
line_bot_api.reply_message(
event.reply_token,
messages=messages,
)

if __name__ == "__main__":
port = os.environ.get('PORT', 3333)
app.run(
host='0.0.0.0',
port=port,
)



:star: 完成!!


  • 以上でLINEで画像を送ると商品検索するbotができました

  • 正直そんなに検索に引っかかりません(涙)


:star: 参考