Laravel + Python でウェブサイトのサムネイルをつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、突然PRみたいになってしまいますがこのブログのメニューにあるprereleという無料プレスリリース配信サイトは私が管理をしているサイトです。

ただ、このサイトは正直ボランティアのような形で運営しているので、それほど頻繁に拡張していませんが、この間プレスリリースのページに何か変更を加えられるものはないかと考えていると、「ある便利なウェブサービス」を長年利用させてもらっていることに気がつきました。

それが「サムネイル」です。

サービス名は「Blinky(旧・MozShot)」というのですが、なんとこのサービス。URLをつけてアクセスするだけでサムネイルを表示してくれるという神がかったものになっています。

当初は「こんないいサービス、きっとすぐ有料化するかなくなっちゃうんだろうなー😅」なんて思っていたのですが、記憶が正しければ4〜5年は普通に無料で利用可能なままです。

ということで、今回はこの「Blinky」のようにサムネイルをつくるコードをご紹介したいと思います。(ただし、Blinkyは気に入っているのでこのまま使わせていただきます。Thank you😊✨)

ぜひ皆さんのお役に立てると嬉しいです!

開発環境: Ubuntu 18.04、Python 2.7、Laravel 5.8

サムネイルをつくる仕組み

さすがにPHPだけではサムネイルを作成することは出来ないので、コマンドからGoogle Chrome(正確にはChromium)を使います。

というのも、Google Chromeはバージョン59からヘッドレス(画面に表示しない)モードが追加されたので、コマンドラインからいろいろな操作ができるようになったからなんですね。

そこで、サムネイルをつくる手順としては以下のようになります。

  1. サムネイルを作成するURLをデータベースに追加する
  2. タイマー設定で定期的にPythonスクリプトを実行し、Chromeを操作してスクリーンショットをとる
  3. 作成されたスクリーンショットを加工してサムネイルを作成する

※ つまり、データベースを間に入れることでLaravelなどのウェブサイトとPythonの連携を想定しています。

ではひとつずつ見ていきましょう!

環境を整える

Chromiumのインストール

では、まずは実行環境を整えておきましょう。
UbuntuChromiumをインストールします。

sudo apt install chromium-browser

mysql-connectorのインストール

また、PythonからMySQLにアクセスするのでpipmysql-connectorもインストールしておきましょう。

pip install mysql-connector

DBテーブルをつくる

そして、PythonPHPの仲介役になるデータベース・テーブルの作成です。
Laravelのフォルダに移動して以下のコマンドを実行してください。

php artisan make:model Screenshot -m

すると、

