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

CS/ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ…

Serverless Discord Chatbot with AWS Lambda

728x90

๊ฐœ์š”

AWS Lambda ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋””์Šค์ฝ”๋“œ ์ฑ—๋ด‡์„ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค.
๊ทธ ๊ณผ์ •์—์„œ API Gateway, Lambda function, and Amazon SNS (Simple Notification Service) ์— ๋Œ€ํ•œ ์‹ค์Šต ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ๊ฒƒ์ด๋‹ค.

๊ธฐ๋Šฅ

  1. /time : ํ˜„์žฌ ์‹œ๊ฐ„ ๋ณด์—ฌ์ฃผ๊ธฐ
  2. /rice : ๊ณต๋Œ€ ์‹๋‹น ์˜ค๋Š˜์˜ ๋ฉ”๋‰ด ๋ณด์—ฌ์ฃผ๊ธฐ
  3. /email : ๋‹น์‹ ์˜ ์ •๋ณด๊ฐ€ ๋‹ด๊ธด ์ด๋ฉ”์ผ ์ „์†ก

๋ณธ๊ฒฉ์ ์ธ ๊ฐœ๋ฐœ ์‹œ์ž‘์— ์•ž์„œ์„œ developer mode ๋ฅผ ํ™œ์„ฑํ™”์‹œ์ผœ์•ผํ•œ๋‹ค.
Discord UI ํ•˜๋‹จ์— User Settings ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  Advanced ํƒญ์— ๊ฐ€์„œ Developer Mode ๋ฅผ ํ™œ์„ฑํ™”์‹œํ‚จ๋‹ค.

 

 

๋ด‡ ์„ค์ •ํ•˜๊ธฐ

 

Discord’s Developer Portal (https://discord.com/developers/applications) ์— ๊ฐ€์„œ New Application ๋ฒ„ํŠผ์„ ํ†ตํ•ด ์›ํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์„ฑํ•œ๋‹ค.

 

 

 

์ขŒ์ธก์—  ์žˆ๋Š” OAuth2 – URL Generator ๋ฉ”๋‰ด์— ๋“ค์–ด๊ฐ€์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•œ ํ›„ ๋งจ ์•„๋ž˜์— ์Šคํฌ๋กคํ•˜์—ฌ generated URL๋ฅผ ํ™•์ธํ•˜๊ณ  ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ—ค๋‹น ๋งํฌ์— ์ ‘์†ํ•œ๋‹ค.

 

 

์ปค๋งจ๋“œ ์„ค์ •ํ•˜๊ธฐ

 

์ง€๊ธˆ์€ ๋ด‡์— ์ƒˆ๋กœ์šด / ๋ช…๋ น์–ด๋ฅผ ๋“ฑ๋กํ•  ๊ฒƒ์ด๋‹ค. Discord๋Š” ๋ด‡์ด ์–ด๋–ค ๋ช…๋ น์–ด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ์ž๋™์œผ๋กœ ํŒŒ์•…ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ ๋ช…๋ น์–ด๋ฅผ ์ง์ ‘ ์•Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค. ์•ˆํƒ€๊น๊ฒŒ๋„ ์ด๋ฅผ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)๋Š” ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉฐ ํ˜„์žฌ๋กœ์„œ๋Š” HTTP ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ด๋ฅผ ์œ„ํ•ด register_command.py๋ผ๋Š” ์ด๋ฆ„์˜ Python ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.

 

import requests
APP_ID = ""
SERVER_ID = ""
BOT_TOKEN = ""
url = f''
json = [
 {
 'name': 'time',
 'description': 'Shows the current time',
 'options': []
 },
 {
 'name': 'rice',
 'description': 'Provides today\'s menu at Engineering Building Cafeteria',
2024-2 Cloud Computing (01)
-4-
 'options': []
 },
 {
 'name': 'email',
 'description': 'Send an email message',
 'options': []
 }
]
response = requests.put(url, headers={'Authorization': f'Bot {BOT_TOKEN}'}, json=json)
print(response.json())

 

์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒ์„ธ์ •๋ณด ์ฐฝ์— ๋ช…์‹œ๋œ ๊ฒƒ์„ ๋ฐ”ํƒ•์œผ๋กœ APP_ID(application ID), SERVER_ID,  BOT_TOKEN ์„ ์ฑ„์šด๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

python3 register_command.py

 

 

๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต ์ฐฝ์ด ๋œจ๋ฉด ๋“ฑ๋ก์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋œ ๊ฒƒ์ด๋‹ค.

 

 

 

https://discord.com/oauth2/authorize?client_id=1313891142965792890&permissions=8&integration_type=0&scope=bot

 

Discord - Group Chat That’s All Fun & Games

Discord is great for playing games and chilling with friends, or even building a worldwide community. Customize your own space to talk, play, and hang out.

discord.com

 

 

 BeautifulSoup library์œผ๋กœ ์›นํŽ˜์ด์ง€ ํŒŒ์‹ฑํ•˜๊ธฐ 

 

