LoginSignup
37
37

More than 5 years have passed since last update.

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

Posted at

作ったもの

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

友だち登録はこちらから

①エリアを選択する
IMG_3202.png

②カルーセルでイベント情報が見れる
IMG_3078.png

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

用意するもの

  1. Heroku または PHPが動くサーバー (QiitaかGoogle調べてください)
  2. LINE Message APIアカウント (QiitaかGoogle調べてください)
  3. linebot_libをダウンロード (こちらから) < 重要!

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

こちらはHerokuのアプリケーションフォルダにlinebot_libを入れた状態です。
スクリーンショット 2018-08-16 22.37.39.png

「sample_linebot.php」を動かしてみましょう。
下記記事を参考にdefine.phpにAPIのアクセストークンとシークレットを入れれば動くはずです。
▼▼
LineBot PHPで簡単シンプルに使えるライブラリを使ってLineBotを作る FlexMessaging対応

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行で取得する方法

まず対象のウォーカープラスのイベントページのHTMLを解析。
本日+新宿のページ > こちら

▼こちらはChromeのデベロッパーツールで要素の検証をしているところ
スクリーンショット 2018-08-16 23.25.11.png

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

スクリーンショット 2018-08-09 0.42.20.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ライブラリー使わないと3倍以上のコードかつ工数もかかるので、今回かなり楽でした。(実際3時間半で出来ました)

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

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

今回紹介したLINE Bot「遊び場さがし」の友だち登録はこちら

自作LINE Bot紹介

[LINE Bot] 位置情報から食べログ3.5以上の優良店を検索するbot作った

37
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
37