ペチパーノート

WEB開発系Tipsブログです。

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を書くイメージで使えました。

作ったツールはこちら

github.com

目的は達成しました。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 サービス層とは?

サービスとは各クラスから提供される機能
コントローラからはサービスクラスを呼び出し、サービスクラス内でモデル操作などを行うイメージの解釈


下記、記事が大変参考になる。感謝。

Service層を意識したLaravelのMVCモデル(概念編) - Qiita

Service層を意識したLaravelのMVCモデル(実践編) - Qiita


LaravelはデフォルトでServicesディレクトリとかはないので作るのがよい。

app
├── Services
│   ├──  {任意の名前}Service.php // ビジネスロジックを書くクラス