PuppeteerでスクレイピングしてMacBook Pro整備済製品をカートに入れる！もう逃さない！

# はじめに
MacBook Proを安く購入したい！

その場合、Apple製品を特別価格で購入できる[認定の整備済製品](https://www.apple.com/jp/shop/browse/home/specialdeals)という選択肢があります。（1年間の特別保証付き）
ただ、ラインナップに並んだとしても手に入れるのはなかなか難しく入荷時期は予測不可能で数量は非常に少なく、先着順となります。
なので、購入しようと思うと定期的にサイトにアクセスしてラインナップを確認する必要があります。

うーん、めんどい・・・

そこで、定期的にPuppeteerでスクレイピングして希望する商品があればカートに入れて通知が来るようにしたいと思います。

# Puppeteerとは
[Puppeteer](https://github.com/GoogleChrome/puppeteer)はDevToolsプロトコルで ChromeまたはChromiumを制御するための高水準APIを提供するNodeライブラリです。
ブラウザで手動で行うことができるほとんどのことができます！

# 環境
```sh
$ node -v
v8.11.2

$ yarn -v
1.7.0
```

# プロジェクトの作成
```sh
$ mkdir get-macbook
$ cd get-macbook
```

# package.jsonを作成
```sh
$ yarn init -y
```

# パッケージのインストール
```sh
$ yarn add node-notifier puppeteer -S
```

- [Puppeteer](https://github.com/GoogleChrome/puppeteer)
- [node-notifier（デスクトップ通知）](https://github.com/mikaelbr/node-notifier)

# ファイルを作成＆編集
今回は、「2017年6月発売モデル」の「15.4インチMacBook Pro 3.1GHzクアッドコアIntel Core i7 Retinaディスプレイモデル - スペースグレイ」をターゲットに30秒間隔でスクレイピングします。

```sh
$ vi index.js
```
```js:index.js
const puppeteer = require('puppeteer')
const notifier = require('node-notifier')

const getMacbook = setInterval(() => {
  !(async() => {
    try {
      const browser = await puppeteer.launch()
      const page = await browser.newPage()

      // ページアクセス
      await page.goto('https://www.apple.com/jp/shop/browse/home/specialdeals/mac/macbook_pro/15')

      /**
       * findItem
       *
       * @return {object}
       */
      const findItem = await page.evaluate(() => {
        let result = {}
        const nodeList = document.querySelectorAll('.specs')

        for (let i = 0; nodeList.length > i; i++) {
          const target = nodeList[i].innerText

          const check_1 = '15.4インチMacBook Pro 3.1GHzクアッドコアIntel Core i7 Retinaディスプレイモデル - スペースグレイ'
          const check_2 = '2017年6月発売モデル'

          if (target.indexOf(check_1) !== -1 && target.indexOf(check_2) !== -1) {
            result.isHit = true
            result.itemNumber = i + 1
            
            break
          }
        }

        return result
      })

      if (findItem.isHit) {
        // ログイン部分のDOM読み込み（isLogin）
        await page.click('#ac-gn-bag > a')
        await page.waitFor(500)

        /**
         * isLogin
         *
         * @return {boolean}
         */
        const isLogin = await page.evaluate(() => {
          const findItem = document.querySelectorAll('.ac-gn-bagview-nav-item-signIn')

          return findItem.length ? false : true
        })

        if (!isLogin) {
          // ページ移動
          await page.click('.ac-gn-bagview-nav-link-signIn')
          await page.waitForNavigation()

          // ログイン情報設定
          await page.type('input[name="login-appleId"]', 'ログインメールアドレス');
          await page.type('input[name="login-password"]', 'ログインパスワード');

          // ログイン
          await page.click('#sign-in')
          await page.waitForNavigation()

          // ログイン部分のDOM読み込み（isCartEmpty）
          await page.click('#ac-gn-bag > a')
          await page.waitFor(500)

          /**
           * isCartEmpty
           *
           * @return {boolean}
           */
          const isCartEmpty = await page.evaluate(() => {
            const findItem = document.querySelectorAll('.ac-gn-bagview-message-empty')

            return findItem.length ? true : false
          })

          if (isCartEmpty) {
            // ページ移動
            await page.click(`.box-content > table:nth-child(${findItem.itemNumber}) .product > .purchase-info > a`)
            await page.waitFor('.form-submit-btn', {
              timeout: 120000
            })

            // カートに追加
            await page.click('.form-submit-btn button')

            // デスクトップ通知
            notifier.notify({
              'title': 'アイテムヒット！',
              'message': 'カートに追加したよ！'
            })

            console.log('カートに追加したよ！')
          } else {
            console.log('もうカートにあるよ！')
            clearInterval(getMacbook)
          }
        }
      } else {
        console.log('みつからないなー。。。')
      }

      browser.close()
    } catch (e) {
      console.error(e)
    }
  })()
}, 30000)
```

# 実行（スクレイピング開始）
```sh
$ node index.js
```

- ヒット ~ カートに追加

```sh

カートに追加したよ！
```

- ヒット ~ カートに追加済み（定期処理を解除します）

```sh
もうカートにあるよ！
```

- ヒットなし

```sh
みつからないなー。。。
```

# まとめ
以上、「PuppeteerでスクレイピングしてMacBook Pro整備済製品をカートに入れる！」の紹介でした。

今回は簡易的に作成しましたが、Cloud Functionsを使ったり、ターゲットを複数にしたり、通知を Slack や LINE等にするのもいいかもですね！