์•„๋ž˜๋Š” ๊ธฐ๋ณธ์ ์ธ BeautifulSoup์˜ ํŠœํ† ๋ฆฌ์–ผ์ด๋‹ค.

https://www.scrapingbee.com/blog/python-web-scraping-beautiful-soup/

 

BeautifulSoup tutorial: Scraping web pages with Python

In this tutorial, we will learn how to scrape the web using BeautifulSoup and CSS selectors with step-by-step instructions.

www.scrapingbee.com

 

์šฐ๋ฆฌ๋Š” ์•„๋ž˜ ๊ณต๋Œ€์‹๋‹น ์›น์‚ฌ์ดํŠธ๋ฅผ ํŒŒ์‹ฑํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.

https://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10

 

๊ณต๋Œ€์‹๋‹น

 

ewha.ac.kr

 

 

Getting the HTML

 

๊ฐ€์žฅ ๋จผ์ €, HTTP GET request ๋ฅผ ํ†ตํ•˜์—ฌ URL๋กœ๋ถ€ํ„ฐ contents๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.

์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ Python Requests library ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

HTML data ๋ฅผ ๋นผ์˜จ ํ›„์—๋Š” ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ๋นผ๋‚ด๊ธฐ ์œ„ํ•ด์„œ BeautifulSoup ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด์„œ ํŠœํ† ๋ฆฌ์–ผ์— ๋‚˜์™€์žˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์€ main.py ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์‹คํ–‰์‹œ์ผฐ๋‹ค.

 

import requests
from bs4 import BeautifulSoup

response = requests.get("https://news.ycombinator.com/")
html_content = response.content

# Use Beautiful Soup to parse the HTML
soup = BeautifulSoup(html_content, "html.parser")

 

 

๊ทธ๋Ÿฐ๋ฐ HTML ์„ ๋ฐ›์•„์˜จ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊นจ์ง€๋Š” ํ˜„์ƒ์„ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

์ฒ˜์Œ์—” ์ธ์ฝ”๋”ฉ ์„ค์ •์ด ์ž˜๋ชป๋˜์—ˆ๋‚˜ํ–ˆ๋Š”๋ฐ, ์•Œ๊ณ ๋ณด๋‹ˆ response.encoding ์„ ํ™•์ธํ•ด๋ณธ ๊ฒฐ๊ณผ ๋™์ผํ•˜๊ฒŒ utf-8 ๋กœ ์„ค์ •์ด ๋˜์–ด์žˆ์—ˆ๋‹ค.

๋ญ๊ฐ€ ๋ฌธ์ œ์ง€.. ํ•˜๊ณ  ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐพ์•„๋ณด๋Š”๋ฐ response.text ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

https://requests.readthedocs.io/en/latest/user/quickstart/#response-content

 

 

response.content ์™€ response.text ์˜ ์ฐจ์ด์ ์ด ๊ถ๊ธˆํ•ด์„œ ์ฐพ์•„๋ณด์•˜๋Š”๋ฐ

 

- response.content :  bytes (๋ฐ”์ดํŠธ์—ด) , ์„œ๋ฒ„์—์„œ ์ „์†ก๋œ ์›์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜, ์‘๋‹ต ๋ฐ์ดํ„ฐ์˜ ์ธ์ฝ”๋”ฉ๊ณผ ๊ด€๊ณ„์—†์ด, ๋ณ€ํ™˜ ์—†์ด ์›๋ณธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜

- response.text : str (๋ฌธ์ž์—ด), ์„œ๋ฒ„์—์„œ ์ „์†ก๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •๋œ ์ธ์ฝ”๋”ฉ์œผ๋กœ ๋””์ฝ”๋”ฉํ•˜์—ฌ ๋ฐ˜ํ™˜, ์„œ๋ฒ„ ์‘๋‹ต์˜ Content-Type ํ—ค๋”์— ๋ช…์‹œ๋œ ์ธ์ฝ”๋”ฉ์„ ์‚ฌ์šฉ

 

๊ฒฐ๊ตญ content ๋Š” raw binary data ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๊ณ  text ๋Š” ๋””์ฝ”๋”ฉ ํ›„์— ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๋Š” ์ ์ด ๋‹ฌ๋ž๋‹ค.

๊ทธ๋ž˜์„œ content -> text ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์‚ด์ง ์ˆ˜์ •ํ•ด์ฃผ์—ˆ๋”๋‹ˆ ์ด์ œ ํ•œ๊ธ€๋กœ ์ž˜ ๋‚˜์˜จ๋‹ค ใ…Žใ…Ž

import requests

# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8'  # ๊ฐ•์ œ๋กœ UTF-8 ์„ค์ •

# Check if the request was successful (status code 200)
if response.status_code == 200:
    html_content = response.text
    print(html_content)
else:
	print(response)

 

 

์ „์ฒด ์•„ํ‚คํ…์ณ ๊ตฌ์กฐ ์„ค๊ณ„

 

