ギークなエンジニアを目指す男

機械学習系の知識を蓄えようとするブログ

AWS Lambdaを利用してarXivの論文を(日本語に翻訳して)slack通知する

f:id:taxa_program:20190714223621p:plain

こんにちは。takapy(@takapy0210)です。

最近下記のような勉強会が賑わっており、論文を読む機運が高まってきているのでは?と思い始めています。

lpixel.connpass.com

しかし、そもそも自分の読みたい内容の論文を探すのが難しかったり、時間がかかったりして、どうしてもはじめの一歩の障壁が高いよな〜などと感じていました。

そこで、無料かつタイムリーに、しかもなるべく労力をかけずにいい感じの論文が知りたいぞ〜というお気持ちになり、AWS Lambdaを使用してarXivから論文を取得して(必要であれば日本語翻訳して)slackに通知するようなコトをやってみました。

arXivとは

以下、wiki引用

arXivは、物理学、数学、計算機科学、量的生物学、計量ファイナンス、統計学の、プレプリントを含む様々な論文が保存・公開されているウェブサイトである。論文のアップロード、ダウンロードともに無料で、論文はPDF形式である。1991年にスタートして、プレプリント・サーバーの先駆けとなったウェブサイトである。

世界中の強い人たちが書いた論文が載っているサイトです。

結果

こんな感じで、毎朝10時に前日更新された論文が少しリッチに(日本語翻訳され)slack通知されます。

f:id:taxa_program:20190715110719p:plain

slackの設定

slackにIncoming Webhooksアプリケーションを追加し、そこからWEBHOOK_URLを取得します。
(このURLは後半で使用します)

詳しくは下記を参照してみてください。 qiita.com

AWSの設定

AWSのアカウントを所持している前提で進めます。
アカウント登録や初期設定については下記を参照してみてください。

aws.amazon.com

AWS Lambda

AWS Lambdaのコンソールにアクセスし、関数の作成をクリックします。

f:id:taxa_program:20190715095253p:plain
AWS Lambdaのコンソール

基本的な情報を入力し、関数を作成します。
関数名には任意の値を設定していただいて構いませんが、ランタイムはPython 3.7を選択してください。

f:id:taxa_program:20190715095440p:plain
Lambda関数の作成

すると、下記のように初期関数が生成されます。
この関数は後述の作業で上書きしますので、ここでコンソールからは一旦離れます。

f:id:taxa_program:20190715095853p:plain
Lambda初期関数

googletrans(google翻訳)パッケージの追加

今回のLambda関数では、googletransを利用します。しかしデフォルトではLambdaにgoogletransがインストールされていないので、No module named 'googletrans'というエラーが発生してしまいます。

このエラーを解決するために、今回はpipを使用してgoogletransをローカルディレクトリにインストール→デプロイパッケージの作成、そしてそれをLambdaへ追加していきます。

このときにLambda関数の実行スクリプトであるlambda_function.pyも一緒に作成して追加していきます。

1. 任意の場所にディレクトリを生成

$ mkdir package
$ ls
package

2. -tオプションでライブラリをパッケージディレクトリにインストール

$ cd package
$ pip install googletrans -t .

3. lambda_function.pyの作成

package配下に、下記のようなpythonスクリプトファイルを作成します。
ファイル名は「lambda_function.py」としてください。

import os
import re
import json
import urllib.request
import datetime
from googletrans import Translator

def parse(data, tag):
    pattern = "<" + tag + ">([\s\S]*?)<\/" + tag + ">"
    if all:
        obj = re.findall(pattern, data)
    return obj

