언어/웹 개발 강의

미니 프로젝트 : 스파르타피디아

JM Lee 2023. 3. 3. 15:26
728x90

이번 시간엔 스파르타피디아 라는 창을 만들어보려고 한다.

백엔드(python)와 프론트엔드(html)을 이어야 하기 때문에

03.pedia 폴더

다음과 같이 app.py를 만들고 templates 폴더 안에 index.html 파일을 만들었다.

그리고 가상환경을 만들어야 하기 때문에 python -m venv venv를 실행해 만들어주고

인터프리터 역시 venv에 맞춰 설정했다.

이번 시간에는 설치할 것이 많았는데, 먼저 내 놋북에 호환을 시켜주기 위해 certifi를 설치했고,

그 다음에도 flask pymongo dnspython requests bs4를 설치했다.

pymongo로 원활한 데이터 이동이 필요하기에 certifi 설치가 필요했던 것으로 현재까지는 판단된다.

 

이번 시간에는 meta 태그에 대해 배웠는데, 썸네일 사진은 og:image, 제목은 og:title, 설명은 og.description으로 나온다.

쉽게 설명하자면 공유하고 싶은 웹이 있을 때 링크만 보내면 정보가 딸려오는데, 그것을 meta 태그를 통해 해결한다.

 

우선은 미리 크롤링 실험하기 위해

위 사이트에 들어가 보았다.
 
보스베이비 영화를 소개해주는 사이트로, 우리는 여기서 방금 말한 썸네일의 제목, 설명, 사진을 가져올 계획이다.
원활하게 이것들을 가져오기 위해 먼저 기본 코드를 설정했다.
meta ogtitle, ogdesc, ogimage를 위한 기본 코드

requests에를 수입하고, bs4로부터 bs를 수입한다. 그 다음 url을 가져온다. user-agent는  다음과 같이 설정하고,

헤더를 통해 접속한 url을 프론트로부터 얻어와서 요청을 수락하기 위해 데이터화시킨다.

그 다음에 data를 분해하고 html 텍스트화시켜 골라온다.

 

*beatuifulsoup은 웹브라우저에 담겨있는 내용들 중 필요한 것들을 골라낼 때 사용할 수 있는 도구이다.

 

사이트 코드에서 원하는 데이터를 추출하기 위한 코드

기본적인 장치들을 세팅했으니, title과 description, image를 가져와야 한다.

og코드를 입력하여 'title'과 'desc', 'image'라고 불리는 파일을 추출할 거고,

우리는 이 파일들에 property="og:___"라고 불리는 코드를 meta로 끌어들이려고 한다.

단, ['content']를 실행하여 거기서 <title></title> 등의 삽입코드를 제외한 내용만 가져온다.

그래서 그것들을 한꺼번에 print하면!

이런 결과를 파이썬 터미널에 올려둘 수 있게 된다. meta 코드에 대해서는 여기까지 알아보겠다.

 

이제 본 서버를 만든다.

먼저 app.py에 튜터님이 올려주신 기본코드를 올린다.

@app.route(api)가 3개가 있는데,

맨 위 route는 HTML을 주는 api다. 그럼 나머지 두개는 뭘까?

첫 번째 post는 sample_give를 받아서 무얼 하려고 하고, msg로 연결완료를 하는 것 같다.

두 번째 get은 아직 무엇을 받지는 않지만 movie_get이 있는 것을 보니 결국 무언가를 받거나 주는 것 같다.

 

그 다음은 INDEX.HTML인데, 워낙 내용이 많아서 끊어서 올리겠다.

$(document).ready, 즉 페이지 로딩이 완성되면 listing이라는 함수를 부른다.