AWS API Gateway์™€ Lambda ํ•จ์ˆ˜๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์—์„œ ํ•ต์‹ฌ์ ์œผ๋กœ ์—ฐ๊ณ„๋˜์–ด ์‚ฌ์šฉ๋œ๋‹ค.

 

 

  • API Gateway: ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(์›น, ๋ชจ๋ฐ”์ผ, IoT ๋“ฑ)์ด ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณต. ์š”์ฒญ์„ ๋ฐ›์•„ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋กœ ์ „๋‹ฌํ•˜๊ณ , ์‘๋‹ต์„ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜
  • Lambda ํ•จ์ˆ˜: API Gateway๊ฐ€ ์ „๋‹ฌํ•œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ์ปดํ“จํŒ… ๋ฆฌ์†Œ์Šค. ์‹ค์ œ๋กœ ์š”์ฒญ์— ๋Œ€ํ•œ ๋กœ์ง(๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…, ์™ธ๋ถ€ ์„œ๋น„์Šค ํ˜ธ์ถœ ๋“ฑ)์„ ์‹คํ–‰

์ž‘๋™ํ๋ฆ„

 

 

  • ํด๋ผ์ด์–ธํŠธ(์˜ˆ: ๋ธŒ๋ผ์šฐ์ € ๋˜๋Š” ๋ชจ๋ฐ”์ผ ์•ฑ)๊ฐ€ API Gateway์— ์š”์ฒญ(์˜ˆ: HTTP POST)์„ ๋ณด๋‚ธ๋‹ค.
  • API Gateway๋Š” ์š”์ฒญ์„ Lambda ํ•จ์ˆ˜๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  • Lambda ํ•จ์ˆ˜๋Š” ์ง€์ •๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์‹คํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ(์‘๋‹ต ๋ฐ์ดํ„ฐ ๋˜๋Š” ์˜ค๋ฅ˜)๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  • API Gateway๋Š” Lambda ํ•จ์ˆ˜์˜ ์‘๋‹ต์„ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•œ๋‹ค.

์ฆ‰, ์šฐ๋ฆฌ๋Š” API Gateway ๋ฅผ ์ง„์ž…์ ์œผ๋กœ ํ•˜๊ณ , Lambda Function ์„ ์ปดํ“จํŒ… ๋ฆฌ์†Œ์Šค๋กœ ์‚ฌ์šฉํ•˜๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ด๋‹ค.

 

 

Lambda Function ์ƒ์„ฑํ•˜๊ธฐ

์ด๋ฅผ ์œ„ํ•ด ๊ฐ€์žฅ ๋จผ์ € Lambda Function์„ ์ƒ์„ฑํ•œ๋‹ค. AWS Lambda Console ๋กœ ๊ฐ€์„œ Create function ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค. ๋‹ค์Œ ํŽ˜์ด์ง€์—์„œ Author from scratch ๋ฅผ ์„ ํƒํ•˜๊ณ  function์— ์ด๋ฆ„์„ ๋ถ€์—ฌํ•œ๋‹ค. ๋Ÿฐํƒ€์ž„์€ Python 3.8, ์•„ํ‚คํ…์ณ๋Š” x86_64์œผ๋กœ ์„ค์ •ํ•˜๊ณ  ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

 

import json
import requests
from bs4 import BeautifulSoup
import datetime
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError
import pytz
import json
import boto3
client = boto3.client('sns')
PUBLIC_KEY = ?
PING_PONG = {"type": 1}
RESPONSE_TYPES = { "PONG": 1,
 "ACK_NO_SOURCE": 2,
 "MESSAGE_NO_SOURCE": 3,
 "MESSAGE_WITH_SOURCE": 4,
 "ACK_WITH_SOURCE": 5}
def verify_signature(event):
 raw_body = event.get("rawBody")
 signature = event['params']['header'].get('x-signature-ed25519')
 timestamp = event['params']['header'].get('x-signature-timestamp')

 verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
 verify_key.verify(f'{timestamp}{raw_body}'.encode(), bytes.fromhex(signature))
def ping_pong(body):
 if body.get("type") == 1:
 return True
 return False

def lambda_handler(event, context):
# verify the signature
 try:
 verify_signature(event)
 except Exception as e:
 raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")
 # check if message is a ping
 body = event.get('body-json')
 if ping_pong(body):
 return PING_PONG

 if body.get("type") == 2:
 return command_handler(body)

 # dummy return
 return {
 "type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
 "data": {
 "tts": False,
 "content": "BEEP BOOP",
 "embeds": [],
 "allowed_mentions": []
 }
 }
def command_handler(b):
 command = b['data']['name']
 if command == 'rice':
 pass # Fill in here
 elif command == 'time':
 timeZ_S = pytz.timezone('Asia/Seoul')
 dt = datetime.datetime.now(timeZ_S)
 return {'type': 4,
 'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
 }
 elif command == 'email':
 pass # Fill in here

 

 

