Programming/Web

[개발일지/회고] 스파르타코딩클럽 웹개발 종합반 - 4주차

망고밥 2022. 1. 27. 21:50

알바하면서 쓰는 4주차 회고..ㅋㅋㅋ

#가보자고 ~~~~~

 


수업 목표

  1. Flask 프레임워크를 활용해서 API를 만들 수 있다.
  2. '모두의 책리뷰' API를 만들고 클라이언트에 연결한다.
  3. '나홀로 메모장' API를 만들고 클라이언트와 연결한다.

 

배운 것

Flask 시작하기

  • 컴퓨터 한 대로 서버와 요청을 함께 한다. 즉, 클라이언트 = 서버가 된다. (로컬 개발환경)

 

  • Flask 패키지 설치: File → setting → Python interpreter → + 버튼 → flask 검색 후 Install package 클릭

 

 

Flask 기초

  • Flask 프레임워크: 서버를 편리하게 구동시켜주는 Python 웹 프레임워크이다.

 

  • Flask 시작 코드
from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
   return 'This is Home!'

if __name__ == '__main__':  
   app.run('0.0.0.0',port=5000,debug=True)

 

 

  • 실행 후 터미널에 아래와 같은 메시지가 뜨면 실행 성공!

 

  • http://localhost:5000/ 으로 접속하면 서버가 구동된다.

 

  • URL 나누기: @app.route('/') 부분을 수정하여 URL을 나눌 수 있다. (URL 별로 함수명이 동일하거나 route('/')내의 주소가 같으면 안됨!)

 

  • Flask 기본 구조
    1. static : 이미지, CSS 파일을 넣어두는 폴더
    2. templates : HTML 파일을 넣어두는 폴더
    3. app.py : 서버 코드가 작성된 파일

 

  • HTML 파일 불러오기: Flask 내장함수 render_template를 이용해 index.html 파일을 불러올 수 있다.
from flask import Flask, render_template
app = Flask(__name__)

## URL 별로 함수명이 같거나,
## route('/') 등의 주소가 같으면 안됩니다.

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

if __name__ == '__main__':
   app.run('0.0.0.0', port=5000, debug=True)

 

 

API 만들기

  • GET/POST 방식 복습
  GET POST
용도 통상적으로 데이터 조회(Read)를 요청할 때
ex) 영화 목록 조회
통상적으로 데이터 생성(Create), 변경(Update), 삭제(Delete) 요청할 때
ex) 회원 가입, 회원 탈퇴, 회원정보 수정
데이터 전달 방식 URL 뒤에 ?을 붙여 key=value 형태로 전달 HTML body에 key:value 형태로 전달

 

  • GET 요청 API 코드
@app.route('/test', methods=['GET'])
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})

 

  • GET 요청 확인 Ajax 코드
$.ajax({
    type: "GET",
    url: "/test?title_give=봄날은간다",
    data: {},
    success: function(response){
       console.log(response)
    }
  })

 

  • POST 요청 API 코드
@app.route('/test', methods=['POST'])
def test_post():
   title_receive = request.form['title_give']
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 POST!'})

 

  • POST 요청 확인 Ajax 코드
$.ajax({
    type: "POST",
    url: "/test",
    data: { title_give:'봄날은간다' },
    success: function(response){
       console.log(response)
    }
  })

 

 

POST 연습 (모두의 책리뷰 - 리뷰 저장하기)

  • 서버 코드 - app.py
# 요청 URL, 요청 방식
@app.route('/review', methods=['POST'])
def write_review():
    # title_receive로 클라이언트가 준 title 가져오기
    title_receive = request.form['title_give']
    # author_receive로 클라이언트가 준 author 가져오기
    author_receive = request.form['author_give']
    # review_receive로 클라이언트가 준 review 가져오기
    review_receive = request.form['review_give']

    # DB에 삽입할 review 데이터 만들기
    doc = {
        'title': title_receive,
        'author': author_receive,
        'review': review_receive
    }
    # DB reviews에 review 데이터 저장하기
    db.bookreview.insert_one(doc)
    # 성공 여부 & 성공 메시지 반환
    return jsonify({'msg': '리뷰가 성공적으로 작성되었습니다.'}) # 응답 데이터

 

  • 클라이언트 코드 - index.html
