環境構築からWEBアプリ開発・スマホアプリ開発まで。ときには動画制作やゲームも。

supilog
すぴろぐ

【Laravel】はじめてのAPI構築(第3回) 情報を取得してみる編

前回、非常に基礎的な形ではありましたが、URLを叩いてJSONデータが取得できるAPIが作成できました。

ただ初心者の方は、実践的なデータ投入であったり、DBからの取得であったりの具体的なイメージがある方が理解が進むと思うので、今回は実際にデータを入れてみます。

実は、気象庁のWEBサイトから天気情報を取得したり、Yahoo!さんのリアルタイムトレンドワードを取得したりしようかと思っていたのですが、他サービスに迷惑がかかるといけないので、本ブログ上で情報を取得することにしました。

題材としてはつまらないものですが、ご了承ください><

目的

ということで今回は、このsupilog(本ブログ)の右ナビゲーションにある、最近の投稿3件の情報(タイトルとリンクURL)を取得し、さらにAPIとして出力するというところまでやってみたいと思います。

インストール

composer require fabpot/goutte

HTTPクライアントのライブラリとして、Goutteを利用します。

コマンド生成

より実践的にということで、後々crontabにも設定しやすいようにコマンドを作成しようと思います。

php artisan make:command GetBlog

実行すると、api/app/Console/Commands/GetBlog.phpが生成されます。生成されたファイルを開いて編集します。

signature

このコマンドは、artisanコマンドとして実行できるようになります。このファイルを生成するときに使ったのもartisanコマンドです。

signatureを設定することで実行するコマンドを指定することが出来ます。やってみましょう。

protected $signature = 'command:name';

↓ 編集

protected $signature = 'blog:get';

これで、「php artisan blog:get」コマンドを実行すると、このクラスが実行されるようになります。

description

コマンドの説明文です。自分用に好きな説明を入力します。日本語でOK。

protected $description = '最近のブログ投稿を取得する';

__construct()

今回は修正なし

handle()

コマンドを実行したときに、行いたい処理をここに記述します。まずは簡単な命令を書いて実行できるか確かめてみましょう。

    public function handle()
    {
        print "Command blog:get done.\n";
    }

動作確認

artisanコマンドリスト
php artisan list

---------------------------------------------
...
 blog
  blog:get            最近のブログ投稿を取得する
...
---------------------------------------------

↑先程設定したコマンドと説明文が表示されていますね。
実行
php artisan blog:get
---------------------------------------------
Command blog:get done.
---------------------------------------------

print文で指定した文字列が出力されていれば成功です。

コマンド実装

DB準備

取得したデータを保管しておくテーブルを用意します。

php artisan make:migration create_blogs_table --create=blogs

migrationファイルが作成されるので、下記のように編集します。(api/database/migrations/YYYY_MM_DD_XXXXXX_create_blogs_table.php)

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTrendsTable extends Migration
{
    public function up()
    {
        Schema::create('blogs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('blogtitle1');
            $table->string('blogurl1');
            $table->string('blogtitle2');
            $table->string('blogurl2');
            $table->string('blogtitle3');
            $table->string('blogurl3');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('blogs');
    }
}

修正したら下記コマンドを実行し適用させます。これでテーブルが作成されます。

php artisan migrate
---------------------------------------------
Migrating: 2021_12_08_133726_create_blogs_table
Migrated:  2021_12_08_133726_create_blogs_table (0.06 seconds)
---------------------------------------------

モデルも作成しておきます。

php artisan make:model Blog

データ取り込み

GetBlogコマンドの中身を実装します。難しことはやっていませんが、HTML上の要素を指定して値を取得して、最終的に先程作成したblogsテーブルに保存しています。

<?php
namespace App\Console\Commands;

use App\Blog;
use Goutte\Client;
use Illuminate\Console\Command;
use Symfony\Component\HttpClient\HttpClient;

class GetBlog extends Command
{

    protected $signature = 'blog:get';

    protected $description = '最近のブログ投稿を取得する';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $client = new Client(HttpClient::create([
            'timeout' => 10
        ]));
        $crawler = $client->request('GET', 'https://supilog.supisupi.com/');

        $blog = new Blog();

        // 3番目までのブログ情報取得
        $blogdata = array();
        for ($i = 0; $i < 3; $i ++) {
            $tmp = array();

            $article = $crawler->filter('.recent-list')
                ->filter('a')
                ->eq($i);
            $tmp['url'] = $article->attr("href");
            // タイトルのみ文字列の前後にあるスペースを除去
            $tmp['title'] = preg_replace('/\A[\p{C}\p{Z}]++|[\p{C}\p{Z}]++\z/u', '', $article->text());

            array_push($blogdata, $tmp);
        }

        $blog->blogtitle1 = $blogdata[0]['title'];
        $blog->blogtitle2 = $blogdata[1]['title'];
        $blog->blogtitle3 = $blogdata[2]['title'];
        $blog->blogurl1 = $blogdata[0]['url'];
        $blog->blogurl2 = $blogdata[1]['url'];
        $blog->blogurl3 = $blogdata[2]['url'];

        // DBに格納
        $blog->save();
    }
}