โ‘  /rice command ์— ๋Œ€ํ•˜์—ฌ ์›นํŽ˜์ด์ง€๋ฅผ ํŒŒ์‹ฑํ•˜๊ธฐ ์œ„ํ•ด BeautifulSoup library ๋ฅผ ์ด์šฉํ•  ๊ฒƒ์ด๋‹ค.

โ‘ก /email command ์— ๋Œ€ํ•˜์—ฌ ์ด๋ฉ”์ผ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•˜์—ฌ boto3 library  ๋ฅผ ์ด์šฉํ•  ๊ฒƒ์ด๋‹ค.

 

 

Parsing the HTML with Beautiful Soup

๊ทธ๋Ÿฐ๋ฐ  BeautifulSoup ๋ฅผ ์ด์šฉํ•  ๋•Œ์—๋Š”  text representation์„ decode ํ•˜๋Š” ๊ฒƒ์— ๋” ์šฉ์ดํ•ด response.content๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹๋‹ค๊ณ  ํ•œ๋‹ค. character encoding ์œผ๋กœ ์ธํ•œ ์ž ์žฌ์  ์ด์Šˆ๋ฅผ ๋ฐฉ์ง€ํ•ด์ฃผ๋Š” ํšจ๊ณผ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

BeautifulSoup ๋Š” HTML content ์™€ parser ๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋ฐ, ์ด ๋ถ€๋ถ„์— content ๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  get_text()๋ฅผ ํ†ตํ•˜์—ฌ ์›นํŽ˜์ด์ง€์˜ ํ…์ŠคํŠธ๋“ค์„ ๋ชจ๋‘ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

 

import requests
from bs4 import BeautifulSoup

# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8'  # ๊ฐ•์ œ๋กœ UTF-8 ์„ค์ •

# Check if the request was successful (status code 200)
if response.status_code == 200:
    html_content = response.content
    #print(html_content)
    # Use Beautiful Soup to parse the HTML
    soup = BeautifulSoup(html_content, "html.parser")
    
    # The title tag of the page
    print(soup.title)

    # The title of the page as string
    print(soup.title.string)

    # Text from the page
    print(soup.get_text())
else:
	print(response)

 

๊ฒฐ๊ณผ

๊ฒฐ๊ณผ

 

 

Targeting DOM elements

 

BeautifulSoup์„ ์‚ฌ์šฉํ•˜์—ฌ ์›น ์Šคํฌ๋ž˜ํ•‘์„ ํ•  ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ž‘์—… ์ค‘ ํ•˜๋‚˜๋Š” ํŠน์ • DOM(Document Object Model) ์š”์†Œ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜์—ฌ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ด๋‹ค. DOM์€ ์›น ๋ฌธ์„œ๋ฅผ ์œ„ํ•œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ์›นํŽ˜์ด์ง€์˜ HTML์„ upside-down tree๋กœ์„œ ์ƒ์ƒํ•ด๋ณด์ž. ๊ฐ HTML ์š”์†Œ(ํ—ค๋”ฉ, ๋ฌธ๋‹จ, ๋งํฌ ๋“ฑ)๋Š” ์ด ํŠธ๋ฆฌ์˜ ๋…ธ๋“œ์ด๋‹ค. 

 

BeautifulSoup(BS4)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•„์š”ํ•œ DOM ์š”์†Œ๋ฅผ ๋น ๋ฅด๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ๋Œ€์ƒ์œผ๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์•„๋ž˜๋Š” Beautiful Soup์ด DOM ๋‚ด์—์„œ ์ด๋Ÿฌํ•œ ์š”์†Œ๋ฅผ ๋Œ€์ƒ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•๋“ค์ด๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํŠน์ • ํƒœ๊ทธ๋‚˜ ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด ์›ํ•˜๋Š” ์š”์†Œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

 

soup.find("a")
soup.find_all("span")

soup.find(class_="athing")
soup.find_all(class_="athing")

 

 

์šฐ๋ฆฌ๊ฐ€ ๋ฐ›์•„์™€์•ผ ํ•˜๋Š” ์ •๋ณด๋Š” ์˜ค๋Š˜ ๋‚ ์งœ์— ํ•ด๋‹นํ•˜๋Š” ์ค‘์‹ ๋ฉ”๋‰ด ๋ชฉ๋ก์ด๋‹ค. 

๋”ฐ๋ผ์„œ  HTML ๊ฐ€ ์–ด๋–ค ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ๊ณ  ํ•ด๋‹น ์ ์‹ฌ ๋ฉ”๋‰ด Text ๊ฐ€ ์–ด๋–ค ํƒœ๊ทธ, ํด๋ž˜์Šค์— ํ•ด๋‹นํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•œ๋‹ค.

 

 

 

๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด์–ด์„œ HTML ์„ ํ™•์ธํ•ด๋ณด๋‹ˆ ul ํƒœ๊ทธ์˜ class = b-menu-box ์— ์ „์ฒด ๋ฐ•์Šค ๋‚ด์šฉ์ด ๋‹ด๊ฒจ์žˆ๊ณ ,

