AWS Lambda に50MB以上のレイヤーがアップロードできない場合の一つの対処法

前の記事までで

  1. Slackからメッセージを取得する
  2. 文が五・七・五かを判定する
  3. Slackにメッセージを送信する
  4. 定期的に行動(処理)を実行する

全てが可能になりました。必要そうな能力は全て手に入れたはずです。この記事では、それらを組み合わせます。

後は組み合わせるだけ…のはずだったのにできない

必要な能力は得ました。後は組み合わせるだけです。Lambdaに乗っているプログラムを能力1〜3の組み合わせに変え、必要なパッケージをレイヤーに載せればいいんでしょう?と思いながらそうします。

まずはプログラムを書き換えます。

import jaconv
import MeCab
import regex
import requests
import time


get_url = "https://slack.com/api/conversations.history"
post_url = "https://slack.com/api/chat.postMessage"
token = "xoxb-xxxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxx..."# tokenを入れてください

m = MeCab.Tagger() #形態素解析用objectの宣言

def post_message(thread_ts):
    data["thread_ts"] = thread_ts
    requests.post(post_url, headers=header, data=data)


def extract_hiragana(text):
    return "".join(regex.findall(r'[\p{Hiragana}ー]', text))


def get_pronunciation_list(text):
    m_result = m.parse(text).splitlines() #mecabの解析結果の取得
    m_result = m_result[:-1] #最後の1行は不要な行なので除く
    pro = [] #いい感じに切った読みを全体を格納する変数
    for v in m_result:
        if '\t' not in v: continue
        surface = v.split('\t')[0] #表層形
        p = v.split('\t')[1] #読みを取得
        if p == '*':#発音が取得できていないときsurfaceで代用
            p = surface
        if v.split('\t')[4].startswith("助詞") or v.split('\t')[4].startswith("助動詞") and pro:
            pro[-1] += p
        else:
            pro.append(p)
    pro = [jaconv.kata2hira(p) for p in pro] #ひらがなをカタカナに変換
    pro = [extract_hiragana(p) for p in pro if p] #余計な記号を削除
    return pro


def is_575(text: str) -> bool:
    words = get_pronunciation_list(text)
    length = 0
    reversed_expected_length_list = [5, 7, 5]
    for word in words:
        if not reversed_expected_length_list:  # 575が終わったのにまだ続く
            return False
        length += len(regex.sub(r'[ぁぃぅぇぉゃゅょ]', '', word))  # 1音を構成しない文字
        if length == reversed_expected_length_list[-1]:
            length = 0
            reversed_expected_length_list.pop()
    if reversed_expected_length_list:  # 575と切り取れなかった
        return False
    return True


header={
    "Authorization": "Bearer {}".format(token)
}

payload  = {
    "channel" : "C0123456789",# Conversation IDを入れてください
    "oldest" : (time.time() - 60)
}

res = requests.get(get_url, headers=header, params=payload)

data  = {
    "channel" : "C0123456789",# Conversation IDを入れてください
    "text" : "それにつけても金の欲しさよ"
}

def post_77_to_575(event, context):
    json_channel_history = res.json()
    for message in json_channel_history["messages"]:
        if is_575(message["text"]):
            post_message(message["ts"])

if __name__ == "__main__":
    post_77_to_575("", "")

Conversation IDが複数あるとか、いろいろ直すべきところがあるのはともかく、とりあえず「合体させたプログラム」です。手元で動作を確認できています。

プログラムのファイル名も関数名も変えたのでハンドラも変えなきゃいけません。

Lambdaのランタイム設定のハンドラを、「kanehosi_tukeai.post_77_to_575」に変えます。

先ほどrequestsをレイヤーに追加しましたが、jaconv, MeCab, regexはレイヤーに追加する必要がありそうです。先ほどのPythonファイルに他のもインストールしていきま…

あれ?brew install mecab-ipadicの部分ってどうなんでしょ。ipadicは…使わなくて良いかと思い手元でmecab-ipadicをアンインストールして見ましょう。

brewでインストールしたもののアンインストールはbrew uninstall mecab-ipadicだと思いつつも、「brew uninstall」で検索しMac のHomebrew でインストールしたものを削除する – Macガレージを見つけます。予想通り「brew uninstall mecab-ipadic」だったのでこれをターミナルで実行し、再度先ほどのプログラムを実行したら動きました。

mecab-ipadicはなくても動きそうです。先ほどのpythonフォルダにその他のパッケージを以下のように追加します。

(pip install -t python requests)
pip install -t python jaconv
pip install -t python mecab-python3
pip install -t python unidic-lite
pip install -t python regex

そしてまたzipを作ります

zip -r9 layer.zip python

出てきたzipをレイヤーに入れます。せっかく動いたレイヤーを書き換えて動かなくなったら嫌だけど、やたらめったら作るのもなあと思いながら、先ほど作ったレイヤーを開こうとします。