이 listing 기능은 fetch('/movie), 무비에 요청을 한다.

그리고 posting 기능은 "sample_give"라는 데이터를 축적해서 body: formData에 실어 /movie로 보낸다(fetch).

 

그럼 다시 백엔드로 가서 app.route(api)를 보면 ("/movie") 쪽에서 요청한 양식(request form)인'sample_give'를 receive했음을 확인할 수 있다. 이것을 print해서 다시 프론트로 보내면,  이 데이터가 console.log에 쏙 들어온다는 것을 index에서 확인할 수 있다. = console.log(Data) 그리고 데이터의 메세지 부분을 alert로 띄워준다.

 

이 정도로 간단히 기본 코드의 원리가 무엇인지 알아보고, html 파일이 잘 돌아가는지 파이썬 터미널을 돌렸다.

아무 이상이 없으니 다음 단계로 넘어간다.

localhost:5000을 눌러 내가 만든 서버를 열어본다.

서버가 이상이 없으니, 이제 본격적으로 서버 api를 더 만들어볼 예정이다.

 

api 만드는 순서는 먼저 데이터를 쌓는 것(기록하기)이다. 그 다음에 데이터를 가져와야 한다.

 


그럼 이제 post 연습을 시작하겠다.

영화 url과 코멘트를 받아서 url을 기반으로 제목, 설명, 이미지 가져온 다음 url 코멘트까지 해서 mongoDB에 넣는 것.

그러려면 이제 서버를 만들어야 한다.

app.py에서 POST 부분의 api를 이용해야 한느데, 'sample_give'에 url 등을 투입해야 한다.

그래서 sample 대신 url과 comment를 쓴다.

 

그 다음엔 url 기반으로 크롤링을 해야 하는데, html에 url이 있으므로 잘 가져와야 한다.

원활한 크롤링을 위해 먼저 impor request from bs4 import BeatifulSoup 코드를 파이선으로 가져온다.

그 다음 url을 가져오기 위해 meta.py에서 헤더와 og 등을 가져온다.

그리고 받은 url을 크롤링 해야 하기 때문에 url 대신 url_receive를 넣는다.

그런 다음 dbprac.py에서 pymongo로 무난하게 이동시키게끔,

코드를 가져온다.

코드를 가져왔으니 사이트에 필요한 title, description, image, comment를 담아가기 위해

doc{ } 코드를 실현한다.

그리고 데이터베이스로 넘기는 작업을 하기 위해

db.movies.insert_one(doc) doc{ }를 movies db로 옮긴다.

이로써 서버작업은 마무리되었다.

 

그 다음 단계는 클라이언트작업 단계다.

function posting에서 우리는 body에 속한 url과 comment의 id를 가져와서

각각 'url', 'comment'라는 이름으로 포스팅하려고 한다.

그래서 let url = $('$url').val()

let comment = $('$comment').val()로 하여 포스팅하는 것에 큰 문제가 없게끔 기능 설정을 했다.

 

*정보

$ : jquery(jquery를 사용한다는 식별자의 역할)

# : id selctor(html 중 id 값이 __인 태그를 의미, 여기서는 id 값이 url)

val() : 해당 태그의 내용을 가져온다는 뜻

 

그럼 이제 formdata로 옮겨야 하니 마찬가지로 formData도 url과 comment를 보내는 것으로 한다.

이제는 data를 굳이 console에서 처리하는 것이 아니라 백엔드인 파이선에서 처리하기 때문에

console.log태그는 지우면 된다.

그리고 이것이 완료되면 alert를 띄운다.

 

이제 다 되었으니 맞는지 확인을 한다.

눌러봤더니

movies 폴더에 잘 들어온 걸 확인했으므로 post 연습은 이것으로 마무리한다.


다음은 get 연습이다.

마찬가지로 서버부터 먼저 조정해줘야 한다.

영화 관련 데이터를 다 가져와야 하기에 여러 개 찾기 코드를 넣는다.

코드를 내 것으로 만들기 위해 변수들을 내가 가진 것으로 바꿔주고,

이 코드를 내리기 위해서는 return jsonify로 하여 'result' 값에 all_movies를 대입한다.

이러면 서버에서 할 일은 끝났다.

 

다시 클라이언트 쪽으로 왔다.

이제 잘 내려받은 데이터를 어떻게 하면 되겠는가.

우선은 post 할 때 처럼 이미 정리된 데이터이기 때문에 console을 다시 지워주고

이번엔 alert도 지워준다.

이번엔 all_movies를 console에 대입해보기로 했다.

이제 이걸 let rows(나열) 해서 모두 로딩이 되면 자동으로 fetch를 날려서 rows를 console에 찍을 것이다.

그렇게 해서 영화기록에 몇 가지를 등록해보았다.

그럼 pymongo에 이렇게 리스트가 올라오고

마찬가지로 console에도 이렇게 리스트가 올라온다.

그럼 리스트를 만나면? 돌리고 싶다.

rows.forEach((a)=>{

* a : 요소(영화내용) 하나하나를 뜻함 : 변수

 

위 코드를 사용해서 리스트를 정리해서 나열한다.

잘 되는지 확인해볼까?

console.log(comment,title,desc,image) 확인해본다.

원하는 것이 다 나왔다.

그럼 확인할 것 했으니 다시 console.log는 지운다.

 

이제 아래에 카드도 만들어야 하니 카드를 나타내는 temp_html을 투입한다.

이것은 명령어를 어떻게 쓸까?

밑에 body에서 class="col"인 코드들만 내놓으면 이렇게 나온다.

여기서 id는 cards-box니깐 이걸 가지고 가면 된다.

$('#cards-box').append(temp_html)
 

위에서 복습했다. $는 jquery를 사용한다는 뜻이고, #은 오른쪽 이름의 id를 가져오겠다는 뜻이다.

append는 영어로 첨부한다는 뜻이다.

 

근데 정작 가져온 id인 cards-box를 비우고 시작해야 한다. 그래서 다른 코드를 투입한다.

rows를 사용하기 전에 미리 비워야 한다는 조건 코드를 건다

$('cards-box').empty()

이제 그럼 temp_html() 괄호 사이에 넣어줄 것이 필요한데,

그것은 div class="col"의 한 문단을 그대로 넣어주어서 큰 틀을 만들고

title, desc, image를 하나하나 jquery 식에 맞게 넣어준다.

이러면 이제 완성이다!


근데 여기서 url과 코멘트 외에 별점도 남겨보자는 숙제가 나타났다.

튜터님께서 일단 star의 갯수가 몇 개인지에 따라 그것을 그대로 보내는 코드를 써놓았기 때문에,
우리는 프론트에서 받은 요청을 서버로 넘겨 서버에서 처리하는 코드만 첨부하면 되었다.

별점 기능이 있는 서버를 먼저 만들고 클라이언트를 만들어야 하기 때문에 먼저 백엔드부터 손 대려고 했다.
그런데 별점은 크롤링할 필요도 없기 때문에 클라이언트를 손 보기로 했다.
우선 html/body에 별점 html이 일단 존재하기 때문에 우리는 여기에 움직임을 넣어보기로 했다.
그래서 let star=$('#star').val()을 function posting에 붙여서 넣었다.
그리고 이 star 기능을 사용자가 요청했으니 컴퓨터는 이 요청을 백엔드로 넘겨야 한다.
그래서 formData.append("star_give", star) 코드를 넣어서 백엔드로 넘겼다.
그럼 다시 파이선 파일을 본다.
우선은 프론트엔드에서 백엔드로 넘겨줬으니, 백엔드에서 이 'star_give'를 받는 코드를 써야 한다.
star_receive = request.form['star_give']
그럼 백엔드에서 이 요청을 수락하는 코드를 다시 프론트에 보내야 하는데
바로 doc dictionary에 star_receive를 첨부하는 것이다.
그래서 'star' : star_receive 을 doc dictionary에 붙여서 같이 보낸다.

 

이렇게 완성하니 아래와 같이 별점이 무난하게 나오면서 숙제를 마친다.

 

그럼 이것으로 스파르타피디아 제작을 마친다.

풀 코드를 아래에 첨부한다.

app.py

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

from pymongo import MongoClient
import certifi

ca = certifi.where()
client = MongoClient('mongodb+srv://sparta:test@sparta.b5yz26h.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

import requests
from bs4 import BeautifulSoup

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

@app.route("/movie", methods=["POST"])
def movie_post():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']
    star_receive = request.form['star_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')

    # 여기에 코딩을 해서 meta tag를 먼저 가져와보겠습니다.
    ogtitle = soup.select_one('meta[property="og:title"]')['content']
    ogdesc = soup.select_one('meta[property="og:description"]')['content']
    ogimage = soup.select_one('meta[property="og:image"]')['content']

    doc = {
        'title' : ogtitle,
        'desc' : ogdesc,
        'image' : ogimage,
        'comment' : comment_receive,
        'star' : star_receive
    }
    db.movies.insert_one(doc)
   
    return jsonify({'msg':'저장완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
    all_movies = list(db.movies.find({}, {'_id':False}))
    return jsonify({'result' : all_movies})

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

index.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">

        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>

    <title>스파르타 피디아</title>


    <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() {
            fetch('/movie').then((res) => res.json()).then((data) => {
                let rows = data['result']
                $('#cards-box').empty()
                rows.forEach((a)=>{
                    let comment = a['comment']
                    let title = a['title']
                    let desc = a['desc']
                    let image = a['image']
                    let star = a['star']

                    let star_repeat = '⭐'.repeat(star)

                    let temp_html = `<div class = "col">
                                        <div class = "card h-100">
                                            <img src = "${image}"
                                                class = "card-img-top">
                                            <div class = "card-body">
                                                <h5 class = "card-title">${title}</h5>
                                                <p class = "card-text">${desc}</p>
                                                <p>${star_repeat}</p>
                                                <p class = "mycomment">${comment}</p>
                                            </div>
                                        </div>
                                    </div>`
                    $('#cards-box').append(temp_html)
                })
            })
        }

        function posting() {
            let url =$('#url').val()
            let comment =$('#comment').val()
            let star =$('#star').val()

            let formData = new FormData();
            formData.append("url_give", url);
            formData.append("comment_give", comment);
            formData.append("star_give", star)

            fetch('/movie', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
                alert(data['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">
                        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">
                        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">
                        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">
                        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>