レシピ提案アプリ part2

はじめに

この章では、実際にソースコードを書きながらデータを取得していきます。

part1を見ていない方は以下ご確認ください。

この章で学べること

  1. Beautiful Soupの使い方
  2. 楽天レシピからデータを取得する

Beautiful Soupの使い方

Webサイトからデータを取得するのにおすすめなのがBeautiful Soupというライブラリになります。

これを利用することで、HTMLのデータを扱いやすいように解析、加工してくれます。

Beautiful SoupでHTMLデータを解析するためには、まずWebサイトからHTMLデータを取得する必要があります。

HTMLデータを取得するためには以下のようにすればOKです。

import urllib.request as req
url = 'https://recipe.rakuten.co.jp/'
res = req.urlopen(url)

req.urlopen(url)という部分で、urlからHTMLデータを取得することができます。

取得結果はresに格納されます。

resに格納されている状態だと扱いづらいため、それをBeautiful Soupというライブラリで扱えるようにしてあげる必要があります。

先ほどのコードに続いて、以下のコードを実行します。

from bs4 import BeautifulSoup
soup = BeautifulSoup(res, 'html.parser')
print(soup)

こうすることで、Beautiful Soupライブラリを用いて、HTMLデータを扱い、データの取得がやりやすくなります。

Beautiful Soupで私がよく利用するメソッドは以下になります。

  • find(), find_all()
    • 要素名、属性名から検索する
    • 要素(divやaタグなど)を指定取得する、id属性を指定して取得するなど
    • findが最初にヒットした1件を返し、find_all が全ての検索結果を返す
  • select_one(), select()
    • cssのclass名から検索する
    • select_oneが最初にヒットした1件を返し、selectが全ての検索結果を返す

上記のメソッドを活用することで、大体のデータは取得できるかと思います。

楽天レシピからデータを取得する

これでpart1含めて、環境構築と必要なライブラリの使い方を人と取り記載したので、これから実際にデータを取得する部分の作成に入ります。

※前提知識として、pythonを使ってif, for文が書ける程度の知識は必要になります。

