스파르타 코딩클럽 4주차

BYEONGMIN CHOI·2022년 5월 4일
0

WIL(Week I Learned)

목록 보기
4/8

Flask 프레임워크 활용해 API

Flask 프레임워크를 활용하여 배포하기

👉 HTML과 mongoDB를 연동해 서버구축!

👉 헷갈리면 안되는 것! 우리는 컴퓨터가 한 대... 그래서 같은 컴퓨터에다 서버도 만들고, 요청도 할 거예요. 즉, 클라이언트 = 서버가 되는 것이죠. 이것을 바로 "로컬 개발환경"이라고 한답니다! 그림으로 보면, 대략 이렇습니다.

👉 `mongoDB Atlas`라는 클라우드 서비스를 이용하니, 아래와 같습니다.

Flask 시작하기

  • GET, POST 요청타입

    클라이언트가 요청 할 때에도, "방식"이 존재
    HTTP라는 통신 규약을 따른다. 따라서 어떤 요청 종류인지 응답하는 서버 쪽에 정보를 알려주는 것

👉 GET, POST 방식
여러 방식(링크)이 존재하지만 우리는 가장 많이 쓰이는 GET, POST 방식에 대해 다루겠습니다.


GET → 통상적으로! 데이터 조회(Read)를 요청할 때
예) 영화 목록 조회
데이터 전달 : URL 뒤에 물음표를 붙여 key=value로 전달
→ 예: google.com?q=북극곰
POST → 통상적으로! 데이터 생성(Create), 변경(Update), 삭제(Delete) 요청 할 때
예) 회원가입, 회원탈퇴, 비밀번호 수정
데이터 전달 : 바로 보이지 않는 HTML body에 key:value 형태로 전달

  • GET, POST 요청에서 클라이언트의 데이터를 받는 방법
    - JQuery ajax 이용하여 GET, POST

Flask 연습하기 - 두 개의 간단한 결과물

  • Flask 프레임워크를 사용하는데 있어 꼭 지켜야할 규칙
    - application 의 경로 지정
    • project 폴더
    • 하위 경로로 templates, static 디렉토리 설정 (templates : html파일, static : 이미지파일)
    • project 폴더 안에 app.py 파일 존재

첫번째 결과물

  • 프로젝트에 맞는 패키지 설치

    • flask, pymongo, dnspython, bs4
  • POST 연습

    • API 만들고 사용하기 - 이름, 주소, 평수 저장하기(Create \rightarrow POST)
      1. 요청 정보 : URL= /mars, 요청 방식 = POST
      2. 클라(ajax) → 서버(flask) : name, address, size
      3. 서버(flask) → 클라(ajax) : 메시지를 보냄 (주문 완료!)
  • GET 연습

    • API 만들고 사용하기 - 저장된 주문을 화면에 보여주기(Read \rightarrow GET)
      1. 요청 정보 : URL= /mars, 요청 방식 = GET
      2. 클라(ajax) → 서버(flask) : (없음)
      3. 서버(flask) → 클라(ajax) : 전체 주문을 보내주기
Flask 첫번째 결과물
  • app.py
from flask import Flask, render_template, request, jsonify
from pymongo import MongoClient

app = Flask(__name__)

