Wikipediaから検索するLINE botを作った

Type: Tech BlogDate: 2019 - 5 - 4Tags:
#Python#チャットボット

flask, line-bot-sdkwikipediaで作って,herokuにデプロイします。

ぜひ使って下さい。

screenshot

レポジトリはこちら


pip install -r requirements.txtさせたいので,上記3つを requirements.txt へ記述し,heroku の設定と LINE@の設定を終わらせておく (アクセストークンとチャンネルシークレットあたりを入手しておく)。runtime.txtprocfileを置く。基本Heroku で LINE BOT(python)を動かしてみたを真似た。


みんなだいすき Wikipedia。

1pip instal wikipedia

で入るライブラリであるWikipediaは非常に便利で,Python 用のMedia Wiki APIのラッパーだと解釈している。requestsで API 叩いて,BeautifulSoup4か何かでマークアップをバラして,返してくれるものだったはず。

1import wikipedia
2
3wikipedia.set_lang("ja")

で日本語 Wikipedia に設定した後,

1wikipedia.search("文字列")

をすることで各ページ名のリストが返り,書く Wikipedia のページはその名前(タイトル)が ID となっており,

1wikipedia.page("ページ名")

wikipedia.WikipediaPageオブジェクトを取得できる。この page オブジェクトがcategories, links, content, summaryなどの attributes をもっており,これらは基本的に URL か文字列かのリストである。

1>>> help(wikipedia.WikipediaPage)
2>>>
3"""
4categories
5    List of categories of a page.
6content
7    Plain text content of the page, excluding images, tables, and other data.
8coordinates
9    Tuple of Decimals in the form of (lat, lon) or None
10images
11    List of URLs of images on the page.
12links
13    List of titles of Wikipedia page links on a page.
14    Only includes articles from namespace 0, meaning no Category, User talk, or other meta-Wikipedia pages.
15parent_id
16    Revision ID of the parent version of the current revision of this
17    page. See ``revision_id`` for more information.
18references
19    List of URLs of external links on a page.
20    May include external links within page that aren't technically cited anywhere.
21revision_id
22    Revision ID of the page.
23    The revision ID is a number that uniquely identifies the current
24"""

今回つくる LINE Bot では,検索単語に対して取得した候補の中から 1 ページ選び,そのページの summary (タイトル直後の概要・OGP とかに表示される?)とページへのリンクを LINE トークルームにかえしてあげよう,というものを作ることにした。


1.
2├── Procfile
3├── README.md
4├── __pycache__
5│   ├── app.cpython-36.pyc
6│   └── parser.cpython-36.pyc
7├── app.py
8├── assets
9│   └── img
10│       └── linebot-icon.webp
11├── messenger.py
12├── parser.py
13├── requirements.txt
14├── runtime.txt
15└── test.py

次に,flaskベースでアプリケーション部分をササっと書きますが,これもほぼコピペ。面倒な部分をlinebotが隠してくれていて,非常に便利。

1from flask import Flask, request, abort
2
3from linebot import (
4    LineBotApi, WebhookHandler
5)
6from linebot.exceptions import (
7    InvalidSignatureError
8)
9from linebot.models import (
10    MessageEvent, TextMessage, TextSendMessage,
11)
12
13import parser
14import os
15
16app = Flask(__name__)
17
18YOUR_CHANNEL_ACCESS_TOKEN = os.environ.get("YOUR_CHANNEL_ACCESS_TOKEN")
19YOUR_CHANNEL_SECRET = os.environ.get("YOUR_CHANNEL_SECRET")
20
21line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN)
22handler = WebhookHandler(YOUR_CHANNEL_SECRET)
23
24
25@app.route("/callback", methods=['POST'])
26def callback():
27    # get X-Line-Signature header value
28    signature = request.headers['X-Line-Signature']
29
30    # get request body as text
31    body = request.get_data(as_text=True)
32    app.logger.info("Request body: " + body)
33
34    # handle webhook body
35    try:
36        handler.handle(body, signature)
37    except InvalidSignatureError:
38        print("Invalid signature. Please check your channel access token/channel secret.")
39        abort(400)
40
41    return 'OK'
42
43
44@handler.add(MessageEvent, message=TextMessage)
45def handle_message(event):
46    line_bot_api.reply_message(
47        event.reply_token,
48        TextSendMessage(text=parser.answer(event.message.text)))
49
50
51if __name__ == "__main__":
52    port = int(os.getenv("PORT", 5000))
53    app.run(host="0.0.0.0", port=port)

line-bot-sdk-python の公式リポジトリに,app.pyとして flask でのサンプルが公開されている。 [sample] app.py

また,今回はそもそも README にガッツリ載っていたものを使った。sample code on GitHub


parser.py

parser はモジュールの変数として言語設定を持っている。設計として微妙ですかね?モジュール(グローバル)変数は。
なんか使い方間違えたりとか,ヘルプ出したい時のためのusage()は未実装。
意外とWikipediaPage.summaryの文字数が長く,Messaging API の上限を叩いてしまったときのために,1500 文字以上は切っている。

1import wikipedia
2
3
4# init language setting
5lang = "ja"
6wikipedia.set_lang(lang)
7
8def init() -> None:
9    global lang
10    wikipedia.set_lang(lang)
11
12
13def tokenize(text: str) -> list:
14    """Tokenize input Sentence to list of word"""
15    splited = text.split()
16    if len(splited) == 1:
17        return splited
18    elif len(splited) == 2:
19        if splited[0] in wikipedia.languages.fn().keys():
20            change_lang(splited[0])
21        return splited[1]
22    else:
23        usage()
24
25def search(text: str, rank=0) -> "wikipedia.wikipedia.WikipediaPage":
26    """Search Wikipedia page by Word
27    arg
28    ---
29    rank : int : Return the contents of the search result of the set rank.
30    """
31    try:
32        page = wikipedia.page(wikipedia.search(text)[rank])
33    except wikipedia.exceptions.DisambiguationError:
34        page = wikipedia.page(wikipedia.search(text)[rank+1])
35    return page
36
37
38def encode(page: "wikipedia.wikipedia.WikipediaPage", threshold=1500) -> str:
39    """Transform data into the text for LINE message
40    """
41    summary = page.summary
42    if len(summary) > threshold:
43        summary = summary[:threshold] + "..."
44
45    return f"Result: {page.title}\n\n{summary}\n\n{page.url}"
46
47
48def answer(text: str) -> str:
49    init()
50    word = tokenize(text)
51    page = search(word)
52    return encode(page)
53
54
55def change_lang(language: str) -> None:
56    wikipedia.set_lang(language)
57    return
58
59def usage():
60    pass
61
62if __name__ == "__main__":
63    import argparse
64
65    parser = argparse.ArgumentParser()
66    parser.parse_args()

一応ちゃんとPEP8スタイルだし,type hints も docstring 書いている。クセにしとおきたい。


デプロイ等

1$ heroku login
2$ heroku create heroku-line-bot
3$ heroku config:set LINE_CHANNEL_SECRET="<Channel Secret>"
4$ heroku config:set LINE_CHANNEL_ACCESS_TOKEN="<アクセストークン>"
5$ git push heroku master

references