ぴよまるの雑多な技術所

間違えて休職中ってずっと書いていて心配されていました。全部ちょっとずつ齧って好きって思える技術を見つけたい。研究分野は数理最適化。

Mecabの形態素解析の精度を向上する

形態素解析によく使うMecabですが、何も考えずに使うと変なところで切れてしまうことがあったり、品詞が違ったりしました。今回は米津玄師さんのLemonを題材に、Mecab形態素解析の精度の向上について書きます。

Mecabとは

taku910.github.io 上記を参照してください。形態素解析に用いるエンジンとしては他にChaSen隠れマルコフモデル)やJuman(RNN言語モデル)がありますが、Mecabは高速に動作するため現場でよく使われています。 インストールについても上記の記事を参考にしてください。Macの場合は

#Mecab
brew install mecab
#初期の辞書
brew install mecab mecab-ipadic
#python用のmecab
pip install mecab-python3

で最低限の環境が整うはずです。今回の検証環境は以下です。

mecab -v
#mecab of 0.996
python -V
#Python 3.6.5

歌詞を読み込む

歌詞をテキストファイルで用意します。今回はLemonlemon.txtというファイル名で用意しました。

夢ならばどれほどよかったでしょう
未だにあなたのことを夢にみる
忘れた物を取りに帰るように
古びた思い出の誇りを払う

この歌詞をPythonで読み込みます。 一行ずつlistに入った入れ子のlistになるので、reduceを用いて一行ずつのstrのlistに直します。

import pandas as pd
import numpy as np
import MeCab
from functools import reduce

lyrics = pd.read_table("./lemon.txt", header=None, encoding="utf-8").values.tolist()
lyrics = [reduce(lambda x: x, l) for l in lyrics]
print(lyrics)
#['夢ならばどれほどよかったでしょう', '未だにあなたのことを夢にみる', '忘れた物を取りに帰るように', '古びた思い出の誇りを払う']

辞書なしで形態素解析をする

#辞書なし
tagger = MeCab.Tagger()
no_dict_result = []
for line in lyrics:
    tagger.parse("")
    node = tagger.parseToNode(line)
    while node:
        if node.surface:
            result = "「{}」の品詞は{}".format(node.surface, node.feature.split(",")[0])
            no_dict_result.append(result)
        node = node.next

tagger.parse("")については、入れないとエンコードエラーを起こすことがあります。 詳しくは下記を参照してください。

qiita.com

辞書ありで形態素解析をする

辞書としてmecab-ipadic-neologdを使います。説明は下記を参照してください。

github.com

指示にしたがってインストールしていきます。

git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd
./bin/install-mecab-ipadic-neologd -n

Mecabを使う際に辞書の位置を指定する必要がありますが、下記のコマンドで確認できます。

echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
#辞書あり
tagger_with_dict = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
use_dict_result = []
for line in lyrics:
    tagger_with_dict.parse("")#エンコードエラーのため(https://qiita.com/kasajei/items/0805b433f363f1dba785)
    node = tagger_with_dict.parseToNode(line)
    while node:
        if node.surface:
            result = "「{}」の品詞は{}".format(node.surface, node.feature.split(",")[0])
            use_dict_result.append(result)
        node = node.next

テキストファイルに書き出して比較する

いままでの結果を下記のようなコードでテキストファイルに書き出して比較してみました。

write_path = "no-dict.txt"
print(no_dict_result)
with open(write_path, mode='w',encoding='utf-8') as f:
    f.write('\n'.join(no_dict_result))

上記は辞書なしで形態素解析をするの方の結果を書き出しています。 結果はこんな感じです。左が辞書なしで形態素解析をするの結果で、右が辞書ありで形態素解析をするの結果です。辞書なしもともとの精度も今回の歌詞については悪くありませんが、「夢ならば」が「夢」「なら」「ば」とわかれるなど、さらに精度が向上していることがわかります。