part1のおさらいですが、データ取得までの流れとして、以下のようなものを検討していました。

  1. Topページ(https://recipe.rakuten.co.jp/)
    • レシピカテゴリ一覧の料理から探す欄にある料理名(カテゴリ)を取得する
  2. カテゴリごとのランキングページ(https://recipe.rakuten.co.jp/search/料理名)
    • 1で取得した料理名をもとに、ランキングページを表示する
    • ランキングからレシピのタイトルと、そのレシピへのリンクを取得する
  3. レシピページ(https://recipe.rakuten.co.jp/recipe/{:id})
    • 2で取得したレシピへのリンクから、レシピページを表示する
    • 必要な材料と、レシピの手順を取得する
  4. データの取得と出力

これに従い、1から実装していきます。

1. Topページ(https://recipe.rakuten.co.jp/)

Topページのhtmlデータを取得して、それをBeautifulSoupで扱えるようにして、料理名を取得していきます。

構造を見る限り、料理一覧全体はクラス名で取得することができそうなので、select_one()を使って料理名を取得します。

料理一覧から個別の料理名を取得するには属性を指定して取得できそうなので、find_all()を使って取得していきます。取得したい件数は1件だけではなく、複数件なのでfind()ではなくfind_all()になります。

最終的な取得結果を返すようにします。

def GetFoodNames():
    food_name_link_list = []
    url = 'https://recipe.rakuten.co.jp/'
    res = req.urlopen(url)
    soup = BeautifulSoup(res, 'html.parser')
    
    # レシピ一覧の範囲を選択
    category = soup.select_one("#料理")
    tmp_food_names = category.find_all('a', class_= 'category_top-smallList__link')
    for food_name in tmp_food_names:
        food_name_link_list.append([food_name.text, 'https://recipe.rakuten.co.jp' + food_name.get('href')])
    return food_name_link_list

2. カテゴリごとのランキングページ(https://recipe.rakuten.co.jp/search/料理名)

ランキングページから、レシピのタイトルとURLを取得します。

# ランキングページで何ページ分取得するか
page_count = 2
# レシピのタイトルとURLを取得
def GetRecipeURL(url):
    recipe_title_url_list = []
    res = req.urlopen(url)
    soup = BeautifulSoup(res, 'html.parser')

    # レシピランキングの一覧を取得
    recipe_ranking_item_list = soup.find_all('li', class_= 'recipe_ranking__item')
    for recipe_ranking_item in recipe_ranking_item_list:
        recipe_link = recipe_ranking_item.find('a', class_='recipe_ranking__link').get('href')
        recipe_title = recipe_ranking_item.find('span', class_='recipe_ranking__recipe_title').text
        recipe_title_url_list.append([recipe_title, 'https://recipe.rakuten.co.jp' + recipe_link])
    
    return recipe_title_url_list

3. レシピページ(https://recipe.rakuten.co.jp/recipe/{:id})

レシピページから、レシピの材料、手順など詳細情報を取得します。

def SearchRecipeInfo(url):
    res = req.urlopen(url)
    soup = BeautifulSoup(res, 'html.parser')
    # レシピタイトル
    recipe_title = soup.find('h1', class_='page_title__text').text
    recipe_material_item_list = soup.find_all('li', class_='recipe_material__item')
    # 材料を格納する
    materials = []
    for recipe_material_item in recipe_material_item_list:
        # レシピの材料名
        material_name = recipe_material_item.find('span', class_='recipe_material__item_name').text.replace('\n','')
        # 材料の分量
        material_quantity = recipe_material_item.find('span', class_='recipe_material__item_serving').text
        materials.append({material_name: material_quantity})
    recipe_process_list = soup.find_all('li', class_='recipe_howto__item')
    # 手順を格納する
    process = []
    for recipe_process in recipe_process_list:
        # 順番
        order = recipe_process.find('span', class_='ico_recipe_howto_order').text
        # 説明
        text = recipe_process.find('span', class_='recipe_howto__text')
        if text:
            process.append({order: text.text})
        else:
            process.append({order: '完成(---システムで自動補完---)'})
    recipe_detail = soup.find('div', class_='recipe_detail')
    # レシピID
    recipe_id = recipe_detail.find('input', id='fullRecipeId').get('value')
    recipe_report_count = recipe_detail.find('div', class_='recipe_info_report__count').find('span').text
    reaction_list = soup.find_all('li', 'recipe_reaction_button__item')
    reactions = []
    for reaction in reaction_list:
        # 「リピートしたい」、「簡単だった」、「節約できた」のリアクション
        reaction_name = reaction.find('p', class_='recipe_reaction_button__head is_pushed').text
        reaction_count = reaction.find('p', class_='recipe_reaction_button__count is_pushed').find('span').text
        reactions.append({reaction_name: reaction_count})
    # Jsonで出力したいので、辞書型で格納しておく
    data = dict()
    data['recipeId'] = recipe_id
    data['recipeTitle'] = recipe_title
    data['materials'] = materials
    data['process'] = process
    data['reactions'] = reactions
    data['url'] = url
    return data

4. データの取得と出力

上記1~3で作成したメソッドを呼び出して、実データの取得と、取得結果を出力していきます。

どれくらい時間がかかるかを確認したいので、時間を測るようにします。

ちなみに実行時間はかなり長くて、数時間かかると思うのでPCがスリープ状態にならないようにする必要がありますので事前にPC設定を変更しておいた方がよいです。

start_time = time.time()

# Topページからカテゴリ(料理名)を取得する
food_name_link_list = GetFoodNames()

# 取得した料理名のリストから取得対象レシピのurlを取得する
recipe_title_url_list = []
for food_name_link in food_name_link_list:
    for num in range(page_count):
        #特定のページ以降を取得する場合
        # num = num + 50

        if num == 1:
            # urlを結合する(1ページのみurl)
            recipe_title_url_list.extend(GetRecipeURL(food_name_link[1]))

        if num  > 1:
            # urlを結合する(2ページ以降のurl)
            # 人気順
            base_url_other =  food_name_link[1] + '/' + str(num)
            # 新着順
            # base_url_other =  'https://recipe.rakuten.co.jp/search/' + name_quote + '/' + str(num) + '/?s=0&v=0&t=2'
            recipe_title_url_list.extend(GetRecipeURL(base_url_other))

        time.sleep(1)

count = 1
recipe_data = []
# レシピのURLリストから、レシピの詳細情報を取得
for recipe_title_url in recipe_title_url_list:
    # 最後はhttps://recipe.rakuten.co.jp/recipe/1580048610/
    recipe_data.append(SearchRecipeInfo(recipe_title_url[1]))
    # スクレイピングの1秒ルールを適用
    time.sleep(1)
    print(str(count), '/', len(recipe_title_url_list))
    count += 1
print("--- %s seconds ---" % (time.time() - start_time))

# recipe_dataをファイルとして出力
with open('recipe_data.json', mode='wt', encoding='utf-8') as file:
  json.dump(recipe_data, file, ensure_ascii=False, indent=2)

上記を実行することで、recipe_data.jsonというファイルが出力されて、レシピ情報が書き込まれているかと思います。

これで、アプリとして提案するレシピの情報の取得ができました。

次からは実際にWebアプリのこのデータを表示していく部分の実装に入ります。