๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป dev

puppeteer aws lambda์— ๋ฐฐํฌํ•˜๊ธฐ

puppeteer๋Š” ํฌ๋กฌ์ด๋‚˜ ํŒŒ์ด์–ดํญ์Šค์—์„œ ๋™์ž‘์„ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.
์˜ค๋Š˜์€ ์ด puppeteer๋ฅผ ํ†ตํ•ด ์›น์Šคํฌ๋ž˜ํ•‘์„ ํ•˜์—ฌ ์žฅ์†Œ ์ •๋ณด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ณ ์ž ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  aws ๋žŒ๋‹ค๋ฅผ ํ†ตํ•ด ๋ฐฐํฌํ•˜๋Š” ๊ณผ์ •์„ ๋‹ด์€ ํŠœํ† ๋ฆฌ์–ผ์„ ์ฐจ๊ทผํžˆ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ์ž‘์—…ํ•œ ์‚ฌํ•ญ์„ ๊ธฐ๋กํ•˜๊ณ ์ž ํ•œ๋‹ค.

๊ตฌ๊ธ€ place api๋ฅผ ์‚ฌ์šฉํ•˜๋Š๋ผ 30๋งŒ ์›์–ด์น˜ ์ด์ƒ์˜ ๋น„์šฉ์ด ๋ฐœ์ƒํ•ด์„œ,
์ฐจ์„ ํƒ์œผ๋กœ ์›น์Šคํฌ๋ž˜ํ•‘ํ•œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ ์จ๋ณด๋Š” ๋ฐฉํ–ฅ์„ ์„ ํƒํ•ด ๋ดค๋‹ค.
๋ฌผ๋ก  api์— ๋น„ํ•ด์„œ ์‘๋‹ต ์†๋„ ๋“ฑ์ด ๋Š๋ ค์งˆ ๋ถ€๋ถ„์ด ์—ผ๋ ค๋˜์ง€๋งŒ ๊ธˆ์•ก์ ์œผ๋กœ ์ตœ์„ ์ธ ๊ฑฐ ๊ฐ™๊ณ ,
ํ‰์†Œ์— ๋“ค์–ด๋ณด๊ธฐ๋งŒ ํ–ˆ๋˜ lambda์™€ docker๋ฅผ ๊ฒฝํ—˜ํ•ด ๋ณผ ์ข‹์€ ๊ฒฝํ—˜ ๊ฐ™์•„์„œ ๊ณต๋ถ€ ์‚ผ์•„ํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

์ค€๋น„๋ฌผ

- aws ๊ณ„์ • : lambda์— ๋ฐฐํฌ

   - aws sam CLI (์„ค์น˜ ๋งํฌ) : SAM(Serverless application model)๋กœ ์„œ๋ฒ„๋ฆฌ์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œ

   - access key ID์™€ secret key ID

- docker (์„ค์น˜ ๋งํฌ) : ๋ฐฐํฌ & ๋กœ์ปฌ์—์„œ ์ฝ”๋“œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด

๊ธฐ๋ณธ ์„ธํŒ…

npm init ์œผ๋กœ ํŒจํ‚ค์ง€๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŒจํ‚ค์ง€๋“ค์„ ์„ค์น˜ํ•ด ์ค€๋‹ค.

๋‹ค๋ฅธ ๋ฒ„์ „์œผ๋กœ ์„ค์น˜ํ–ˆ์„ ๊ฒฝ์šฐ ์ฝ”๋“œ๊ฐ€ ๋Œ์•„๊ฐ€์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•„๋ž˜์˜ ๋ฒ„์ „ ์ด์ƒ๋“ค๋กœ ์„ค์น˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

"@sparticuz/chromium": "^130.0.0",
"cheerio": "^1.0.0",
"puppeteer-cluster": "^0.24.0",
"puppeteer-core": "^23.6.1"

 

index.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด ์ฃผ์ž.

๊ทธ๋ฆฌ๊ณ  lambda์— ์ด๋ฏธ ์„ค์น˜๋œ chromium์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํŒŒ์ผ ์ตœ์ƒ๋‹จ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑํ•ด ์ฃผ์ž.

lambda์— ๋ฐฐํฌํ–ˆ์„ ๋•Œ, chromium์ด ๋‹ค์‹œ ๋‹ค์šด๋กœ๋“œ๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค.

PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = 1;

 

๋‹ค์Œ์œผ๋กœ๋Š” ์‚ฌ์šฉํ•  ํŒจํ‚ค์ง€๋“ค์„ ๋ถˆ๋Ÿฌ์™€ ์„ ์–ธํ•ด ์ฃผ์ž.

const chromium = require("@sparticuz/chromium");
const puppeteer = require("puppeteer-core");
const cheerio = require("cheerio");

exports.handler = async (event, context, callback) => {
// !!TODO ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ
}

 

์™ธ๋ถ€์—์„œ url์„ ๋ฐ›์•„์˜ค๊ณ  ์‹ถ๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด event.json ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์ž.

ํ…Œ์ŠคํŠธ๋กœ ์šฐ๋ฆฌ๊ฐ€ ํ˜„์žฌ ๋ฐ›์•„์˜ฌ ์˜ˆ์‹œ ์ง€์—ญ ์ •๋ณด๋Š” ๊ฒฝ๋ณต๊ถ์ด๋‹ค. 

{ "url": "https://www.google.com/maps/search/๊ฒฝ๋ณต๊ถ"}

https://www.google.com/maps/search/${query}์™€ ๊ฐ™์€ ํ˜•ํƒœ๋กœ url์„ ๋ฐ›์œผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŽ˜์ด์ง€๋ฅผ ๋ ์›Œ์ฃผ๋„๋ก ํ•˜์ž.

๋งŒ์•ฝ url์„ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด 400์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜์ž.

// ...์ƒ๋žต...
// https://www.google.com/maps/search/${query}
const websiteUrl = event.url;
if (!websiteUrl) {
	return { statusCode: 400, body: "Please provide an URL" };
}

const executablePath = await chromium.executablePath();

const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath,
    headless: chromium.headless,
    ignoreHTTPSErrors: true,
});

const page = await browser.newPage();

await page.goto(websiteUrl, { timeout: 0, waitUntil: "networkidle0" });

const contents = await page.content();

// ...์ƒ๋žต...

 

sam cli๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์ „์— ๋จผ์ € docker desktop์ด ์ž˜ ์„ค์น˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด ๋ณด์ž. 

์ผ๋‹จ์€ signin ํ•˜์ง€ ์•Š์€ ์ƒํƒœ๋กœ ์ง„ํ–‰ํ•ด๋„ ๋œ๋‹ค. default ๋กœ ์„ค์ •์„ ํ•˜๊ณ  ์„ค์น˜๊ฐ€ ์ž˜ ๋˜์—ˆ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฐฝ์ด ๋œจ๊ฒŒ ๋œ๋‹ค.

sam ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ด ์ฃผ๊ธฐ ์ „์— template.yml ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด ์ฃผ์ž.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•ด ์ฃผ์ž. puppeteer๋ฅผ ๋กœ๋“œํ•ด์ค˜์•ผ ํ•˜๋ฏ€๋กœ ๋ฉ”๋ชจ๋ฆฌ ํฌ๊ธฐ๋Š” 512(์ตœ๋Œ€)๋กœ ์„ค์ •ํ•ด ์ฃผ๋„๋ก ํ•œ๋‹ค.

Timeout ์„ค์ •์„ ํ•ด์ค˜์„œ ์ฝ”๋“œ๊ฐ€ ๋ฌดํ•œ์ • ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด ์ฃผ์ž.

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs18.x
      CodeUri: .
      MemorySize: 512
      Timeout: 300

 

vscode ํ„ฐ๋ฏธ๋„๋กœ ๋“ค์–ด์™€์„œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜๊ธฐ ์œ„ํ•ด sam build๋ฅผ ํ•ด์ฃผ์ž. (sam build๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜๊ณ ์ž ํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰ํ•ด์ค˜์•ผ ํ•œ๋‹ค.)

๋งŒ์•ฝ ํ„ฐ๋ฏธ๋„์—์„œ sam ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ–ˆ๋Š”๋ฐ ๋ถ‰์€ ๊ธ€์”จ๋ผ๋ฉด sam cli install ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ฑฐ๋‚˜ ์„ค์น˜๋ฅผ ํ•ด์ฃผ์ง€ ์•Š์•˜์„ ์ˆ˜ ์žˆ๋‹ค. (์„ค์น˜ ๋งํฌ)

 

