Flask - 스파르타피디아

장현웅·2023년 8월 15일
0

EXAMPLE) Project 2. [스파르타피디아]

1. Flask로 만들 프로젝트 폴더 구조 만들기

1) 프로젝트 폴더(PEDIA) 생성
2) 프로젝트 폴더 안에 app.py 파일 생성
3) 가상환경(venv) 만들고 활성화하기
4) PEDIA 폴더 안에 templates 폴더 생성
5) templates 폴더 안에 index.html 파일 생성

pedia 폴더 구조
- - -
pedia 
|— venv
|— app.py (서버)
|— templates
         |— index.html (클라이언트 파일)

6) 패키지 설치 (flask, pymongo, dnspython, requests, bs4,)
pip install flask pymongo dnspython requests bs4(여러 개를 설치할 때는 띄어쓰기로 구분)

2. [스파르타피디아] - 조각 기능 구현해보기

스파르타피디아에서 나는 URL, 별점, 코멘트만 작성했는데 기록되는 정보들은 이미지, 제목, 영화 설명까지 딸려와 기록된다. 이것은 meta 태그를 활용하면 된다.

  • meta (<meta 속성 = "속성값">) 태그란?
    meta는 이 웹사이트가 어떤 정보를 담고 있는지 더 자세하게 알려주는 태그이다. 현재 페이지의 인코딩(언어정보), 페이지 설명, 페이지에 관련한 주요 키워드 등을 담는 역할을 한다.

ex) 카카오톡에서 영화 링크 보낼 때

  1. 썸네일 사진 - og:image
  2. 썸네일 제목 - og:title
  3. 썸네일 설명 - og:description

링크만 보냈는데 위의 세 가지 정보가 딸려오는데 이 정보들이 meta태그이다.

  • 조각기능이란?
    말 그대로 조각들을 붙여 하나로 만든다는 의미인데, 웹스크래핑으로 URL에서 페이지 정보를 가져오기 위해 meta태그를 활용할 때 이 작업을 꼭 프로젝트 내부에서 만들지 않고 다른 파일에서 코드를 작업해보고 구현되는 것이 확인 되면 app.py로 복사해서 가져오는 것이다. 서버를 만들고나서 이 기능을 구현할 수도 있겠지만 그럴 경우 오류가 났을 때 어느 부분에서 오류가 났는지 쉽게 찾기 힘들 가능성이 있다. 다른 파일에서 코드를 먼저 작업해볼 경우 일일히 flask에서 브라우저를 열고 테스트하지 않아도 되기 때문에 더 수월하게 작업할 수 있다.

- meta 태그 정보 가져오는 조각기능 만들기

가져올 meta 태그 정보들은 스파르타피디아에서 포스팅 정보에 들어간다. meta 태그로 크롤링을 하면 원하는 정보를 더 쉽게 가져올 수 있다.

1) 테스트를 위해 새 파일(meta_prac.py)파일을 만든다.
2) 크롤링 기본 코드를 넣어준다.

import requests
from bs4 import BeautifulSoup

URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
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를 먼저 가져와보겠습니다.

3) 이제 meta 태그를 크롤링 → 구글링: meta 태그 크롤링

URL을 들어가보면,

크롤링 해올 정보들이 meta 태그에 들어가 있는 것을 볼 수 있다.

이전에 크롤링 했던 방법은 개발자도구의 데이터에서 원하는 부분의 Copy → Copy selector로 선택자를 복사해서 select_one()함수를 이용해서 값을 담고 .text()로 텍스트 부분만 출력했다.

이번에는 좀 더 특정지어서 구체적으로 명령내리는 방법을 써보자.

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

print(ogtitle)

→ meta 태그 중 'property'가 "og:title"인 것의 'content'를 가지고와서 print해라.

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

print(ogimage,ogtitle,ogdesc)

만약, URL을 바꿔본다면,

import requests
from bs4 import BeautifulSoup

URL = 'https://spartacodingclub.kr'
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')

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

print(ogimage,ogtitle,ogdesc)

잘 작동한다. 이것은 어떤 URL들이 공통적으로 가지고 있는 요소들을 크롤링 해와서 보여주는 방법인 것이다. 왜냐면,

유저가 저 URL에 어느 URL을 넣을지 모르기 때문에 이런 방법이 있는 것이다.