f:id:let_piyomaru:20200414223138p:plain
形態素解析の結果の比較

まとめ

辞書を使うことでMecab形態素解析の精度を向上できました。 他にも形態素解析前に前処理として記号やタグ文字を取り除くことや、速度を気にしないならJuman++を使うなどの精度向上の方法が考えられます。今回は取り上げませんでしたが、前処理にはneologdnというPythonのライブラリを使うこともできるようです。

今回のソースコードは結果も含めて下記にあげてあります。

github.com

PythonでTwitterのフォロワー数をグラフ化する

3月からブログメンタリングを受けており、ブログ週間PV累計はてなブックマーク数ブログ読者登録数Twitterフォロワー数をKPIとして設定しています。 今まではただ確認するだけでしたが、せっかくなのでグラフにして施策を打ちやすい状態を作ることにしました。Jupyter Notebook上でスクレイピングをして、Matplotlibを用いてグラフ化し、ipynbファイルをバッチで実行してhtmlファイルを吐き出します。なお、Google Apps Scriptを検討していましたが、デフォルトアカウントでしかスクリプトがかけないため、複数アカウントを使っている私には大変そうなのでやめました。 今回の記事ではTwitterフォロワー数スクレイピングとバッチ設定とグラフ化について書きます。

環境

Jupyter Notebookを使います。

python -V
#Python 3.6.5
jupyter --version
#4.4.0

Tweepyでフォロワー数を取得

Twitterのフォロワー数についてはTweepyというライブラリを用いて行います。

pip install tweepy

次に下記の記事を参考にTwitter APIの登録をして、APIキーなどを取得してください。

qiita.com

auth = tweepy.OAuthHandler(API_KEY, API_SECRET_KEY)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
api = tweepy.API(auth,wait_on_rate_limit = True)

Jupyter Notebookを新しく開き上記のコードで事前に設定をしたら、api.me()で自分のTwitterアカウントの情報を取得できます。私はtwitter_collection.ipynbという名前で実行することにしました。

api.me().followers_count
api.me().friends_count

Twitterのフォロワー数の履歴はtweepyを用いては取得できず、他のサイトからクローリングする必要があります。そのため、今回は日々Twitterのフォロワー数をCSVに記録することとします。

PATH = "data/twitter.csv"
dt_now = datetime.datetime.now()

#もしファイルがあれば
if os.path.exists(PATH):
    with open(PATH, 'a') as f:
        writer = csv.writer(f)
        writer.writerow([dt_now.strftime('%Y年%m月%d日'), followers, followees])
else:
    with open(PATH, 'w') as f:
        writer = csv.writer(f)
        writer.writerow(["日付", "フォロワー数", "フォロー数"])
        writer.writerow([dt_now.strftime('%Y年%m月%d日'), followers, followees])

ipynbのバッチ化

Jupyter Notebookのバッチ化はrunipyというライブラリを用いて行います。

pip install runipy

これで下記のようにコマンドラインからipynbを実行できるようになります。

runipy twitter_collection.ipynb

このコマンドをcronで設定します。ターミナルで下記を入力します。

$ crontab -e

何も書き込まれていないファイルが開かれるので、下記を入力します。毎朝7時にコマンドを実行します。パスは絶対パスで指定してください。

PYTHONIOENCODING = 'utf-8'
LANG=ja_JP.UTF-8

0 7 * * * cd (実行パス);(runipyの絶対パス)(ipynbの絶対パス)

0 7 * * *は(分)(時)(日)(月)(曜日)です。ワイルドカードで指定すると毎分や毎時という設定になります。ホームディレクトリでコマンドが実行されるので、cdで移動してからコマンドを叩くようにします。コマンド自体のパスも絶対パスで指定します。

集計したデータのグラフ化

Jupyter Notebookをまた新たに開き、モジュールを読み込みます。私はtwitter_analysis.ipynbという名前で実行することにしました。

