LINE Engineering
Blog

あらゆるWebを問答無用でChatbot化する手法

Kazuki Nakajima 2018.08.01

こんにちは、LINEでDeveloper Advocateをしている中嶋です。この記事はLINE Engineering Blog 「夏休みの自由研究 -Summer Homework-」の1日目の記事です。

Webを問答無用でChatbot化する

  • スマホに対応していないWebサイトでイライラしながら入力する。
  • 毎回ログインが必要でやる気をそがれる。
  • そもそもスマホでのフォーム入力画面はつらい。

そんな経験ありませんか?

LINEは当然ながらスマートフォンで最も利用されており、必然的にLINEにとってそのスマートフォンでのUXはとても重要で、多くの工夫が凝らされています。

そこで、前述のようなスマホでは使いにくいサイトをLINEから操作できるようにしたら便利になるかも?という実験をこの記事では紹介したいと思います。

WebをChatbotから操作できるHeadless Chrome

Headless Chromeというツールをご存知でしょうか?

Chromeは言わずと知れたWebブラウザーですが、Headless Chromeはそれをコマンドラインで操作するための仕組みです。テスト自動化などで重宝される機能ですが、ChatbotにWebを操作させるのにもうってつけです。

例えば下図のChatbotが図書館のWebサイトを通じて本の貸し出し予約をおこなうデモをご覧ください。

Chatbotとの会話にしたがってWebサイトが操作されているのがわかります。任意の本を予約する、というオーダーだけChatbotに伝えると、あとはChatbotがWebサイト上で必要な操作をおこなってくれています。このような形で複数の手順が必要になるWeb操作、特に繰り返しおこなうような作業をChatbotにお任せすることで、手順を簡略化し、UXに良い変化をもたらすことができる可能性があります。

こちらのデモでは裏側の動きがわかりやすいようにHeadless ChromeのUIを通常のブラウザのように表示させていますが、実際に稼働させる際にはheadlessモードにすることでUIを起動せずに動作させます。

実装方法

前掲のデモの中で基本の実装パートをご紹介していきます。

今回Chatbot本体はNode.jsベースで作っていきます。Node.jsからHeadless Chromeを利用する手段として puppeteer というNPMパッケージがありますのでこちらをインストールして利用します。

インストール

$ npm install --save puppeteer

スクリプト

まず、ブラウザのインスタンスを起動します。インスタンス起動にはある程度時間がかかる上、かなりメモリを消費するので、サーバー起動時に一つ起動しておくのが多くのユースケースで望ましいと思われます。

const pptr = require("puppeteer");

(async () => {
    // ブラウザのインスタンスを起動
    let browser = await pptr.launch({
        headless: true
    });
})();

次に、ユーザーからメッセージを受信しWebの操作が必要になったタイミングでブラウザー・コンテキストを作成します。これはCookieなどユーザーごとの情報を独立させるための実行空間になります。

// ブラウザコンテキストを作成(ユーザー毎の独立した空間)
let bc = await browser.createIncognitoBrowserContext();

続けてページを作成します。これはタブに該当するものです。

// ページを作成(タブに該当)
let page = await bc.newPage();

これでHeadless Chromeのインスタンスは任意のURLにアクセスし、レスポンスを返したり、フォームを操作したりできる状態にあります。

// 任意のURLにアクセス
await page.goto(任意のURL, {
    waitUntil: "networkidle0"
});

例えばインプット項目に値を入力するにはtype()メソッドを利用します。

await page.type(CSSセレクター, `hogehoge`);

ボタンのクリック。

await page.click(CSSセレクター);

そして例えば下記のようなテーブルがあったとします。

<table>
    <tr>
        <td>番号</td>
        <td>タイトル</td>
        <td>作者</td>
    </tr>
    <tr>
        <td>1</td>
        <td>火花</td>
        <td>又吉</td>
    </tr>
        <tr>
        <td>2</td>
        <td>日本再興戦略</td>
        <td>落合陽一</td>
    </tr>
</table>

このテーブルにあるデータを取得してタイトルのリストを作成するには下記のようにします。

// 任意のテーブルの行をすべて取得。
let rows = await page.$$(`table > tr`);
let title_list = [];
for (let row of rows){
    let cols = await row.$$(`td`);
    title_list.push(await (await cols[1].getProperty("textContent")).jsonValue());
}

// title_listにタイトル一覧がstringの配列で入っている
console.log(title_list);

これで title_list にタイトルの一覧が格納されます。あとはFlex Messageを使っていい感じにユーザーに表示してあげればOKです。

つまり、ユーザーの発話から必要なWebでの処理を選択して実行、その結果に応じてユーザーに返信をおこなうことでLINE上でのWebとのインタラクションを実装していくわけです。

留意点

  • HTMLの構造に依存します。したがってWebサイトのHTMLが変更された場合、特にボタンやインプットフィールドを捕捉するためのCSS Selectorについて改修する必要がでてきます。
  • メモリを消費します。1ユーザーにつき、20MByteくらいは最低でも見ておく必要があるため、大量アクセスのあるサービスには向きません。
  • Chatbotが保持する文脈情報(これまでの会話の内容、これからユーザーに聞かなければいけない内容など)とブラウザの状態を合わせる必要があるためコーディングの難易度が高いです。会話が予定通りのシナリオで進んでいれば問題ないのですが、Web上の数ページ前の入力項目を修正するケースなどは取り扱いが難しくなります。

ということで万人にお勧めできる手法ではありませんが、いままでつながらないと思っていたものがつなげられる可能性があるので技としては知っておいても良いのでは、と思います。

例えばSmart Speakerも

今回はChatbotとWebサイトを連携させましたが、例えばSmart SpeakerとWebサイトを連携させる、といったことも十分可能なわけです。これまではWebは閲覧するものでしたが、「音声で対話する」というあらたなUXを実現できるかもしれません。

ぜひ試してみてください。


明日は吉田啓二さんによるLINEの全社員が必要に応じて担当サービスのデータを分析できる環境の構築についての記事です。お楽しみに!

chatbot summer homework headless chrome

Kazuki Nakajima 2018.08.01

Add this entry to Hatena bookmark

リストへ戻る