はじめに
先に結論を書いておくと、Lambda上で動かすところまで行けませんでした。
他の方法のあてはあるので、そちらがうまくいったら追記、もしくは別記事としてあげようと思います。
今回は、前回(その1)作成したweather_spider.py
をAWSlambdaに乗せてサーバレスで実行できるようにしていきます。
前回からだいぶ時間が空いてしまいましたが、理由は後ほど・・・。
目標
Lambdaを使用して、Yahoo!天気(東京)のデータを6時間おきに取得する。
方法
今回は、サーバレスアプリケーションモデル(SAM)を使用して、lambda諸々を構築していきます。
SAMについてはこちらをご覧ください。
以下はawsコマンド,samコマンドが実行できることを前提としています。
やってみる
1.SAM Projectの作成(sam init)
今回はPython3.7で実装していくため、runtimeにpython3.7を指定してsam initします。
$ sam init --runtime python3.7
[+] Initializing project structure...
Project generated: ./sam-app
Steps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api
Read sam-app/README.md for further instructions
[*] Project initialization is now complete
こんな感じで一瞬でsam projectが作成されます。
フォルダ構成は以下。
sam-app
├── README.md
├── events
│ └── event.json
├── hello_world
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ └── app.cpython-37.pyc
│ ├── app.py
│ └── requirements.txt
├── template.yaml
└── tests
└── unit
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ └── test_handler.cpython-37.pyc
└── test_handler.py
sam-app直下に前回作成したyahoo_weather_crawlをコピー
$ cp -r yahoo_weather_crawl sam-app/
$ cd sam-app/
$ ls
README.md hello_world tests
events template.yaml yahoo_weather_crawl
2.weather_spider.pyを修正
lambdaからキックできるように、handlerを追加します。
# -*- coding: utf-8 -*-
import scrapy
from yahoo_weather_crawl.items import YahooWeatherCrawlItem
from scrapy.crawler import CrawlerProcess
# spider
class YahooWeatherSpider(scrapy.Spider):
name = "yahoo_weather_crawler"
allowed_domains = ['weather.yahoo.co.jp']
start_urls = ["https://weather.yahoo.co.jp/weather/jp/13/4410.html"]
# レスポンスに対する抽出処理
def parse(self, response):
# 発表日時
yield YahooWeatherCrawlItem(announcement_date = response.xpath('//*[@id="week"]/p/text()').extract_first())
table = response.xpath('//*[@id="yjw_week"]/table')
# 日付ループ
for day in range(2, 7):
yield YahooWeatherCrawlItem(
# データ抽出
date=table.xpath('//tr[1]/td[%d]/small/text()' % day).extract_first(),
weather=table.xpath('//tr[2]/td[%d]/small/text()' % day).extract_first(),
temperature=table.xpath('//tr[3]/td[%d]/small/font/text()' % day).extract(),
rainy_percent=table.xpath('//tr[4]/td[%d]/small/text()' % day).extract_first(),
)
# lambda handler
def lambda_handler(event,context):
process = CrawlerProcess({
'FEED_FORMAT': 'json',
'FEED_URI': '/tmp/result.json'
})
process.crawl(YahooWeatherCrawler)
process.start()
print('crawl success')
3.template.yamlの修正
先ほどsam init
コマンドで作成されたtamplate.yaml
を修正します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Yahoo weather crawler template on SAM
Globals:
Function:
Timeout: 3
Resources:
WeatherCrawlerFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: ./yahoo_weather_crawl/spiders
Handler: weather_spider.lambda_handler
Runtime: python3.7
Events:
WeatherCrawlEvent:
Type: Schedule
Properties:
#毎日6時間おきに実行
Schedule: cron(0 */6 * * ? *)
ここでは、Events
に毎日六時間おきに実行するcronを仕込んで、
Cloudwatch eventsから起動するようにします。
4.build(デプロイするモジュールをまとめる)用shellの作成
AWS上にデプロイする際に必要なモジュールをbuildという名前のフォルダに纏めます。
が、その前に、今回scrapyをインポートしてPythonを実行しますが、scrapyの依存ライブラリ
の中に、lxml
というライブラリが有ります。
pip install scrapy
とすると、lxmlも自動的にインストールはしてくれるのですが、
Python3.7ランタイムのAWS Lambdaにアップロードすると、そのままではモジュールを読み込むことができません。
(ここに苦戦して、かなり時間がかかりました・・・。)
そこで、今回はこちらの記事で作成した、秘伝のタレ(EC2上でコンパイルしたlxmlライブラリ、詳しくは記事参照)をlib
という名前のフォルダに保存しておき、buildシェル内でbuildフォルダにコピーするようにします。
# build
dir=yahoo_weather_crawl
echo '仮想環境を作成します'
python3 -m venv .venv
echo '仮想環境を有効化します'
. .venv/bin/activate
rm -rf ${dir}/build
# buildフォルダの作成
echo '${dir}をbuildします'
mkdir ${dir}/build
# buildフォルダにpip install
echo 'requirements.txtからpip installします'
pip3 install -r ${dir}/requirements.txt -t ${dir}/build
# libフォルダからbuildフォルダへコピー
echo 'libフォルダからbuildフォルダへ必要モジュールをコピーします'
cp -rf ./lib/* ${dir}/build
# pyファイルのコピー
echo 'pyファイルをbuildフォルダにコピーします'
cp -f ${dir}/*.py ${dir}/build
cp -f ${dir}/spiders/*.py ${dir}/build
# echo '仮想環境を無効化します'
deactivate
echo 'ビルドが完了しました'
5.sam-deploy用のshellを作成
コマンドからデプロイできるようにdeploy用のshellを作成します。
# build
echo 'YahooWeatherCrawlerをビルドします'
sh build.sh
# templateをuploadするためのS3バケットの作成
# バケット名は世界中で一意にする必要があるので、コピペで作成する場合はバケット名を変更してください。
if aws s3 ls "s3://weather-crawl-bucket" 2>&1 | grep -q 'NoSuchBucket' ; then
echo "weather-crawl-bucketを作成します。"
aws s3 mb s3://weather-crawl-bucket
else
echo "weather_crawl-bucketを空にします。"
aws s3 rm s3://weather-crawl-bucket --recursive
fi
# デプロイ用パッケージの作成
# 作成されたパッケージをS3にuploadします。作成したバケット名を指定してください。
echo "デプロイ用のパッケージを作成します。"
aws cloudformation package --template-file template.yaml \
--output-template-file output-template.yaml \
--s3-bucket weather-crawl-bucket
# デプロイ
aws cloudformation deploy --template-file output-template.yaml \
--stack-name weather-crawler \
--capabilities CAPABILITY_IAM
6.デプロイ
sam-app $sh deploy.sh
..
Successfully created/updated stack - weather-crawler
7.実行
今度は別のモジュールのImportError...
Macのローカル上でビルドするのは少し大変そうなので別の方法を考えたいと思います。
終わりに
毎週Qiitaに記事を投稿すると決めてから一月以上経ちましたが、
今年は結局三記事しかかけませんでした。(一回ハマると抜け出せない!)
来年も?引き続き頑張っていきますので、よろしくお願いいたします。