[웹개발 종합반] 5주 1일차 개발일지(5주 1-8강)


💻TIL Web.501

‘무비스타’ 프로젝트

1. DB 만들기(데이터 쌓기)

통상적으로, 데이터를 먼저 모아두고 그 다음에 모아둔 데이터를 보여준다. 이번 프로젝트에서 사용될 데이터는, 크롤링을 통해 영화인들의 이름, 프로필, 최신작 등이다. init_db.py 파일을 생성한 후(파일명은 상관 없음) 아래 크롤링 코드를 붙여넣자. 파일을 실행하면 출력창에 데이터들이 차례로 크롤링되며, Robo 3T에서 데이터들이 잘 쌓인 것을 확인할 수 있다. 데이터가 쌓인 것을 확인한 후 init_db 파일은 삭제한다.

import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient

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


# DB에 저장할 영화인들의 출처 url을 가져옵니다.
def get_urls():
    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('https://movie.naver.com/movie/sdb/rank/rpeople.nhn', headers=headers)

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

    trs = soup.select('#old_content > table > tbody > tr')

    urls = []
    for tr in trs:
        a = tr.select_one('td.title > a')
        if a is not None:
            base_url = 'https://movie.naver.com/'
            url = base_url + a['href']
            urls.append(url)

    return urls


# 출처 url로부터 영화인들의 사진, 이름, 최근작 정보를 가져오고 mystar 콜렉션에 저장합니다.
def insert_star(url):
    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')

    name = soup.select_one('#content > div.article > div.mv_info_area > div.mv_info.character > h3 > a').text
    img_url = soup.select_one('#content > div.article > div.mv_info_area > div.poster > img')['src']
    recent_work = soup.select_one(
        '#content > div.article > div.mv_info_area > div.mv_info.character > dl > dd > a:nth-child(1)').text

    doc = {
        'name': name,
        'img_url': img_url,
        'recent': recent_work,
        'url': url,
        'like': 0
    }

    db.mystar.insert_one(doc)
    print('완료!', name)


# 기존 mystar 콜렉션을 삭제하고, 출처 url들을 가져온 후, 크롤링하여 DB에 저장합니다.
def insert_all():
    db.mystar.drop()  # mystar 콜렉션을 모두 지워줍니다.
    urls = get_urls()
    for url in urls:
        insert_star(url)


### 실행하기
insert_all()


2. 뼈대 준비하기

아래 코드를 각각 app.pyindex.html에 붙여넣자.

from pymongo import MongoClient

from flask import Flask, render_template, jsonify, request

app = Flask(__name__)

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


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


# API 역할을 하는 부분
@app.route('/api/list', methods=['GET'])
def show_stars():
    sample_receive = request.args.get('sample_give')
    print(sample_receive)
    return jsonify({'msg': 'list 연결되었습니다!'})


@app.route('/api/like', methods=['POST'])
def like_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'like 연결되었습니다!'})


@app.route('/api/delete', methods=['POST'])
def delete_star():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg': 'delete 연결되었습니다!'})


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


