毎月のサプリ代を抑えるため、AWS×Next.jsで完全自動のサプリ価格比較システムを個人開発した話
はじめに
こんにちは!
普段はフリーランスのシステムエンジニアとして、バックエンドやインフラ周りを中心に開発を行っているレディントンです。
筋トレが趣味で、現在は完全テレワークなのでお昼休憩の1時間のうち45分を使って近所のジムにいく生活をしていますが、日々摂取するサプリ(プロテイン、EAA、クレアチン、グルタミンetc..)の費用がかなりかさんでいました。
その時にコスパが良いものを買えればいいんですけど、流石にAmazonのソート機能でもg単位でどの商品のコスパが良いか分かるようなソート機能は無かったので、重い腰を上げて自分以外の人も汎用的に使えるような形で作ってみました!
完全自動で1日1回、その時の値段、評価、過去1か月で購入された点数を取得して更新されるようにしています。
↓実際のサービス
🔗 最安サプリ価格ナビ
(SEO的にも被リンクは有利に働くとのことで...)
本記事では、このサービスの裏側であるAWS Lambdaによるスクレイピング基盤とNext.jsによるSEOを意識したフロントエンド構築のアーキテクチャについて解説します。
システムアーキテクチャ全体像
今回のシステムの技術スタックは以下の通りです。
- フロントエンド: Next.js (App Router), TypeScript, Tailwind CSS
- インフラ・ホスティング: Vercel
- バックエンド (バッチ処理): AWS Lambda (Python), EventBridge
- データベース: Amazon DynamoDB
- アクセス解析: Google Analytics (GA4)
処理フロー
- AWS EventBridgeにて日次でLambdaを起動。
- Lambda (Python) がAmazon等の対象ページをスクレイピングし、価格や評価データを取得。
- 容量(g・粒)から「1単位あたりの単価」を計算し、DynamoDBへUpsert。
- ユーザーがサイトにアクセスした際、Next.jsがDynamoDBから最新データを取得してレンダリング。
バックエンド:AWS Lambdaによるデータ抽出
モノにもよりますが、個人的には何かを購入する際は「値段」「レビュー」「過去にどれくらい購入されているか?(レビューが良くても数点だと、ん?ってなるので)」を重視しています。
最安サプリ価格ナビも単なる価格比較サイトではなくユーザーが「自分が欲しい基準でサプリを選べる」ようにするため、Lambda側のPythonスクリプトで以下の3つの重要指標を抽出する処理を実装しました。
- 実売価格からの「1g・1粒あたりの単価」計算
- Amazonの星評価(レビュースコア)
- 過去1ヶ月の購入数(トレンド)
Amazonからのデータ抽出実装
実際に特定のデータを抜く処理はBeautifulSoupを用いて以下のように実装しています。
import re
from decimal import Decimal
from bs4 import BeautifulSoup
# ... (HTML取得処理) ...
review_score = Decimal("0.0")
bought_past_month = 0
# 1. 星評価の取得
star_elem = soup.select_one('.a-popover-trigger .a-size-small.a-color-base')
if not star_elem:
star_elem = soup.select_one('span.a-icon-alt')
if star_elem:
match = re.search(r'(\d+\.\d+)', star_elem.text)
if match:
review_score = Decimal(match.group(1))
# 2. 過去1か月の購入数を取得
bought_elem = soup.select_one('#social-proofing-faceout-title-tk_bought')
if bought_elem:
bought_text = bought_elem.text.strip()
match_bought = re.search(r'([0-9,]+)\s*(万)?\s*点', bought_text)
if match_bought:
num_str = match_bought.group(1).replace(',', '')
is_man = match_bought.group(2) == '万'
bought_num = int(num_str)
if is_man:
bought_num *= 10000
bought_past_month = bought_num
上記で抽出してDynamoDBに格納するパイプラインとしています。
ちなみに、最低限しっかりと質が担保された商品だけを対象にしたかったので、比較対象の商品はゴリッゴリ手動でAmazonから探してリスト化してますww
今後の保守運用では商品数を更に増やしていくのと、Amazon以外の販売サイト(現在はマイプロテインも対象にしていますが)も増やしていく予定です!
フロントエンド:Next.jsでのSEO「Thin Content」対策
データは集まったのであとはNext.js (App Router) で画面を作るだけですがふと思ったのがSEOってどうなるの?
キャリア的にもtoBの開発が多くSEOに関しては知見が深くないので調べたところ、EC系や比較系の個人開発サイトで陥りがちなのがGoogleから「内容が薄いページ(Thin Content)」と判定されてインデックスされない問題があるみたいです。
当たり前ですがDBから引っ張ってきた「商品名・価格・単価」という変数を並べただけではどのページも同じようなテンプレートに見えてしまいますということですね。
データドリブンな独自テキスト生成
そこで今回は取得した様々なデータ(カテゴリ、順位、レビュー、売上数)を組み合わせて、「システムによる自動分析レポート」という独自のテキスト領域を動的に生成するアプローチを取りました。
// 実際のコンポーネント内での動的テキスト生成イメージ
<div className="bg-gray-50 rounded-xl p-6">
<h3 className="font-bold">データに基づく自動分析レポート</h3>
<p>
<strong>{product.title}</strong> は、現在 <strong>{platformName}</strong> にて <strong>¥{product.price.toLocaleString()}</strong> で販売されています。
1{unit}あたりの単価は <strong>約¥{Number(pricePerUnit).toFixed(2)}</strong> です。
</p>
<p>
本製品は過去1か月間で【{boughtPastMonth >= 10000 ? `${boughtPastMonth / 10000}万` : boughtPastMonth}点以上】が購入されており、現在追跡中の{currentCategory}カテゴリ内でも第{salesRank}位の売れ筋商品です。
また、購入者からの評価も「★{reviewScore.toFixed(1)}」と高く、価格と品質のバランスが取れた確かな選択肢と言えます。
</p>
</div>
正直ここの実装はまだ納得がいっていないので今後諸々試していく可能性が大ですが、とりあえずは商品の属性ごとに自然な日本語のレポートが自動生成される仕組みを作ることでページ単位での固有のテキスト量を確保しました。
おわりに
普段割とガチガチなtoBでJavaとSpringとOracleをメインに使用している人間としてはAWSを用いたクローラー基盤の構築からNext.jsによるフロントエンドのパフォーマンス最適化までモダンなスタックを一通り触れる非常に楽しい個人開発でした。
まだまだ商品数やデザイン面など荒削りな部分はありますが、似たようなシステムを作りたいと考えている方や、「EAAのコスパを知りたい」「クレアチンを買う前に最安値を確認したい」といった全国のトレーニー仲間のお役に立てれば嬉しいです。
※「このサプリも追加してほしい!」や「こんな機能も欲しい!」といったご要望があればぜひコメントやX(Twitter)でお気軽にお声がけくださいww
