2021年3月23日 • ☕️ 3 min read

コストコオンラインしか売っていない商品(エコバックスという掃除ロボット)を購入したいんですが、やっはり値引きするとき買うのが楽しいですね。しかし、コストコオンラインに価格変動で知らせる機能がないので、今回はe2eテストにもよく使われるpuppeteerで価格監視するバッチを作ってみました。

要望

  • 特定の商品一覧に対して、価格の変動を監視し、変動がある場合のみメールで知らせる
  • 個人用なので、管理画面いらない
  • ログインしなければ価格わからない商品も監視できる
  • 1日1回のみ価格をチェックできればいい
  • 簡易システムなので、DBいらないが、価格データの保存が必要
  • ログイン情報を保存しない

技術スタック

要望から技術スタックを選定しました。

コアの部分はこちらです。

  • nodejs
  • puppeteer
  • nodemailer
  • ログイン情報、商品リストの部分を環境ファイル.env

開発を快適になる部分はこちらです。

  • TypeScript
  • prettier
  • jest
  • eslint

データ部分はこちらです。

  • jsonファイルでCRUD処理を行う

コアの部分

商品価格の取得

puppeteerで使うとdom操作と同じなので、とても簡単です。ただし、3つのパターンがのに注意する必要があります。

  1. 会員じゃなくても価格を確認できる商品
  2. 会員しか確認できない商品
  3. プロモーション商品

なので、ログインしてから価格を取れば楽でしょう。(ログインの部分を省く) 価格の取得は簡単にできます。

Copy
import puppeteer from "puppeteer";

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto("https://www.costco.co.jp/Baby-Kids-Toys/Brio-World-Travel-Rail-Set/p/19155", { timeout: 0 });

/* DOMがないときExceptionがスローされるため今回の仕様に向いていない */
// const priceString = await page.$eval(
//   SelectorNames.PRICE,
//   (el?: Element) => el?.textContent,
// );

const priceString = await page.evaluate(
  () => {
    // Get normal price
    let priceDom = document.querySelector(".product-price-container .product-price-amount");

    // if priceDom is null, try to get discount price
    if (priceDom == null) {
      priceDom = document.querySelector(".product-price-container span.you-pay-value");
    }

    return priceDom?.textContent;
  }
);

const price = Number(priceString.replace(/[^0-9.]/g, ""));

ただheadlessをtrueにする場合、うまく取れません。 原因は不正アクセスとコストコのサイトにされたからです。

headlessモード

いくつかの方法がありますが、一番シンプルな方法はUserAgentを設定することです。 こうなります。

Copy
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36");

.
.
.

これで無事に取れるようになりました。

UserAgentを固定ではなく、いくつかの選択肢からランダムで出したがいいでしょう。 さらに、proxyを使えば、失敗する可能性がなくなるでしょう。 使い方こちらへ https://docs.browserless.io/docs/using-a-proxy.html#specifying-the-proxy

.envからチェックしたい商品URLリストを読み込む

.envが配列をサポートしないんだが、JSON形式で簡単に実現できるでしょう。

Copy
Urls=["https://www.costco.co.jp/Baby-Kids-Toys/Brio-World-Travel-Rail-Set/p/19155", "https://www.costco.co.jp/c/ECOVACS-DEEBOT-506/p/17884"]
Copy
import dotenv from "dotenv";

dotenv.config();

const urls = JSON.parse(process.env.Urls) as string[];

nodemailerで送信

詳しくはこちらの記事を参考にすればいいと思います。

nodemailerでgmailのアカウントを利用して送信する

商品価格をJSONファイルで維持

ファイル処理となるので、ここで割愛します。興味があればソースコードを参考してください。

完了

価格の変わりましたらメールが飛んできます。(初回目に価格情報がJSONファイルにないため必ず送信) price-mail.png

1件を取るのに50秒ほどかかりますね。本当に遅いです。

フルソースコードはこちらとなります。 https://github.com/thundermiracle/costco-monitor


関連投稿

TypeScriptをコマンドラインで実行する

2021年4月7日

create-react-appのTypescriptに絶対パスでimport

2021年3月11日

ThunderMiracle

Blog part of ThunderMiracle.com