유저가 URL, 별점, 코멘트를 작성하고 '기록하기'를 누르면 기록한 데이터와 이 URL의 meta 태그(title, description, image)도 같이 db에 넣어줄 때 백엔드가 사용될 것이다. 이제 이것이 app.py의 어떤 부분에 들어갈지 알아보자.

3. [스파르타피디아] - 뼈대 준비하기

  • app.py - 스파르타피디아
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

@app.route('/')
def home():
	return render_template('index.html') # html을 app.py로 가져오는 애(Flask의 render_template)

@app.route("/movie", methods=["POST"]) # API 1
def movie_post():
	sample_receive = request.form['sample_give'] # 'sample give'를 받아서 뭔가를 함.
	print(sample_receive)
	return jsonify({'msg':'POST 연결 완료!'}) # 'POST 연결 완료'라는 'msg'를 클라이언트로 내려줌.

@app.route("/movie", methods=["GET"]) # API 2 # "/movie"로 들어옴.
def movie_get(): # 아무것도 안받음.
	return jsonify({'msg':'GET 연결 완료!'}) # 'GET 연결 완료!'라는 'msg'를 클라이언트로 내려줌.

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

    <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(); # listing()이라는 함수를 부름.
        });

        function listing() {
            fetch('/movie').then((res) => res.json()).then((data) => { # app.py의 API(GET요청)'/movie'로 뭔가를 요청함(fetch).
            console.log(data) # app.py에서 내려준 데이터를 콘솔에 찍음.
            alert(data['msg']) # app.py에서 내려준 데이터의 'msg'값을 알림으로 띄운다.
            })
        }

        function posting() { # 기록하기 버튼에 연결된 함수.
						let formData = new FormData();
            formData.append("sample_give", "샘플데이터"); # "sample_give"라는 곳에 데이터를 축적해서 

            fetch('/movie', {method : "POST",body : formData}).then((res) => res.json()).then((data) => { # formData에 실어서 POST 요청으로 '/movie'보낸다(fetch).
            console.log(data) # app.py에서 내려준 데이터를 콘솔에 찍음.
            alert(data['msg']) # app.py에서 내려준 데이터의 'msg'값을 알림으로 띄운다.
            })
        }

        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="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>
  • mongoDB Atlas 창 띄워두기
  • 잘 연결되었는지 브라우저 localhost:5000에서 확인.

4. [스파르타피디아] - POST 연습(포스팅하기) 데이터 쌓기 먼저

1) 데이터 명세

    1. 요청 정보 : URL= /movie, 요청 방식 = POST
    1. 클라(fetch) → 서버(flask) : url, comment
    1. 서버(flask) → 클라(fetch) : 메시지를 보냄 (포스팅 완료!)

2) 클라이언트와 서버 연결 확인하기

[서버 코드 - app.py]

@app.route("/movie", methods=["POST"])
def movie_post():
    sample_receive = request.form['sample_give']
    print(sample_receive)
    return jsonify({'msg':'POST 연결 완료!'})

[클라이언트 코드 - index.html]

function posting() {
	let formData = new FormData();
    formData.append("sample_give", "샘플데이터");
    
    fetch('/movie', {method: "POST",body: formData,}).then((res) => res.json()).then((data) => {
            console.log(data);
            alert(data["msg"]);
          });
}

<button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>

→ 어느 버튼에 들어가있는지 확인

<button onclick="posting()" type="button" class="btn btn-dark">기록하기</button>

→ ""posting()""함수 확인

function posting() {
	let formData = new FormData();
    formData.append("sample_give", "샘플데이터");
    
    fetch('/movie', {method: "POST",body: formData,}).then((res) => res.json()).then((data) => {
            console.log(data);
            alert(data["msg"]);
          });
}

→ "샘플데이터"라는 데이터를 만들어서 formData라는 변수에 넣고 "sample_give"라는 이름으로 주고 있다(append).

→ "샘플데이터"라는 데이터를 만들어서 formData라는 변수에 넣고 "sample_give"라는 이름으로 주고 있다(append).