๊ทธ ํ•˜์œ„์— ํ•ด๋‹นํ•˜๋Š” li ํƒœ๊ทธ์˜ b-menu-day.mon ์— ๊ฐ ๋‚ ์งœ์— ํ•ด๋‹นํ•˜๋Š” ๋ฐ•์Šค๊ฐ€ ๋‹ด๊ธด๋‹ค.

 

 

 

๊ทธ๋ฆฌ๊ณ  ๊ทธ ๋ฐ•์Šค ๋‚ด๋ถ€์—๋Š” brunch ์™€ lunch ๋กœ ๋‚˜๋ˆ„์–ด์ง„ ์˜์—ญ(div)์ด ์žˆ๊ณ 

lunch ๋ฅผ ๋œปํ•˜๋Š” div -> div -> pre ๋‚ด๋ถ€์˜ text ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ์ค‘์‹ ๋ฉ”๋‰ด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค!

 

<div class="b-day"></div>
<div class="b-menu-b"></div>
<div class="b-menu-l">
<p>์ค‘์‹</p>
<div>
<pre>์†Œ๊ณ ๊ธฐ์„ค๋ ํƒ•
์Œ€๋ฐฅ
๋™๊ทธ๋ž‘๋•ก์•ผ์ฑ„์กฐ๋ฆผ
ํŒŒ๋ž˜๊น€์ž๋ฐ˜๋ณถ์Œ
์น˜์ปค๋ฆฌ๊ฒ‰์ ˆ์ด
๋ฐฐ์ถ”๊น€์น˜

&lt;์ผํ’ˆ&gt;
ํ† ํ•‘์„ ํƒ๋ˆ๊นŒ์Šค
(1.๊ฐˆ๋ฆญ์นฉ2.๋ˆˆ๊ฝƒ์น˜์ฆˆ3.๊ณ ๊ตฌ๋งˆ๋ฌด์Šค)</pre>
</div>
</div>

 

 

 

์—ฌ๊ธฐ์„œ ํ•œ๊ฐ€์ง€ ์ฃผ์˜ํ•ด์•ผํ•  ์ ์€ ์˜ค๋Š˜ ๋‚ ์งœ์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”๋‰ด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š” ๊ฒƒ์ธ๋ฐ,

๋”ฐ๋ผ์„œ ์˜ค๋Š˜์˜ ์š”์ผ์„ ๋ฐ›์•„์˜จ ๋‹ค์Œ์— ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ b-menu-box ์—์„œ ๋ช‡๋ฒˆ์งธ ์š”์ผ ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ฌ ๊ฒƒ์ธ์ง€ ๊ฒฐ์ •ํ–ˆ๋‹ค.

 

import requests
from bs4 import BeautifulSoup
import datetime

# Send a GET request to the specified URL
response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
response.encoding = 'utf-8'  # ๊ฐ•์ œ๋กœ UTF-8 ์„ค์ •

# Check if the request was successful (status code 200)
if response.status_code == 200:
    html_content = response.content
    # print(html_content)
    # Use Beautiful Soup to parse the HTML
    soup = BeautifulSoup(html_content, "html.parser")
    
    # The title tag of the page
    #print(soup.title)

    # Text from the page
    #print(soup.get_text())

    # ์˜ค๋Š˜ ๋‚ ์งœ ๊ฐ€์ ธ์˜ค๊ธฐ
    today = datetime.date.today()

    # ์š”์ผ ๋ฒˆํ˜ธ ๊ฐ€์ ธ์˜ค๊ธฐ (0: ์›”์š”์ผ, 6: ์ผ์š”์ผ)
    weekday_number = today.weekday()
    print(weekday_number)

    week_menu_box = soup.find(class_="b-menu-box")
    day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
    day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text
    
    print(day_menu_text)

else:
	print(response)

 

๊ฒฐ๊ณผ

r

 

Lambda Function ์™„์„ฑํ•˜๊ธฐ

 

์ „์ฒด ์†Œ์Šค ์ฝ”๋“œ์ด๋‹ค. ํ•˜๋‚˜ํ•˜๋‚˜ ์˜๋ฏธ๋ฅผ ์ž ๊น ์‚ดํŽด๋ณด๋ฉด, 

def lambda_handler(event, context):
    # Verify the signature
    try:
        verify_signature(event)
    except Exception as e:
        raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")

    # Check if message is a ping
    body = event.get('body-json')
    if ping_pong(body):
        return PING_PONG

    if body.get("type") == 2:
        return command_handler(body)

    # Dummy return
    return {
        "type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
        "data": {
            "tts": False,
            "content": "BEEP BOOP",
            "embeds": [],
            "allowed_mentions": []
        }
    }

def command_handler(b):
    command = b['data']['name']
    if command == 'rice':
        # Send a GET request to the specified URL
        response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
        response.encoding = 'utf-8'  # ๊ฐ•์ œ๋กœ UTF-8 ์„ค์ •

        if response.status_code == 200:
            html_content = response.content
            soup = BeautifulSoup(html_content, "html.parser")
            today = datetime.date.today()
            weekday_number = today.weekday()
            print(weekday_number)

            week_menu_box = soup.find(class_="b-menu-box")
            day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
            day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text
    
            return {
            'type': 4,
            'data': {'content': day_menu_text}
            }