function makeReview() {
	// 화면에 입력어 있는 제목, 저자, 리뷰 내용을 가져옵니다.
    let title = $("#title").val();
    let author = $("#author").val();
    let review = $("#bookReview").val();
    
    // POST /review 에 저장(Create)을 요청합니다.
	$.ajax({
        type: "POST",
        url: "/review",
        data: { title_give: title, author_give: author, review_give: review },
        success: function (response) {
            alert(response["msg"]);
            window.location.reload();
        }
    })
}

 

 

GET 연습 (모두의 책리뷰 - 리뷰 보여주기)

  • 서버 코드 - app.py
@app.route('/review', methods=['GET'])
def read_reviews():
    # 1. DB에서 리뷰 정보 모두 가져오기
    reviews = list(db.bookreview.find({}, {'_id': False}))
    # 2. 성공 여부 & 리뷰 목록 반환하기
    return jsonify({'all_reviews': reviews})

 

  • 클라이언트 코드 - index.html
function showReview() {
                $.ajax({
                    type: "GET",
                    url: "/review",
                    data: {},
                    success: function (response) {
                        let reviews = response['all_reviews']
                        for (let i = 0; i < reviews.length; i++) {
                            let title = reviews[i]['title']
                            let author = reviews[i]['author']
                            let review = reviews[i]['review']

                            let temp_html = `<tr>
                                                <td>${title}</td>
                                                <td>${author}</td>
                                                <td>${review}</td>
                                            </tr>`
                            $('#reviews-box').append(temp_html)
                        }
                    }
                })
            }

 

 

API 설계하기 (나홀로 메모장)

  • 포스팅 API
  1. 요청 정보
    • 요청 URL: /memo
    • 요청 방식: POST
  2. 서버가 제공할 기능
    • 영화 URL의 meta 태그 정보를 바탕으로 제목, 설명, 이미지 URL을 스크래핑
    • 스크래핑한 영화 정보를 모두 DB에 저장
  3. 응답 데이터
    • API가 정상적으로 작동하는지 클라이언트에게 알려주기 위해 성공 메시지 보내기 (JSON 형식 'result'= 'success')

 

  • 리스팅 API
  1. 요청 정보
    • 요청 URL: /memo
    • 요청 방식: GET
  2. 서버가 제공할 기능
    • DB에 저장돼있는 모든 정보를 가져오기
  3. 응답 데이터
    • 아티클들의 정보 카드를 만들어서 붙이기 (JSON 형식 'articles': 아티클 정보)

 

 

조각 기능 구현 (나홀로 메모장)

  • meta 태그: <head></head> 부분에 들어가는, 눈으로 보이는 것(body) 외에 사이트의 속성을 설명해주는 태그이다. (ex.구글 검색 시 표시 될 설명문, 사이트 제목, 카톡 공유 시 표시 될 이미지 등)

 

  • 크롤링 기본 코드
import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=171539'

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,headers=headers)

soup = BeautifulSoup(data.text, 'html.parser')

# 여기에 코딩을 해서 meta tag를 먼저 가져와보겠습니다.

 

  • select_one으로 meta 태그 가져오기
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)

 

  • meta 태그 내용 가져오기
url_image = og_image['content']
url_title = og_title['content']
url_description = og_description['content']

print(url_image)
print(url_title)
print(url_description)

 

 

POST 연습 (나홀로 메모장 - 메모하기)

  • 포스팅 API: 포스팅 카드 생성 후 클라이언트에서 받은 URL, Comment를 이용해서 페이지 정보를 찾아 저장하기(Create)
  • 리스팅 API: 저장된 카드 보여주기(Read)

 

  • 서버 코드 - app.py