import pandas as pd
import numpy as np
%matplotlib inline

CSVからデータを読み込みます。

PATH = "data/twitter.csv"
dates = []
followers = []
followees = []
with open(PATH) as f:
    h = next(csv.reader(f))#ヘッダー行を読み飛ばす
    reader = csv.reader(f)
    for row in reader:
        dates.append((row[0]))
        followers.append(int(row[1]))
        followees.append(int(row[2]))

読み込んだデータをグラフ化します。

df = pd.DataFrame(
    {
        'follower': followers,
        'followee': followees
    },
    index = dates
    )

df.plot()

最後に作ったJupyter Notebookからhtmlファイルを吐き出します。ターミナルで以下を実行してください。

runipy twitter_analysis.ipynb twitter_analysis_out.ipynb
jupyter nbconvert twitter_analysis_out.ipynb --to html

上記のコマンドもcronに登録してもいいでしょう。

参考資料

qiita.com

qiita.com

qiita.com

ソースコードは以下に置きました。

github.com

今回の分のソースコードスクレイピング部分がtwitter_collection.ipynb、グラフ化部分がtwitter_analysis.ipynbです。

グラフは下記のように出力されます。

f:id:let_piyomaru:20200414150810p:plain
Twitterのフォロワー数

ブログメンタリングを受けて1ヶ月たったのでまとめた