๋กœ์ปฌ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋„์ปค๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ , ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„์—์„œ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค.

sam local invoke --event ./event.json

 

(์ ‘์€ ๊ธ€์€ ๊ตฌ๊ธ€ ์ง€๋„ ๊ฒ€์ƒ‰ ํŽ˜์ด์ง€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ฝ”๋“œ๋‹ค.)

๋”๋ณด๊ธฐ
// 	์ƒ๋žต
  const contents = await page.content();

  const $ = cheerio.load(contents);
  const results = [];

  const ifUndefinedReturnNull = (item) => {
    if (item == null || item == "") {
      return null;
    } else {
      return item;
    }
  };

  const container = $(".m6QErb.DxyBCb.kA9KIf.dS8AEf.XiKgde.ecceSd > div");

  // Results๋ผ๊ณ  ์•ˆ๋‚˜์˜ค๊ณ  ๋ฐ”๋กœ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ฃผ์–ด์ง€๋Š” ๊ฒฝ์šฐ
  if (
    (container.html() != null && $("h1").text(),
    ifUndefinedReturnNull($("h1").text()) == null)
  ) {
    let tags = null;
    let info = null;
    let address = null;
    let placeName = null;
    let openCloseInfo = null;
    let otherInfo = [];
    let price = null;

    container.children().each((i, el) => {
      const item = $(el).find(".Nv2PK.Q2HXcd.THOPZb");
      const tempPlaceName = item.find(".fontHeadlineSmall").text();
      if (tempPlaceName != null) {
        placeName = tempPlaceName;
      }
      const tempTags = item
        .find(".fontBodyMedium > .W4Efsd > .AJB7ye > span")
        .eq(1)
        .text();

      if (tempTags != null) {
        tags = tempTags;
      }

      const tempPrice = item
        .find(".fontBodyMedium > .W4Efsd > .AJB7ye > span")
        .eq(2)
        .find("span")
        .last()
        .text();

      if (tempPrice != null) {
        price = tempPrice;
      }

      const tempInfo = item
        .find(".fontBodyMedium > .W4Efsd > .W4Efsd > span")
        .eq(0)
        .find("span")
        .text();

      if (tempInfo != null) {
        info = tempInfo;
      }

      const tempAddress = item
        .find(".fontBodyMedium > .W4Efsd > .W4Efsd > span")
        .eq(1)
        .find("span")
        .last()
        .text();

      if (tempAddress != null) {
        address = tempAddress;
      }

      const tempOpenCloseInfo = item
        .find(".fontBodyMedium > .W4Efsd > .W4Efsd")
        .last()
        .find("span > span > span")
        .text();

      if (tempOpenCloseInfo != null) {
        openCloseInfo = tempOpenCloseInfo;
      }

      const tempOtherInfo = item
        .find(".qty3Ue > .fontBodySmall > .Ahnjwc.fontBodyMedium ")
        .text();

      if (tempOtherInfo != null) {
        otherInfo = tempOtherInfo;
      }

      if (placeName != null && placeName != "") {
        results.push({
          placeName,
          tags: ifUndefinedReturnNull(tags),
          price: ifUndefinedReturnNull(price),
          info: ifUndefinedReturnNull(info),
          address: ifUndefinedReturnNull(address),
          openCloseInfo: ifUndefinedReturnNull(openCloseInfo),
          otherInfo: ifUndefinedReturnNull(otherInfo),
        });
      }
    });

    const response = {
      statusCode: 200,
      contents: results,
    };

    return response;
  }

  //  ๋‹จ์ผ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ
  else if (ifUndefinedReturnNull($(".DUwDvf.lfPIob").text().trim()) != null) {
    const searchResults = $(
      "div.w6VYqd > div.bJzME.tTVLSc > div.k7jAl.miFGmb.lJ3Kh > div.e07Vkf.kA9KIf > div.aIFcqe > div.m6QErb.WNBkOb.XiKgde"
    );

    let img = null;
    let tags = null;
    let info = null;
    let address = null;
    let placeName = null;
    let openCloseInfo = null;
    let otherInfo = [];
    let price = null;

    if (searchResults != null) {
      searchResults.children().each((index, element) => {
        const imgs = $(element)
          .find(".ZKCDEc > .RZ66Rb.FgCUCc > button > img")
          .attr("src");
        if (ifUndefinedReturnNull(imgs) != null) {
          img = imgs;
        }

        const tempPlaceName = $(element)
          .find(".TIHn2 > .tAiQdd > .lMbq3e > div > h1")
          .text()
          .trim();

        if (ifUndefinedReturnNull(tempPlaceName) != null) {
          placeName = tempPlaceName;
        }

        const tempTag = $(element)
          .find(
            ".TIHn2 > .tAiQdd > .lMbq3e > div.LBgpqf > div.skqShb > div.fontBodyMedium.dmRWX > div.F7nice"
          )
          .text()
          .trim();

        if (ifUndefinedReturnNull(tempTag) != null) {
          tags = tempTag;
        }

        const tempPrice = $(element)
          .find(
            ".TIHn2 > .tAiQdd > .lMbq3e > div.LBgpqf > div.skqShb > div.fontBodyMedium > span > span > span > span"
          )
          .not(".google-symbols")
          .not(".fjHK4")
          .text()
          .trim();

        if (ifUndefinedReturnNull(tempPrice) != null) {
          price = tempPrice;
        }

        const tempInfo = $(element)
          .find(
            ".TIHn2 > .tAiQdd > .lMbq3e > div.LBgpqf > div.skqShb > div.fontBodyMedium > span > span > .DkEaL"
          )
          .not(".google-symbols")
          .not(".fjHK4")
          .text()
          .trim();

        if (ifUndefinedReturnNull(tempInfo) != null) {
          info = tempInfo;
        }

        const tempAddress = $(element)
          .find(".Io6YTe.fontBodyMedium.kR99db.fdkmkc")
          .eq(0)
          .text()
          .trim();

        if (ifUndefinedReturnNull(tempAddress) != null) {
          address = tempAddress;
        }

        const tempOpenCloseInfo = $(element).find(".ZDu9vd").text().trim();

        if (ifUndefinedReturnNull(tempOpenCloseInfo) != null) {
          openCloseInfo = tempOpenCloseInfo;
        }

        $(element)
          .find(".E0DTEd")
          .children("div.LTs0Rc")
          .each((_, el) => {
            const tempOtherInfo = $(el).attr("aria-label");
            if (ifUndefinedReturnNull(tempOtherInfo) != null) {
              otherInfo.push(tempOtherInfo);
            }
          });
      });

      const response = {
        statusCode: 200,
        contents: [
          {
            placeName,
            tags,
            price,
            info,
            address,
            img,
            openCloseInfo,
            otherInfo: otherInfo.join(" · "),
          },
        ],
      };

      return response;
    }
  } else {
    // w6VYqd > bJzME tTVLSc >  k7jAl miFGmb lJ3Kh PLbyfe > e07Vkf kA9KIf > aIFcqe > m6QErb WNBkOb XiKgde > m6QErb DxyBCb kA9KIf dS8AEf XiKgde ecceSd
    const searchResults = $(
      "div.w6VYqd > div.bJzME.tTVLSc > div.k7jAl.miFGmb.lJ3Kh.PLbyfe > div.e07Vkf.kA9KIf > div.aIFcqe > div.m6QErb.WNBkOb.XiKgde > div.m6QErb.DxyBCb.kA9KIf.dS8AEf.XiKgde.ecceSd > div.m6QErb.DxyBCb.kA9KIf.dS8AEf.XiKgde.ecceSd"
    );

    const results = [];

    if (searchResults != null) {
      // Iterate through each matching element
      searchResults.children().each((index, element) => {
        const result = $(element)
          .find("div > div")
          .not(".JrN27d.SuV3fd.Zjt37e.TGiyyc") // Results (์—ฌ๋Ÿฌ๊ฐœ์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ)
          .not(".m6QErb.XiKgde.z7i0C"); // Convert the element to a Cheerio object
        const img = $(result).find("img").attr("src");
        const placeName = $(result).find(".fontHeadlineSmall").text();

        const tags = $(result)
          .find(".fontBodyMedium > span")
          ?.attr("aria-label");

        const price = $(result)
          .find(".AJB7ye > span")
          .eq(2)
          ?.find("span")
          ?.eq(1)
          .text()
          .trim();

        const info = $(result)
          .find(".W4Efsd > .W4Efsd > span > span")
          .eq(0)
          .text()
          .trim();

        const address = $(result)
          .find(".W4Efsd > .W4Efsd")
          .eq(0)
          .find("span")
          .not(".google-symbols")
          .last()
          .text()
          .trim();

        const openCloseInfo = $(result)
          .find(".W4Efsd > span > span > span")
          .not(".doJOZc")
          .text()
          .trim();

        const otherInfo = $(result).find(".qty3Ue").text().trim();

        if (placeName != null && placeName != "") {
          results.push({
            placeName,
            tags: ifUndefinedReturnNull(tags),
            price: ifUndefinedReturnNull(price),
            info: ifUndefinedReturnNull(info),
            address: ifUndefinedReturnNull(address),
            openCloseInfo: ifUndefinedReturnNull(openCloseInfo),
            img: ifUndefinedReturnNull(img),
            otherInfo: ifUndefinedReturnNull(otherInfo),
          });
        }
      });
    }
    const response = {
      statusCode: 200,
      contents: results,
    };

    return response;
  }
  
  // 	์ƒ๋žต