/database/migrations/****_**_**_******_create_screenshots_table.php

というファイルが作成されるので中身を次のように変更します。

<?php

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

class CreateScreenshotsTable extends Migration
{
    public function up()
    {
        Schema::create('screenshots', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->string('url');
            $table->string('status')->default('waiting');
            $table->timestamps();
        });
    }

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

これでマイグレーションを実行してください。

php artisan migrate

テーブルが作成されると次のようになります。

テストデータを登録する

なお、本来はLaravelにデータを登録するページを作成すべきですが、今回はサムネイルの作成がメインなので、データは事前にSeederを使って登録しておきます。

以下のコマンドを実行してください。

php artisan make:seed ScreenshotsTableSeeder

/database/seeds/ScreenshotsTableSeeder.phpを開いて以下のようにしてください。

<?php

use Illuminate\Database\Seeder;
use \Illuminate\Support\Str;

class ScreenshotsTableSeeder extends Seeder
{
    public function run()
    {
        $websites = [
            ['title' => 'ヤフー!', 'url' => 'https://www.yahoo.co.jp/'],
            ['title' => 'Google', 'url' => 'https://www.google.com/'],
            ['title' => 'Amazon', 'url' => 'https://www.amazon.co.jp/'],
            ['title' => 'Microsoft', 'url' => 'https://www.microsoft.com/'],
            ['title' => 'Apple', 'url' => 'https://www.apple.com/'],
        ];

        foreach ($websites as $website) {

            $screenshot = new \App\Screenshot();
            $screenshot->title = $website['title'];
            $screenshot->url = $website['url'];
            $screenshot->save();

        }
    }
}

内容としては、誰でも知ってる「GAFA + ヤフー!」のサイト名とURLを登録しているだけです。(statusはデフォルトのwaitingになります)

そして、/database/seeds/DatabaseSeeder.phpを開いてrun()内に以下を追加します。

public function run()
{
     $this->call(ScreenshotsTableSeeder::class);
}

では、以下を実行してSeederからテストデータを追加してください。

php artisan db:seed

# もしくは、テーブルを一新したい場合はこちら
php artisan migrate:fresh --seed

完了するとテーブルはこのようになります。

サムネイルを保存するフォルダを作成する

今回は作成されたサムネイルをstorage内のフォルダに保存しますが、ブラウザから直接アクセスできるようにしたいので、以下のコマンドでstorageフォルダ内にシンボリックリンクを作成します。

php artisan storage:link

これで、/storage/app/public/フォルダが作成されるので、この中にthumbnailsというフォルダを作成しておいてください。(※ もちろんsudo chmod 777 thumbnails -Rなどとして書き込み権限をつけるのも忘れず行ってください)

サムネイルを作成するPythonコードをつくる

では、やっとメインのサムネイル作成部分です。
LaravelではなくPythonになりますので、適当なフォルダの中にgenerate_thumbnail.pyというような名前のファイルを作成し以下のコードを追加してください。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import mysql.connector

# 定数(変更が必要な場所)
THUMBNAIL_PATH = '/(Laravelのフォルダ・パス)/storage/app/public/thumbnails'
DB_HOST = 'DB_HOST'
DB_DATABASE = 'DB_DATABASE'
DB_USERNAME = 'DB_USER'
DB_PASSWORD = 'DB_PASSWORD'

# DB接続
try:
    db = mysql.connector.connect(
            host=DB_HOST,
            database=DB_DATABASE,
            user=DB_USERNAME,
            passwd=DB_PASSWORD
        )
except:
    print('[Error] DB接続失敗。')
    exit()

# DBから1件データを取得
query = 'SELECT id, url FROM screenshots WHERE status = "waiting" ORDER BY id ASC LIMIT 1'
cursor = db.cursor()
cursor.execute(query)
result = cursor.fetchone()
if result == None:
    print('[Warning] データが存在しません。')
    exit()

# コマンド実行(サムネイル作成)
id = result[0]
url = result[1]
filename = str(id) +'.png'
original_filename = 'original_'+ filename
moving_path = THUMBNAIL_PATH +'/'+ filename
commands = [
    'chromium-browser --headless --disable-gpu --hide-scrollbars --screenshot='+ original_filename +' --window-size=1000,1000 '+ url,
    'convert '+ original_filename +' -resize 92x92 '+ filename,
    'rm '+ original_filename,
    'mv '+ filename +' '+ moving_path
]

status = 'failed';
try:
    for command in commands:
        os.system(command)
    status = 'completed'
except:
    pass

# ステータスの更新
query = 'UPDATE screenshots SET status = "%s" WHERE id = %d' % (status, id)
cursor = db.cursor()
cursor.execute(query)
db.commit()

このコードの中でやっていることは次のとおりです。

定数

コードを実行するために必要な情報です。

THUMBNAIL_PATHは先ほど作成したLaravel内のthumbnailsフォルダのある位置を指定してください。

残りはデータベースへの接続情報になります。(DB_HOSTlocalhostが多いです)

DB接続

先ほどpipでインストールしたmysql.connectorを使ってデータベースに接続しています。なお、もし接続に失敗すると強制的にストップするようになっています。

DBから1件データを取得

データベースから1件だけサムネイルを作るデータを取得するのですが、条件は以下2つです。

  • 「status」が「waiting」のもの
  • 「id」が一番小さい(つまり一番古いデータ)

なお、データが存在しない場合は強制ストップします。

コマンド実行(サムネイル作成)

ここでサムネイルを作成しますが、実際に実行するのは以下4つのコマンドです。

  1. Chromiumを使ってサイトのスクリーンショットを「1000 x 1000」ピクセルで作成する
  2. 作成したスクリーンショットをImageMagickを使って「92×92」ピクセルにリサイズ
  3. 「1000 x 1000」のスクリーンショットは不要なので削除
  4. Laravelのサムネイルフォルダへリサイズした画像を移動

ステータスの更新

サムネイルが作成されたら(たとえエラーであっても)ステータスを変更して、次の実行時には同じデータを呼び出さないようにします。

今回のコードで存在するステータスは以下の3つになります。

  • waiting ・・・ サムネイル作成を待機中
  • completed ・・・ サムネイルが作成された
  • failed ・・・ サムネイルの作成に失敗した

テストしてみる

では実際にサムネイルが作成できるかどうかをテストしてみましょう。
以下のコマンドを実行します。

crontab -e

するとエディタが起動してcrontab(タイマー実行)を編集できるようになりますので、次のように追加して毎分ごとにPythonファイルを実行するようにします。

* * * * * python /PATH/TO/YOUR/PYTHON/generate_thumbnail.py

※ ちなみに保存はEsc => :wq! => Enterキーです。

あとは待つだけです。

 

(約5分後・・・・・)

 

サムネイルが作成され、thumbnailsフォルダはこのようになりました。
全て92x92のサムネイルです。

※ もし上手く行かない場合は、おそらく実行権限が足りていない場合がほとんどだと思います。そんな場合はchmodコマンドで権限を変更してみてください。

お疲れ様でした!

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで今回はヘッドレスのGoogle Chromeを使ってサムネイルを作成してみました。

今回は全てローカル環境で実行していますが、Ubuntuでやりましたのでサーバー内でもきっとうまくいくはずです。(なんなら独自にサムネイル作成サービス公開してみようかなー、とも思いますがBlinkyさんとの差別化が思いつかないのもありますし、もうひとつのサムネイル化サービスsimpleAPIはどうやらもう動いてないようなので、あまり需要はないのかも、、、と思っております😅)

なお、今回使ったヘッドレスGoogle Chromeを使えばPDFの作成やDOM(つまりJavaScript実行後のHTML)の取得もできるので今後機会があったら試してみようと思います。

ぜひ皆さんもいろいろとやってみてくださいね。

ではでは〜!

「ヘッドレスっていう名前、冷静に考えると怖いですね(笑)」

このエントリーをはてなブックマークに追加       follow us in feedly