# mongoDB
client = MongoClient('mongodb+srv://test:sparta@cluster0.zosuv.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.dbsparta


@app.route('/')
def home():
    return render_template('index.html')

# 클라이언트로 부터 데이터를 받아 서버(mongoDB)에 저장(POST)
@app.route("/mars", methods=["POST"])
def web_mars_post():
    name_receive = request.form['name_give']
    address_receive = request.form['address_give']
    size_receive = request.form['size_give']

    doc = {
        'name': name_receive,
        'address': address_receive,
        'size': size_receive
    }
    db.mars.insert_one(doc)

    return jsonify({'msg': '주문 완료!'})

# 서버에서 데이터를 가져와 클라이언트에 제공(GET)
@app.route("/mars", methods=["GET"])
def web_mars_get():
    order_list = list(db.mars.find({}, {'_id': False}))
    return jsonify({'orders': order_list})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)  
  • idex.html : ajax 를 통해 서버와 통신하여 데이터를 받아오고 보낸다.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
            crossorigin="anonymous"></script>

    <link href="https://fonts.googleapis.com/css2?family=Gowun+Batang:wght@400;700&display=swap" rel="stylesheet">

    <title>선착순 공동구매</title>

    <style>
        * {
            font-family: 'Gowun Batang', serif;
            color: white;
        }

        body {
            background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://cdn.aitimes.com/news/photo/202010/132592_129694_3139.jpg');
            background-position: center;
            background-size: cover;
        }

        h1 {
            font-weight: bold;
        }

        .order {
            width: 500px;
            margin: 60px auto 0px auto;
            padding-bottom: 60px;
        }

        .mybtn {
            width: 100%;
        }

        .order > table {
            margin: 40px 0;
            font-size: 18px;
        }

        option {
            color: black;
        }
    </style>
    <script>
        $(document).ready(function () {
            show_order();
        });

        function show_order() {
            $(`#order-box`).empty()
            $.ajax({
                type: 'GET',
                url: '/mars',
                data: {},
                success: function (response) {
                    let rows = response['orders']
                    for(let i = 0; i < rows.length; i++) {
                        let name = rows[i]['name']
                        let address = rows[i]['address']
                        let size = rows[i]['size']
                        let tmp_html = `<tr>
                                            <td>${name}</td>
                                            <td>${address}</td>
                                            <td>${size}</td>
                                        </tr>`
                        $(`#order-box`).append(tmp_html)
                    }
                }
            });
        }

        function save_order() {
            let name = $(`#name`).val()
            let address = $(`#address`).val()
            let size = $(`#size`).val()

            $.ajax({
                type: 'POST',
                url: '/mars',
                data: { name_give: name, address_give: address, size_give: size },
                success: function (response) {
                    alert(response['msg'])
                    window.location.reload()
                }
            });
        }
    </script>
</head>
<body>
<div class="mask"></div>
<div class="order">
    <h1>화성에 땅 사놓기!</h1>
    <h3>가격: 평 당 500원</h3>
    <p>
        화성에 땅을 사둘 수 있다고?<br/>
        앞으로 백년 간 오지 않을 기회. 화성에서 즐기는 노후!
    </p>
    <div class="order-info">
        <div class="input-group mb-3">
            <span class="input-group-text">이름</span>
            <input id="name" type="text" class="form-control">
        </div>
        <div class="input-group mb-3">
            <span class="input-group-text">주소</span>
            <input id="address" type="text" class="form-control">
        </div>
        <div class="input-group mb-3">
            <label class="input-group-text" for="size">평수</label>
            <select class="form-select" id="size">
                <option selected>-- 주문 평수 --</option>
                <option value="10평">10평</option>
                <option value="20평">20평</option>
                <option value="30평">30평</option>
                <option value="40평">40평</option>
                <option value="50평">50평</option>
            </select>
        </div>
        <button onclick="save_order()" type="button" class="btn btn-warning mybtn">주문하기</button>
    </div>
    <table class="table">
        <thead>
        <tr>
            <th scope="col">이름</th>
            <th scope="col">주소</th>
            <th scope="col">평수</th>
        </tr>
        </thead>
        <tbody id="order-box">
        <tr>
            <td>홍길동</td>
            <td>서울시 용산구</td>
            <td>20평</td>
        </tr>
        <tr>
            <td>임꺽정</td>
            <td>부산시 동구</td>
            <td>10평</td>
        </tr>
        <tr>
            <td>세종대왕</td>
            <td>세종시 대왕구</td>
            <td>30평</td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

두번째 결과물

  • 프로젝트에 맞는 패키지 설치
    • flask, pymongo, dnspython, bs4, requests
  • 주어진 영화 네이버 페이지에 있는 제목, 별점, 포스터, 설명을 크롤링하여 데이터로 저장 및 보여준다.
  • 조각 기능
  • 제목, 썸네일 이미지, 내용

    meta 태그에 대해 알아보기
    • 메타 태그는, 부분에 들어가는, 눈으로 보이는 것(body) 외에 사이트의 속성을 설명해주는 태그들입니다.

      예) 구글 검색 시 표시 될 설명문, 사이트 제목, 카톡 공유 시 표시 될 이미지 등

    • 우리는 그 중 og:image / og:title / og:description 을 크롤링 할 예정입니다.

      예시)
       # select_one을 이용한 meta tag를 가져오기
       og_image = soup.select_one('meta[property="og:image"]')
       og_title = soup.select_one('meta[property="og:title"]')
       og_description = soup.select_one('meta[property="og:description"]')
      
       print(og_image)
       print(og_title)
       print(og_description)
  • POST 연습

    • API 만들고 사용하기 - 포스팅API(Create \rightarrow POST)
      1. 요청 정보 : URL= /movie, 요청 방식 = POST
      2. 클라(ajax) → 서버(flask) : url, star, comment
      3. 서버(flask) → 클라(ajax) : 메시지를 보냄 (주문 완료!)
  • GET 연습

    • API 만들고 사용하기 - 저장된 주문을 화면에 보여주기(Read \rightarrow GET)
      1. 요청 정보 : URL= /mars, 요청 방식 = GET
      2. 클라(ajax) → 서버(flask) : (없음)
      3. 서버(flask) → 클라(ajax) : 전체 주문을 보내주기
