
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、突然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
からヘッドレス(画面に表示しない)モードが追加されたので、コマンドラインからいろいろな操作ができるようになったからなんですね。
そこで、サムネイルをつくる手順としては以下のようになります。
- サムネイルを作成するURLをデータベースに追加する
- タイマー設定で定期的にPythonスクリプトを実行し、Chromeを操作してスクリーンショットをとる
- 作成されたスクリーンショットを加工してサムネイルを作成する
※ つまり、データベースを間に入れることでLaravel
などのウェブサイトとPython
の連携を想定しています。
ではひとつずつ見ていきましょう!
環境を整える
Chromiumのインストール
では、まずは実行環境を整えておきましょう。
Ubuntu
にChromium
をインストールします。
sudo apt install chromium-browser
mysql-connectorのインストール
また、Python
からMySQL
にアクセスするのでpip
でmysql-connector
もインストールしておきましょう。
pip install mysql-connector
DBテーブルをつくる
そして、Python
とPHP
の仲介役になるデータベース・テーブルの作成です。
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_HOST
はlocalhost
が多いです)
DB接続
先ほどpip
でインストールしたmysql.connector
を使ってデータベースに接続しています。なお、もし接続に失敗すると強制的にストップするようになっています。
DBから1件データを取得
データベースから1件だけサムネイルを作るデータを取得するのですが、条件は以下2つです。
- 「status」が「waiting」のもの
- 「id」が一番小さい(つまり一番古いデータ)
なお、データが存在しない場合は強制ストップします。
コマンド実行(サムネイル作成)
ここでサムネイルを作成しますが、実際に実行するのは以下4つのコマンドです。
- Chromiumを使ってサイトのスクリーンショットを「1000 x 1000」ピクセルで作成する
- 作成したスクリーンショットをImageMagickを使って「92×92」ピクセルにリサイズ
- 「1000 x 1000」のスクリーンショットは不要なので削除
- Laravelのサムネイルフォルダへリサイズした画像を移動
ステータスの更新
サムネイルが作成されたら(たとえエラーであっても)ステータスを変更して、次の実行時には同じデータを呼び出さないようにします。
今回のコードで存在するステータスは以下の3つになります。
- waiting ・・・ サムネイル作成を待機中
- completed ・・・ サムネイルが作成された
- failed ・・・ サムネイルの作成に失敗した
テストしてみる
では実際にサムネイルが作成できるかどうかをテストしてみましょう。
以下のコマンドを実行します。
crontab -e
するとエディタが起動してcrontab
(タイマー実行)を編集できるようになりますので、次のように追加して毎分ごとにPythonファイルを実行するようにします。
* * * * * python /PATH/TO/YOUR/PYTHON/generate_thumbnail.py
※ ちなみに保存はEsc
=> :wq!
=> Enter
キーです。
あとは待つだけです。
(約5分後・・・・・)
サムネイルが作成され、thumbnails
フォルダはこのようになりました。
全て92x92
のサムネイルです。
※ もし上手く行かない場合は、おそらく実行権限が足りていない場合がほとんどだと思います。そんな場合はchmod
コマンドで権限を変更してみてください。
お疲れ様でした!
おわりに
ということで今回はヘッドレスのGoogle Chrome
を使ってサムネイルを作成してみました。
今回は全てローカル環境で実行していますが、Ubuntu
でやりましたのでサーバー内でもきっとうまくいくはずです。(なんなら独自にサムネイル作成サービス公開してみようかなー、とも思いますがBlinky
さんとの差別化が思いつかないのもありますし、もうひとつのサムネイル化サービスsimpleAPI
はどうやらもう動いてないようなので、あまり需要はないのかも、、、と思っております)
なお、今回使ったヘッドレスGoogle Chrome
を使えばPDF
の作成やDOM
(つまりJavaScript
実行後のHTML
)の取得もできるので今後機会があったら試してみようと思います。
ぜひ皆さんもいろいろとやってみてくださいね。
ではでは〜!
「ヘッドレスっていう名前、冷静に考えると怖いですね(笑)」