【AI × チラシ解析】忙しい社会人のための“節約レシピ提案アプリ”を作ってみた

## :pencil: 概要
忙しい毎日、気づけばコンビニ飯やウーバーに頼りがち…。
でも「安くて美味しい、健康的な手料理も食べたい」って思いませんか？

そこで今回、Googleの大規模言語モデル「Gemini」 を活用して、
近所のスーパーのチラシ画像から“その日オススメの晩ご飯”を提案してくれるPythonアプリを作ってみました。

「節約 × 健康 × AI」がうまくマッチした、面白いアプリになったと思います。


## :fork_knife_plate: 作ったもの
以下の流れで、チラシ画像からレシピを提案してくれる仕組みです:
* スーパーのチラシ画像をスクレイピング
* Geminiで掲載商品情報を抽出
* 抽出された食品情報から、晩ご飯メニューを提案

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2377934/b498ae14-5f6c-4357-8697-d475613329d2.png)


## :wrench: 開発環境
### 言語・実行環境
* Python 3.12.3
### ライブラリ
* requests(2.32.3) － チラシ画像の取得
* BeautifulSoup(4.13.3) － HTMLスクレイピング
* pillow(11.1.0) － チラシ画像の読み込み
* google(3.0.0) － Gemini APIの利用

## :keyboard: 実装の流れ
### :camera_with_flash: チラシ画像のスクレイピング
#### 使用サービス : トクバイ
チラシ画像はスーパーマーケットやドラッグストアなどの広告をまとめられている[トクバイ](https://tokubai.co.jp/ "トクバイ")というWebサービスを使います。


普段よく使うスーパーのチラシページを探し、URLを特定しましょう。
今回は「いなげや 金町店」のチラシ画像を例にスクレイピングしてみます。

:link: [https://tokubai.co.jp/いなげや/26878](https://tokubai.co.jp/%E3%81%84%E3%81%AA%E3%81%92%E3%82%84/26878)

![スクリーンショット 2025-04-05 211222.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2377934/e584ffca-2617-41f9-9616-912c813994cd.png)
:point_up: HTMLをパースし、Webサイト中央のチラシ画像URLを抽出してローカルに保存。
その画像をGemini APIに渡して解析してもらいます。


`AdvertisementScraper`クラスを使うことで、高画質なチラシ画像を自動取得できます:

```python: AdvertisementScraperクラス
import requests
from bs4 import BeautifulSoup
import urllib.parse as urlparse

class AdvertisementScraper:
    def __init__(self):
        self.target_url = r"https://tokubai.co.jp/%E3%81%84%E3%81%AA%E3%81%92%E3%82%84/26878"  # 対象スーパーのURL（適宜書き換え）
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        }

    def getURL(self):
        """
        メインページから広告画像ページのURLを取得
        高画質画像はこの遷移先に埋め込まれている
        """
        response = requests.get(self.target_url, headers=self.headers)
        soup = BeautifulSoup(response.text, "html.parser")
        items = soup.find_all(class_=['scroll_item', 'scroll_item_scroll_target'])

        image_url_list = []
        for item in items:
            a_links = item.find_all("a")
            for a_link in a_links:
                path = a_link.get('href')
                image_url = urlparse.urljoin(self.target_url, path, "about")
                image_url_list.append(image_url)

        self.image_url_list = image_url_list

    def getImageSrc(self):
        """
        各広告ページからチラシ画像の src を取得
        """
        image_src_list = []
        for target_url in self.image_url_list:
            response = requests.get(target_url, headers=self.headers)
            soup = BeautifulSoup(response.text, 'html.parser')
            img_tag = soup.find("img", class_="leaflet")

            if img_tag:
                img_src = img_tag.get("src")
                image_src_list.append(img_src)
            else:
                print("Not found")

        self.image_src_list = image_src_list

    def downloadImage(self):
        """
        画像のsrcからjpgファイルをローカルに保存
        ./source フォルダ配下に ad_0.jpg, ad_1.jpg ... という形式で保存される。
        """
        for i, img_src in enumerate(self.image_src_list):
            print(img_src)
            r = requests.get(img_src, stream=True)
            if r.status_code == 200:
                with open(f'./source/ad_{i}.jpg', 'wb') as f:
                    f.write(r.content)
        return 0
```

### :white_check_mark: メソッド解説
`__init__` : 対象ページのURLとUser-Agent設定
`getURL` : 店舗のチラシページから、広告画像ページのURL一覧を取得
`getImageSrc` : 各広告ページから、チラシ画像の`img src`を抽出
`downloadImage` : 画像をローカルフォルダ(`./source`)にダウンロード

### :dividers: 実行後のイメージ
スクリプトを実行すると、以下のような画像ファイルが`source`フォルダに保存されます:
```bash
./source/ad_0.jpg
./source/ad_1.jpg
・・・
```
この画像をもとに、Geminiで商品分析を実施していきます。

***


### :crystal_ball: Geminiへの画像送信とメニューの取得
Gemini APIを利用するには、事前にAPIキーを取得しておく必要があります。
Google AI StudioでGeminiのAPIキーの発行を行ってください。
:link:　https://aistudio.google.com/app/apikey


```python:Geminiクラス
from google import genai
from PIL import Image

class Gemini:
    def __init__(self):
        API_KEY = "生成したAPIキー"
        self.model = "gemini-2.0-flash"
        self.client = genai.Client(api_key=API_KEY)
        self.prompt = "スーパーマーケットの広告画像です。それぞれの広告に掲載されている商品と価格を”全て”抽出してリストにしてください。また掲載されている食材を使った今晩のレシピを提案してください。その際、1人前のおおよその価格も計算して教えてください。なお、調味料や米などは自由に使えるものとします。"

    def loadImage(self):
        # スクレイピングした画像データをロード
        image_paths = glob.glob("./source/*.jpg")
        images = []
        for image_path in image_paths:
            images.append(Image.open(image_path))
        self.images = images

    def run(self,prompt="",image=""):
        prompt = self.prompt
        images = self.images

        response = self.client.models.generate_content(
            model=self.model
            ,contents=[images,prompt]
        )
        self.response = response.text
        print(self.response)
```

### :white_check_mark: メソッド解説
* `__init__` : Geminiモデル・APIクライアント・プロンプトの初期化
* `loadImage` : 指定フォルダ内の画像ファイル読み込み・リスト化
* `run` : 画像とプロンプトをGeminiに渡し、生成結果を取得・表示

***
### :star2: 実行例
#### :corn: 商品情報リスト
```
**広告掲載商品と価格リスト**

**表面左側**

*   国産若鶏手羽元：69円/100g (税込74.52円)
*   国産豚挽肉：99円/100g (税込106.92円)
*   きゅうり：59円/本 (税込63.72円)
*   パパイヤ (ベトナム産)：59円/個 (税込63.72円)
*   ほうれん草：99円/束 (税込106.92円)
*   国産若鶏むね肉：69円/100g (税込74.52円)
*   エリンギ：79円/パック (税込85.32円)
*   刺身各種 (まぐろ、サーモン、甘エビなど)：価格は種類により異なる
*   ロースステーキ：399円/100g (税込420.62円)
*   ローススライス：399円/100g (税込420.62円)
*   豚ヒレカツ：399円/パック (税込420.62円)
*   豚ロース (生姜焼き用)：139円/100g (税込150.12円)
*   ウインナー：199円/パック (税込214.92円)
*   冷凍エビ：299円/パック (税込322.92円)
*   冷凍イカ：299円/パック (税込322.92円)
*   冷凍ホタテ：299円/パック (税込322.92円)
*   食パン：199円 (税込214.92円)
*   カップラーメン各種：99円 (税込106.92円)
*   コーラ、ジンジャーエール：149円/本 (税込160.92円)
*   お茶：79円/本 (税込85.32円)
*   マヨネーズ：299円/本 (税込322.92円)
*   ドレッシング：379円/本 (税込409.32円)
*   アイス各種：239円/個 (税込258.12円)
*   ヨーグルト：299円/個 (税込322.92円)
*   牛乳：399円 (税込430.92円)
*   ベーコン：169円/パック (税込182.52円)
*   たまご：139円/パック (税込150.12円)
*   ヨーグルト各種：649円/パック (税込689.92円)
*   あんぱん：109円/個 (税込117.72円)

**表面右側**

*   焼き肉用牛肉：699円/100g (税込754.92円)
*   刺身盛り合わせ：値段は種類により異なる
*   海鮮丼：値段は種類により異なる
*   ステーキ肉：値段は種類により異なる
*   寿司各種：値段は種類により異なる
*   冷凍食品各種：値段は種類により異なる
*   ミートボール：240円/パック (税込259.20円)
*   スモークチキン：299円/パック (税込322.92円)
*   焼きそば：399円/パック (税込430.92円)
*   鶏肉団子：299円/パック (税込322.92円)
*   お惣菜各種：値段は種類により異なる
*   野菜各種：値段は種類により異なる
*   パン各種：値段は種類により異なる
*   アイス各種：値段は種類により異なる

**裏面**

*   焼きそば：129円 (税込139.32円)
*   お茶：69円 (税込74.52円)
*   チョコレート：99円 (税込106.92円)
*   缶詰各種：値段は種類により異なる
*   カップ麺各種：値段は種類により異なる
*   ヨーグルト：149円 (税込160.92円)
*   アイス各種：値段は種類により異なる
*   パン各種：値段は種類により異なる
```

#### :cooking: 提案メニュー
```
**今晩のレシピ提案**

**メニュー：鶏むね肉とほうれん草のソテー、きゅうりの酢の物、ごはん**

**レシピ:**

1.  **鶏むね肉のソテー:**
    *   鶏むね肉を一口大に切り、塩コショウで下味をつけます。
    *   フライパンに油をひき、鶏むね肉を焼き色がつくまで焼きます。
    *   ほうれん草を加えて炒め、醤油、みりんなどで味を調えます。
2.  **きゅうりの酢の物:**
    *   きゅうりを薄切りにし、塩もみして水気を絞ります。
    *   酢、砂糖、醤油を混ぜ合わせ、きゅうりを和えます。
3.  **ごはん:**
    *   お好みのご飯を用意します。

**1人前のおおよその価格:**

*   鶏むね肉：69円/100g → 150g使用 = 103.5円
*   ほうれん草：99円/束 → 1/3束使用 = 33円
*   きゅうり：59円/本 → 1/2本使用 = 29.5円
*   調味料：約20円
*   ごはん：約50円

合計：約236円

**補足:**

*   価格はあくまで目安です。使用する量や調味料の種類によって変動します。
*   広告に掲載されている他の食材を追加して、メニューをアレンジすることも可能です。
*   広告全体を考慮すると刺身や寿司なども手頃な価格で購入できそうです。
*   ヨーグルトや果物などを加えればデザートも楽しめます。

ご希望に応じて、他のレシピや価格帯の提案も可能です。
```

チラシ情報の抽出とメニュー提案が上手くできていますね！
レシピやお値段も一緒に提示してくれるエリートくんです！！

## :dividers: スクリプト全体
```python
import os
import glob

import requests
from bs4 import BeautifulSoup
import urllib.parse as urlparse

from google import genai
from PIL import Image

class AdvertisementScraper:
    def __init__(self):
        self.target_url = r"https://tokubai.co.jp/%E3%81%84%E3%81%AA%E3%81%92%E3%82%84/26878"  # 対象スーパーのURL（適宜書き換え）
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
        }

    def getURL(self):
        """
        メインページから広告画像ページのURLを取得
        高画質画像はこの遷移先に埋め込まれている
        """
        response = requests.get(self.target_url, headers=self.headers)
        soup = BeautifulSoup(response.text, "html.parser")
        items = soup.find_all(class_=['scroll_item', 'scroll_item_scroll_target'])

        image_url_list = []
        for item in items:
            a_links = item.find_all("a")
            for a_link in a_links:
                path = a_link.get('href')
                image_url = urlparse.urljoin(self.target_url, path, "about")
                image_url_list.append(image_url)

        self.image_url_list = image_url_list

    def getImageSrc(self):
        """
        各広告ページからチラシ画像の src を取得
        """
        image_src_list = []
        for target_url in self.image_url_list:
            response = requests.get(target_url, headers=self.headers)
            soup = BeautifulSoup(response.text, 'html.parser')
            img_tag = soup.find("img", class_="leaflet")

            if img_tag:
                img_src = img_tag.get("src")
                image_src_list.append(img_src)
            else:
                print("Not found")

        self.image_src_list = image_src_list

    def downloadImage(self):
        """
        画像のsrcからjpgファイルをローカルに保存
        ./source フォルダ配下に ad_0.jpg, ad_1.jpg ... という形式で保存される。
        """
        for i, img_src in enumerate(self.image_src_list):
            print(img_src)
            r = requests.get(img_src, stream=True)
            if r.status_code == 200:
                with open(f'./source/ad_{i}.jpg', 'wb') as f:
                    f.write(r.content)
        return 0

class Gemini:
    def __init__(self):
        API_KEY = "生成したAPIキー"
        self.model = "gemini-2.0-flash"
        self.client = genai.Client(api_key=API_KEY)
        self.prompt = "スーパーマーケットの広告画像です。それぞれの広告に掲載されている商品と価格を”全て”抽出してリストにしてください。また掲載されている食材を使った今晩のレシピを提案してください。その際、1人前のおおよその価格も計算して教えてください。なお、調味料や米などは自由に使えるものとします。"

    def loadImage(self):
        # スクレイピングした画像データをロード
        image_paths = glob.glob("./source/*.jpg")
        images = []
        for image_path in image_paths:
            images.append(Image.open(image_path))
        self.images = images

    def run(self,prompt="",image=""):
        prompt = self.prompt
        images = self.images

        response = self.client.models.generate_content(
            model=self.model
            ,contents=[images,prompt]
        )
        self.response = response.text
        print(self.response)

def main():
    # --- 広告画像のスクレイピング
    scraper = AdvertisementScraper()
    scraper.getURL()
    scraper.getImageSrc()
    scraper.downloadImage()

    # --- Geminiにプロンプトと画像データを送信
    # --- 広告画像解析とレシピ提案のテキストデータを受信
    gemini = Gemini()
    gemini.loadImage()
    gemini.run()

if __name__ == "__main__":
    main()
```


## :rocket: 今後やりたいこと
* :iphone: LINE Bot化 → 毎日おすすめレシピを送ってくれるように
* :bar_chart: レシピ提案の精度向上（カテゴリ分け・カロリー計算など）
* :pencil: 数日前のレシピと提案が被らないように、過去データを考慮した実装

## :speech_balloon: 最後に
大規模言語モデルにチラシ画像を解析してもらって、改めて昨今のAIの進化に驚かされました。
Gemini × チラシ画像解析というシンプルなアイディアで、「AIを使って生活をちょっと便利に」をコンセプトとしたアプリが作れたと思います。
今後も「AIで、人生をちょっと面白く」系のプロジェクトに色々と取り組んでみようと思います！