else:
	print(response)

        pass  # Fill in here
    elif command == 'time':
        timeZ_S = pytz.timezone('Asia/Seoul')
        dt = datetime.datetime.now(timeZ_S)
        return {
            'type': 4,
            'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
        }
    elif command == 'email':
        pass  # Fill in here

 

 

์š”์ฒญ ์„œ๋ช… ๊ฒ€์ฆ ํ•จ์ˆ˜

def verify_signature(event):
    raw_body = event.get("rawBody")
    signature = event['params']['header'].get('x-signature-ed25519')
    timestamp = event['params']['header'].get('x-signature-timestamp')

    verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
    verify_key.verify(f'{timestamp}{raw_body}'.encode(), bytes.fromhex(signature))

 

 

  • Discord์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด Ed25519 ์„œ๋ช… ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰
  • ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์š”์ฒญ์„ ๋ฌดํšจํ™”
  • PUBLIC_KEY๋Š” Discord ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ณต๊ฐœ ํ‚ค๋กœ, ์™ธ๋ถ€ ์š”์ฒญ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅ.

 

Ping-Pong ์ฒ˜๋ฆฌ ํ•จ์ˆ˜

 

def ping_pong(body):
    if body.get("type") == 1:
        return True
    return False

 

 

  • Discord ์„œ๋ฒ„๊ฐ€ ๋ณด๋‚ธ Ping ์š”์ฒญ์ธ์ง€ ํ™•์ธ
  • Ping ์š”์ฒญ์ด๋ฉด True๋ฅผ ๋ฐ˜ํ™˜

 

๋ฉ”์ธ ํ•ธ๋“ค๋Ÿฌ (lambda_handler)

def lambda_handler(event, context):
    try:
        verify_signature(event)
    except Exception as e:
        raise Exception(f"[UNAUTHORIZED] Invalid request signature: {e}")

    body = event.get('body-json')
    if ping_pong(body):
        return PING_PONG

    if body.get("type") == 2:
        return command_handler(body)

    return {
        "type": RESPONSE_TYPES['MESSAGE_NO_SOURCE'],
        "data": {
            "tts": False,
            "content": "BEEP BOOP",
            "embeds": [],
            "allowed_mentions": []
        }
    }

 

 

  • ์„œ๋ช… ๊ฒ€์ฆ: ์š”์ฒญ์ด Discord ์„œ๋ฒ„์—์„œ ์˜จ ๊ฒƒ์ด ๋งž๋Š”์ง€ ํ™•์ธ.
  • Ping ์ฒ˜๋ฆฌ: Ping ์š”์ฒญ์ด๋ฉด ๋ฐ”๋กœ PING_PONG ์‘๋‹ต ๋ฐ˜ํ™˜.
  • ๋ช…๋ น์–ด ์ฒ˜๋ฆฌ: type == 2์ธ ๊ฒฝ์šฐ, command_handler๋กœ ๋ช…๋ น์–ด๋ฅผ ์ „๋‹ฌ.
  • ๊ธฐ๋ณธ ์‘๋‹ต: "BEEP BOOP" ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜.

 

def command_handler(b):
    command = b['data']['name']
    if command == 'rice':
        response = requests.get("http://ewha.ac.kr/ewha/life/restaurant.do?mode=view&articleNo=905&article.offset=0&articleLimit=10")
        response.encoding = 'utf-8'
        if response.status_code == 200:
            html_content = response.content
            soup = BeautifulSoup(html_content, "html.parser")
            weekday_number = 0  # ํ˜„์žฌ ๋‚ ์งœ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์š”์ผ ๊ณ„์‚ฐ ๊ฐ€๋Šฅ
            week_menu_box = soup.find(class_="b-menu-box")
            day_menu_box = week_menu_box.select_one(f'li:nth-of-type({weekday_number+1})')
            day_menu_text = day_menu_box.select_one(f'.b-menu-l > div > pre').text

            return {
                'type': 4,
                'data': {'content': day_menu_text}
            }
    elif command == 'time':
        timeZ_S = pytz.timezone('Asia/Seoul')
        dt = datetime.datetime.now(timeZ_S)
        return {
            'type': 4,
            'data': {'content': dt.strftime('%Y-%m-%d %H:%M:%S %Z %z')}
        }
    elif command == 'email':
        pass

 

 

