🐄

study laravel #1

に公開

Scraping JOBを実装する

背景

とあるサイトの膨大なページをスクレイピングして必要な情報だけDBに保存したい。その後、必要な情報に対してRDBのクエリで検索したい。システムを置くサーバでは最小の処理時間を要求される。計算量の追求のみでは限界があるので、処理単位を細分化し、具体的には非同期処理で行いたい。

勉強の目的

  • どのような状態をキューに入れることができるか
    • 最上の期待値:実行待ちのJOBオブジェクトそのもの
    • 次点の期待値:JOBの実行に必要なパラメタとそれを必要とするJOB名のセット
    • 最低の期待値:単独のJOBの実行に必要なパラメタ

実装の細分化

  1. JOB稼働環境のセットアップ(JOB, RDB, migrate)
  2. ジョブ実行結果のModel作成(SampleJOBModel: url,content)
  3. サンプルジョブ作成(SampleJOB: getURIContents())
  4. ジョブ実行コントローラ作成

JOB稼働環境のセットアップ

laravelチュートリアル参照。あるいはChatGPTに教えてもらってください。

ジョブ実行結果のModel作成

SampleJobModelという適当な名前をつけたモデルを作成。
url :String, content :String とする。

php artisan make:model SampleJobModel
php artisan make:migration create_sample_job_models_table
    public function up(): void
    {
        Schema::create('sample_job_models', function (Blueprint $table) {
            $table->id();
            $table->string('uri'); // uri列(文字列)
            $table->text('content'); // content列(テキスト)
            $table->timestamps();
        });
    }
php artisan migrate

自分用メモ:
LaravelはModelクラスに手を加える必要がない。コード補完がしたければIDEで工夫するのみ

サンプルジョブ作成

最も原始的なScrapingを実装するProcessSampleJob。

handle()の仕様は、指定URLに対してcurl GETした結果をすべて文字列としてSampleJobModelへ保存。

php artisan make:job ProcessSampleJob
    public function handle(): void
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->uri);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_HTTPGET, true);
        $response = curl_exec($ch);
        curl_close($ch);

        $model = new SampleJobModel;
        $model->uri = $this->uri;
        $model->content = $response;
        $model->save();
    }

ジョブ実行コントローラ作成

webのcontrollerでなくて良い。app/Console/Commandsでいい。Laravelのジョブを動かせれば何でもいい。

php artisan make:command DoSampleJob
    public function handle()
    {
        $uri = "https://httpbin.org/delay/5";
        ProcessSampleJob::dispatch($uri);
        ProcessSampleJob::dispatch($uri);
        ProcessSampleJob::dispatch($uri);
        ProcessSampleJob::dispatch($uri);
        ProcessSampleJob::dispatch($uri);
    }
php artisan app:do-sample-job 
# JOBSテーブルの1行分
{
  "uuid": "7653dc8a-27cd-46ba-889a-0e5895776b3d",
  "displayName": "App\\Jobs\\ProcessSampleJob",
  "job": "Illuminate\\Queue\\CallQueuedHandler@call",
  "maxTries": null,
  "maxExceptions": null,
  "failOnTimeout": false,
  "backoff": null,
  "timeout": null,
  "retryUntil": null,
  "data": {
    "commandName": "App\\Jobs\\ProcessSampleJob",
    "command": "O:25:\"App\\Jobs\\ProcessSampleJob\":1:{s:6:\"\u0000*\u0000uri\";s:27:\"https://httpbin.org/delay/5\";}"
  }
}
php artisan queue:work

   INFO  Processing jobs from the [default] queue.  
  2024-01-04 06:32:44 App\Jobs\ProcessSampleJob ...... RUNNING
  2024-01-04 06:32:50 App\Jobs\ProcessSampleJob ...... 6s DONE
  2024-01-04 06:32:50 App\Jobs\ProcessSampleJob ...... RUNNING
  2024-01-04 06:32:56 App\Jobs\ProcessSampleJob ...... 6s DONE
  2024-01-04 06:32:56 App\Jobs\ProcessSampleJob ...... RUNNING
  2024-01-04 06:33:03 App\Jobs\ProcessSampleJob ...... 6s DONE
  2024-01-04 06:33:03 App\Jobs\ProcessSampleJob ...... RUNNING
  2024-01-04 06:33:09 App\Jobs\ProcessSampleJob ...... 6s DONE
  2024-01-04 06:33:09 App\Jobs\ProcessSampleJob ...... RUNNING
  2024-01-04 06:33:15 App\Jobs\ProcessSampleJob ...... 5s DONE
# SampleJobModelテーブルの一行分

uri:
https://httpbin.org/delay/5

content:
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "X-Amzn-Trace-Id": "Root=1-6596510d-3a03259e4bdf41e73f6c7109"
  }, 
  "origin": "211.2.76.67", 
  "url": "https://httpbin.org/delay/5"
}

最上の期待値を満たしたと思う。

次の課題は

  • scraping結果からURLを抽出して、非同期の多段scraping
  • php artisan queue:work をコマンドライン実行なしに定期実行する方法

Discussion