@app.route('/memo', methods=['POST'])
def saving():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']

    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')

    title = soup.select_one('meta[property="og:title"]')['content']
    image = soup.select_one('meta[property="og:image"]')['content']
    desc = soup.select_one('meta[property="og:description"]')['content']

    doc = {
        'title':title,
        'image':image,
        'desc':desc,
        'url':url_receive,
        'comment':comment_receive
    }

    db.articles.insert_one(doc)

    return jsonify({'msg':'저장이 완료되었습니다!'})

 

  • 클라이언트 코드 - index.html
function postArticle() {
      let url = $('#post-url').val()
      let comment = $('#post-comment').val()

      $.ajax({
          type: "POST",
          url: "/memo",
          data: {url_give:url, comment_give:comment},
          success: function (response) { // 성공하면
              alert(response["msg"]);
              window.location.reload()
          }
      })
  }

 

 

GET 연습 (나홀로 메모장 - 보여주기)

  • 서버 코드 - app.py
@app.route('/memo', methods=['GET'])
def listing():
    articles = list(db.articles.find({}, {'_id': False}))
    return jsonify({'all_articles':articles})

 

  • 클라이언트 코드 - index.html
function showArticles() {
    $.ajax({
        type: "GET",
        url: "/memo",
        data: {},
        success: function (response) {
            let articles = response['all_articles']
            for (let i = 0; i < articles.length; i++) {
                let title = articles[i]['title']
                let image = articles[i]['image']
                let url = articles[i]['url']
                let desc = articles[i]['desc']
                let comment = articles[i]['comment']

                let temp_html = `<div class="card">
                                    <img class="card-img-top"
                                         src="${image}"
                                         alt="Card image cap">
                                    <div class="card-body">
                                        <a target="_blank" href="${url}" class="card-title">${title}</a>
                                        <p class="card-text">${desc}</p>
                                        <p class="card-text comment">${comment}</p>
                                    </div>
                                </div>`
                $('#cards-box').append(temp_html)
            }
        }
    })
}

 

 

과제 - 원페이지 쇼핑몰

  • 원페이지 쇼핑몰 - app.py
from flask import Flask, render_template, jsonify, request

app = Flask(__name__)

from pymongo import MongoClient

client = MongoClient('localhost', 27017)
db = client.dbhomework


## HTML 화면 보여주기
@app.route('/')
def homework():
    return render_template('index.html')


# 주문하기(POST) API
@app.route('/order', methods=['POST'])
def save_order():
    name_receive = request.form['name_give']
    count_receive = request.form['count_give']
    address_receive = request.form['address_give']
    phone_receive = request.form['phone_give']

    doc = {
        'name': name_receive,
        'count': count_receive,
        'address': address_receive,
        'phone': phone_receive
    }
    db.orders.insert_one(doc)

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


# 주문 목록보기(Read) API
@app.route('/order', methods=['GET'])
def view_orders():
    orders = list(db.orders.find({}, {'_id': False}))
    return jsonify({'result': 'success', 'orders': orders})


if __name__ == '__main__':
    app.run('0.0.0.0', port=5000, debug=True)

 

  • 원페이지 쇼핑몰 - index.html