@kakakakakku(https://twitter.com/kakakakakku)さんのブログメンタリングを受け始めて1ヶ月がたちます。 これまでの振り返りや学んだことについて記事にまとめます。 ブログメンタリングについては

kakakakakku.hatenablog.com

上記の記事をご参照ください。

受けようと思った動機

プログラマーになれていない」と感じていたので、その状況を改善したく、ブログメンタリングに応募しました。 プログラマーになれていない」を分析すると、

  1. 業務範囲が広くて自分が何をしている人かわからない、それぞれの分野の知識が浅い
  2. 技術面で聞かれたことに答えていると知識が浅くて苦しくなってくる
  3. 一人で開発することが多くて交流機会が少ない

という3つが要素としてあると自覚をしていて、ブログメンタリング応募時にはその旨を伝えました。

今月やったこと

「週に1度、決めた日にブログ記事を公開する」が基本ですが、その他にやったことを列挙します。

- KPIの決定
- ブログネタの書き出し
- はてなブログの設定
- Twitterやブログの紹介文の見直し
- フォローや読者登録の習慣化
- ブログネタ探しの習慣化

KPIの決定

私ははてなブログでブログを書いているので、KPIとして週間PV累計はてなブックマーク数はてなブログの読者数と、自己ブランディングをしたいのでTwitterのフォロワー数をKPIとして設定しました。1ヶ月目時点では可視化ができておらず、数値もなんとなく眺めるだけに終わっています。

ブログネタの書き出し

Trelloにまずは20個、ブログネタを書き出しました。テーマとしては普段使うライブラリを列挙したうえでその機能についてだとか、業務で調べる内容だとか、普段から気になっていたことを最初は列挙しました。使うライブラリのドキュメントを読んで、気になる機能を見つけました。ブログネタはブログを書き出してからも増えていて、ブログを書いている時に検索した内容や、目についてキーワード、日々の業務での躓きもブログネタとしてストックしています。@kakakakakkuさんに検索履歴がテーマになるとアドバイスをいただいたのは目から鱗でした。

はてなブログの設定

最初ははてなブログGoogle AnalyticsGoogle Search Consoleを設定しました。 ブログを書くたびにカテゴリを設定し、サイドバーにカテゴリ一覧を表示するようにしました。 さらにサイドバーにプロフィール画像として、Twitterのアカウントと同じアイコンを設定しました。

Twitterやブログの紹介文の見直し

ブログの説明文を見直しました。具体的には、

> コピペ職人のPGだけどまともなPGになりたいしなんならkaggleもやりたい

という説明文でしたが、

> コーディング力に自信のないWebエンジニアが自信をつけるために試したことまとめます。コンパイラ言語を試したり、フレームワークのコードを読んだり、業務で使うAWSやReactについての記事を書きます。

と変更しました。 ブログデザインも可愛い見た目で可愛いフォントのデザインを使っていたのですが、内容とマッチしておらずフォントも読みにくかったので、シンプルなブログデザインに変更しました。

Twitterの方は自己紹介文を見直しました。

> AWS 機械学習 React

という内容を書いていたのですが、普段は機械学習についてはほとんどツイートしていませんでした。 ブログにはコンパイル言語の内容やコーディング力を上げる系の記事を書いていたので、

> 業務系とコンパイル言語が気になりだしてコーディング力を底上げする方法を模索中。

を追加して、ツイート内容も見直すようにしました。最新の情報紹介をしたりリツイートをするのは今後の課題です。

フォローや読者登録の習慣化

先ほども書いたリツイートをするとか、自分からフォローしたりはてなブログなら読者登録をすることで、人と交流することを習慣化するよう努力しています。 とはいえ、現状は普段まったくしていなかったところから、意識するようにしただけです。こちらも数値で目標を立てて、来月は習慣化できるようにしていきます。

ブログネタ探しの習慣化

ブログネタをストックするため、

  1. 業務中に躓いたことや検索したキーワードをメモする
  2. 2日に1回ははてなブックマークのテクノロジーページTwitterQiitaのトピックを眺める

を実践しました。また、ブログのネタを探す時に軽いネタ重いネタとに分けるようにしました。分け方はブログを書くまでにかかりそうな時間で、重いネタばかりにならないように注意しています。

ブログの書き方について

毎回ブログを書いたら確認するようなこととして、表記揺れシンタックスハイライトURLの埋め込みなどはご指摘いただきました。 あとは記事を書く時のコツとして画像を入れる強弱をつけるため強調を使うということも意識しています。 最後に、ブログメンタリングを受けるようになってから、他の人のブログをインプットとして読んでから記事を書くようにしています。 最初の頃に書いた記事

Pythonでテトリスを作ることでよく知らない分野の実装手順について考察する(1)設計編 - ぴよまるがアウトプットするってさ

主観的でメモのようで、書き方も強調もなく再現性もなく読みにくいです。 一方で最近書いた記事

textlintを使ってブログの表記揺れをなくす - ぴよまるがアウトプットするってさ

は他のブログを参考に読む人が欲しい内容を分析して、客観的に書くようにして、再現性や画像の量に気をつけました。

来月やること

  • ツイート内容を見直す(最新の情報紹介をしたりリツイートをする)
  • フォロー数読者登録数について目標数を決めて達成する
  • 設定したKPIを可視化し、毎週振り返りと施策立てをする
  • OGPの設定をする
  • 自分用のメモの記事の数はブログには書かず、ターゲットを想定した記事を増やす

textlintを使ってブログの表記揺れをなくす

3月頭から@kakakakakkuさんにブログメンタリングをしていただいています。その中でよくご指摘いただく事項に表記揺れがありました。 そんな時に役立つ文書校正ツールのtextlintについて紹介します。

textlintとは

textlint.github.io

特定のルールにしたがって自然言語をチェックしてくれます。 CLI経由でも利用できますし、Visual Studio CodeAtomとの連携も可能です。

環境

textlintはnpm経由でインストールします。今回は特定のフォルダ内のあるマークダウンファイルを対象とし、 ~/blogというリポジトリの中に~/blog/textlint.mdを作成したとして進めます。Node.jsはインストールされているとします。

npm -v
#6.5.0

手順

まず、package.jsonの生成をします。

npm init --yes

このコマンドでpackage.jsonが生成されます。

次に、textlintをインストールします。

npm install --save-dev textlint

次に、ルールをインストールします。 結論から言うと、技術ブログの校正であればtextlint-rule-proofdicttextlint-rule-preset-ja-technical-writingでことたりそうです。

npm install --save-dev textlint-rule-proofdict textlint-rule-preset-ja-technical-writing

次に、textlintのルールを有効にするファイルを生成します。

npx textlint --init

このコマンドで.textlintrcが生成されます。

最後に、textlint-rule-proofdictの辞書の設定をします。

proofdict.github.io

こちらにあるようにリポジトリをforkして細かく表記揺れ検知の設定をすることも可能です。 .textlintrc

  "filters": {},
  "rules": {
    "preset-ja-technical-writing": true,
    "proofdict": {
      "dictURL": "https://azu.github.io/proof-dictionary/"
    }
  }
}

