コストコオンラインしか売っていない商品(エコバックスという掃除ロボット)を購入したいんですが、やっはり値引きするとき買うのが楽しいですね。しかし、コストコオンラインに価格変動で知らせる機能がないので、今回はe2eテストにもよく使われるpuppeteer
で価格監視するバッチを作ってみました。
要望
- 特定の商品一覧に対して、価格の変動を監視し、変動がある場合のみメールで知らせる
- 個人用なので、管理画面いらない
- ログインしなければ価格わからない商品も監視できる
- 1日1回のみ価格をチェックできればいい
- 簡易システムなので、DBいらないが、価格データの保存が必要
- ログイン情報を保存しない
技術スタック
要望から技術スタックを選定しました。
コアの部分はこちらです。
- nodejs
- puppeteer
- nodemailer
- ログイン情報、商品リストの部分を環境ファイル
.env
に
開発を快適になる部分はこちらです。
- TypeScript
- prettier
- jest
- eslint
データ部分はこちらです。
- jsonファイルでCRUD処理を行う
コアの部分
商品価格の取得
puppeteerで使うとdom操作と同じなので、とても簡単です。ただし、3つのパターンがのに注意する必要があります。
- 会員じゃなくても価格を確認できる商品
- 会員しか確認できない商品
- プロモーション商品
なので、ログインしてから価格を取れば楽でしょう。(ログインの部分を省く) 価格の取得は簡単にできます。
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
を設定することです。
こうなります。
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形式で簡単に実現できるでしょう。
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"]
import dotenv from "dotenv";
dotenv.config();
const urls = JSON.parse(process.env.Urls) as string[];
nodemailerで送信
詳しくはこちらの記事を参考にすればいいと思います。
nodemailerでgmailのアカウントを利用して送信する
商品価格をJSONファイルで維持
ファイル処理となるので、ここで割愛します。興味があればソースコードを参考してください。
完了
価格の変わりましたらメールが飛んできます。(初回目に価格情報がJSONファイルにないため必ず送信)
1件を取るのに50秒ほどかかりますね。本当に遅いです。
フルソースコードはこちらとなります。 https://github.com/thundermiracle/costco-monitor