Serverless FrameworkでデプロイしたLambdaでスクレイピングする。

## 目的
こちらの記事は、以下の記事の第1, 2回目となります。

https://qiita.com/ShiroUz/items/56e230d286d09d275d32


Lambdaを使用して、スクレイピングします。
また、Lambda Layerを使用して、Pythonで使用するパッケージを読み込ませます。インポートするパッケージをLambda Layerから読み込んでハンドラーと切り分けることで、デプロイするLambdaの容量を減らし、大規模なLambda関数を作成可能にするとともに、デプロイするまでの時間を短縮させます。


## 環境
```
$ sw_vers
ProductName:    macOS
ProductVersion: 11.6
BuildVersion:   20G165

$ sls -v
Framework Core: 2.65.0
Plugin: 5.5.1
SDK: 4.3.0
Components: 3.17.2

$ npm -v
8.1.0

$ docker -v
Docker version 20.10.8, build 3967b7d
```
## ディレクトリ構成
ディレクトリ構成は以下。

```
.
├── README.md
├── lambda
│   ├── handler.py
│   └── serverless.yml
├── layer
│   ├── requirements.txt
│   └── serverless.yml
└── package.json
```
ちなみに、ディレクトリ構成を他の人に共有したい時は、treeコマンドを実行すると良いことに気がつきました。-Iオプションをつけると、`node_modules`とかの共有したくないディレクトリは排除してくれます。

## Lambda Layerの作り方
(1). `serverless`コマンドを実行し、適当なテンプレートで作成する。私は`AWS - Python - Scheduled Task`を選択しました。
(2). layerディレクトリに移動し、serverless-python-requirementsをインストールする。`npm i serverless-python-requirements`
(3). Lambda Layerに入って欲しい、requirements.txt定義する。今回デプロイするLambdaには以下を定義しておく。

```
beautifulsoup4>=4.10.0
requests>=2.26.0
lxml>=4.6.4
```
(4). serverless.ymlを作り、以下のようにする。

```
service: python-requirements-layer

plugins:
  - serverless-python-requirements
provider:
  name: aws
  runtime: python3.8
  region: us-east-1

custom:
  pythonRequirements:
    dockerizePip: true
    layer: true

resources:
  Outputs:
    PythonRequirementsLambdaLayerExport:
      Value:
        Ref: PythonRequirementsLambdaLayer
```
ポイントとしては、2つ。
1. pythonRequirementsの箇所にdockerizePipとlayerを定義しておくこと。
2. OutputsにPythonRequirementsLambdaLayerExportを定義しておくこと。

1では、dockerでパッケージ化したファイルをLambda Layerに入れることになる。結果として、一意なパッケージとなり、どこで実行しても同じパッケージになる。
2では、デプロイするLambdaからLambda Layerを読み込むためのCloudFormationへのOutputを定義している。次の章で使用する。

(5)`sls deploy`する。

## デプロイするコード
```python
import datetime
import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
import requests
import sys
sys.path.append("lib.bs4")
from bs4 import BeautifulSoup

target_url = 'https://www.yahoo.co.jp/'

def run(event, context):
    current_time = datetime.datetime.now().time()
    name = context.function_name
    logger.info("Your cron function " + name + " ran at " + str(current_time))
    r = requests.get(target_url)
    soup = BeautifulSoup(r.text, 'lxml')
    a_tag = soup.find('a')
    logger.info(a_tag)

```


## デプロイするserverless.yml
```
org: ShiroUz
app: aws-python-scheduled-cron-project
service: aws-python-scheduled-cron-project

frameworkVersion: '2'

provider:
  name: aws
  runtime: python3.8
  logRetentionInDays: 1
  iamRoleStatements:
  - Effect: Allow
    Action:
      - logs:CreateLogGroup
      - logs:CreateLogStream
      - logs:PutLogEvents
    Resource:
      - "*"
  lambdaHashingVersion: 20201221

custom:
  default_stage: dev
  stage: ${opt:stage, self:custom.default_stage}
  requirements_service: python-requirements-layer
  requirements_export: PythonRequirementsLambdaLayerExport
  requirements_layer: ${cf:${self:custom.requirements_service}-${self:custom.stage}.${self:custom.requirements_export}}
  prune:
    automatic: true
    number: 5

functions:
  cronHandler:
    handler: handler.run
    layers:
      - ${self:custom.requirements_layer}
```
上記コードのポイントは以下の3つ。
1. Lambda Layerをハンドラーで読み込む。
2. IAMロールを設定する。
3. pruneを設定する。

1について、customの中で、設定しているrequirements_layerを読み込んで、Lambda Layer化する。
2について、providerでIAMロールを定義して、ログの出力を有効化する。
3について、pruneを設定しておくことで、Lambdaの古いバージョンが残らなくなる。`npm i serverless-prune-plugin`でインストールする。

上記、serverless.ymlとhandler.pyを適応させた状態で、`sls deploy`することで、AWSにデプロイできる。

## 参考
以下、とっても参考にさせていただきました。ありがとうございます。
https://blog.ikedaosushi.com/entry/2019/04/07/012612

https://qiita.com/ShiroUz/items/1827e026c12d2ce3334b