Flask 두번째 결과물
  • app.py [서버 코드]

      from flask import Flask, render_template, request, jsonify
      import requests
      from bs4 import BeautifulSoup
      from pymongo import MongoClient
    
      app = Flask(__name__)
    
      # mongoDB
      client = MongoClient('mongodb+srv://test:sparta@cluster0.zosuv.mongodb.net/Cluster0?retryWrites=true&w=majority')
      db = client.dbsparta
    
      @app.route('/')
      def home():
      return render_template('index.html')
    
      # 클라이언트에서 POST로 보낸 url, star, comment 데이터와 크롤링을 통한 title, image, description 데이터를 doc 딕셔너리로 mongoDB에 저장 
      @app.route("/movie", methods=["POST"])
      def movie_post():
      url_receive = request.form['url_give']
      star_receive = request.form['star_give']
      comment_receive = request.form['comment_give']
    
      # Crawling
      headers = {
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}
    
      data = requests.get(url_receive, headers=headers)
    
      soup = BeautifulSoup(data.text, 'html.parser')
    
      # 영화 네이버 사이트에서 크롤링해서 meta tag를 먼저 가져와보겠습니다.
      title = soup.select_one('meta[property="og:title"]')['content']
      img = soup.select_one('meta[property="og:image"]')['content']
      desc = soup.select_one('meta[property="og:description"]')['content']
    
      doc = {
        'title': title,
        'image': img,
        'desc': desc,
        'star': star_receive,
        'comment': comment_receive
      }
      db.movies.insert_one(doc)
    
      return jsonify({'msg': '저장 완료!'})
    
      @app.route("/movie", methods=["GET"])
      def movie_get():
      movie_list = list(db.movies.find({}, {'_id': False}))
      return jsonify({'movies': movie_list})
    
      if __name__ == '__main__':
      app.run('0.0.0.0', port=5000, debug=True)
    	
  • idex.html [클라이언트 코드]

        <!doctype html>
        <html lang="en">
    
        <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
            <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
                  integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
            <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
            <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
                    integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
                    crossorigin="anonymous"></script>
    
            <title>스파르타 피디아</title>
    
            <link href="https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap" rel="stylesheet">
    
            <style>
                * {
                    font-family: 'Gowun Dodum', sans-serif;
                }
    
                .mytitle {
                    width: 100%;
                    height: 250px;
    
                    background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url('https://movie-phinf.pstatic.net/20210715_95/1626338192428gTnJl_JPEG/movie_image.jpg');
                    background-position: center;
                    background-size: cover;
    
                    color: white;
    
                    display: flex;
                    flex-direction: column;
                    align-items: center;
                    justify-content: center;
                }
    
                .mytitle > button {
                    width: 200px;
                    height: 50px;
    
                    background-color: transparent;
                    color: white;
    
                    border-radius: 50px;
                    border: 1px solid white;
    
                    margin-top: 10px;
                }
    
                .mytitle > button:hover {
                    border: 2px solid white;
                }
    
                .mycomment {
                    color: gray;
                }
    
                .mycards {
                    margin: 20px auto 0px auto;
                    width: 95%;
                    max-width: 1200px;
                }
    
                .mypost {
                    width: 95%;
                    max-width: 500px;
                    margin: 20px auto 0px auto;
                    padding: 20px;
                    box-shadow: 0px 0px 3px 0px gray;
    
                    display: none;
                }
    
                .mybtns {
                    display: flex;
                    flex-direction: row;
                    align-items: center;
                    justify-content: center;
    
                    margin-top: 20px;
                }
    
                .mybtns > button {
                    margin-right: 10px;
                }
            </style>
            <script>
                $(document).ready(function () {
                    listing();
                });
    
                function listing() {
                    $(`#cards-box`).empty()
                    $.ajax({
                        type: 'GET',
                        url: '/movie',
                        data: {},
                        success: function (response) {
                            let rows = response['movies']
                            for(let i = 0; i < rows.length; i++) {
                                let title = rows[i]['title']
                                let comment = rows[i]['comment']
                                let star = rows[i]['star']
                                let img = rows[i]['image']
                                let desc = rows[i]['desc']
                                let star_img = '⭐'.repeat(star)
    
                                let tmp_html = `<div class="col">
                                                    <div class="card h-100">
                                                        <img src=${img}
                                                             class="card-img-top">
                                                        <div class="card-body">
                                                            <h5 class="card-title">${title}</h5>
                                                            <p class="card-text">${desc}</p>
                                                            <p>${star_img}</p>
                                                            <p class="mycomment">${comment}</p>
                                                        </div>
                                                    </div>
                                                </div>`
                                $(`#cards-box`).append(tmp_html)
                            }
                        }
                    })
                }
    
                function posting() {
                    let url = $(`#url`).val()
                    let star = $(`#star`).val()
                    let comment = $(`#comment`).val()
    
                    $.ajax({
                        type: 'POST',
                        url: '/movie',
                        data: {url_give: url, star_give: star, comment_give: comment},
                        success: function (response) {
                            alert(response['msg'])
                            window.location.reload()
                        }
                    });
                }
    
                function open_box() {
                    $('#post-box').show()
                }
    
                function close_box() {
                    $('#post-box').hide()
                }
            </script>
        </head>
    
        <body>
        <div class="mytitle">
            <h1>내 생애 최고의 영화들</h1>
            <button onclick="open_box()">영화 기록하기</button>
        </div>
        <div class="mypost" id="post-box">
            <div class="form-floating mb-3">
                <input id="url" type="email" class="form-control" placeholder="name@example.com">
                <label>영화URL</label>
            </div>
            <div class="input-group mb-3">
                <label class="input-group-text" for="inputGroupSelect01">별점</label>
                <select class="form-select" id="star">
                    <option selected>-- 선택하기 --</option>
                    <option value="1"></option>
                    <option value="2">⭐⭐</option>
                    <option value="3">⭐⭐⭐</option>
                    <option value="4">⭐⭐⭐⭐</option>
                    <option value="5">⭐⭐⭐⭐⭐</option>
                </select>
            </div>
            <div class="form-floating">
                <textarea id="comment" class="form-control" placeholder="Leave a comment here"></textarea>
                <label for="floatingTextarea2">코멘트</label>
            </div>
            <div class="mybtns">
                <button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>
                <button onclick="close_box()" type="button" class="btn btn-outline-dark">닫기</button>
            </div>
        </div>
        <div class="mycards">
            <div class="row row-cols-1 row-cols-md-4 g-4" id="cards-box">
                <div class="col">
                    <div class="card h-100">
                        <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                             class="card-img-top">
                        <div class="card-body">
                            <h5 class="card-title">영화 제목이 들어갑니다</h5>
                            <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                            <p>⭐⭐⭐</p>
                            <p class="mycomment">나의 한줄 평을 씁니다</p>
                        </div>
                    </div>
                </div>
                <div class="col">
                    <div class="card h-100">
                        <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                             class="card-img-top">
                        <div class="card-body">
                            <h5 class="card-title">영화 제목이 들어갑니다</h5>
                            <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                            <p>⭐⭐⭐</p>
                            <p class="mycomment">나의 한줄 평을 씁니다</p>
                        </div>
                    </div>
                </div>
                <div class="col">
                    <div class="card h-100">
                        <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                             class="card-img-top">
                        <div class="card-body">
                            <h5 class="card-title">영화 제목이 들어갑니다</h5>
                            <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                            <p>⭐⭐⭐</p>
                            <p class="mycomment">나의 한줄 평을 씁니다</p>
                        </div>
                    </div>
                </div>
                <div class="col">
                    <div class="card h-100">
                        <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                             class="card-img-top">
                        <div class="card-body">
                            <h5 class="card-title">영화 제목이 들어갑니다</h5>
                            <p class="card-text">여기에 영화에 대한 설명이 들어갑니다.</p>
                            <p>⭐⭐⭐</p>
                            <p class="mycomment">나의 한줄 평을 씁니다</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        </body>
    
        </html>

    결과물

profile
스스로 성장하는 개발자가 되겠습니다.

0개의 댓글