TAMINIF’s blog

京都在住のWebエンジニア。iOSアプリもやってます。勉強会にも参加してます。最近はマネージャー色強め

Headless Chromeとpuppeteerを使って馬券購入APIを実装する

99%の人には言ったことはないのですが、実は競馬を毎週見るくらいのファンです。

やりたいこと

Headless Chromiumpuppeteerを使って馬券購入の手順を自動化、入力値を渡すことで
その馬券を購入。WEBで完結するようにしてAPI化する。

前提として

JRAのサイトなんですが、公式サイトやインターネット購入サイト、購入履歴を見れるサイトなどいくつかあるのですが
もれなく現代のWebから取り残されたような作りになっています。
おそらくスクレイピング対策としてこのような作りにしているのではないかと邪推しているのですが、真意はわかりません。
例えば、以下のサイトから「出馬表」 -> 「開催のボタン」 -> 「レース指定」と進んでみてください。全てPOSTで遷移、URLにパラメータは一切残さない仕様です。
それでもこの速度を出せるのはおそらくサーバーを札束で殴っているからではないかと思います。
http://www.jra.go.jp/

設計図

github.com

スクレイピング部分

とにかくawait連打です。全ての返り値はpromiseなので、awaitで処理を待たないと欲しい要素は取れません。
またスクレイピングの仕様上、pageをずっと持ち回る必要があって、かつ都度処理を待たないといけないので
必然的にawaitで処理を待つ必要があります。
あとは必要な要素をセレクターで取得し、それに対してtype, click, pressなど操作したい処理を実行します。

Headless Chromeでつまづいた点

ヘッドレスChromeことはじめというページがあって、そこでHeadless Chromeの紹介がされていて実行手順も書いてあるんですけど
日本語訳のページが古く、Nodeで利用する手順をそのままやろうとしても動きません。
英語のページを読みましょう。(Chromeの言語設定を英語にして下記ページを表示)
https://developers.google.com/web/updates/2017/04/headless-chrome

puppeteerでつまづいた点

clickが動かない

clickやhoverなどは処理時にスクロールする処理が入っており、画面内にないと上手く処理が動かないため、
画面ロード時に初めからものすごい大きなウィンドウをsetViewPortで指定することで、画面内に全てある状態を実現しました。

waitForNavigation

画面遷移時にwaitForNavigationで遷移を待つんですけど、SPAのような画面遷移後に要素をロードするような画面は
要素が何も取得できないという問題が発生するため、waitoForで秒を指定して確実にロードが終わるのを待った方が良いケースがあります。

AWSAPI化する

ただ作るだけでは自分のPCでしか動かずそれではあまり意味がないため、今回はAWS Lambdaで動かして
最終的にAPI Gatewayで呼び出すことでAPI化するところまでやりました。

AWS Lambda

puppeteerを使うためには当然ですがChromeと、Nodeが動く環境が必要です。
普通にサーバー構築すれば良いのですが、構築はめんどくさいので調べてみると
Lambdaで動かすためのStarterKitを提供されている方がいらっしゃいましたので、こちらを使わせていただきました。 github.com

AWS Lambdaの注意点

Lambdaでは、Nodeのバージョンを指定できるんですけど、これがv8だと動きません。Node6.10を指定します。
puppeteerのLambda関連のissueを見ると結構あるのですが、なかなか気付かずつまづきました。

API Gatewayから呼び出す際の注意点

puppeteerでスクレイピングすると、どうしても処理を待たないといけないのでwaitForで3秒とか余裕を持って処理を待つようにしています。
そうすると、API Gatewayタイムアウトである29秒がどうしても間に合わないため、API Gateway -> Lambda -> Lambdaという形で
LambdaからLambdaを呼び出すことで、タイムアウト問題を解決しました。

まとめ

スクレイピングはとても楽しい。普段使ってるWebがシステム化されていく感があってとても良いです。
puppeteerを使われている方の記事を見ると結構な割合でE2Eテストに使われているのですが、私はWebのAPI化を中心にこれからも使っていきたいですね。
puppeteerのオプションにheadlessというパラメータがあって、これをfalseにして実行すると
実際にChromeが立ち上がって動いていく様が見れるので、作ったものがどう動くか見るだけでも楽しいと思います。

JRA利用規約は一応確認して、他のアプリケーションを組み合わせての使用は禁止とあるのですが、
そもそもChrome使わないと使えないのでこれはセーフだと思ってます。怒られたら消します。