(左のカラムからレイヤーを選び、先ほど作ったレイヤーを選ぶと…書き換えられそうにないですね。更新ボタンを押したら403が返ってきました。しばらくタブを開きっぱなしだったせいでセキュリティ的にログアウトしていたみたいです。ログインし直します)

左のカラムからレイヤーを選び、先ほど作ったレイヤーを選ぶと右上にバージョンの作成という橙色のボタンがあります。これでバージョン管理ができる…とまでは言わないけれど今まで作ったものを破壊せずに追加できそうなので新しく作ったもりもりのレイヤーを入れてみます。50MB以上なのでアップロードできません。

Q. 50MB以上のレイヤーはどうすればいいの? A. エフェメラルストレージを使うか、50MB以下にしよう

フォルダを見てみると辞書の容量が大きいため、zipにしても50MBを超えてしまっていました。unidicよりも小さな辞書ならいけそうだと感じ、「形態素解析辞書 語彙数」で検索して形態素解析辞書の使用感まとめ | marmooo's blogを見つけます。ipadicならいけそうだと感じ、ipadicのインストール方法を調べ、[Python]Mecabのipadic辞書が超簡単に使えるようになっていたを見つけ、レイヤーpythonフォルダを再作成します。install -t python unidic-liteの代わりにpip install -t python ipadicとしてインストールして、14.5MBになりました。zipをlayerにあげます。

ちなみに辞書が変わったことで、Pythonのプログラムも読みや品詞を見る場所などが変わりました。regexもreにすれば節約できると思い、reを使うようにしました。

import ipadic
import jaconv
import MeCab
import re
import requests
import time


get_url = "https://slack.com/api/conversations.history"
post_url = "https://slack.com/api/chat.postMessage"
token = "xoxb-xxxxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxx..."# tokenを入れてください

m = MeCab.Tagger(ipadic.MECAB_ARGS) #形態素解析用objectの宣言

def post_message(thread_ts):
    data["thread_ts"] = thread_ts
    requests.post(post_url, headers=header, data=data)


def extract_hiragana(text):
    return "".join(re.findall(r'[ぁ-ゔー]', text))


def get_pronunciation_list(text):
    m_result = m.parse(text).splitlines() #mecabの解析結果の取得
    m_result = m_result[:-1] #最後の1行は不要な行なので除く
    pro = [] #いい感じに切った読み全体を格納する変数
    for v in m_result:
        if '\t' not in v:
            continue
        if v.split('\t')[1].split(",")[0] in ["記号"]:
            continue
        surface = v.split('\t')[0] #表層形
        p = v.split('\t')[1].split(",") [7] #読みを取得, 8は発音
        if p == '*':#発音が取得できていないときsurfaceで代用
            p = surface
        if v.split('\t')[1].split(",")[0] in ["助詞", "助動詞"] and pro:
            pro[-1] += p
        else:
            pro.append(p)
    pro = [jaconv.kata2hira(p) for p in pro] #ひらがなをカタカナに変換
    pro = [extract_hiragana(p) for p in pro if p] #余計な記号を削除
    return pro


def is_575(text: str) -> bool:
    words = get_pronunciation_list(text)
    length = 0
    reversed_expected_length_list = [5, 7, 5]
    for word in words:
        if not reversed_expected_length_list:  # 575が終わったのにまだ続く
            return False
        length += len(re.sub(r'[ぁぃぅぇぉゃゅょ]', '', word))  # 1音を構成しない文字
        if length == reversed_expected_length_list[-1]:
            length = 0
            reversed_expected_length_list.pop()
    if reversed_expected_length_list:  # 575と切り取れなかった
        return False
    return True


header={
    "Authorization": "Bearer {}".format(token)
}

payload  = {
    "channel" : "C0123456789",# Conversation IDを入れてください
    "oldest" : (time.time() - 60)
}

res = requests.get(get_url, headers=header, params=payload)

data  = {
    "channel" : "C0123456789",# Conversation IDを入れてください
    "text" : "それにつけても金の欲しさよ"
}

def post_77_to_575(event, context):
    json_channel_history = res.json()
    if "messages" not in json_channel_history:
        print("no message recentry, check token and channel ID")
        return 
    for message in json_channel_history["messages"]:
        print("message: ", message["text"])
        if is_575(message["text"]):
            post_message(message["ts"])
            print("kanehosi-!")
    return

if __name__ == "__main__":
    post_77_to_575("", "")

でもやっぱり動きません。"errorMessage": "Unable to import module 'kanehosi_tukeai': No module named 'ipadic'",と書かれています。

エフェメラルストレージを使わずに済んでしまったので、エフェメラルストレージについては割愛します。

この記事では、容量を抑えてなんとかレイヤーに乗せてしまう方法ではあるけれどレイヤーに乗らない問題は解決しました。次の記事では、レイヤーに乗せたのにimportできない問題を解決します。

© 2019- estie, inc.