概要
Lambda+Rubyでgem(Nokogiri)を使おうと思ったところ
cannot load such file -- nokogiri/nokogiri
というエラーになってしまい、使えなかったので使えるようにした話
前段
調べると、割とAWS SAM CLI
を使った方法が出てきて手順自体は簡単なのですが何してるかわからん...というふうになってしまったので、今回はそちらを使わない方法にしたいと思います。
準備するもの
- Lambdaのソースファイル
- Docker環境
手順
Lambdaで動かすソースコードの準備
自分の使っているコードが有る場合は
この記事を参考にエクスポートしてもらうと良いかと。
自分は今回このようなコードを準備しました。
Gemfile
source 'https://rubygems.org'
gem 'nokogiri'
lambda_funcrion.rb
require 'json'
require 'nokogiri'
require 'kconv'
require 'net/http'
require 'uri'
def lambda_handler(event:, context:)
area = URI.encode_www_form_component('横浜')
keyword = URI.encode_www_form_component('居酒屋')
uri = URI.parse("https://r.gnavi.co.jp/area/jp/rs/?fwp=#{area}&date=&fw=#{keyword}")
request = Net::HTTP::Get.new(uri)
request["Authority"] = "r.gnavi.co.jp"
request["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
request["Accept-Language"] = "ja"
request["Cache-Control"] = "max-age=0"
request["Cookie"] = "_td_global=2c7201bb-65d9-447f-8871-5c8aef70e6f0; _ra=1664900409701|e581f62a-e16f-4c02-97dc-b7ab203924e6; GU=e9f8c8528b624ea8cb1a55aadf705bd0; gt=GT139faa476002ac1e4ae520fpfeoIzpEqTDWQ1Rwc2xzV; ds=622474de466075942cc80e90a606c14bc6c474c7b09327535e48d587017aa4c0; gUser=03139faa476003ac1e4ae520L_I2dpFCb1q8eMMN-5-LmU; gcom=%7B%22login_type%22%3A1%2C%22guser_type%22%3A0%7D; _gid=GA1.3.151665219.1669743686; __gads=ID=61224817bcdcc5e2:T=1669743685:S=ALNI_MYQItLoO9f62eC3t0aHssBPge-pXQ; __gpi=UID=00000b86e8eecc21:T=1669743685:RT=1669743685:S=ALNI_MZ324J7fmISVdaU-A1IuoqdhZBTIQ; _ts_yjad=1669743686386; _fbp=fb.2.1669743686439.1159228644; _gcl_au=1.1.1560200380.1669743686; __pp_uid=INDH6tuKhtkFKURk66AXkbVTWOoJTybu; __lt__cid=c5331b8a-2798-4df1-9a8c-afb6b1af0c3c; __lt__sid=ab800c27-56441560; _dctagfq=1318:1670338799.0.0|1319:1670338799.0.0|1320:1670338799.0.0|1321:1670338799.0.0|1536:1670338799.0.0|1841:1670338799.0.0|1856:1670338799.0.0|1871:1670338799.0.0|1886:1670338799.0.0|1901:1670338799.0.0|1916:1670338799.0.0|1931:1670338799.0.0; ___o2u_o2id=0793b44a-526d-4f6f-bbfb-c923eb5be40f; rtosrch=gg7t002%3Dhttps%253A%252F%252Fr.gnavi.co.jp%252Farea%252Fjp%252Frs%252F%253Ffwp%253D%2525E6%2525A8%2525AA%2525E6%2525B5%25259C%2526date%253D%2526fw%253D%2525E3%252581%25259F%2525E3%252581%252593%2525E7%252584%2525BC%2525E3%252581%25258D%26a567203%3Dhttps%253A%252F%252Fr.gnavi.co.jp%252Farea%252Fjp%252Frs%252F%253Ffw%253D%2525E5%2525B1%252585%2525E9%252585%252592%2525E5%2525B1%25258B%2526fwp%253D%2525E6%2525A8%2525AA%2525E6%2525B5%25259C; GHistory=a567203%3A110%3A1669744542%2Cgg7t002%3A110%3A1669744522; ghistory_reserve=a567203%3A1669744542%2Cgg7t002%3A1669744522; __cribnotes_prm=__t_1669744547013_%7B%22uuid%22%3A%227a38921c-4d2e-4d30-bcb9-80d0686da7f0%22%7D; cto_bundle=sGp-Ll9kUDVaaHJYMHN3SkFtYyUyQkFmUTh2QWdka0pTQ0xqWlhHVFpETzB3ckh4dG1ya0J4NTB1Y0Z1V09mOUtMRDJWRlBKbDNSU2FTMWFDRXgyOFU4Q0l6UzAzYWFoWE9TMVd0WmVqOFZVcjBmQ3JHVHJyNkFodzhVcDlKTGdRYTBIWXNEdHdET3RzeENSc0Q0eDRGdUlwJTJGS3pWJTJGaXk1QW9sajk1ZUhJRHNaelpaYSUyRllHR0klMkZlNnhWWXIlMkJYTXB3eU00eUw; s_sess=%20sc_oncook%3D%3B%20sc_prop1%3Dr%3B%20cpnt_referer%3D%3B; _dc_gtm_UA-43329175-1=1; _td=639fb236-c732-4fb7-b246-0a6ed66c2a87; _ga_L9BHK8C28C=GS1.1.1669743685.1.1.1669744820.51.0.0; _ga=GA1.1.111137152.1669743686"
request["Sec-Ch-Ua"] = "\"Google Chrome\";v=\"105\", \"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"105\""
request["Sec-Ch-Ua-Mobile"] = "?0"
request["Sec-Ch-Ua-Platform"] = "\"macOS\""
request["Sec-Fetch-Dest"] = "document"
request["Sec-Fetch-Mode"] = "navigate"
request["Sec-Fetch-Site"] = "same-origin"
request["Sec-Fetch-User"] = "?1"
request["Upgrade-Insecure-Requests"] = "1"
request["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
req_options = {
use_ssl: uri.scheme == "https",
}
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
http.request(request)
end
doc = Nokogiri::HTML.parse(response.body.toutf8, nil, 'utf-8')
result = doc.css('.style_restaurantNameWrap__wvXSR').map do |target|
target.text
end
{ statusCode: 200, body: JSON.generate(result) }
end
中身の細かい話が知りたければ
これとか読んでください。
1文で要約すると「ぐるなびのサイトを検索して情報を店舗名を取得するコード」です。
これをLambdaで動かします。
Dockerfileの準備
次にDockerfileを準備します。
Lambdaのソースファイルが有るディレクトリにDockerfileを作成します。
そして下記のような記述をします。
FROM lambci/lambda:build-ruby2.7
CMD "/bin/bash"
そもそもなんでローカルのbundle installをしただけでは動かないのかという話なのですが、nokogiriなどのgemは環境ごとのネイティブなライブラリを参照しており、手元の環境とLambda環境ではそれが異なるために起きてしまっているんですね。
そのため、Lambda用のDocker環境を準備してそこでビルドしてあげたものをアップすればいいじゃんという話なのです!
Dockerビルド
下記のコマンドでDockerビルドを行います。
docker build -t lambda-ruby2.7 .
bundle install
次に下記のコマンドでDocker環境の中に入り
docker run --rm -it -v $PWD:/var/task -w /var/task lambda-ruby2.7
bundle installを行います
bundle install --path vendor/bundle --clean
終わったら
exit
で抜けます。
ソースファイルをZIP化
そしたらbundle installしたライブラリごとzip化します。
zip -q -r deploy.zip .
Lambdaにアップロード
最後にLambdaの環境にアップロードして、完了!!