実際に使う

npx textlint textlint.md

自動修正も可能です。

npx textlint --fix textlint.md

Visual Studio Codeでの使用

marketplace.visualstudio.com

vscode-textlintという拡張機能を入れます。 これだけで.textlintrcの配置されているプロジェクトを開くと、リアルタイムで画像のように校正されます。

f:id:let_piyomaru:20200326093934p:plain
textlint使用時

まとめ

textlintを使用することで表記揺れを減らすことができ、ブログを見直す時の心理的ハードルが下がりました。 ぜひ導入してみてください。

Serverless Frameworkを使ってLambda + API GatewayのプロジェクトをProduction / Developmentに分けて開発する

概要

Lambda + API GatewayでSPAからAPI Gateway経由でAPIを叩くという構成の時のServerless Frameworkの設定についてまとめます。 長くなりそうなので今回はフロントエンド側のServerless Frameworkの設定内容と、最初のデプロイについてに絞りました。

構成

Lambda, API Gateway, S3, CloudFront

Serverless Frameworkの設定(フロントエンド)

S3、CloudFrontを自動で作成するという内容でyamlを書きます。 S3のバケットポリシー、CloudFrontのhttpsの設定もしています。

serverless.yml

service: test-serverless #ここは自由に名前を入力する

provider:
  name: aws 
  region: ap-northeast-1
  stage: ${opt:stage, self:custom.defaultStage}
  # profile: (もしProfileでアカウントをわけていればここに入力)

custom:
  defaultStage: development
  env:
    development:
      siteName: ${self:provider.stage}.${self:service}

resources:
  Resources:
    StaticContentsS3:
      Type: AWS::S3::Bucket
      DeletionPolicy: Retain
      Properties:
        BucketName: ${self:custom.env.${self:provider.stage}.siteName}
        AccessControl: Private
        WebsiteConfiguration:
          IndexDocument: index.html
          ErrorDocument: index.html
        Tags:
          - Key: Environment
            Value: ${self:provider.stage}

    StaticContentsS3Policy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: StaticContentsS3
        PolicyDocument:
          Statement:
            - Effect: Allow
              Principal:
                CanonicalUser:
                  Fn::GetAtt:
                    - StaticCloudFrontOriginAccessIdentity
                    - S3CanonicalUserId
              Action: s3:GetObject
              Resource:
                Fn::Join:
                  - "/"
                  - - Fn::GetAtt:
                        - StaticContentsS3
                        - Arn
                    - "*"

    StaticContentsCloudFront: #CloudFrontの設定
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Enabled: true
          Comment: "Delivery static contents"
          PriceClass: PriceClass_200 # CloudFrontを配置する場所
          DefaultRootObject: index.html
          CustomErrorResponses:
            - ErrorCode: 403
              ResponseCode: 200
              ResponsePagePath: /index.html
            - ErrorCode: 404
              ResponseCode: 200
              ResponsePagePath: /index.html
          Origins:
            - Id: S3Origin
              DomainName:
                Fn::GetAtt:
                  - StaticContentsS3
                  - DomainName
              S3OriginConfig:
                OriginAccessIdentity:
                  Fn::Join:
                    - "/"
                    - - origin-access-identity
                      - cloudfront
                      - Ref: StaticCloudFrontOriginAccessIdentity
          DefaultCacheBehavior:
            AllowedMethods:
              - HEAD
              - GET
            CachedMethods:
              - HEAD
              - GET
            Compress: true
            DefaultTTL: 900
            MaxTTL: 1200
            MinTTL: 600
            ForwardedValues:
              QueryString: true
              Cookies:
                Forward: none
            SmoothStreaming: false
            TargetOriginId: S3Origin
            ViewerProtocolPolicy: redirect-to-https
          ViewerCertificate:
            CloudFrontDefaultCertificate: 'true'

    StaticCloudFrontOriginAccessIdentity:
      Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
      Properties:
        CloudFrontOriginAccessIdentityConfig:
          Comment:
            Ref: AWS::StackName

  Outputs:
    StaticContentsCloudFrontUrl:
      Value:
        Fn::Join:
          - ""
          - - "https://"
            - Fn::GetAtt:
              - StaticContentsCloudFront
              - DomainName