php artisan blog:get を実行して、エラーが出なければ処理は成功しています。

それではいよいよ、このデータを使って、APIの出力を行いましょう。

データ出力(API)

routeを修正

前回、/api/testというルートで、testアクションを実行していましたが、testという名前も忍びないので、更新しておきます。

<?php

use Illuminate\Http\Request;

Route::get ( '/blog', 'ApiController@blog' );
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ApiController extends Controller
{
    public function blog(Request $request) {
//         $ret = array();
//         $ret['version'] = '1.0.0';
//         $ret['result'] = 'success';
//         return $ret;
    }
}

trendアクション実装

ApiControllerのblogアクションを以下のように修正してみましょう。

<?php
namespace App\Http\Controllers;

use App\Blog;
use Carbon\Carbon;
use Illuminate\Http\Request;

class ApiController extends Controller
{
    public function blog(Request $request)
    {
        // DBから最新データを取得
        $blog = Blog::latest()->first();

        // 値をセットする
        $ret = array();
        $ret['version'] = '1.0.0';
        $dt = new Carbon($blog->created_at);
        $ret['update_date'] = $dt->format('Y/m/d H:i:s');
        $ret['blogs'] = array();
        $ret['blogs'][0]['title'] = $blog->blogtitle1;
        $ret['blogs'][0]['url'] = $blog->blogurl1;
        $ret['blogs'][1]['title'] = $blog->blogtitle2;
        $ret['blogs'][1]['url'] = $blog->blogurl2;
        $ret['blogs'][2]['title'] = $blog->blogtitle3;
        $ret['blogs'][2]['url'] = $blog->blogurl3;
        return $ret;
    }
}

確認

起動をして、http://127.0.0.1:8000/api/blogにアクセスしてみましょう。

なにやら記号が出ましたが、こちらは文字化けではなくエンコードされているだけなので、ちゃんと出力されています。

試しにこの文字列(JSON)を整形してみました。

{
	"version": "1.0.0",
	"update_date": "2021/12/09 02:59:46",
	"blogs": [
		{
			"title": "【Laravel】はじめてのAPI構築(第2回) 環境構築+はじめてのAPI編",
			"url": "https://supilog.supisupi.com/blog/is4aifa8ffpd/"
		},
		{
			"title": "【Laravel】はじめてのAPI構築(第1回) 座学編",
			"url": "https://supilog.supisupi.com/blog/ltuopp8vmr99/"
		},
		{
			"title": "ダミー画像作成サービス – Dynamic Dummy Image Generator",
			"url": "https://supilog.supisupi.com/blog/9ranotb1e4jb/"
		}
	]
}

しっかり出力されてます。今回の作業はこれで終了です。

情報を取得してみる まとめ

いかがでしたか。簡単ではありますが、「データの収集」から「APIでDBに保管されている最新データを出力する」ところまで、しっかり行ってみました。

今回データの取得は、artisanコマンドを利用しましたので、そのままcrontabに記述してコマンド実行させることが容易です。リアルタイムにデータが変化する場合などは短い間隔で定期実行してあげることで、定期的にデータベースが更新されていくことになり、それによってAPIが出力する内容も新しくなります。

【注意1】スクレイピングによって、他サービスからデータを利用する場合には、対象サービスの規約をよく読み、規約違反をすることのないように注意してください。

【注意2】スクレイピングは対象のHTMLが更新されることによりデータが取得できなくなることは、容易にあり得ることなので、実運用する場合にはエラーが検知できるようにする工夫も必要です。

次回は、APIの認証部分に触れていきたいと思います。←実はここが一番やりたかったところ(私も復習したくて。。