command ๋ถ„๊ธฐ:

  1. /rice:
    • ์ดํ™”์—ฌ๋Œ€ ์‹๋‹น ๋ฉ”๋‰ด ํŽ˜์ด์ง€๋ฅผ ํฌ๋กค๋งํ•˜์—ฌ ์ฃผ์–ด์ง„ ๋‚ ์งœ์˜ ๋ฉ”๋‰ด๋ฅผ ๋ฐ˜ํ™˜.
    • HTML ํŒŒ์‹ฑ์€ BeautifulSoup์„ ์‚ฌ์šฉ.
  2. /time:
    • ํ•œ๊ตญ(์„œ์šธ) ์‹œ๊ฐ„๋Œ€๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ˜„์žฌ ์‹œ๊ฐ„์„ ๋ฐ˜ํ™˜.
  3. /email:
    • ๊ตฌํ˜„๋˜์ง€ ์•Š์€ ์ƒํƒœ(์ž‘์„ฑ ํ•„์š”).

 

์†Œ์Šค์ฝ”๋“œ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  Deploy ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค.

 

ใ…Œ

 

๊ทธ๋ฆฌ๊ณ  AWS Lambda Console ๋กœ ๋Œ์•„์™€ Create Layer ๋ฅผ ๋ˆ„๋ฅธ๋‹ค.

 

 

 

๊ทธ๋ฆฌ๊ณ  Lambda ์— ์‚ฌ์šฉ๋œ ์—ฌ๋Ÿฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŒŒ์ผ๋“ค์„ ํ•˜๋‚˜๋กœ ์••์ถ•ํ•˜์—ฌ dependency.zip ์„ ๋งŒ๋“ ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๋ ˆ์ด์–ดํƒญ์— ๊ฐ€์„œ ๋ฐฉ๊ธˆ ์ƒ์„ฑํ•œ dependency ๋ ˆ์ด์–ด๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

 

 

 

 

 

AWS API Gateway

์ด์ œ ์šฐ๋ฆฌ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด API Gateway์—์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ•œ๋‹ค.

AWS API Gateway๋Š” Amazon Web Services(AWS)์—์„œ ์ œ๊ณตํ•˜๋Š” ์™„์ „ ๊ด€๋ฆฌํ˜• ์„œ๋น„์Šค๋กœ, ๊ฐœ๋ฐœ์ž๊ฐ€ API(Application Programming Interface)๋ฅผ ์ƒ์„ฑ, ๊ฒŒ์‹œ, ์œ ์ง€ ๊ด€๋ฆฌ, ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋ณด์•ˆ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. API Gateway๋Š” ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค์™€ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‚ฌ์ด์˜ ์ค‘๊ฐœ ์—ญํ• ์„ ํ•˜๋ฉฐ, HTTP, RESTful API, WebSocket API ๋“ฑ์˜ ๋‹ค์–‘ํ•œ ์œ ํ˜•์˜ API๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

 

 ๋จผ์ € ๊ธฐ๋ณธ API๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๊ธฐ๋ณธ URL์„ ์–ป๊ณ , ๊ทธ๋Ÿฐ ๋‹ค์Œ API ๋‚ด์—์„œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •์˜ํ•˜์—ฌ POST ์š”์ฒญ์ด ๊ฐ€๋Šฅํ•œ ์™„์ „ํ•œ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ API๋ฅผ ๋ฐฐํฌํ•˜์—ฌ ์ธํ„ฐ๋„ท์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•œ๋‹ค.

 

๋จผ์ € API Gateway Console๋กœ ์ด๋™ํ•œ๋‹ค. Create API -> REST API Build ํด๋ฆญ 

๊ทธ๋Ÿฌ๋ฉด ๋นˆ ๋ฃจํŠธ API๋ฅผ ๋ณด์ธ๋‹ค. ์ด์ œ API๋ฅผ ์ฑ„์›Œ์•ผ ํ•˜๋Š”๋ฐ, ์ด๋Š” ๋ฆฌ์†Œ์Šค์™€ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜์—ฌ ์ˆ˜ํ–‰๋œ๋‹ค.  Create resource ๋ฅผ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋ฆฌ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

์ด๋ฒˆ์—๋Š” Create method ๋ฅผ ๋ˆŒ๋Ÿฌ ์ž์› HW3 ์— ๋Œ€ํ•œ POST ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค.

 

 

 

 

 

 

๊ธฐ๋ณธ ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•ด์ฃผ๊ณ , API Gateway mapping template๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค.

 

https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html

 

API Gateway mapping template and access logging variable reference - Amazon API Gateway