デプロイ手順

まずはServerless Frameworkのデプロイコマンドを叩きます。こうするとCloudFormation経由でAWSリソースが作られます。

serverless deploy --stage development

次に出来上がったバケットに対して、フロントエンドのソースコードをアップロードします。詳細は割愛しますが、例えばNuxt.jsを使っている場合は「yarn generate」でできあがったdistの中のファイルをそのままアップロードすれば大丈夫です。

ここまでできるとCloudFrontのURLをブラウザから叩けばフロントエンドの動きが確認できます。

serverless deploy --stage production

という風にすれば、別の名前のS3バケットとCloudFrontができます。何か試すときはdevelopmentで試して、動作確認ができたらproductionにあげる…ということが簡単にできます。(普通はその間にstagingがありますが、今回は突然productionにしています。)

後編は後で書きます!

Pythonでテトリスを作ることでよく知らない分野の実装手順について考察する(3)反省編

ブログ記事を書く際にテトリスの実装を実際にしたわけですが、かなり時間を取られました。 どうしたら早く動かないところを動かすことができるか、web系ではないときのデバッグをどうするべきか少しわかったと思うので、メモしておきます。内容としてはどうやってデバッグしてきたかと、テトリスの実装手順をまとめています。

ぶつかった壁

  • なぜか全体的に動かない
  • テトリスが落ちてこない / 操作しても右に行かない などなど。こういうときにコードを眺めて、変えて、動かして…を繰り返していたら夜が明けました。 その中で試してよかったことと次からこうした方がコーディングが早くなるのではと思ったことを以下にまとめます。

やったこと

youtubeテトリスの実装動画をみて実装手順・ロジックを確認)

www.youtube.com

どうしても動かせずにこちらの動画を視聴しました。「3.ロジックの見直し」はこの動画での実装を参考にしています。 また、実装をしていく中でどの単位で動かすかの参考と、自分の考えたロジックの答え合わせにも使いました。

デバッグしやすい環境を整える)

VSCodeを使っているのですが、F10やF11キーを押すのがMacだとめんどくさいです。

blog.stedplay.com

こちらを参考にMacでもデバッグしやすい環境、つまりF11キーだけでステップイン実行できる環境を整えました。

( ロジックの見直し(テトリスが衝突したときの扱い))

テトリスが衝突したとき、メインのゲームクラスが持っているテトリミノの配列に新しいテトリミノを入れていました。テトリミノはスクリーンに即時反映します。つまり、描画処理はメインのゲームクラスが持っているスクリーンをprintするだけになります。 こうではなくて、テトリミノは常に1つだけで、壁に衝突したらスクリーンの一部にすることにしました。こうすると移動したときの描画が楽になります。ゲームクラスが持っているスクリーンとテトリミノを重ねて表示するだけなので。