<!doctype html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
          integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
            integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
            crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
            integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
            crossorigin="anonymous"></script>

    <title>스파르타코딩클럽 | 부트스트랩 연습하기</title>

    <link href="https://fonts.googleapis.com/css2?family=Jua&display=swap" rel="stylesheet">

    <style>
        * {
            font-family: 'Jua', sans-serif;
        }

        .item-img {
            width: 500px;
            height: 300px;

            background-image: url("https://t1.daumcdn.net/liveboard/nts/5bcccfbd33da4865817b9c606b6b852e.JPG");
            background-position: center;
            background-size: cover;
        }

        .price {
            font-size: 20px;
        }

        .item-desc {
            width: 500px;
            margin-top: 20px;
            margin-bottom: 20px;
        }

        .item-order {
            width: 500px;
            margin-bottom: 50px;
        }

        .btn-order {
            margin: auto;
            width: 100px;

            display: block;
        }

        .wrap {
            width: 500px;
            margin: auto;
        }

        .rate {
            color: blue;
        }
    </style>

    <script>
        $(document).ready(function () {
            get_rate();
            listing();
        });

        function listing() {
            $.ajax({
                type: "GET",
                url: "/order",
                data: {},
                success: function (response) {
                    if (response["result"] == "success") {
                        let orders = response['orders'];
                        for (let i = 0; i < orders.length; i++) {
                            let name = orders[i]['name'];
                            let count = orders[i]['count'];
                            let address = orders[i]['address'];
                            let phone = orders[i]['phone'];

                            let temp_html = `<tr>
                                                <th scope="row">${name}</th>
                                                <td>${count}</td>
                                                <td>${address}</td>
                                                <td>${phone}</td>
                                            </tr>`
                            $('#orders-box').append(temp_html)
                        }
                    }
                }
            })
        }

        function get_rate() {
            $.ajax({
                type: "GET",
                url: "https://api.manana.kr/exchange/rate.json",
                data: {},
                success: function (response) {
                    let now_rate = response[1]['rate'];
                    $('#now-rate').text(now_rate);
                }
            })
        }

        function order() {
            let name = $('#order-name').val();
            let count = $('#order-count').val();
            let address = $('#order-address').val();
            let phone = $('#order-phone').val();

            $.ajax({
                type: "POST",
                url: "/order",
                data: {name_give: name, count_give: count, address_give: address, phone_give: phone},
                success: function (response) {
                    if (response["result"] == "success") {
                        alert(response["msg"]);
                        window.location.reload();
                    }
                }
            })
        }
    </script>
</head>

<body>
<div class="wrap">
    <div class="item-img"></div>
    <div class="item-desc">
        <h1>양초를 팝니다 <span class="price">가격:3,000원/개</span></h1>
        <p>이 양초는 사실 특별한 힘을 지니고 있어요!</p>
        <p class="rate">달러-원 환율: <span id="now-rate">1219.15</span></p>
    </div>
    <div class="item-order">
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text">주문자이름</span>
            </div>
            <input id="order-name" type="text" class="form-control" aria-label="Default"
                   aria-describedby="inputGroup-sizing-default">
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <label class="input-group-text" for="inputGroupSelect01">수량</label>
            </div>
            <select id="order-count" class="custom-select">
                <option selected>-- 수량을 선택하세요 --</option>
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
            </select>
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text">주소</span>
            </div>
            <input id="order-address" type="text" class="form-control" aria-label="Default"
                   aria-describedby="inputGroup-sizing-default">
        </div>
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <span class="input-group-text">전화번호</span>
            </div>
            <input id="order-phone" type="text" class="form-control" aria-label="Default"
                   aria-describedby="inputGroup-sizing-default">
        </div>
        <button type="button" onclick="order()" class="btn btn-primary btn-order">주문하기</button>
    </div>
    <table class="table">
        <thead>
        <tr>
            <th scope="col">이름</th>
            <th scope="col">수량</th>
            <th scope="col">주소</th>
            <th scope="col">전화번호</th>
        </tr>
        </thead>
        <tbody id="orders-box">
        </tbody>
    </table>
</div>
</body>

</html>

 

만든 것

  • 모두의 책리뷰

 

  • 나홀로 메모장

 

  • 원페이지 쇼핑몰

 

메모

프레임워크를 쓰지 않으면 태양초를 빻아서 고추장을 만드는 격! 프레임워크는 3분 요리 세트라고 생각하면 된다.

 

좋았던 것

클라이언트가 작성한 데이터를 바탕으로 서버에 전달해 웹페이지에 띄우는 일련의 과정이 흥미로웠고, 크롤링이 특히나 재밌었다!