<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>마이 페이보릿 무비스타 | 프론트-백엔드 연결 마지막 예제!</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css"/>
        <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
        <style>
            .center {
                text-align: center;
            }

            .star-list {
                width: 500px;
                margin: 20px auto 0 auto;
            }

            .star-name {
                display: inline-block;
            }

            .star-name:hover {
                text-decoration: underline;
            }

            .card {
                margin-bottom: 15px;
            }
        </style>
        <script>
            $(document).ready(function () {
                showStar();
            });

            function showStar() {
                $.ajax({
                    type: 'GET',
                    url: '/api/list?sample_give=샘플데이터',
                    data: {},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

            function likeStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/like',
                    data: {sample_give:'샘플데이터'},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

            function deleteStar(name) {
                $.ajax({
                    type: 'POST',
                    url: '/api/delete',
                    data: {sample_give:'샘플데이터'},
                    success: function (response) {
                        alert(response['msg']);
                    }
                });
            }

        </script>
    </head>
    <body>
        <section class="hero is-warning">
            <div class="hero-body">
                <div class="container center">
                    <h1 class="title">
                        마이 페이보릿 무비스타😆
                    </h1>
                    <h2 class="subtitle">
                        순위를 매겨봅시다
                    </h2>
                </div>
            </div>
        </section>
        <div class="star-list" id="star-box">
            <div class="card">
                <div class="card-content">
                    <div class="media">
                        <div class="media-left">
                            <figure class="image is-48x48">
                                <img
                                        src="https://search.pstatic.net/common/?src=https%3A%2F%2Fssl.pstatic.net%2Fsstatic%2Fpeople%2Fportrait%2F201807%2F20180731143610623-6213324.jpg&type=u120_150&quality=95"
                                        alt="Placeholder image"
                                />
                            </figure>
                        </div>
                        <div class="media-content">
                            <a href="#" target="_blank" class="star-name title is-4">김다미 (좋아요: 3)</a>
                            <p class="subtitle is-6">안녕, 나의 소울메이트(가제)</p>
                        </div>
                    </div>
                </div>
                <footer class="card-footer">
                    <a href="#" onclick="likeStar('김다미')" class="card-footer-item has-text-info">
                        위로!
                        <span class="icon">
              <i class="fas fa-thumbs-up"></i>
            </span>
                    </a>
                    <a href="#" onclick="deleteStar('김다미')" class="card-footer-item has-text-danger">
                        삭제
                        <span class="icon">
              <i class="fas fa-ban"></i>
            </span>
                    </a>
                </footer>
            </div>
        </div>
    </body>
</html>


3. 목록 보여주기(GET)

1) 클라이언트/서버 연결 확인하기

@app.route('/api/list', methods=['GET'])
def show_stars():
  sample_receive = request.args.get('sample_give')
  print(sample_receive)
  return jsonify({'msg': 'list 연결되었습니다!'})
<script>
  $(document).ready(function () {
    showStar();
  });

  function showStar() {
    $.ajax({
      type: 'GET',
      url: '/api/list?sample_give=샘플데이터',
      data: {},
      success: function (response) {
        alert(response['msg']);
      }
    });
  }
</script>



2) 서버 만들기

web501 data

서버에서는, 클라이언트에서 데이터를 받을 필요 없이, 쌓아둔 데이터들을 리스트(array) 형태로 내려주기만 하면 된다. 단, 이번에는 like 속성값의 역순으로(like가 큰 순서대로) 데이터를 내려줘야 한다.


@app.route('/api/list', methods=['GET'])
def show_stars():
  movie_star = list(db.mystar.find({},{'_id':False}))
  return jsonify({'movie_stars': movie_star})

일단 movie_star라는 새로운 변수에 pymongo를 이용해 데이터 리스트를 담고, 이를 클라이언트에 내려준다.


‘pymongo 정렬’, ‘pymongo sort w3school’를 구글링해보니, sort()가 있다. 예시를 보니 find().sort("name",-1)과 같이 이용할 수 있다. 첫 parameter는 fieldname, 다음 parameter는 오름차순(ascending, 1)과 내림차순(descending, -1)을 결정한다. 기본값은 오름차순이다. 아래 코드를 사용해 데이터를 내림차순 정렬시킬 수 있다.

@app.route('/api/list', methods=['GET'])
def show_stars():
  movie_star = list(db.mystar.find({},{'_id':False}).sort('like', -1))
  return jsonify({'movie_stars': movie_star})



3) 클라이언트 만들기

function showStar() {
  $.ajax({
    type: 'GET',
    url: '/api/list',
    data: {},
    success: function (response) {
      let mystars = response['movie_stars'];
      console.log(mystars);
    }
  });
}

클라이언트(HTML 파일)에서는, 먼저 console.log를 통해 서버가 내려준 데이터가 잘 전달되었는지 확인해 본다. 페이지를 실행시키면 console창에 데이터 리스트가 뜨는 것을 볼 수 있다.


데이터 리스트가 잘 전달되었다면, for 반복문을 사용해 데이터를 돌려야 한다. 이번에도 먼저 console.log로 데이터 전달을 확인하는 단계를 거친다.

