このページはこちらのサービスの内容の解説(忘備)記事になります。
abrd.site
このサイトはその名の通り自動販売機に入っている商品の原材料名やアレルギーがわかるサービスを行っています。
そもそもこれを作った理由は「自販機は買う前に裏面を確認できないから怖い」というアレルギー持ちの友人がいたため。
新商品や自分で覚えている定番商品以外を選べないそうです。
そんな友人の小さな生き辛さを改善できればとコツコツ作っているのですが、ある程度形になったので忘備録の意味も込めてどのように作っていったかを書いていきたいと思います。
まず、流れとしてはこんな形です。
1.各社サイトから商品情報と商品画像をスクレイピングする
↓
2.その情報をJSONデータで記録
↓
3.JSONを読み込んでサイトに表示
さすがに手作業でコピペしていくのは骨なので、スクレイピングで情報を一気に取得します。
スクレイピングと言ったらPythonが有名ですが、今回はphpQueryというjQuery風にDOM操作ができるというPHP用ライブラリを使って行いました。
想定しているのはこのように商品情報がtableになっているページです。
まずはphpQueryを使うためのファイルを読み込みます。
そして読み込み先のページを指定・内容を読み込み、phpQueryが扱える形にしましょう。
<?php
require_once("phpQuery-onefile.php");
$html ="https://●●●.html";
$html =file_get_contents($html);
$dom = phpQuery::newDocument($html);
スクレイピングする準備ができたので早速やっていきます。
$count= count($dom["table. tr"]);
for($i=0;$i<=$count;$i++){
$title=$dom["tr:eq($i) td:eq(0) a"]->text();
$data = $dom["tr:eq($i) td:eq(1)"]->text();
$allr = $dom["tr:eq($i) td:eq(2)"]->text();
情報を取得するにはその情報が位置する要素やclass、idを指定して行く必要があります。
例えばこれなら
$data = $dom["tr:eq($i) td:eq(1)"]->text();
「($i)行目の2つめのtd要素から情報を取得」、という意味になります。(●●:eq(数字)は「●●の(数字)目の要素」という意味。数字は0が1つ目の要素に対応)
今回はテーブルから繰り返し取得するので、ループできるようにまずはtr(行)の数をcount()で取得。
そしてforを使って先程取得した[総tr数]になるまで回します。
これだけだと文字データだけなので画像もスクレイピングしましょう。
$src = $dom["tr:eq($i) td:eq(0) img"]->attr("src");
同じようにテーブルから情報を取得しますが、これまでとは違いattr("src")でsrc属性の値を選択します。
こうすることで画像が配置してあるパス情報を手に入れます。
次は画像が保存される際の名前と保存先を決めましょう。
$fileName = "img".$i.".jpg";
$filePath = "../images/".$fileName;
$imgData = @file_get_contents($src);
ファイル名はtrの順番と対応させたいと思います。
これで必要な情報を取得する準備はOK。
扱いやすいようにJSONとして保存します。
if($imgData){
@file_put_contents("$filePath", $imgData);
}
$jsonArray[] = ['title'=> $title, 'data'=>$data, 'allr'=>$allr, 'filePath'=>$filePath];
$jsonData = json_encode($jsonArray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents("data.json",$jsonData);
sleep(1);
}
書き出されたJSONファイルはこのようになります。
[
{
"title": "プレミアムボス",
"data": "牛乳、コーヒー、砂糖、ぶどう糖、乳加工品、カゼインNa、香料、乳化剤 ",
"allr": "乳 ",
"filePath": "../images/suntory/img1.jpg"
},
{
"title": "ボス レインボーマウンテンブレンド",
"data": "牛乳、砂糖、コーヒー、乳製品、デキストリン、カゼインNa、乳化剤、香料 ",
"allr": "乳 ",
"filePath": "../images/suntory/img2.jpg"
},
{
"title": "ボス 贅沢微糖",
"data": "牛乳、コーヒー、砂糖、乳製品、デキストリン、カゼインNa、乳化剤、香料、甘味料(アセスルファムK) ",
"allr": "乳 ",
"filePath": "../images/suntory/img3.jpg"
},
{
"title": "ボス 無糖ブラック",
"data": "コーヒー ",
"allr": " ",
"filePath": "../images/suntory/img5.jpg"
},
...
]
これでスクレイピングは完了です。
全体のコードはこちら。
問題やより効率的な方法がありましたらコメントお願いします。
<?php
require_once("phpQuery-onefile.php");
$html ="https://●●●.html";
$html =file_get_contents($html);
$dom = phpQuery::newDocument($html);
$count= count($dom["table. tr"]);
for($i=0;$i<=$count;$i++){
$title=$dom["tr:eq($i) td:eq(0) a"]->text();
$data = $dom["tr:eq($i) td:eq(1)"]->text();
$allr = $dom["tr:eq($i) td:eq(2)"]->text();
$src = $dom["tr:eq($i) td:eq(0) img"]->attr("src");
$fileName = "img".$i.".jpg";
$filePath = "../images/".$fileName;
$imgData = @file_get_contents($src);
if($imgData){
@file_put_contents("$filePath", $imgData);
}
$jsonArray[] = ['title'=> $title, 'data'=>$data, 'allr'=>$allr, 'filePath'=>$filePath];
$jsonData = json_encode($jsonArray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
file_put_contents("data.json",$jsonData);
sleep(1);
}
出力ページ
次は実際の出力ページに取り掛かります。
まずは先程のJSONを読み込みましょう。
<?php
$jsonUrl = "data.json";
if(file_exists($jsonUrl)){
$json = file_get_contents($jsonUrl);
// 内容を読み取り
$json = mb_convert_encoding($json, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
// エンコードする
$obj = json_decode($json,true);
// 内容を取り出す
}
?>
JSONの内容を$objに取り出したので、今度はhtml上に表示します。ちなみにBootstrapを使っています
<div class="row">
<?php foreach($obj as $key => $val): ?>
<div class="ddata">
<div class="col-3 dimage">
<img src="<?php echo $val["filePath"]; ?>" onerror="this.src='../images/vm/no-image.png'">
</div>
<div class="product">
<h2>
<?php echo $val["title"]; ?>
</h2>
<div class="card card-body">
<p>
<span>原材料名:
</span>
<?php echo $val["data"]; ?>
</p>
<p>
<span>アレルギー:
</span>
<?php echo $val["allr"]; ?>
</p>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
html内でforeachを使うので、表示したい部分を
<?php foreach($obj as $key => $val): ?>
…
<?php endforeach; ?>
で囲みます。
画像表示部分:
<img src="<?php echo $val["filePath"]; ?>" onerror="this.src='../images/vm/no-image.png'">
さきほどの画像のファイルパスをimgタグ内でechoします。
もとから画像が用意されていない商品は404エラーになるので、
onerror="this.src='../images/vm/no-image.png'
この部分でエラー時に表示するノーイメージ画像を指定しています。
後はそれぞれ同じように商品名、原材料名、アレルギー物質をechoします。
CSSで形を整えて、最終的にはこのように表示されます。
しかしこのままでは全部が表示されて画面を圧迫し見づらいです。
最初は商品名と画像以外折りたたんでおいて、タップをしたらその他の情報を表示するようにjQueryで制御します。(最初はBootstrapのcollapse機能を使って折り畳みをしたのですが、PCではできたのになぜかスマホでは動作せず…)
$(function(){
$('h2').click(function(){
$(this).next(".card").slideToggle();
});
});
これでスマホでも一覧して見やすくなりました。
↓タップ後
これから
現状このサイトはここまでです。
「各社商品をまとめて原材料/アレルギー情報を表示する」という目的は達成できていますが、ズラッと商品が並んでいるためすぐに探しづらいという欠点があります。
そのためこれからは商品ブランドごとにもっと細かく分けたり、検索機能などを追加していくつもりです。
更に使いやすくして、自分の友人のような問題を解決するようなサービスにしたいと思います。