puppeteerでMyProteinの価格を取得するツールを作った
きっかけ
わたくしMyProtein社のプロテインを飲んでいます。
定期的にセールをやっているのですが、 種類毎に価格が違ったりします。
そこでスクレイピングで価格を抽出するツールを作りました。
法律的な話
MyProteinの利用規約に「スクレイピングを禁止する」文面は見当たりませんでした。
サーバーに負荷をかけないように注意すれば問題はなさそうです。
以下の記事を参考にさせて頂きました。
Webスクレイピングに関する10のよくある誤解 - Octoparse | スクレイピングツール
構想
スクレイピングといえばPythonですが、
あまり触った事がないため、JavaScript(Node.js)で作ってみます。
とりあえずテキストで表示できればよいので、CSVで出力することにします。
結果
こんな感じで取得できました。
アイスラテ,1kg,¥3,890 アイスラテ,2.5kg,¥7,390 アイスラテ,5kg,¥14,290 アイスラテ,250g,¥1,590 アイリッシュ コーヒー,1kg,¥3,890 アップルクランブルとカスタード,1kg,¥3,890 アップルクランブルとカスタード,2.5kg,¥7,390 イートン メス,1kg,¥3,290 …省略
開発
使ったライブラリはpuppeteer(パペティア)
コマンドでChromeを制御するためのライブラリです。
(デフォルトはヘッドレスなので画面が表示されることもありません)
GitHub - puppeteer/puppeteer: Headless Chrome Node.js API
ブラウザを起動してホエイプロテインの販売ページにアクセスするソースコード
const puppeteer = require("puppeteer"); (async () => { const browser = await puppeteer.launch({ }); const page = await browser.newPage(); await page.goto( "https://www.myprotein.jp/sports-nutrition/impact-whey-protein/10530943.html" ); /* ここでゴニョゴニョして価格を抽出する */ await browser.close(); })();
ブラウザ操作する例はこんな感じ
# セレクタを指定してクリック
await page.click("#mainContent");
またサーバーに負荷をかけないように、ボタンクリックやドロップダウンの選択は1秒待つようにしました。
await page.waitForTimeout(1000);
APIはdocsにまとめられています。
https://github.com/puppeteer/puppeteer/blob/main/docs/api.md
要素を取得するには、様々な方法があるようですが違いがよくわかっていません…
- page.evaluete()
- page.$()
- page.$$()
- page.$$eval()
- page.$eval()
自分はevaluete()しか使いませんでした。
イメージでは「ブラウザ内で実行する処理」を関数で渡す感じ。
documentオブジェクトが使えたので、ブラウザ内のJavaScriptを書くイメージで使えました。
作ったツールはこちら
目的は達成しました。puppeteerもおもしろかったです。
ちゃんと理解してないので、もう少しやってみたい気持ちになりました。
iOSからAWS SNSでPush通知するための手順
1. Appleサイトで証明書関連の準備
- 開発アプリのAppIDを登録
2. APNsの証明書作成
- Macでキーチェーンアクセス.appでCSRを作成
- AppleサイトでCSR (証明書署名要求)をアップする
- cer(証明書)をダウンロードする
- cerをダブルクリックすると証明書の追加ダイアログが開くので追加ボタンを押しP12ファイルを書き出す
3. AWS SNSの設定
- P12ファイルをアップロード
- ARN(リソース)が生成されるので、Push通知を送るアプリからこのARNに向かってリクエストする
4.アプリからプッシュ通知
※既に登録されているデバイストークンを何度登録しても正常に動作する
※ただし異なるUserDataを持っている場合は例外が発生する
※エンドポイントへ個別に通知を送るのもよいが、一斉に同じ内容の通知を送りたい場合もある
トピックにエンドポイントを紐付けておけばトピック宛に通知を送るだけで、紐付いたエンドポイントに一斉にメッセージを送ることができる
Laravelのルーティング設定
様々なルーティング設定
こんなコントローラがある想定
Controllers └PersonController.php
■よくあるルート
Route::get('person/edit', 'PersonController@edit');
Route::post('person/edit', 'PersonController@update’);
■名前付きルート
ルート設定に名前をつけられる。
リダイレクトなどのときに便利らしい
Route::get(‘person/add', 'PersonController@add’)->name(‘personadd’)
■条件付きルート
正規表現でパラメータを制限できる
Route::get(‘hello/{id}’, 'HelloController@index’)->where(‘id’, ‘[0-9]+’);
■ルートグループ
ルートをグルーピングできる。
などができるっぽい
■名前空間とグループルート
Controllers └Sample └SampleController.php
だとしたら
Route::get(’sample/edit', 'Sample/SampleController@edit’); Route::post(’sample/edit', 'Sample/SampleController@update’);
と書かなきゃいけないが、グループ化するとこのように記述できる
Route::namespace(’Sample’)->group(function() {
Route::get(’sample/edit', 'SampleController@edit’);
Route::post(’sample/edit', 'SampleController@update’);
})
Laravelのデバッグツール (コードプロファイラ)
画面にデバッグ用のバーを表示できる。
表示/非表示は.envで切替可能。
Laravel サービス層とは?
サービスとは各クラスから提供される機能
コントローラからはサービスクラスを呼び出し、サービスクラス内でモデル操作などを行うイメージの解釈
下記、記事が大変参考になる。感謝。
Service層を意識したLaravelのMVCモデル(概念編) - Qiita
Service層を意識したLaravelのMVCモデル(実践編) - Qiita
LaravelはデフォルトでServicesディレクトリとかはないので作るのがよい。
例
app
├── Services
│ ├── {任意の名前}Service.php // ビジネスロジックを書くクラス