反省

  • 実装は1つの処理ずつ行って、実装できたらすぐ動かして確認する
  • なるべく実装する手順をシンプルな単位に切り分けてから実装をする
    • 設計段階ではメソッドやオブジェクトを洗い出しているので、どの順序で実装するかよく考える
  • コードを目で追うのではなくデバッガで変数の中身を確認しながら追う

参考:テトリスの実装手順

(スクリーンの表示)

  1. スクリーンをfor文を使って描画する(配列のインデックスに注意)
  2. テトリミノの種類や回転をEnumなどで表現する
  3. スクリーンとテトリミノをコピーして描画するメソッドを作って動かしてみる

テトリスの落下)

  1. 1秒ごとにテトリスが落下するメソッドを用意し、結果を描画する

テトリスの操作)

  1. キーボード入力を受け付ける
  2. キーボード入力でテトリスの位置が移動することを確認する。この時点では壁にめり込む

(壁との衝突検知)

  1. テトリミノが壁に衝突したか判断するメソッドを用意し、衝突していたら移動しないようにする 実装内容としてはテトリミノの形状と、スクリーンの中でテトリミノを配置すべきところを1pxずつ確認して衝突していないか判断する

(テトリミノの固定)

  1. 落下させたとき、壁にぶつかったらテトリミノはスクリーンと同化する
  2. 同化したら次のテトリミノを生成する

(テトリミノの削除)

  1. テトリミノを1つずつ下に移動させて一番上の行を削除

Pythonで実装したLambda + API Gatewayで任意のエラーコードを返す

サーバレスな構成を作っている時、Lambdaのステータスコードを500で返してもAPI Gatewayで200に書き換わってしまうということがありました。その時は独自エラークラスを作って、マッピングテンプレートを設定してなどなど、大掛かりなことをしたのですが、マッピングテンプレートなしで任意のエラーコードを返す方法をまとめます。

全体図

LambdaがAPI本体、API GatewayAPIのエンドポイントになる構成です。

結論

先に結論を書くと、マッピングテンプレートは必要ありません。API GatewayのPOSTの「統合リクエスト」は「Lambda プロキシ統合の使用」のまま、Lambda側でエラー用のjsonをreturnすれば大丈夫です。

参考記事 https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/handle-errors-in-lambda-integration.html

以下に具体的なコードを載せます。POSTで{'test': '何か文字列'}という値を送信するという想定です。

Lambda

import json

def lambda_handler(event, context):
    body = json.loads(event['body'])#例えばbodyを読み込んで

    if (len(body['test'])>10000):#文字が長すぎたら500エラーを返す
      return {
            'errorType': "InternalServerError",
            'httpStatus': 500,
      }
   #文字が長すぎなければ200のレスポンス
    return {
        'statusCode': 200,
        'isBase64Encoded': False,
        'body': 
            json.dumps({'result': 'OK!'})
        ,
        'headers': {
            "Content-Type" : "application/json",
            "Access-Control-Allow-Origin" : "*"
        }
    }

API Gateway

これから記事を書く予定の記事のserverless.yamlの設定をコピペすれば動きます。書いたらリンクをはります。 おそらく「OPTIONS」と「POST」の2つのリソースができていると思いますが、「OPTIONS」はpreflightレスポンスに使います。「POST」の方が実際にLambdaにリクエストを送るリソースで、細かいマッピングは「統合リクエスト」の中の「Lambda プロキシ統合の使用」にチェックをするとよしなに行ってくれるため、マッピングテンプレートが不要です。

f:id:let_piyomaru:20200310105435p:plain
API Gatewayの設定
私の環境だと「POST」の方は「メソッドレスポンス」にステータスコードを登録する必要すらありませんでした。 留意点としては、Lambda側で返すjsonの項目がAWSによって定められています。その形さえ間違わなければ動くはずです。