→ 그 데이터를 실어서 '/movie'(URL)에 formData를 POST라는 방식으로(method: "POST") 요청함 = 서버(백엔드)로 가져옴(fetch).

    fetch('/movie', {method: "POST",body: formData,}).then((res) => res.json()).then((data) => {

→ app.py(백엔드)에서 보면 "/movie"라는 이름으로 데이터(request.form['sample_give'])가 들어옴(flask).

@app.route("/movie", methods=["POST"])
def movie_post():
    sample_receive = request.form['sample_give']

→ 'sample_give'가 데이터를 찾음 (formData.append("sample_give", "샘플데이터"))
→ '샘플데이터'가 터미널에 찍히고

print(sample_receive)

→ 메세지({'msg':'POST 연결 완료!'})를 내려줌.(서버(flask) → 클라(fetch))

return jsonify({'msg':'POST 연결 완료!'})

→ 내려준 메시지는 클라이언트(index.html)의 console.log(data)에 들어옴.

console.log(data);
console.log({'msg':'POST 연결 완료!'});

→ 이 데이터는 딕셔너리. 이 데이터 값의 'msg'만 가지고 alert를 띄워준다.

alert(data["msg"]);

3) 서버부터 만들기 (요청하기 전에 받을 곳 먼저 만들기)

  • url, comment정보를 받아서, 저장
  • 미리 만든 meta_prac.py 붙여넣기

→ 클라이언트와 서버 연결 확인할 때 'sample_give'로 받아왔던 것처럼 이번엔 'url_give', 'comment_give'로 받아와서 'url_receive', 'comment_receive'의 형태로 각각의 변수에 넣어보자.

@app.route("/movie", methods=["POST"])
def movie_post():
	#sample_receive = request.form['sample_give']
	url_receive = request.form['url_give']
	comment_receive = request.form['comment_give']
	#print(sample_receive)
	return jsonify({'msg':'POST 연결 완료!'})

→ 스파르타피디아에서 나는 URL, 별점, 코멘트만 작성했는데 기록되는 정보들은 이미지, 제목, 영화 설명까지 딸려와 기록된다. 이것은 URL을 기반으로 meta 태그를 이용해서 크롤링을 해오면 되는데, 미리 만들어둔 meta_prac을 참고해서 붙여넣어보자.

[meta_prac 코드]

import requests
from bs4 import BeautifulSoup

URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
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')

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

print(ogimage,ogtitle,ogdesc)

[app.py로 가져옴]

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

import requests
from bs4 import BeautifulSoup

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

@app.route("/movie", methods=["POST"])
def movie_post():
	#sample_receive = request.form['sample_give']
	url_receive = request.form['url_give']
	comment_receive = request.form['comment_give']
	#print(sample_receive)

	URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
	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')

	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']
	
	return jsonify({'msg':'POST 연결 완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
	return jsonify({'msg':'GET 연결 완료!'})

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

→ 이제 이 크롤링 코드에 크롤링해서 가져온 데이터를 받을 URL에 app.py(백엔드)에서 url 데이터를 받는 변수 url_receive를 넣어준다.

URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
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')
URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
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')

→ 이제 받은 데이터들(url_receive, comment_receive, ogtitle, ogdesc, ogimage)를 pymongo를 이용해서 db에 넣는다. pymongo import 코드를 넣어준다.

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

from pymongo import MongoClient
client = MongoClient('mongodb+srv://sparta:test@cluster0.wop0dox.mongodb.net/?retryWrites=true&w=majority')
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():
	#sample_receive = request.form['sample_give']
	url_receive = request.form['url_give']
	comment_receive = request.form['comment_give']
	#print(sample_receive)

	URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
	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')

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

	return jsonify({'msg':'POST 연결 완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
	return jsonify({'msg':'GET 연결 완료!'})

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

→ 이제 document를 만들어서 insert_one(doc)해주면 된다. pymongo로 데이터 db에 저장하기 코드로 받은 데이터들을 넣어준 변수의 값들을 딕셔너리 형태로 넣어준다.

doc = {
    'title':ogtitle,
    'desc':ogdesc,
	'image':ogimage,
    #'url':url_receive, html을 보면 우리가 영화 포스팅을 붙여넣는데 url이 필요한 부분이 없다. 그래서 저장도 해줄 필요 없음.
    'comment':comment_receive
    }

→ 이제 insert_one(doc)으로 db에 movies 프로젝트에 넣어준다.

db.movies.insert_one(doc)

→ db에 저장이 완료 되었다는 데이터의 'msg' 값을 '저장완료!'로 변경한다.

return jsonify({'msg':'저장완료!'})

→ 백엔드 완성

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

from pymongo import MongoClient
client = MongoClient('mongodb+srv://sparta:test@cluster0.wop0dox.mongodb.net/?retryWrites=true&w=majority')
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():
	#sample_receive = request.form['sample_give']
	url_receive = request.form['url_give']
	comment_receive = request.form['comment_give']
	#print(sample_receive)

	URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
	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')

	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,
		'url':url_receive,
        'comment':comment_receive
    }
	db.movies.insert_one(doc)

	return jsonify({'msg':'저장완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
	return jsonify({'msg':'GET 연결 완료!'})

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

4) 클라이언트 만들기(서버가 클라이언트의 요청을 처리하기 위해 클라이언트로부터 넘겨 받을 데이터를 담아야한다.)

이제 서버는 완성했으니까 서버에서 url_receive와 comment_receive가 index.html(클라이언트)로부터 url이 포함되어있는 input 태그의 id값과 comment가 포함되어있는 textarea 태그의 id값을 jQuery를 이용해서 val() 밸류값을 뽑아 각각의 변수에 넣고 formData에 담아서 url_give, comment_give의 형태로 .append 해서 fetch로 서버에 데이터를 보내주자.

→ index_html에서 '기록하기'버튼에 연결되어있는 posting()함수를 수정한다. 스파르타피디아에서 사용자가 입력한 url과 comment 값을 "샘플데이터"의 형태로 담아주기 위해 각 정보가 포함되어있는 태그의 id값을 jQuery의 val()을 이용해서 밸류값을 추출해 각각의 변수에 담고 formData에 담아서 url_give, comment_give의 형태로 .append 해서 fetch로 서버에 데이터를 보내주자.

function posting() {
  let formData = new FormData();
  formData.append("sample_give", "샘플데이터");

  fetch('/movie', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
    console.log(data)
    alert(data['msg'])
  })
}
<input id="url" type="email" class="form-control" placeholder="name@example.com">
<label>영화URL</label>
<textarea id="comment" class="form-control" placeholder="Leave a comment here"></textarea>
<label for="floatingTextarea2">코멘트</label>
function posting() {
            let url = $('#url').val()
            let comment = $('#comment').val()

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

            fetch('/movie', {method: "POST",body: formData}).then(res => res.json()).then(data => {
                    alert(data['msg'])
                    window.location.reload()
                })
        }

→ 잘 되는지 브라우저에서 주문해보고 mongoDB에서 확인해보자.

5. [스파르타피디아] - GET 연습(보여주기)

window.location.reload()로 로딩이 완료되면 자동으로 fetch로 데이터를 날려서 사용자가 기록한 포스팅 카드들이 붙게 만든다.
(POST요청으로 DB에 데이터가 저장되고 저장완료! alert가 뜬 후 Refresh가 되어 페이지가 로딩되면 자동으로 fetch로 데이터를 날려 GET요청을 해서 데이터를 받아와서 보여준다.)

1) 데이터 명세

    1. 요청 정보 : URL= /movie, 요청 방식 = GET
    1. 클라(fetch) → 서버(flask) : 없음
    1. 서버(flask) → 클라(fetch) : 전체 주문을 보내주기

2) 클라이언트와 서버 확인하기

[서버 코드 - app.py]

@app.route("/movie", methods=["GET"])
def movie_get():
    return jsonify({'msg':'GET 연결 완료!'})

[클라이언트 코드 - index.html]

$(document).ready(function(){
	  listing();
});

function listing() {
    fetch('/movie').then((res) => res.json()).then((data) => {
            console.log(data)
            alert(data['msg'])
          })
}

3) 서버부터 만들기

받을 것 없이 db의 movies에서 영화정보를 다 가져와서(담아서) 내려주기만 하면 된다.
→ 일단, DB에서 데이터를 서버로 다 가져옴.(pymongo 사용)

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

4) 클라이언트 만들기

