自動要約&ニューラルネット翻訳で海外の記事を読みやすくするSlackBot

  • 14
    いいね
  • 0
    コメント

NextremerAdventcalender2016の16日目の記事です。

昨日に引き続き自動要約ネタです。

技術は日進月歩であり、エンジニアにとって、日々最新の情報を収集することは重要です。
自分も個人Slackに記事を収集するものの、海外の記事については積読になりがちです。
そこで、今回はSlack上の海外のニュースを自動要約&翻訳し、概要だけでも把握して、2016年を終えたいと思います。
せっかくなので、翻訳には今流行りのニューラルネットを使います。

翻訳API

Google翻訳がニューラルネット対応し、翻訳精度が大幅に向上したことが少し前に話題になりました。
その数日後、Microsoftもニューラルネットに対応しました。
以前からMicrosoftTranslaterは200万文字までなら無料で使用できましたが、もちろんニューラルネットにも対応しています。

この記事ではMicrosoftTranslatorAPIを使用して翻訳を行います。

SlackBot作成

仕様としては

transum <URL>

を入力することで、実行結果が帰ってくるようにします。
こんな感じですね。

161217-0018.png

では、実際に作成していきます。

事前準備

コードを書く前に

  1. SlackBotのアカウント作成 & API Tokenの取得
  2. MicrosoftTranslateAPI のsubscription keyの取得

を済ましておきます。
1.はPythonでSlackbotを作る(1)
2.はMicrosoft Translate API を用いた Slack 翻訳コマンドの実装
が参考になります。

そして、適当なチャンネルを作成し、BotをInviteすれば準備完了です。

依存パッケージ

今回作るプログラムは

  • numpy
  • slackbot
  • sumy
  • newspaper

に依存します。

まずは下記コマンドで前提パッケージをインストールします。
また、Python3でコードを書きます。

$ pip3 install numpy sumy newspaper3k slackbot

フォルダ構成

slackbotを使う都合上、ディレクトリ構成は以下のようになります。

.
├── plugins
│   ├── __init__.py
│   └── transum.py
├── run.py
└── slackbot_settings.py

run.py でBotを起動し、pluginsのコマンドを実行する構成です。
それでは各プログラムを見ていきます。

slackbot_setting.py

slackbot_setting.py
#!/usr/bin/env python
# coding: utf-8

API_TOKEN = "XXXXX"

default_reply = "Sorry...I didn't understand you"

PLUGINS = [
    'plugins',
]

Botのセッティングを行なう部分です。
XXXXX にはBot API Tokenを入力します。
また、pluginsを読み込めるように設定しておきます。

run.py

run.py
#!/usr/bin/env python
# coding: utf-8

from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    main()

run.pyはBotの起動を管理します。
このスクリプトを実行すると、Slack上でBotが反応し始めます。

transum.py

ここが今回のメインで、翻訳&要約を行うスクリプトです。

transum.py
#!/usr/bin/env python
# coding: utf-8

import re
import subprocess
import json

from slackbot.bot import listen_to
from newspaper import Article
from sumy.nlp.stemmers import Stemmer
from sumy.nlp.tokenizers import Tokenizer
from sumy.parsers.plaintext import PlaintextParser
from sumy.summarizers.lex_rank import LexRankSummarizer as Summarizer
from sumy.utils import get_stop_words


SUBSCRIPTION_KEY = "YYYYY"
CATEGORY = "generalnn" # generalnn:ニューラルネット翻訳 general:旧機械翻訳

LANGUAGE = "english"
SENTENCES_COUNT = 3


transum_str = re.compile("^transum \<(.*)\>$")
tag_pattern = re.compile(r"<[^>]*?>")


@listen_to(transum_str)
def transum(message, raw_url):
    """要約と翻訳を行なう"""
    url = raw_url.lstrip('<').rstrip('>')

    en_title, en_text = scrape(url)

    en_summaries = summarize(en_text)
    en_summary = " ".join(en_summaries)

    ja_title_tag = translate(en_title)
    ja_title = tag_pattern.sub("", ja_title_tag[0])

    ja_summary_tag = translate(en_summary)
    ja_summary = tag_pattern.sub("", ja_summary_tag[0])

    attachments = [
        {
            "title": ja_title,
            "title_link": url,
            "text": ja_summary.replace("。", "。\n")

        }
    ]

    message.send_webapi('', json.dumps(attachments))


def scrape(url):
    """記事のタイトルと本文を抽出する"""
    article = Article(url)
    article.download()
    article.parse()

    return article.title, article.text


def summarize(text):
    """テキストを要約する"""
    parser = PlaintextParser.from_string(text, Tokenizer(LANGUAGE))

    stemmer = Stemmer(LANGUAGE)

    summarizer = Summarizer(stemmer)
    summarizer.stop_words = get_stop_words(LANGUAGE)

    return [str(sentence) for sentence in summarizer(parser.document, SENTENCES_COUNT)]


def translate(text):
    """テキストを翻訳する"""
    token_url = "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"
    translate_url = "https://api.microsofttranslator.com/v2/http.svc/Translate"

    text = text.replace(' ', "%20")

    request_token = popen(
        'curl -X POST \"{url}\" -H \"Content-type: application/json\" -H \"Content-length: 0\" -H \"Accept: application/jwt\" -H \"Ocp-Apim-Subscription-Key:{key}\"'.format(
            url=token_url, key=SUBSCRIPTION_KEY))

    result = popen(
        "curl -X GET --header \'Accept: application/xml\' \'{url}?appid=Bearer%20{token}&text={text}&from=en&to=ja&category={category}\'".format(
            url=translate_url, token=request_token[0], text=text, category=CATEGORY))

    return result


def popen(cmd):
    """シェルの実行結果を取得する"""
    outputs = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = outputs.communicate()
    # bytesで受け取った結果をstrに変換する
    return [s for s in stdout.decode('utf-8').split('\n') if s]

SUBSCRIPTION_KEY には取得したsubscription keyを設定してください。
CATEGORYgeneralnnを設定するとニューラルネット翻訳。
generalを設定すると以前までの統計的機械翻訳が行われます。

おわり

今回は自動翻訳&要約を行なうSlackBotで海外のブログ記事を読んでみました。
なんとなく記事の雰囲気くらいは掴めているのではないでしょうか?

この他にPDFからテキスト抽出し、論文を要約して読めないかも試したのですが、そちらは全然でした。
用途としては海外ニュース記事を読みやすくする、くらいが向いているのかなと思います。

参考

PythonでSlackbotを作る(1)
Microsoft Translate API を用いた Slack 翻訳コマンドの実装