function showStar() {
  $.ajax({
    type: 'GET',
    url: '/api/list',
    data: {},
    success: function (response) {
      let mystars = response['movie_stars']

      for(let i=0; i < mystars.length; i++) {
        let img_url = mystars[i]['img_url']
        let name = mystars[i]['name']
        let recent = mystars[i]['recent']
        let url = mystars[i]['url']
        let like = mystars[i]['like']

        let temp_html =`<div class="card">
                            <div class="card-content">
                                <div class="media">
                                    <div class="media-left">
                                        <figure class="image is-48x48">
                                            <img
                                                src="${img_url}"
                                                alt="Placeholder image"
                                            />
                                        </figure>
                                    </div>
                                    <div class="media-content">
                                        <a href="${url}" target="_blank" class="star-name title is-4">${name} (좋아요: ${like})</a>
                                        <p class="subtitle is-6">${recent}</p>
                                    </div>
                                </div>
                            </div>
                            <footer class="card-footer">
                                <a href="#" onclick="likeStar('${name}')" class="card-footer-item has-text-info">
                                    위로!
                                    <span class="icon">
                          <i class="fas fa-thumbs-up"></i>
                        </span>
                                </a>
                                <a href="#" onclick="deleteStar('${name}')" class="card-footer-item has-text-danger">
                                    삭제
                                    <span class="icon">
                          <i class="fas fa-ban"></i>
                        </span>
                                </a>
                            </footer>
                        </div>`

          $('#star-box').append(temp_html)
      }
    }
  })
}

카드 하나에 해당하는 코드를 복사해 temp_html 변수에 그대로 붙여넣고, img, name, recent, url 변수들을 각각 필요한 곳에 ${} 형태로 입력한다. 다음으로, 카드가 붙여질 상위 태그의 아이디를 찾아 append를 사용한다. 나는 처음에 like 변수를 정의하는 것을 잊었다. 서버 쪽에서 sort()를 사용할 때 like라는 새로운 변수를 설정했으므로, like 변수는 서버 쪽의 'movie-stars', 즉 클라이언트 쪽의 response['movie_stars']의 키로 존재한다.

사용되는 변수가 정의되지 않았거나, 변수명에 오타가 있으면 데이터가 제대로 전달되지 않아 화면에 아무것도 뜨지 않는다. 오타 방지를 위해 변수명은 복사 붙여넣기를 사용하고, 사용된 변수가 모두 잘 정의되었는지 확인할 것!



4) 완성 확인하기

위 사진과 같이, 입력한 데이터가 카드 형태로 페이지에 추가되었음을 확인할 수 있다. like의 역순으로 조회되는지 확인하기 위해서, Robo 3T의 ‘mystar’ 데이터베이스에서 like 속성 > ‘Edit Document’를 선택해 속성값을 바꿔본다. 페이지를 새로고침하면 like 순대로 데이터가 정렬되었음을 확인할 수 있다.


4. 좋아요 기능 구현하기(POST)

  • API 설계

클라이언트 쪽에서는 클릭된 카드의 이름을 서버로 전달, 서버에서는 클라이언트에서 받은 이름을 찾아서 like를 1 증가시킨 후 데이터베이스에 업데이트한다.

1) 클라이언트/서버 연결 확인하기

@app.route('/api/like', methods=['POST'])
def like_star():
  sample_receive = request.form['sample_give']
  print(sample_receive)
  return jsonify({'msg': 'like 연결되었습니다!'})
<script>
  function likeStar(name) {
    $.ajax({
      type: 'POST',
      url: '/api/like',
      data: {sample_give: '샘플데이터'},
      success: function (response) {
        alert(response['msg']);
      }
    });
  }
</script>

<body>
  <footer class="card-footer">
    <a href="#" onclick="likeStar('${name}')" class="card-footer-item has-text-info">
    위로!
    <span class="icon">
      <i class="fas fa-thumbs-up"></i>
    </span>
    </a>
    ...
  </footer>
</body>