→ 서버에서 클라이언트로 데이터가 잘 내려갔는지 all_movies를 꺼내서 콘솔에 출력해보자.

$(document).ready(function () {
      listing();
    });

    function listing() {
      fetch('/movie').then((res) => res.json()).then((data) => {
      let rows = data['result']
        console.log(rows) // 콘솔에서만 확인해보자.
        //alert(data['msg']) 내려주는 데이터가 'msg'값이 아니기 때문에 없애줌.
      })
    }

리스트의 형태로 가져옴. 리스트를 만나면 돌려라!

→ 위에서 가져온 데이터 리스트에 저장되는 데이터들이 많아졌을 경우를 생각해보면, 페이지에 ('영화URL'은 필요없고) 'comment', 'desc', 'image', 'title'을 각각 출력하기 위해서는, 서버에서 내려온 리스트 형태의 데이터를 forEach반복문을 통해 하나씩 뽑아내서 그 데이터의 'comment', 'desc', 'image', 'title'의 값을 각각의 변수에 담고 붙여주고 싶은 html의 형태로 jQuery를 사용해서 변수 temp_html에 담아 append해주면 된다.

→ 서버에서 내려온 리스트 형태의 데이터를 forEach반복문을 통해 하나씩 뽑아서 콘솔에 찍어보자.

