【LINE Bot】たった100行でWebサイトをスクレイピングしてBot化する実装方法

#作ったもの
エリアを指定したら本日開催しているイベント情報を検索してくれるLINE Botです。
(ウォーカープラスよりデータを取得)

友だち登録は[こちら](https://line.me/R/ti/p/%40ksq0698f)から

①エリアを選択する
<img width="375" alt="IMG_3202.png" src="https://qiita-image-store.s3.amazonaws.com/0/246680/ff9f7f0c-d102-d00c-a8b1-01fc3097342f.png">

②カルーセルでイベント情報が見れる
<img width="375" alt="IMG_3078.png" src="https://qiita-image-store.s3.amazonaws.com/0/246680/d17a29fa-ba26-20e1-5a94-30d1fc2ca0b6.png">

今回このBotを**100行で実装**しました。
ソースを公開しますので、参考していただきオリジナルBotを作ってみましょう。

#用意するもの
1. Heroku または PHPが動くサーバー (QiitaかGoogle調べてください)
2. LINE Message APIアカウント (QiitaかGoogle調べてください)
3. linebot_libをダウンロード （[こちらから](https://github.com/jyukikami/linebot_lib)) **< 重要!**

#まずはサンプルソースを動くようにする

こちらはHerokuのアプリケーションフォルダに**linebot_lib**を入れた状態です。
![スクリーンショット 2018-08-16 22.37.39.png](https://qiita-image-store.s3.amazonaws.com/0/246680/b35dc3e6-1434-67e3-31f7-e78dc20f941d.png)

「sample_linebot.php」を動かしてみましょう。
下記記事を参考にdefine.phpにAPIのアクセストークンとシークレットを入れれば動くはずです。
▼▼
[LineBot PHPで簡単シンプルに使えるライブラリを使ってLineBotを作る FlexMessaging対応](https://qiita.com/jyuki/items/035cfeb72d7d359c738b#%E8%A8%AD%E5%AE%9A)

**Webhook URL**は「https://{アプリケーション名}.herokuapp.com/sample_linebot.php」

動かない場合は`heroku logs`を打ってエラーをみて解消してください。

#本題の実装へ
##1.エリア選択の表示

メイン部分。
`$messeage_type === 'text'`：メッセージだったらエリア選択を返す。

```linebot_app.php
<?php
require_once __DIR__ . '/linebot_lib-master/linebot.php';
require_once 'phpQuery-onefile.php';

$bot = new LineBotClass();
try {
	while ($bot->check_shift_event()) { // メッセージがなくなるまでループ
		$text = $bot->get_text(); // テキストを取得
		$messeage_type = $bot->get_message_type(); // メッセージタイプを取得
		$event_type = $bot->get_event_type(); // イベントタイプを取得

		if ($event_type === "postback") { // ポストバックのイベントなら
			// あとで
		} else if ($messeage_type === 'text') {
			$bot->add_flex_builder("エリアを選択してください", createSelectAreaMessage());
		}
		$bot->reply();
	}

} catch (Exception $e) {
	$error = $e->getMessage();
	$bot->add_text_builder("エラーキャッチ:" . $error);
	$bot->reply();
}
```


**createSelectAreaMessage**でFlexMessageのコンテナオブジェクトを生成します。
サンプルのflex4を拡張したものです。
post Actionのオブジェクト作るのも繰り返しが面倒だったのでメソッド化してます > **createPostActionText**

```linebot_app.php
function createSelectAreaMessage() {
	global $bot;
	$flex_box_mein = array();
	$flex_components = array();

	$flex_components['body'][] = $bot->create_text_component("検索エリアを選択してください",array("size"=>5,"weight"=>"bold"));
    $area = ['新宿'=>'t_shinjuku', '渋谷'=>'t_shibuya', '池袋'=>'t_ikebukuro', '銀座'=>'t_ginza', '六本木'=>'t_roppongi',
          'お台場'=>'t_odaiba', '吉祥寺'=>'t_kichijoji', '上野'=>'t_ueno', '品川'=>'t_shinagawa', '浅草'=>'t_asakusa', '東京駅周辺'=>'t_tokyoeki'];
    foreach ($area as $key => $value) {
    	$flex_components['body'][] = createPostActionText($key,$value);
    }

	$flex_box_mein['body'] = $bot->create_box_component("vertical",$flex_components['body'],array("spacing"=>4));

	$bubble_blocks = array(
		 "body" => $flex_box_mein['body']
	);
	return [$bot->create_bubble_container($bubble_blocks)];
}

function createPostActionText($label, $data) {
     global $bot;
	 $action = $bot->create_post_action_builder($label,$data,$label);
	 return $bot->create_text_component($label, array("size"=>6,"wrap"=>true,"action"=>$action,"align"=>"center","color"=>"#0000ff"));
}
```

参考：LINEBotクラス($bot)の各関数は**linebot.php**に書かれているので引数が分からない場合は読みましょう。

```linebot.php
	/**
	 * Flex Messageは、複数の要素を組み合わせてレイアウトを自由にカスタマイズできるメッセージです
	 * @param [type] $altText 代替テキスト 最大文字数：400
	 * @param [type] $bubbles Flex Messageのコンテナオブジェクト
	 */
	public function add_flex_builder($altText,$bubbles)
	{
		$bubbles_array = array();
		foreach ((array)$bubbles as $key => $value) {
			$bubbles_array[] = $value;
		}

		// ビルダーを追加
		$this->builder_stok[] = new FlexMessageBuilder($altText,new ContentsBuilder($bubbles_array));
	}
```

おまけ
```$area = ['新宿'=>'t_shinjuku', foreach〜```の部分は下記ベタで書かける所をちょっとスマートにしたカタチです。

```hoge.php
  $flex_components['body'][] = createPostActionText('新宿','t_shinjuku');
  $flex_components['body'][] = createPostActionText('渋谷','t_shibuya');
  $flex_components['body'][] = createPostActionText('池袋','t_ikebukuro');
  $flex_components['body'][] = createPostActionText('銀座','t_ginza');
  $flex_components['body'][] = createPostActionText('六本木','t_roppongi');
  $flex_components['body'][] = createPostActionText('お台場','t_odaiba');
  $flex_components['body'][] = createPostActionText('吉祥寺','t_kichijoji');
  $flex_components['body'][] = createPostActionText('上野','t_ueno');
  $flex_components['body'][] = createPostActionText('品川','t_shinagawa');
```

▼こっちの方がコードを短縮できる

```linebot_app.php
    $area = ['新宿'=>'t_shinjuku', '渋谷'=>'t_shibuya', '池袋'=>'t_ikebukuro', '銀座'=>'t_ginza', '六本木'=>'t_roppongi',
          'お台場'=>'t_odaiba', '吉祥寺'=>'t_kichijoji', '上野'=>'t_ueno', '品川'=>'t_shinagawa', '浅草'=>'t_asakusa', '東京駅周辺'=>'t_tokyoeki'];
    foreach ($area as $key => $value) {
    	$flex_components['body'][] = createPostActionText($key,$value);
    }
```

##2.エリア選択を受け取る〜スクレイピング〜メッセージ作成

post actionを利用したので、```$event_type === "postback"```の時に処理します。

```linebot_app.php
<?php
require_once __DIR__ . '/linebot_lib-master/linebot.php';
require_once 'phpQuery-onefile.php';

$bot = new LineBotClass();
try {
	while ($bot->check_shift_event()) { // メッセージがなくなるまでループ
		$text = $bot->get_text(); // テキストを取得
		$messeage_type = $bot->get_message_type(); // メッセージタイプを取得
		$event_type = $bot->get_event_type(); // イベントタイプを取得

		if ($event_type === "postback") { // ポストバックのイベントなら
			$bot->add_flex_builder("検索しました", createEventSearchMessage($bot->get_post_data()));
		} else if ($messeage_type === 'text') {
			$bot->add_flex_builder("エリアを選択してください", createSelectAreaMessage());
		}
		$bot->reply();
	}

} catch (Exception $e) {
	$error = $e->getMessage();
	$bot->add_text_builder("エラーキャッチ:" . $error);
	$bot->reply();
}
```

###スクレイピング
Web上のHTMLを抜き出します。
phpQueryというライブラリーを使います。
以下参考にしてください。

[【php】webサイトから、欲しい情報を3行で取得する方法](https://qiita.com/dia/items/3cf963fa89b08b87e8ef)

まず対象のウォーカープラスのイベントページのHTMLを解析。
本日＋新宿のページ　> [こちら](https://www.walkerplus.com/event_list/ar0313TER/t_shinjuku/)

▼こちらはChromeのデベロッパーツールで要素の検証をしているところ
![スクリーンショット 2018-08-16 23.25.11.png](https://qiita-image-store.s3.amazonaws.com/0/246680/debdfdb7-6f32-39fc-5d1e-0de8bfd813df.png)

このクラスを取ればいいかなと思ったら、[paiza](https://paiza.io/ja/projects/new)で試してみる。
（phpQuery-onefile.phpを別タブにコピペして、requireしたら使えます）

![スクリーンショット 2018-08-09 0.42.20.png](https://qiita-image-store.s3.amazonaws.com/0/246680/f7ff07e6-4665-8ef0-b859-b1c15744ae3c.png)

aタグの1個目が「タイトル」
aタグの2個目が「イベント内容」
aタグの5個目が「場所」
imgタグが「写真」
aタグの1個目の`href`アトリビュートに「詳細ページのリンク」

上記が判明しました。
あとはlinebot_app.phpにロジックを書いていくだけ

① スクレイピングして、イベントのまとまり```.m-mainlist-item```のListを返す

```linebot_app.php
function getEventList($areaName) {
	$html = file_get_contents('https://www.walkerplus.com/event_list/today/ar0313TER/'.$areaName.'/');
	$doc = phpQuery::newDocument($html);
	$list = $doc[".m-mainlist-item"];
  return $list;
}
```

② 上記リスト(イベントデータ)からFlexMessageを作ります > **createFlexMessage**

Flex Messageはサンプルのflex3のモデルをほぼそのまま使ってます。

```linebot_app.php
function createEventSearchMessage($postData) {
	$flex_bubble = array();
	$searchResult = getEventList($postData);
	foreach ($searchResult as $item) {
		$flex_bubble[] = createFlexMessage($item);
	}
	return $flex_bubble;
}

function createFlexMessage($item) {
	$title = pq($item)->find('a:eq(0)')->text(); //タイトル
	$decs = pq($item)->find('a:eq(1)')->text(); //説明
	$place = pq($item)->find('a:eq(4)')->text(); //場所
	$img = pq($item)->find('img')->attr('src'); //写真(url)
	$link = pq($item)->find('a:eq(0)')->attr('href'); //イベントページ

	global $bot;
	$flex_box_mein = array();
	$flex_components = array();

	$flex_components['header'][] = $bot->create_text_component($place,array("size"=>5,"weight"=>"bold","color"=>"#e60033"));
	$flex_box_mein['header'] = $bot->create_box_component("vertical",$flex_components['header'],array("spacing"=>4));

	$flex_components['body'][] = $bot->create_text_component(trim($title),array("size"=>5,"weight"=>"bold","wrap"=>true));
	$flex_components['body'][] = $bot->create_text_component($decs,array("size"=>4,"wrap"=>true));
	$flex_box_mein['body'] = $bot->create_box_component("vertical",$flex_components['body'],array("spacing"=>3));

	$action = $bot->create_url_action_builder("イベント詳細をみる",'https://www.walkerplus.com'.$link);
	$flex_components['footer'][] = $bot->create_button_component($action,array("style"=>"secondary"));
	$flex_box_mein['footer'] = $bot->create_box_component("vertical",$flex_components['footer'],array("spacing"=>3));

  $image_url = 'https:'.$img;
	$bubble_blocks = array(
		 "header" => $flex_box_mein['header']
		,"hero" => $bot->create_image_component($image_url, array("size"=>11,"aspectRatio"=>"4:3","aspectMode"=>"cover"))
		,"body" => $flex_box_mein['body']
		,"footer" => $flex_box_mein['footer']
	);
	return $bot->create_bubble_container($bubble_blocks);
}
```

はい、これにて完成です！

完成ソースはこちら▼

```linebot_app.php
<?php
require_once __DIR__ . '/linebot_lib-master/linebot.php';
require_once 'phpQuery-onefile.php';

$bot = new LineBotClass();
try {
	while ($bot->check_shift_event()) { // メッセージがなくなるまでループ
		$text = $bot->get_text(); // テキストを取得
		$messeage_type = $bot->get_message_type(); // メッセージタイプを取得
		$event_type = $bot->get_event_type(); // イベントタイプを取得

		if ($event_type === "postback") { // ポストバックのイベントなら
			$bot->add_flex_builder("検索しました", createEventSearchMessage($bot->get_post_data()));
		} else if ($messeage_type === 'text') {
			$bot->add_flex_builder("エリアを選択してください", createSelectAreaMessage());
		}
		$bot->reply();
	}

} catch (Exception $e) {
	$error = $e->getMessage();
	$bot->add_text_builder("エラーキャッチ:" . $error);
	$bot->reply();
}

function createSelectAreaMessage() {
	global $bot;
	$flex_box_mein = array();
	$flex_components = array();

	$flex_components['body'][] = $bot->create_text_component("検索エリアを選択してください",array("size"=>5,"weight"=>"bold"));
  $area = ['新宿'=>'t_shinjuku', '渋谷'=>'t_shibuya', '池袋'=>'t_ikebukuro', '銀座'=>'t_ginza', '六本木'=>'t_roppongi',
          'お台場'=>'t_odaiba', '吉祥寺'=>'t_kichijoji', '上野'=>'t_ueno', '品川'=>'t_shinagawa', '浅草'=>'t_asakusa', '東京駅周辺'=>'t_tokyoeki'];
  foreach ($area as $key => $value) {
  	$flex_components['body'][] = createPostActionText($key,$value);
  }

	$flex_box_mein['body'] = $bot->create_box_component("vertical",$flex_components['body'],array("spacing"=>4));

	$bubble_blocks = array(
		 "body" => $flex_box_mein['body']
	);
	return [$bot->create_bubble_container($bubble_blocks)];
}

function createPostActionText($label, $data) {
   global $bot;
	 $action = $bot->create_post_action_builder($label,$data,$label);
	 return $bot->create_text_component($label, array("size"=>6,"wrap"=>true,"action"=>$action,"align"=>"center","color"=>"#0000ff"));
}

function createEventSearchMessage($postData) {
	$flex_bubble = array();
	$searchResult = getEventList($postData);
	foreach ($searchResult as $item) {
		$flex_bubble[] = createFlexMessage($item);
	}
	return $flex_bubble;
}

function getEventList($areaName) {
	$html = file_get_contents('https://www.walkerplus.com/event_list/today/ar0313TER/'.$areaName.'/');
	$doc = phpQuery::newDocument($html);
	$list = $doc[".m-mainlist-item"];
  return $list;
}

function createFlexMessage($item) {
	$title = pq($item)->find('a:eq(0)')->text();
	$decs = pq($item)->find('a:eq(1)')->text();
	$place = pq($item)->find('a:eq(4)')->text();
	$img = pq($item)->find('img')->attr('src');
	$link = pq($item)->find('a:eq(0)')->attr('href');

	global $bot;
	$flex_box_mein = array();
	$flex_components = array();

	$flex_components['header'][] = $bot->create_text_component($place,array("size"=>5,"weight"=>"bold","color"=>"#e60033"));
	$flex_box_mein['header'] = $bot->create_box_component("vertical",$flex_components['header'],array("spacing"=>4));

	$flex_components['body'][] = $bot->create_text_component(trim($title),array("size"=>5,"weight"=>"bold","wrap"=>true));
	$flex_components['body'][] = $bot->create_text_component($decs,array("size"=>4,"wrap"=>true));
	$flex_box_mein['body'] = $bot->create_box_component("vertical",$flex_components['body'],array("spacing"=>3));

	$action = $bot->create_url_action_builder("イベント詳細をみる",'https://www.walkerplus.com'.$link);
	$flex_components['footer'][] = $bot->create_button_component($action,array("style"=>"secondary"));
	$flex_box_mein['footer'] = $bot->create_box_component("vertical",$flex_components['footer'],array("spacing"=>3));

  $image_url = 'https:'.$img;
	$bubble_blocks = array(
		 "header" => $flex_box_mein['header']
		,"hero" => $bot->create_image_component($image_url, array("size"=>11,"aspectRatio"=>"4:3","aspectMode"=>"cover"))
		,"body" => $flex_box_mein['body']
		,"footer" => $flex_box_mein['footer']
	);
	return $bot->create_bubble_container($bubble_blocks);
}

?>

```

linebotライブラリー使わないと３倍以上のコードかつ工数もかかるので、今回かなり楽でした。（実際３時間半で出来ました）

ぜひ皆さんも使ってみてください。

【参考】LineBot PHPで簡単シンプルに使えるライブラリを使ってLineBotを作る FlexMessaging対応
https://qiita.com/jyuki/items/035cfeb72d7d359c738b

今回紹介したLINE Bot「遊び場さがし」の友だち登録は[こちら](https://line.me/R/ti/p/%40ksq0698f)

#自作LINE Bot紹介
[[LINE Bot] 位置情報から食べログ3.5以上の優良店を検索するbot作った](https://qiita.com/NARI_Creator/items/f29112e6f604c86b3c0d)