This function will turn any regular single quotes (') into escaped ones (\'). However, the escaped single quotes are not valid in JSON. Thus, when the output from this function is used in a JSON property, you must turn any escaped single quotes (\') back t

docs.aws.amazon.com

 

 

๐Ÿ“Œ AWS API Gateway์˜ Mapping Template์ด๋ž€?

 

ํด๋ผ์ด์–ธํŠธ์—์„œ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ(Request) ๋˜๋Š” ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ˜ํ™˜๋˜๋Š” ์‘๋‹ต(Response)์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜(Transform)ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

 

 

 

๊ทธ ๋‹ค์Œ์—๋Š” Method response ํƒญ์œผ๋กœ ๊ฐ€์„œ Create response ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ๋‹ค.

 

 

๊ทธ ๋‹ค์Œ ๋‹ค์‹œ Resource ํƒญ์œผ๋กœ ๋Œ์•„์™€์„œ Deploy API  ๋ฅผ ํ•ด์ค€๋‹ค.

 

 

๊ทธ๋ฆฌ๊ณ  Stages ์—์„œ Invoked URL ์„ ๋ณต์‚ฌํ•œ๋‹ค.

 

 

 

 

 

๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ Developer Portal ๋กœ ๋Œ์•„์™€์„œ INTERACTIONS ENDPOINT URL ์— ๋ฐฉ๊ธˆ ๋ณต์‚ฌํ•œ URL ์„ ๋ถ™์—ฌ๋„ฃ๋Š”๋‹ค.

 

 

 

๊ทธ๋ฆฌ๊ณ  Save changes ๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ๊ณ ๊ฐ€ ๋œฌ๋‹ค.

 

 

๊ทธ๋Ÿฐ๋ฐ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„์„œ CloudWatch ์—์„œ lambda ๊ด€๋ จ ๋กœ๊ทธ๋ฅผ ์‚ดํŽด๋ณด์•„์•ผํ•œ๋‹ค.

 

Lambda ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์‹คํ–‰ ์‹œ ๊ด€๋ จ ๋กœ๊ทธ ๊ทธ๋ฃน์„ ์ž๋™ ์ƒ์„ฑํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜  ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์‚ดํŽด๋ณด๊ธฐ ์ „์— CloudWatch ์™€  lambda ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์—ฐ๋™์ด ์ž˜ ์•ˆ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด์„œ Monitor ํƒญ์„ ํ™•์ธํ•ด๋ณด์•˜๋”๋‹ˆ, 

CloudWatch Logs์—์„œ ์ง€์ •๋œ ๋กœ๊ทธ ๊ทธ๋ฃน(/aws/lambda/HW3Discord)์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋–ด๋‹ค.

๊ทธ๋ž˜์„œ IAM ๊ถŒํ•œ์„ ๋‹ค์‹œ ํ™•์ธํ•ด์ฃผ์—ˆ๋”๋‹ˆ ์ด๋ฒˆ์—” UNAUTHORIZED ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

 

์•Œ๊ณ ๋ณด๋‹ˆ POST ์ชฝ์˜ URL ์„ ๋ณต์‚ฌํ•ด์•ผํ•˜๋Š”๋ฐ root ๊ฒฝ๋กœ์— ์žˆ๋Š” URL ์„ ๋ณต์‚ฌํ•ด์„œ ์ƒ๊ธด ๋ฌธ์ œ์˜€๋‹ค..ใ…Žใ…Ž

 

 

 

์ด์ œ ๋กœ๊ทธ๋„ ์ œ๋Œ€๋กœ ๋œฌ๋‹ค.

 

 

๋ณ€๊ฒฝ์ด ์ž˜ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด์ค€๋‹ค!

 

์ด์ œ ๋””์Šค์ฝ”๋“œ์— ์ ‘์†ํ•˜์—ฌ ๋ด‡์œผ๋กœ ๋ช…๋ น์–ด๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋‹ˆ, time ๋ช…๋ น์–ด๋งŒ ๋จน๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ œ๋Œ€๋กœ ์‘๋‹ต์ด ๋˜์ง€ ์•Š๋Š”๋‹ค..

 

 

 

Simple Notification Service

Lambda function ์œผ๋กœ๋ถ€ํ„ฐ email ์„ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด Amazon SNS ๋ฅผ ํ™œ์šฉํ•œ๋‹ค. Standard Type ์œผ๋กœ Topic ์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•œ๋‹ค.

 

๋ฉ”์ผ ์ธ์ฆ์„ ํ•ด์ฃผ๋ฉด ๊ตฌ๋… ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด์ œ SNS ๋ฅผ lambda ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฐ๊ด€์‹œํ‚ฌ ๊ฒƒ์ธ๋ฐ, Add destination ์„ ๋ˆŒ๋Ÿฌ์„œ Amazon SNS ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

 

 

 

Lambda ํ•จ์ˆ˜์˜ IAM ์—ญํ• ์ด ๋กœ๊ทธ ์ž‘์„ฑ ๊ถŒํ•œ

 

https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html

 

API Gateway mapping template and access logging variable reference - Amazon API Gateway

This function will turn any regular single quotes (') into escaped ones (\'). However, the escaped single quotes are not valid in JSON. Thus, when the output from this function is used in a JSON property, you must turn any escaped single quotes (\') back t

docs.aws.amazon.com

 

 

 

 

API Gateway mapping template and access logging variable reference - Amazon API Gateway

This function will turn any regular single quotes (') into escaped ones (\'). However, the escaped single quotes are not valid in JSON. Thus, when the output from this function is used in a JSON property, you must turn any escaped single quotes (\') back t

docs.aws.amazon.com

 

 

 

 

 

 

 

 

official document: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns/client/publi sh.html

'CS > ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ…' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Cloud Computing] AWS Advanced Details  (0) 2024.09.26