function listing() {
        fetch('/movie').then(res => res.json()).then(data => {
                let rows = data['result']
	            rows.forEach((a) =>	
                	let comment = a['comment']
                    let title = a['title']
                    let image = a['image']
                    let desc = a['desc']
                    console.log(comment,title,image,desc)

→ 붙여주고 싶은 html에는 데이터의 '이름','주소','평수' 각각 따로 붙여줘야 하니까 각각의 변수에 담고 temp_html에 원하는 html의 형태로 백틱으로 감싸서 담아서 id="cards-box"에 붙여준다. 붙여주기전에 .empty()로 원래 있던 카드들은 비워준다.

function listing() {
        fetch('/movie').then(res => res.json()).then(data => {
                let rows = data['result']
                //console.log(rows)
				$('#cards-box').empty()
                rows.forEach((a) => {
																	let comment = a['comment']
                    let title = a['title']
                    let image = a['image']
                    let desc = a['desc']
					//console.log(comment,title,image,desc)
                    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 class="mycomment">${comment}</p>
                                            </div>
                                        </div>
                                    </div>`

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

→ 브라우저에서 서버에서 내려온 데이터가 출력되는지 보자.

5) 완성 확인하기

새로운 영화를 기록해보자.



  • app.py - 스파르타피디아 서버
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

from pymongo import MongoClient
client = MongoClient('내 mongoDB URL')
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']

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

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


    doc = {
        'title':ogtitle,
        'desc':ogdesc,
		'image':ogimage,
        'comment':comment_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">

    <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() {
            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 image = a['image']
                        let desc = a['desc']

                        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 class="mycomment">${comment}</p>
                                                </div>
                                            </div>
                                        </div>`

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

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

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

            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="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>

6. [스파르타피디아] - 별점 추가하기

[ index.html 별점 추가하기 코드]

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


→ 위와 같이 만들기 위해 영화 URL이 있는 div와 코멘트가 있는 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>

→ 이제 위와 같이 별점을 선택해서 기록하면 아래의 형태로 포스팅 카드가 붙게 만들어보자.

→ 별점마다 value 속성값이 들어있다.

일단 선택한 별점을 temp_html에 붙여넣기 위해서 별모양의 문자가 필요한데

위 별점 선택 div의 select 태그에 지정되어있는 id값('id = star')을 forEach 반복문 안에 하나의 변수에 let star = a['star']로 넣어주고

예전에 배웠던 반복함수 repeat()를 이용해서 변수 star의 값에 따라 별모양을 반복한 값을 또 하나의 변수에 star_repeat = '⭐'.repeat(star)로 넣어준다.

이제 temp_html에 'id = star'에서 사용자가 선택한 별점 값이 들어있는 변수 'star'의 값만큼 반복된 별모양들이 들어있는 변수 star_repeat를 <p>${star_repeat}</p>의 형태로 'desc'와 'comment' 사이에 넣어준다.

    $(document).ready(function () {
      listing();
    });

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

→ 이제 이것을 db에 저장하고(POST) db에서 데이터를 가져와서 브라우저에 띄우기 위해(GET) 클라이언트(index.html)의 posting()함수에서 별의 값을 val()로 담아 star라는 변수에 넣고 'star_give'의 형태로 변수 star의 값을 formData에 담아(.append) fetch로 서버(app.py)에 POST요청으로 데이터를 보낼 것이다. 그리고 서버(app.py)에서 'star_give'를 'star_receive'의 형태로 받아서 db에 저장할 값인 document에 'star_receive'를 담을 것이다.

[클라이언트(index.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()
      })
    }

[서버(app.py)]

@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']
	

	URL = 'https://movie.daum.net/moviedb/main?movieId=161806'
	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')

	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,
			'star':star_receive,
        	'comment':comment_receive
   		 }
	db.movies.insert_one(doc)

	return jsonify({'msg':'저장완료!'})

→ 브라우저에서 영화 포스팅 카드를 추가해보고 db에서 잘 저장되는지 확인해보자.


0개의 댓글