좋아요 기능은 like 변수를 업데이트하는 일이므로 POST 요청을 이용한다. 서버에서는 클라이언트 쪽에서 sample_give로 데이터를 받아 sample_receive에 저장, 이를 출력한 후 msg를 내려준다. 클라이언트에서는 name을 인자로 받는 likeStar 함수가 있으며, 함수는 카드 아래쪽 ‘위로!’ 부분을 클릭하면 호출된다. 함수가 호출되면 sample_give 변수로 데이터를 서버로 전달하고, 서버로부터 온 msg를 alert창에 띄운다. ‘위로!’를 클릭하면 alert 창이 뜨는 것으로 연결을 확인할 수 있다.



2) 서버 만들기

@app.route('/api/like', methods=['POST'])
def like_star():
  name_receive = request.form['name_give']

  target_star = db.mystar.find_one({'name': name_receive})
  current_like = target_star['like']
  new_like = current_like + 1

  db.mystar.update_one({'name': name_receive},{'$set':{'like': new_like}})

  return jsonify({'msg': '좋아요 완료!'})

서버에서는 먼저, 클라이언트에서 이름을 받아 해당 이름의 like를 찾아야 한다. 찾은 데이터의 like 변수를 불러온 다음, 하나를 더해야 하므로 원래 변수에 1을 더한 새로운 변수를 지정한다. 단순히 새로운 like 값을 출력하거나 내려주는 것이 아니라, 서버에 업데이트까지 해야 하므로 update()를 이용한다.



3) 클라이언트 만들기

function likeStar(name) {
  $.ajax({
    type: 'POST',
    url: '/api/like',
    data: {name_give: name},
    success: function (response) {
      alert(response['msg']);
      window.location.reload();
    }
  });
}



4) 완성 확인하기

‘위로!’ 버튼을 누르면 ‘좋아요 완료!’ alert 창이 뜨고, 새로고침된 페이지에 업데이트된 like에 따라 데이터가 정렬됨을 확인할 수 있다.


5. 삭제 기능 구현하기(POST)

  • API 설계

클라이언트 쪽에서는 클릭된 카드의 이름을 서버로 전달, 서버에서는 클라이언트에서 받은 이름을 찾아서 해당 이름의 데이터를 삭제한다.

1) 클라이언트/서버 연결 확인하기

@app.route('/api/delete', methods=['POST'])
def delete_star():
  sample_receive = request.form['sample_give']
  print(sample_receive)
  return jsonify({'msg': 'delete 연결되었습니다!'})
<script>
  function deleteStar(name) {
    $.ajax({
      type: 'POST',
      url: '/api/delete',
      data: {sample_give:'샘플데이터'},
      success: function (response) {
        alert(response['msg']);
      }
    });
  }
</script>

<body>
  ...
    <a href="#" onclick="deleteStar('${name}')" class="card-footer-item has-text-danger">
        삭제
    <span class="icon">
      <i class="fas fa-ban"></i>
    </span>
    </a>
  </footer>
</body>

서버 API는 ‘/api/delete’ URL로 POST 요청을 받고 , 클라이언트는 data로 서버에 데이터를 갖고 간다. deleteStar 함수는 ‘삭제’ 버튼이 클릭되었을 때 호출된다. ‘삭제’ 버튼을 누르면 ‘delete 연결되었습니다!’ alert 창이 뜨므로 연결을 확인할 수 있다.



2) 서버 만들기

@app.route('/api/delete', methods=['POST'])
def delete_star():
  name_receive = request.form['name_give']
  db.mystar.delete_one({'name': name_receive})

  return jsonify({'msg': '삭제 완료!'})

API 설계에서 언급했듯, 서버는 클라이언트에서 받은 이름에 해당하는 데이터를 찾아 삭제한다. 삭제 기능의 경우, 이름에 해당하는 데이터의 속성을 사용하지 않으므로 변수 설정 없이 찾은 이름을 그대로 이용한다.



3) 클라이언트 만들기

function deleteStar(name) {
  $.ajax({
    type: 'POST',
    url: '/api/delete',
    data: {name_give: name},
    success: function (response) {
      alert(response['msg']);
      window.location.reload();
    }
  });
}



4) 완성 확인하기

‘삭제’ 버튼을 누르면 msg가 alert 창으로 뜨고, 새로고침된 페이지에서 해당 데이터가 사라진 것을 확인할 수 있다.


Categories:

Updated:

Leave a comment