2018.2.27
カクテルデータベース制作日誌#14【他サイトからのデータ拝借】
当DBの現状致命的な欠陥として挙げられるのは
単純なデータ不足が最優先解決課題となっています。
管理者画面からぽちぽちとデータを入れているのですが、万を超えるデータを自分ひとりで入力するのって結構キツいです。
そこで、他サイトからデータを拝借しちゃおう、という趣旨のシステムを開発しようかなと思います。
というか、データの拝借は既にやってるんですけど
それを自動化しちゃおうということですね。人力じゃn年スパンで時間かかります。しんどい(笑)
データの抽出というのは倫理観が問われるなぁとも思うんですが、カクテルレシピ自体もはや誰かのレシピのコピーみたいなとこあるし、まぁいいかなって感じです(笑)
まず用意するものですが、
Goutteというライブラリを使います。
何なのかっていうと、その名の通りHTMLをイイ感じに操作してくれるらしいライブラリですね。こいつを使って、ゴニョゴニョやろうかなと思います。
どうせ一回作ったらもう手は加えないだろうということで、スパゲッティーコードで実装してやりました。ちょっと後悔しています(笑)
今回の記事では、このGoutteを使ってる部分だけ、ちょろっとご紹介。
private function get_contents(){
//インスタンス生成
$client = new Client();//←Goutteのインスタンス
while(true){
//取得とDOM構築
$request=$client->request('GET',$this->generate_url());
//もしnot foundであればカウンタを進めてやり直し
if(count($request->filter('ul.study li div.no_link'))){
$bool = $this->next(true);
if($bool===false){
//最後まで来てしまった。終われ。
return false;
}
}else{
break;//見つかったのならそのまま
}
}
return $request;
}
使い方はこんな感じです。Clientクラスを生成し、そこにリクエストのメソッドとURLを投げてくるとデータが手に入ります。
正直false返すより例外を投げた方がいいとは思うんですけど、なんでそうしなかったんでしょうね。()
で、返ってきたデータが格納されているのが
$requestですね。
んじゃ、これをパースしてる部分の処理も載せますね。
private function parse(Crawler $node){
$column=$node->filter("strong")->text();//←strongタグの部分をフィルタリング
try{
$text=explode("\n",$node->text())[1];//ほんとはstrong部分だけ剥がしたかったんだけど無理っぽいので強引に。ダメならstrong部分を空白に置換かな。
} catch (\Exception $e){
return;//何もデータがないカラムが存在している。ただの入力ミスなのでカット。
}
//要素があるときのみ取得処理を行う
try{
if(strpos($column,"Glass")!==FALSE){
//グラスが指定されている
$this->result->glass=$this->get_glass($text);
}elseif(strpos($column,"Method")!==FALSE){
$this->result->method=$this->get_method(explode(" (", $text)[0]);
}elseif(strpos($column,"How to Make")!==FALSE){
$this->result->note.=str_replace([
"全ての材料をシェークしてグラスに注ぐ。",
"全ての材料をシェークして",
"全ての材料をミキシンググラスでステアしてグラスに注ぐ。",
"全ての材料をブレンダ―でミキシングしてグラスに注ぐ。",
"クラッシュアイスを入れたグラスに注ぐ。",
],"",$text);//作り方も内容が予想できないのでコピー。
}elseif(strpos($column,"Meaning")!==FALSE){
$this->result->note.=$text;//意味はそのままコピーしちゃえ!
}elseif(strpos($column,"Material")!==FALSE){
//材料!
$result=$this->get_recipies($text);
$this->result->recipes=$result["result"];
if(!empty($result["error"])){throw new \Exception($result["error"]);}
}
elseif(strpos($column,"Garnish")!==FALSE){
//副材料
$this->result->note.="副材料:".$text.PHP_EOL;//なんかややこしくなりそうだから補足にぶっ込んでおく
$this->result->garnish_txt=$text;//解析用にコピーしておく(最初にやるとレシピの順番が狂う)
}
} catch (\Exception $ex) {
//なんかエラーをキャッチしたらエラーにぶっ込む
$this->errors[]=$ex->getMessage();
}
}
こういうプログラムで動いてます。大事なのは
filter()部分ですね。
勘のいい人はわかるかも知れませんが、CSSセレクタと同じ書き方で使えます。めっちゃ直感的でわかりやすい。jQueryみたいに要素を消し去ったりできるとより良かったんですけどね。remove的なものはあるっぽいんですけど、平文はなんか取れないっぽい・・・・・・?
で、後は自分の使いやすいようにゴニョゴニョしてる部分です。全体的によろしくない書き方をしている自覚はあるんですが、回してみないとどういう操作が必要になるのかよくわかんなかったので、取り敢えずこんな形で書いてるって感じですね。あんまり考えて書いてないです(笑)
こんな感じのプログラムをここ3日ぐらいかけて調整してました。
これが管理画面だったりするんですが、このオートインプットを実行すると、自動解析可能なデータは全部ドラフトにぶっこまれます。
雰囲気はこんな感じです。いきなり正規データにぶっ込まれると困るので、ドラフトに入れているわけですね。
最終チェックは人力でせざるを得ませんが、レシピは基本的に全部自動入力です。できるようにしました。(笑)
で、カクテルが重複してるとか、自動解析不可能なデータが出現したら処理を止めて、人力で判断する・・・・・・といった流れにしています。
問題なければそのままいじって挿入してもいいですし、完全に重複してるデータだと思われたら処理をスキップして次を探索してもいいです。
とりあえずこんな感じのシステムを作りました。でも、思ったより処理が止まることが多くて、なかなかデータの入力が進まないですね・・・・・・。
とりあえず1日100件ぐらいは頑張って入れていきたいと思うところなんですけど、ア始まりだけでカクテルは1000超えてるんですよね・・・・・・()
しばらくはデータ入力に勤しみますが、そろそろ動画も再開したいなぁ・・・・・・。就活が終わってからかなぁ・・・・・・。