見出し画像

Amazon価格チェッカーを作ってみた

こんにちは!ざいやです。

第1週目の「YouTube分析ツール」に続き、今週は実用性を重視して「Amazonの価格チェッカー」を開発しました。ブラウザから登録・管理ができる「1つの完成されたWebアプリケーション」の形に仕上げました。

今週の学びと、直面した壁をどう乗り越えたかの技術的な振り返りをコード付きで共有します!

★今回作ったツールの全体像

今回のアプリは、以下のような3つのパーツが連携して動く仕組みにしました。

  1. 管理画面(フロントエンド): ブラウザで開き、最大3品までのURLと目標価格を入力・削除できる画面(Expressで配信)。

  2. データベース(JSONファイル): 登録されたデータを保持する、シンプルな products.json。

  3. 巡回バッチ(バックエンド): 1日1回(を想定して)、裏側でPlaywrightを動かしてAmazonをスクレイピングし、値下げを検知したらDiscordに通知するスクリプト。

★直面した大きな壁:Amazonの強固なボット判定

開発の初期段階で最大の壁となったのが、Amazonのセキュリティです。Playwrightで単純に商品ページを開こうとすると、サーバー側でアクセスを完全にブロックされ、30秒でタイムアウト(TimeoutError)してしまいました。裏側では「画像認証(CAPTCHA)」の画面が表示されて困っていました。

【解決策】ステルスプラグインの導入

これを突破するために、自動操縦であることを徹底的に隠蔽するプラグイン(中身はPlaywrightでも動く互換性の高いPuppeteer用のもの)を導入しました。

JavaScript

// batch.js より抜粋:ボット判定を回避する心臓部
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();

// Playwrightにステルスプラグインを登録(ブラウザの指紋を人間らしく偽装)
chromium.use(stealth);

(async () => {
  const browser = await chromium.launch({ headless: true }); // 自動化を見据えて画面非表示
  const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...', // ブラウザ情報の偽装
    locale: 'ja-JP',
  });
  
  // (中略)

  // ページの全要素を待つと重いため、サーバーから応答が返ってきた瞬間(commit)に次の処理へ進む
  await page.goto(url, { waitUntil: 'commit', timeout: 45000 });
  await page.waitForTimeout(2000); // 人間らしさを出すための「2秒のタメ」

この「ステルス化」と「人間らしい待機時間の確保」を行うことで、Amazonの強固なガードをすり抜け、安定して最新価格をスクレイピングできるようになりました。

★UX(使いやすさ)へのこだわり:URLの自動クレンジング

3つの商品を登録して動かしてみた際、新たな問題に気づきました。AmazonのURLをブラウザからそのままコピペすると、検索の足跡(パラメータ)や日本語のエンコードによって、1つのURLが何行にも渡る異常に長い文字列になってしまいました。

これだと保存先の products.json が非常に見づらくなり、将来的なバグの原因にもなります。

【解決策】正規表現によるURLのスリム化

ユーザーがわざわざ手動でURLを削らなくても、ツール側が保存する直前に自動で「商品ID(ASINコード)」だけを抜き出すロジックをサーバー側に追加しました。

JavaScript

// index.js より抜粋:URLのデータクレンジング処理
app.post('/api/save', (req, res) => {
  const { products } = req.body;

  const cleanedProducts = products.map(product => {
    if (!product.url) return product;

    // 正規表現を使い、URLからAmazon特有の「/dp/【英数字10桁のASINコード】」を抽出
    const match = product.url.match(/\/dp\/([A-Z0-9]{10})/i);

    if (match) {
      // マッチしたら余計なパラメータをすべて削り、最もスリムなURLに強制変換!
      product.url = `https://www.amazon.co.jp/dp/${match[1]}`;
    }
    return product;
  });

  // 整形した綺麗なデータをファイルに書き込み
  fs.writeFileSync(DATA_FILE_PATH, JSON.stringify(cleanedProducts, null, 2), 'utf8');
  res.json({ success: true });
});

この工夫のおかげで、どんなに長いURLを貼り付けても、ファイル内には https://www.amazon.co.jp/dp/B0FDKTKVJL のような美しい形で保存されるようになりました。システムが扱いやすいだけでなく、人間が見てもスッキリして気持ちいい構造です。

今週のまとめと次への一歩

少し遅くなりましたが今週は、「自動化の難しさ(ボット対策)」「入力データの整形(クレンジング)」という、実用的なスクレイピングに不可欠な2つの大きな学びを得ることができました。

目標価格を下回った際に、Discordに「値下げ通知」がピコン!と飛んできた瞬間は、これまでの苦労が吹き飛ぶほど嬉しかったです。

もし今後このツールをさらに進化させるなら、以下の二点に挑戦したみたいと思っています。

  • プログラム内部で定時実行(毎日夜20時など)を完結させる

  • 価格の履歴を蓄積して、画面にグラフ表示する

3週目は何を作ろうかな。。。


いいなと思ったら応援しよう!