์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ™•์ธ๊ฐ€๋Šฅํ•˜๋‹ค.

 

ํ•˜์ง€๋งŒ ์ผ์ผ์ด ์ด ๊ธด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ๊ท€์ฐฎ์œผ๋ฏ€๋กœ 

package.json์— ์ž‘์„ฑํ•ด ์ฃผ์ž. ์ด์ œ npm run test ๋กœ ๋กœ์ปฌ์—์„œ ์‹คํ–‰๊ฐ€๋Šฅํ•˜๋‹ค.

 // ์ƒ๋žต
{
 	"scripts": {
        "build": "sam build",
        "test": "sam local invoke --event ./event.json",
}
// ์ƒ๋žต

 

aws cli ์„ค์น˜๋ฅผ ์™„๋ฃŒํ—€๋‹ค๋ฉด ์ด์ œ ์„ค์ •์„ ํ•ด์ฃผ์ž. 

(accessํ‚ค์™€ secret ํ‚ค๊ฐ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ์ด๋ฏธ์ง€๋Š” ํŠœํ† ๋ฆฌ์–ผ ์˜์ƒ์˜ ์Šคํฌ๋ฆฐ์ˆ์œผ๋กœ ๋Œ€์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค.)

์ค€๋น„ํ•ด ๋‘” aws access key์™€ secret access ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์ž. 

region์€ ์ง€์—ญ ์ •๋ณด(ap-northeast-2)๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ๋ฉด ๋œ๋‹ค. 

output format์€ ๋”ฐ๋กœ ์ง€์ •ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

ํ„ฐ๋ฏธ๋„์—์„œ sam deploy ๋ฅผ ํ•ด์ฃผ์ž.

Deploy this changeset ๋ฌธ๊ตฌ๊ฐ€ ๋‚˜์˜ค๋ฉด 'y'๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์ž.

aws lambdaํŽ˜์ด์ง€๋กœ ๋“ค์–ด๊ฐ€๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด function์ด ๋งŒ๋“ค์–ด์ง„๋‹ค.

 

๋ฐฐํฌ๋œ lambda function์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋Š”๋ฐ, ๋” ํŽธํ•œ ๊ฑธ๋กœ ํ•˜๋ฉด ๋œ๋‹ค.

  1. ํ„ฐ๋ฏธ๋„์—์„œ
  2. cli๋กœ

ํ„ฐ๋ฏธ๋„์—์„œ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Funciton ARN์„ ๋ณต์‚ฌํ•ด ์ฃผ์ž.

ํ„ฐ๋ฏธ๋„๋กœ ๋‹ค์‹œ ๋Œ์•„์™€์„œ ๋‹ค์Œ์„ ์ž…๋ ฅํ•ด ์ฃผ์ž.

sam remote invoke ARN๋ณต์‚ฌ๋ถ™์—ฌ๋†“๊ธฐ --event-file ./event.json

 

ARN์„ ํ•ญ์ƒ ๋ณต์‚ฌํ•ด ์˜ค๋Š” ๋ฒˆ๊ฑฐ๋กœ์šด ์ผ ๋Œ€์‹ ,  package.json์— ๋ช…๋ น์–ด๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ์ž.

 // ์ƒ๋žต
"build": "sam build",
"deploy": "sam deploy",
"test": "sam local invoke --event ./event.json",
// ์ถ”๊ฐ€ํ•ด์ฃผ๊ธฐ
"prod": "sam remote invoke ARN_๋ณต์‚ฌ๋ถ™์—ฌ๋†“๊ธฐ --event-file ./event.json"
// ์ƒ๋žต

 

์ด์ œ npm run prod์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” ํŠœํ† ๋ฆฌ์–ผ ์˜์ƒ ์—†์ด ์ง„ํ–‰๋˜๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

 

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” sam cli์„  ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด Test tab์— ๋“ค์–ด๊ฐ€๋ฉด 

๋‹ค์Œ๊ณผ ๊ฐ™์ด param์„ ๋ณ€๊ฒฝํ•˜์—ฌ ๋ฐฐํฌ๋œ lambda function์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.

์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋‹ค๋ฅธ ๊ฒ€์ƒ‰์–ด 'ํ•œ๋ผ์‚ฐ'์œผ๋กœ ๋ณ€๊ฒฝ ํ›„ ๋‹ค์‹œ test๋ฅผ ๋Œ๋ฆฌ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

{
  "statusCode": 200,
  "contents": [
    {
      "placeName": "ํ•œ๋ผ์‚ฐ",
      "tags": "4.7(729)",
      "price": null,
      "info": "์‚ฐ๋ด‰์šฐ๋ฆฌ",
      "address": "์„œ๊ท€ํฌ์‹œ",
      "img": "https://lh5.googleusercontent.com/p/AF1QipOAkhVKrq3broFnCMCx4sdqm45jxANDfoC2k3bi=w426-h240-k-no",
      "openCloseInfo": null,
      "otherInfo": ""
    }
  ]
}

๋งˆ์น˜๋ฉด์„œ

์ฒซ lambda ํ•จ์ˆ˜ ๋งŒ๋“ค์–ด/๋ง›๋ณด๊ธฐ!
์ฒ˜์Œ์—๋Š” vercel๋กœ ๋ฐฐํฌํ•ด์„œ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ–ˆ์œผ๋‚˜ hobby ํ”Œ๋žœ์ด์–ด์„œ ๊ทธ๋Ÿฐ์ง€
๊ฐœ๋ฐœํ™˜๊ฒฝ์—์„œ ์ž˜๋งŒ ๋ฐฐํฌ๋˜๊ณ  ๋Œ์•„๊ฐ€๋˜ ์ฝ”๋“œ๊ฐ€ ๋Œ์•„๊ฐ€์ง€๋„ ๋ชปํ•ด์„œ ์ขŒ์ ˆํ–ˆ์—ˆ๋‹ค. ๊ธฐ๊ป puppeteer๊นŒ์ง€ ์จ๊ฐ€๋ฉด์„œ ํ•ด๋’€๋”๋‹ˆ ใ…œใ…œ
์„ฑ๊ณต์ ์ด์–ด์„œ ํ”„๋กœ์ ํŠธ์— ๋ถ™์˜€์ง€๋งŒ ์•„์‰ฌ์šด ์ ์€
์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ์—๋„ ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆฌ๋Š”๋ฐ, 
์—ฌํ–‰ ์ผ์ •์„ ์งœ๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ (์ตœ์†Œ 3๊ตฐ๋ฐ)๋ฅผ ์ฐพ๋Š” ๋ฐ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์„ ์–ด๋–ป๊ฒŒ ์ค„์ผ ์ˆ˜ ์žˆ์„์ง€.
์›๋ž˜๋Š” puppeteer cluster๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๋ ค๊ณ  ํ–ˆ๋‹ค๊ฐ€ ๋ฒ„์ „ ์ด์Šˆ๋•Œ๋ฌธ์ธ์ง€ ์‹คํŒจ..
์•„์‰ฝ์ง€๋งŒ ์ผ๋‹จ ๋งŒ๋“ค๊ณ  ๊ฐœ์„ ํ•˜๋Š” ๋ฐฉํ–ฅ์„ ์ƒ๊ฐํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

References

https://www.youtube.com/watch?v=INlCCRdOfj4