ヨドバシ・ドット・コムの入荷通知を自動化してPS4 Proを買おう

# 三行で
- PS4 Proが品薄で買えない。たまに商品入荷するがすぐ売り切れる。
- ヨドバシ・ドット・コムの商品ページを監視して入荷したら通知するスクリプトを書いた。
- ついでに購入もワンボタンでできるようにした。

# 背景：PS4 Proがネットショップで品薄だった
PS4 Proという家庭用ゲーム機が2016年11月10日に発売されました。2016年12月15日現在になっても、各種ネットショップでは品薄です。
PS4の後継とはいえ基本的にはグラフィック性能を強化しただけのモデルであり、そこまで買い替え需要はないかなと素人予想していたのですが完全に外れました（ソフトによってはPSVRの画質が向上するという需要はあるとはいえ）。

生産が少ないのか、国内ゲームハードの売れ行きがよくない（[付録](#a国内家庭用ゲーム機市場の縮退)参照）ので、ハードウェア生産の日本割当が少ないのかはわかりませんが、とりあえず入荷通知が来たらすぐ返る体制をつくることにしました。

# 調査：ヨドバシ・ドット・コムとAmazonの在庫復活を通知する既存の方法

買い物は基本的にAmazonを使っているのですが、ヨドバシ・ドット・コムは新商品でもポイント還元で割り引かれるので便利です。転売屋は多くのショップを監視していると思いますが、転売が目的ではないので個人的な利用頻度の高いこの2つを監視することを目標にします。両ショップの通知機能を調べてみましょう。

- Amazonには[お知らせEメール](https://www.amazon.co.jp/gp/help/customer/display.html?nodeId=202003580)という機能が公式に提供されています。
    - 中古があると（？）これは機能しないようです。今回のように一時的にプレミア価格がつくハードウェアの場合、この機能は使えません。
    - [Keepa](https://keepa.com/#!)という価格追跡サービスがあるのでこれを使うことで通知が可能です。
- ヨドバシ・ドット・コムには通知機能が存在しません。

というわけで、ヨドバシ・ドット・コムの通知機能を実装することを考えます。

# ヨドバシ・ドット・コムの入荷通知する仕組みを作る
## 既存サービスの調査
特定のページの変更を通知するというのはRSSの存在を考えると基本的な欲求な気がするので既存サービスがあると思って調べましたところ、以下のようなサービスがありました。

- [Website checker, website change detection, monitoring and alerts](https://visualping.io/)
- [Follow That Page \- web monitor: we send you an email when your favorite page has changed\.](https://www.followthatpage.com/)

### 既存サービスを利用する際の問題
無料の範囲でやろうとすると制限が厳しいことが問題になります。例えば、定期実行の間隔が1時間に1回とか、月にX回までとかの制限があります。
今回の場合、1時間の猶予は致命的です。（後述しますが、実際には5分間隔でも厳しかったです）
自分で作ろーヽ(･ω･)/

## どういう仕組をつくればいいか考える
今回のように通知を待ちたいケースは個人的にはレアなので、汎用性は考えません。ちゃちゃっとできそうな仕組みを考えます。

![Untitled Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/87448/8be37814-f622-15b5-b33a-0139a894ae83.png)
図：システム構成のイメージ

図の手順1では何らかの方法でヨドバシ・ドット・コムの販売ページの状態を定期的に監視します。もし在庫が復活した場合、手順2に進みます。通知はメールでもいいですが、通知基盤であるSlackで自分にメンションを飛ばすことにしました。
こうするとApple Watchに通知が来て便利です（なおApple Watchをつかうと業務の通知もぱんぱかくるので*マジ便利*）。

手順3と4はたんにヨドバシ・ドット・コムで購入するというだけす。商品をカートに入れて決済するだけです。作っている最中はここまでする必要性を感じなかったのですが、運用してみたらPS4 Proの販売競争は熾烈なことがわかり、もたもたしていると売り切れてしまうことがわかったので試しに作ってみました。

以下のスクリプトは[事前に準備した実行環境](#b実行環境の用意)において実行することを想定しています。

## 手順1：販売ページのDOMから在庫チェック
Pythonだと[urlwatch](https://github.com/thp/urlwatch)というツールがあり便利そうです。ただ今回は難しいことはやらないので、E2Eテストなどで利用される[nightmare](https://github.com/segmentio/nightmare)を使いました。

販売ページのDOMを見て、classなどの要素が文字列一致したら通知する泥臭いスクリプトを定期実行します。画像の差分までいけると便利ですが、かき捨てなのでそこまで頑張らないことにしました（上で紹介したVisualpingはこれができて便利です。）

[完成したスクリプトがこちら](https://gist.github.com/kadoyau/db24d8e55b3c9bfe8cae1d8df984d1e8#file-polling-js)

### 定期実行
crondでも十分ですが、上位互換のRundeckを使います。
[Job Scheduler and Runbook Automation \- Rundeck\.org](http://rundeck.org/)

実行コマンドはこんな感じです。5分ごとに定期実行します。

```
xvfb-run -d --server-args="-screen 0 1024x768x24" node path/to/script/polling.js
```

## 手順2：通知
プッシュ通知基盤（Slack）に通知をします。npmモジュールの[slack\-webhook](https://www.npmjs.com/package/slack-webhook)を使いました。上述のスクリプトを実行すると、在庫がある場合にSlackに通知が飛びます。
（当然ですが、サーバにnode.jsとnpmモジュールはインストールしておく必要があります）。



# 実際に使ってみた
## トラブル１： スクリプトが止まった
### エラー内容
スクリプトが、最初はうまく行ってたけど途中からコケ始めました。エラーは次のようなものでした。

> /bin/xvfb-run: line 186: kill: (9619) - No such process

xvfbでエラーが起きているので、とりあえずログをはいてみます。xvfb-runコマンドは`-e` でログの出力先を指定できます（デフォルトは/dev/null）。

```bash
[rundeck@akagi webpage-watcher]$ xvfb-run --server-args="-screen 0 1024x768x24" -e /tmp/xvfb.log node polling.js
予定数の販売を終了しました
/bin/xvfb-run: line 186: kill: (9619) - No such process
[rundeck@akagi webpage-watcher]$ cat /tmp/xvfb.log
(EE)
Fatal server error:
(EE) Server is already active for display 99
	If this server is no longer running, remove /tmp/.X99-lock
	and start again.
(EE)
```
どうもアクティブにするサーバ番号が重複すると死ぬようです。

### 解決策
同様の問題を先人が解決していました

[shell \- xvfb\-run: line 171: kill: \(25939\) \- No such process \- Stack Overflow](http://stackoverflow.com/questions/30504274/xvfb-run-line-171-kill-25939-no-such-process)
[Running py\.test with xvfb\-run](http://stackoverflow.com/questions/12693609/running-py-test-with-xvfb-run)

これに従って実行コマンドのオプションを調整し、サーバの番号を自動的に割り当てるようにします。
不要なプロセスも殺しておきましょう。

```
[rundeck@akagi webpage-watcher]$ ps auwx | grep "Xvfb" | grep -v grep
rundeck  14179  0.0  0.1 128448  1432 ?        S    Dec09   0:00 Xvfb -ac -screen scrn 1280x2000x24 :9.0
rundeck  22647  0.0  0.2 121684  2456 ?        S    Dec09   0:03 Xvfb :99 -screen 0 1024x768x24 -nolisten tcp
```

また念のためrundeckでタイムアウトの設定します。

## トラブル2 ：5分間隔では頻度が足りなかった
実際に運用してみましたが、出先でプッシュ通知が来てカートに入れるところまでいきましたが、購入途中に品切れになり、買うことができませんでした。

<img width="352" alt="スクリーンショット 2016-12-16 1.07.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/87448/7b73522b-9e9d-752f-2154-204248fb7cf6.png">
図：5分間隔で通知をしたとき、2回目の通知で気づいても買えなかった。

<img width="414" alt="IMG_8123.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/87448/ee8e9cc8-ff31-fc47-4a94-71e4b5c9fccf.png">
図：カートまで入っても決済中に売り切れてしまう

### 解決策
監視の実行間隔は1分ごとに再設定しました。

# 手順3,4（追加対応）：ワンボタンで購入する
カートまで入っても決済中に売り切れるということは、早く購入するために、いっそログインやパスワードの入力も自動化しましょう。

[PS4購入ボタン](https://gist.github.com/kadoyau/db24d8e55b3c9bfe8cae1d8df984d1e8#file-order-js)

_注意：ヨドバシの規約は[こちら](http://www.yodobashi.com/ec/sitepolicy/agreement/001/index.html?selected_user_interface=old)。そもそもパスワードやセキュアコードを平文で保存するのは結構なリスクのため利用は推奨しません_

Rundeckにこのスクリプトを実行するジョブを作成し、Slackの通知にはこのジョブのページを追加します。こうすると、Slackには購入できるタイミングで購入スクリプトを実行するボタンへのリンクが表示されるので、rundeckにログインしてクリックすると購入が実行されます。

（[Slackのボタン機能](https://api.slack.com/docs/message-buttons)をつかってもいいかもしれませんが、権限管理できるのか確認していないので誰かが押すたびにPS4 Proが注文されてしまいます）。

## 運用した結果
このアドベントカレンダーの公開される前の日の夜に丁度通知が来ました。

<img width="290" alt="スクリーンショット_2016-12-16_1_28_27.png" src="https://qiita-image-store.s3.amazonaws.com/0/87448/6b094ceb-af9a-45e5-3c60-81459ee11888.png">

早速リンクに飛んでログインして購入ボタンをポチー。

……待てどもヨドバシ・ドット・コムから購入通知のメールが来ません。

嫌な予感がして、決済用のデビットカードの残高を見てみると、案の定2万円しか入っていませんでした。これでは決済できません。
大急ぎで入金し、手動で注文しました（結局手動でやってる）。

<img width="414" alt="IMG_8123.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/87448/ee8e9cc8-ff31-fc47-4a94-71e4b5c9fccf.png">
図：カートまで入っても決済中に売り切れてしまう

**To Be Continued...**

## 教訓
- **デビットカードにはお金をたくさん入れておこう**
- 売り切れるのが早すぎる（入荷後5-10分で完売する）ので、きっと同じようなことをしている人はたくさんいる。

# 付録
## A：国内家庭用ゲーム機市場の縮退
日本の家庭用ゲーム機の市場は縮退傾向にあります（ゲーム全体で見れば市場は拡大しているようです）。

[2015年の国内・海外のゲーム市場動向を調査した『ファミ通ゲーム白書2016』が6月15日に発刊 \- ファミ通\.com](http://www.famitsu.com/news/201606/09107833.html)

2015年のカプコンの資料からも、北米よりも日本の方が家庭用ゲーム機市場が縮退傾向にあることが読み取れます。

> コンシューマ市場は主に北米・欧州・日本の3地域で構成されていますが、市場の約80％を占める欧米では、現行機の普及が好調に推移したことに加え、ダウンロード販売も拡大したものの、上記の(1)(2)の要因により前年比5.3％減となりました。日本ではスマートフォン向けアプリとの競合などにより、前年比約25％の減少となりました。
[株式会社カプコン \| マーケットデータ](http://www.capcom.co.jp/ir/kojin/market.html)

このような環境の中で、ゲーム機の生産割当てで日本が軽視されるのは致し方ないと想像ができます。実際に、日本ではほぼ全く手に入らないと形容しても過言ではないPSVRはAmazon.comにはよく入荷しているようで、個人輸入するほうが入手性が高いです。

## B：実行環境の用意
環境構築はやや紆余曲折ありました。学生時代からろくに管理していないCentOSの6系のサーバがあったのでそこで動かそうとしたのですが（よって、Dockerイメージの配布などはできません…）、ElectronはCentOS6のglibcでは動かないようです。nightmareはElectronベースなので動かないということです。

参考：[HeadlessなLinux環境でNightmare\(v2\)を動かすためにしたこと \- Qiita](http://qiita.com/imunew/items/f1cf5e2553fc77df28ed)

ということでサーバをCentOS 7系にするところからはじめました。_Java8,rundeckをyumで入れ、npmを入れてnodeのバージョン管理システムnを入れて、nodejsを（略）…_

さて、CentOSにはWindow Systemが入っていないため、Electronを実行するためにX Window systemの仮想ディスプレイを作るためにxvfbを導入します。

```bash
sudo yum -y install xorg-x11-server-Xvfb
```
参考：[Xvfb を使って仮想ディスプレイを作る \- CUBE SUGAR CONTAINER](http://blog.amedama.jp/entry/2016/01/03/115602)

また、Electron実行に必要な依存関係は公式サイトにまとまっているので、いれます。

[Build Instructions \(Linux\) \- Electron](http://electron.atom.io/docs/development/build-instructions-linux/)

さらにCentOSの場合これだけでは動かず、以下を入れたらうまく動きました。

```bash
yum install xorg-x11-server-Xvfb  gtk2 libXtst GConf2 alsa-lib xorg-x11-fonts*
```

参考：[Running Nightmare headlessly on Linux · Issue \#224 · segmentio/nightmare](about:blank)