def get_thesis(query, basedate, previousdate, translate=True):
    translator = Translator()
    url = 'http://export.arxiv.org/api/query?search_query=submittedDate:[' + \
            previousdate.strftime('%Y%m%d') + '0000+TO+' + \
            basedate.strftime('%Y%m%d')+'0000]+AND+' + query
    data = urllib.request.urlopen(url).read().decode('utf-8')
    entries = parse(data, "entry")

    if len(entries) == 0:
        print('直近の論文はありません')
        return
    else:
        print('{}件の論文があります'.format(len(entries)))

    for entry in entries:
        # 各値の取得
        url = parse(entry, "id")[0]
        title = parse(entry, "title")[0]
        title = title.replace('\n', '')
        title_jp = translator.translate(str(title), dest='ja').text
        date = parse(entry, "published")[0]
        date = date[:10]
        author = ', '.join(parse(entry, "name") )
        summary = parse(entry, "summary")[0]
        summary = summary.replace('\n', '')

        # 日本語化
        if translate:
            summary = translator.translate(str(summary), dest='ja').text

        attachment = {
            'title': title_jp + " ( " + title + " )",
            'title_link': url,
            "author_name": author,
            "fields": [
                {
                    "title": "Abstract",
                    "value": summary
                },
                {
                    "title": "Published",
                    "value": date
                }
            ]
        }

        data = {
            'username': 'arXiv',
            'icon_emoji': ':arxiv:',
            'channel': '#python',
            'attachments': [attachment]
        }

        data = json.dumps(data).encode('utf-8')

        # slack通知
        api_url = os.environ.get('SLACK_INCOMING_WEBHOOK_URL')
        req = urllib.request.Request(api_url, data=data)
        urllib.request.urlopen(req)

def lambda_handler(event, context):
    query = "(cat:cs.AI+OR+" + "cat:cs.CV+OR+" + "cat:cs.CL+OR+" + "cat:stat.ML) +AND+"+ \
            "((abs:NLP)+OR+(abs:depth)+OR+(abs:deep)+OR+(abs:network)+OR+(abs:natural)+OR+(abs:language))"
    # 日本時間に合わせる
    basedate = datetime.datetime.now() + datetime.timedelta(hours=9)
    previousdate = basedate + datetime.timedelta(days=-1)
    get_thesis(query, basedate, previousdate, True)
補足
  • get_thesis関数の引数のtranslateをFalseに設定すると、翻訳せずにslack通知させることができます。
  • data =の部分に記載されている情報は、皆さんのslackの設定に合わせて修正して使用してください
  • query =の部分で検索する論文の種類を指定しています。
    APIの詳細は下記に載っていますので、自分の目的に合わせて修正してみてください。 arxiv.org

4. zip化

ここまでで作成したpackageフォルダをzipに圧縮します。

$ zip -r9 ../function.zip .

上記を実行するとpackageディレクトリと同じ階層にfunction.zipというファイルが出来ていると思います。

f:id:taxa_program:20190715110118p:plain:w330
ディレクトリイメージ

5. Lambda関数へ追加

AWS Lambdaのコンソールに戻り、.zipファイルをアップロードを選択します。
ここで、先に作成したfunction.zipを指定します。

f:id:taxa_program:20190715102715p:plain

すると、下記のようなモジュール群と実行ファイルがLambda上にアップロードされます。 f:id:taxa_program:20190715110307p:plain

6. タイムアウト時間の変更

デフォルトのタイムアウト時間は3秒になっているため、取得する論文の本数が多い場合はタイムアウトエラーになってしまう可能性があります。

そこで、今回は1分に変更しました。

f:id:taxa_program:20190715115145p:plain

7. 環境変数の追加

最後に、冒頭の「slackの設定」で取得したWEBHOOK_URLを環境変数に設定します。

f:id:taxa_program:20190715110504p:plain

AWS CloudWatch Events

次にlambdaの起動元となるトリガーをCloudWatch Eventsで作成します。 f:id:taxa_program:20190715112145p:plain

トリガーは毎朝10時に設定しました。
(ここでの時間はUTCなので、AM10時 - 9時間 = 1時 を設定しています)

f:id:taxa_program:20190715113954p:plain
CloudWatch Eventsの設定

cron式の詳細は下記を参照してください。

docs.aws.amazon.com

これにて設定終了です!

最後に

slack通知ドリブンな論文ライフをお楽しみください!