レシピ検索サイトの構成について_その1

コロナ禍はまだまだ猛威を振るいそうですが、そんなことにもめげずに画像から料理レシピを検索するサイトを作成したので備忘録を残したいと思います。

https://kimareci.nakamurake-site.com/

サイトの全体構成はこんな感じです。

このサイトは、大きく2つのコンポーネントから構成されています。

  • 楽天レシピ系APIから情報を取得し、Elasticsearchに情報を投入する。
  • ユーザーからの画像を解析し、Elasticsearchからレシピを検索する。

各コンポーネントについてポイントを記載しようと思います。

楽天レシピ系APIからの情報取得処理


情報取得系のは以下のフローで情報を取得します。

1、3日に1回楽天レシピカテゴリ一覧APIからカテゴリーを取得し、IDをSQSに投入する。
2、1分に1回SQSから1件IDを取得し、IDを楽天レシピカテゴリ別ランキングAPIに投入する。
  得られたレシピデータをElasticsearchに投入する

楽天レシピカテゴリ別ランキングAPIから一度にすべてのレシピデータを取得しないのは、ランキングAPI及びElasticsearchに高負荷を与えないようにするためです。

また、ランキングAPIから提供される情報は重複している場合がある為Elasticsearch投入前に重複排除を行います。

#楽天レシピカテゴリ一覧APIからカテゴリーIDを取得する処理
import requests
import json
import boto3

name = 'SQS名'
sqs = boto3.resource('sqs')

def lambda_handler(event, context):
    # TODO implement
    try:
    # キューの名前を指定してインスタンスを取得
        queue = sqs.get_queue_by_name(QueueName=name)
    except:
    # 指定したキューがない場合はexceptionが返るので、キューを作成
        queue = sqs.create_queue(QueueName=name)
        
    url = "https://app.rakuten.co.jp/services/api/Recipe/CategoryList/hogehoge"
    response = requests.get(url)
    jsonData = response.json()
    Count = 1
    for jsonObj in jsonData["result"]["small"]:
        category = ((jsonObj["categoryUrl"])[38:])[:-1]
        response = queue.send_messages(Entries=[{'Id' : '{}'.format(Count), 'MessageBody' : '{}'.format(category)}])
        Count = Count + 1 
    
    return ''
#レシピデータをElasticsearchに投入する処理
import requests
import json
import boto3
import hashlib
from elasticsearch import Elasticsearch, helpers

es = Elasticsearch(
    ['Elasticsearchドメイン名'],
    http_auth=('ユーザー名', 'パスワード'),
    scheme="https",
    port=443,
)

name = 'SQS名'
sqs = boto3.resource('sqs')

def lambda_handler2(event, context):
    # TODO implement
    queue = sqs.get_queue_by_name(QueueName=name)
    messages = queue.receive_messages()
    if not messages:
        print("messages is empty")
        return ''
    for message in messages:
        ID = message.body
        message.delete()
    
    url = "https://app.rakuten.co.jp/services/api/Recipe/CategoryRanking/hogehoge"
    response = requests.get(url)
    jsonData = response.json()
    Count = 1
    for jsonObj in jsonData["result"]:
        ESID = ID + "-" + str(Count)
        Count = Count + 1
        doc = {
            'detail_url':  jsonObj["recipeUrl"],
            'materials':   jsonObj["recipeMaterial"],
            'image_url':   jsonObj["mediumImageUrl"],
            'description': jsonObj["recipeDescription"],
            'title':       jsonObj["recipeTitle"]
        }
        
        flag = True
        es_key = str(jsonObj["recipeUrl"])
        es_hashval = hashlib.md5(es_key.encode('utf-8')).digest()
        try:
            for hit in helpers.scan(es, index='recipe_1'):
                key = str(hit['_source']["detail_url"])
                hashval = hashlib.md5(key.encode('utf-8')).digest()
                if hashval == es_hashval:
                    flag = False
        except:
            res = es.index(index="recipe_1", id=ESID, body=doc)
            print(res['result'],ESID)
        else:
            if flag:
                res = es.index(index="recipe_1", id=ESID, body=doc)
                print(res['result'],ESID)
    
    return ''

関数名などはかなり適当です。

「ユーザーからの画像を解析し、Elasticsearchからレシピを検索する」は長くなりそうなので、次回に書きます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)