[WEB_풀스텍]/Code Review

[Localhost] 내 인생 최고의 영화!

Code_Otaku 2022. 7. 11. 16:02

 

이런 식으로 돌아가는 또 하나의 에플리케이션을 만들어보려는 것이다.

저번 시간에 다뤘던 '화성 땅 공동구매 ([Localhost] 화성 부동산 공동구매! (tistory.com))' 웹과 거의 유사한 구조로 만들었다.

 

주 목적은 뭐다?

 

첫째, GET / POST 기능적 이해 및 숙달

둘째, Server / Client 간 통신 및 API 개념 이해

 

딱 저 두가지 키 포인트만 머리 속에 집어넣고 가면 된다.

말인 즉슨, API의 개념을 이해하지 못한다면 아주 간단한 웹 페이지 하나 구현할 수 없다는 얘기다.

이미 했던 내용을 또 한 번 다뤄봄으로서 숙달하는 시간을 가져보려는 것이니..

필자와 함께 차근, 차근 과정을 밟아가도록 하자.

 


1. POST (CREATE)

 

 

가장 먼저 이 기능을 구현해 보려는 것이다.

문제 없이 앱이 구동되고 있다면 아틀라스 서버에도 반드시 기록이 남을 것이다.

한 번 확인 해볼까?

 

 

봐라! 역시 거짓말을 하지 않는다니까?

아직 GET 기능을 구현하지 않았기 때문에 목록이 제대로 생성 되었는지

서버에서 직접 확인을 해본 것이다.

시험 삼아 리스트를 몇 개만 더 업데이트 해보겠다.

 

 

서버에도 정상적으로 반영이 되었다.

이따가 GET 기능 역시 완성을 시키면..

 

 

이렇게 클라이언트 화면에도 목록이 최신화 될 것이다.

 

다짜고짜 소스코드부터 들이밀면 숨 막힐까봐 완성본으로 리프레시를 먼저 시킨 것이다.

누가 보건, 안보건 필자는 어려운 내용을 최대한 쉽게 풀어서 쓰는 것이 주 목적이다.

이 블로그를 포플로서 마주할 인사 담당관이 코딩의 'ㅋ' 자도 모르는 코알못이라도 말이다.

 

자! 건물의 전, 후, 좌, 우.. 그리고 동, 서, 남, 북을 모두 살펴보았으니!

이제는 설계도를 확인해 볼 차례이다.

설계도 없이 올라가는 건물이 어디 있겠는가?

중국이 아니고서야..

 

1-1) Server / Client 구현!

 

 

여러분은 지금 건물의 조감도를 보고 있는 것이다.

 

static과 templates 디렉토리는 프로젝트를 새롭게 생성하자마자 만들어주라고 했다.

 

이 자그마한 토이프로젝트의 실행파일이 될 app.py (Server)

그리고 고객들에게 제공될 화면단을 구성하고 있는 index.html (Client)

 

요 두 놈도 기본으로 깔고 가라고 저번 프로젝트에서 설명했다.

필자가 위에서 친절하게 링크까지 첨부 해놨으니..

기억이 안난다면 미리 보고 오는 것도 괜찮겠지?

[Localhost] 화성 부동산 공동구매! (tistory.com)

 

[Localhost] 화성 부동산 공동구매!

이번 시간에 만들어 볼 장난감이다. 기능 자체는 별 것 없다. 입주자의 이름과 주소, 희망 평수 정도를 등록 (POST)한 다음.. 데이터가 제대로 입력 (INSERT) 되었는지 조회 (GET)까지 해보려는 것이다.

codeotaku.tistory.com

 

화면 좌측에는 이번 프로젝트에서 사용된 패키지를 따로 마킹 해놨다.

 

1. flask

2. pymongo

3. dnspython

4. requests

5. bs4

뭘 눈 아프게 그걸 찾아보려고 하는가?

필자가 따로 코드블록에 정리해놨다.

1 ~ 3번까지는 파이참으로 어떤 프로젝트를 만들던지간에 기본적으로 들어가야 할 패키지들이고..

4번과 5번 패키지는 웹 크롤링 (Crawling)을 위해 따로 추가한 것이다.

웹 크롤링??? 그게 모지이???

 

ㅎㅎㅎ... 보고 와!

[3주차] Python / Web Crawling / MongoDB (tistory.com)

 

[3주차] Python / Web Crawling / MongoDB

안녕! 안녕! 드디어 HTML / CSS / JavaScript를 이용한 프론트 단 꾸미기가 끝이 나고.. 3주차에 들어서는 본격적으로 컴파일 언어를 하나 선택하여 백엔드 서버를 구축하는 과정에 돌입했다. 바로 파이

codeotaku.tistory.com

 

from flask import Flask, render_template, request, jsonify

app = Flask(__name__)

import requests
from bs4 import BeautifulSoup


from pymongo import MongoClient
client = MongoClient('mongodb+srv://admin:<password>cluster0.n5ejj.mongodb.net/?retryWrites=true&w=majority')
db = client.dbsparta

사용한 패키지는 모두 최상단에 예쁘게 몰아넣었다.

외우려고 하지 말아라.

필요할 때 마다 가져다가 쓰다보면 언젠가 외워질 것이다.

 

첫 번째, 두 번째 라인은 플라스크에서 필요한 패키지를 따로 또 다시 꺼내온 것이고..

 

세 번째, 네 번째 라인은 웹 크롤링을 위해 꺼내온 패키지들이다.

 

세 번째부터 다섯 번째 라인은 pymongo 서버와 아틀라스 서버 간 통신을 위해 기본적으로 필요한 구성요소들이다.

하나의 패키지처럼 가져다가 쓰라고 했지?

 

늘 강조하는 거지만 코드를 외우려고 하지 말아라.

필자도 문과충이지만 그런 문과식 공부는 여기에서부터 통하지 않는다.

프로젝트의 대략적인 구조부터 하나씩 파고 들어가는 습관을 길러야 한다.

 

# Required Variable
# 1. url
# 2. star
# 3. comment

@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']
    
    (중략..)
    
    return jsonify({'msg': '등록 완료!'})

요 놈은 app.py 파일에 구현한 서버단 소스코드고..

 

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()
        }
    });
}

요 놈은 index.html 파일에 구현한 클라이언트 화면단 소스코드다.

 

둘을 대조해가면서 살펴보길 바란다.

각각 receivegive로 상응하고 있다.

 

이해를 돕기 위해 필자가 비유를 들어보겠다.

 

 

"ajax (Client): 홀에 url, star, comment 버거 세트 각각 하나씩이요!!!"

 

잠시 후...

 

"flask (Server): 요청하신 url, star, comment 버거 세트 가져가세요!!!"

 

코드가 어떠한 원리로 동작하고 있는지 감이 좀 잡히는가?

도대체가 왔다리 갔다리 하는 과정이 여간 헷갈리는 게 아니라서..

다소 억지스럽더라도 필자가 비유를 좀 들어봤다.

실제로도 저러한 방식으로 POST가 이루어지고 있는 것이다.

 

ajax는 Flask 서버에게 필요한 데이터를 POST 하도록 요청하고 있고..

Flask 서버는 POST가 완료되었다는 둥의 메시지를 응답값으로 되돌려 주고 있는 것이다.

이러한 과정 자체를 API라고 하는 것이다.

 

클라이언트에서 하는 역활은 이걸로 끝이 났고..

아직 서버단에 남아있는 일을 좀 더 살펴보도록 할까?

필자가 아까도 말했듯이 우리는 지금 설계도면을 보고 있는 것이다.

코드 한 줄, 한 줄에 집중하기 보다는 전체적인 맥락을 파악하도록 하자.

 

# Start Web 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')

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

웹 크롤링을 위해 별도로 작성한 코드이다.

필자는 이미 개념을 알고 있지만, 우리 독자 여러분은 분명 첨부해준 링크를 가볍게 무시하고 왔겠지?

...

간략하게만 설명하겠다, 아쎄이..!

 

 

우리는 이러한 화면을 출력하고자 하는 것이다.

HTML 태그 중에 OG 태그라는 녀석이 있다.

딱 저런 식으로 영화 제목 (title), 미리보기 이미지 (image), 영화 설명 (desc)과 같은 정보를 미리 노출시켜주려는 것이다.

요즘 단톡방에 유튜브 영상 하나를 공유하더라도 url 주소 하나만 딸랑 붙지는 않지?

 

그렇다면 우리에게 필요한 데이터는?

당연히 title과 image, desc.. 요 세 놈이 되시겠다.

그 놈들만 쏙! 쏙! 쏙! 뽑아오기 위해 굳이 번거롭게 bs4 패키지를 사전에 설치한 것이다.

 

필요한 데이터만 긁어오는 기법!

그것이 바로 Web Crawling이다.

 

아오.. 쫌!!! ㅡㅡ

간략하게만 설명한다고 했는데 거기에만 문단을 세 개나 써버리고 말았잖슮..

고작 장난감 하나 리뷰하는 데 뭐 이리 글이 늘어지는거야?

좀 더 속도를 높여야겠다.

 

다음!

 

# INSERT INTO MOVIES('TITLE','IMAGE','DESC','STAR','CONTENT') VALUE(?,?,?,?,?)
doc = {
    'title':title,
    'image':image,
    'desc':desc,
    'star':star_receive,
    'comment':comment_receive
}
db.movies.insert_one(doc)

여러분이 Oracle이나 MySQL을 쓸 줄 안다면

최상단 주석만으로도 설명은 충분할 것이다.

하지만 필자는 코알못을 상대로 하고 있다고 가정해야 하니..

 

doc는 하나의 '컬럼 (Column)' 으로 생각하면 쉽다.

컬럼???

데이터베이스를 따로 공부하는 건 차치하고, 구글링 먼저 해보고 와라.

엑셀은 써봤을 거 아니야? ㅡㅡ

 

암튼..

doc라는 컬럼은 title, image, desc, star, comment라는 값들을 갖고 있다.

거기에 상응하는 변수 값들이 있을 것이고..

우리는 그 값들을 하나씩 아틀라스 서버에 밀어넣어줄 것이다.

 

솔직히 사이트를 이용하는 고객들이 감상평을 남기거나 말거나..

 

우리는 무조건 Flask 내부 서버와 아틀라스 서버 간 통신까지는 완벽하게 구현 해줘야 한다.

그럼 백엔드 개발자가 할 일은 거기서 그냥 끝나는거야.

 

 

디렉터: "00님! 클라 화면에서 모아보기가 안 뜨는데.. 혹시 서버 쪽에 문제가 있는 게 아닐까요?"

 

백엔드 개발자: "어디 봅시다.. DB는 빠짐없이 잘 넘어오고 있는데요?"

 

 

그럼 이 다음부터는 프론트 개발자를 조지고 보면 되는 일이다.

 

POST (CREATE) 기능구현은 이 정도로 충분한 것 같다.

마지막 하나 남은 GET (READ)로 얼른 넘어가보자.

 


2. GET (READ)

 

 

바로 이전 챕터 마지막 즈음에서 비유로 들었었던..

디렉터와 백엔드 개발자의 대화를 기억하는가?

디렉터는 분명 '모아보기' 화면에 목록이 넘어오지 않는다고 했다.

 

 

요런 식으로 말이다.

다짜고짜 서버쪽에 문제 있는거 아니냐면서 서버 개발자를 찾아올 법도 하지..

분명 클라 개발자가 귀찮아서 짬시킨 게 분명하다. ㅡㅡ

 

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

실제로 app.py 파일에서 저 코드를 작성해주지 않는다면

때려 죽여도 ajax로 넘어가는 데이터가 없을 테니까..

평소처럼 좀 더 개소리를 짓거릴 줄 알았는데 갑자기 훅 들어오니까 당황했는가?

질질 끌어서 무엇 하겠는가?

서버단에서는 저 정도 코드만 작성해주면 할 일 끝나는데..

 

movie_get() 이라는 함수 이름 자체는 별 볼 일 없고..

 

# SELECT * FROM MOVIES
movie_list = list(db.movies.find({}, {'_id': False}))

요 놈이 사실 알맹이이다.

실제 DB에서 모든 데이터를 조회해주기 위해 작성해준 SQL문 같은 녀석이다.

고 위에 주석으로 달아놨지?

우선은 movie_list 라는 변수에 배열 형태로 값들을 저장해주겠다는 뜻이다.

당연히 그 값들은 이전에 DB 서버에 밀어 넣어준 전체 컬럼과 로우를 뜻함이다.

 

# Return Movie List Array to ajax
return jsonify({'movies': movie_list})

이제 그 movie_list라는 변수에 저장된 값들을 JSON화 시켜서 반환해주겠다는 것이다.

어디로??

 

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

function listing() {
	
    (생략..)
    
    }

ajax로..

 

맨 윗줄부터 ready() 함수가 실행되면 곧바로 listing() 함수를 발동시키겠다고 선언하고 있다.

아! 잠깐만...

 

 

아틀라스 서버에는 모든 데이터가 이상없이 등록되었다.

어떻게든 클라쪽으로 책임을 떠안기려는 개수작인 것이다.

아니, 개수작이 아니라 서버단은 이미 할 일이 끝났다니까?

 

function listing() {
    $.ajax({
        type: 'GET',
        url: '/movie',
        data: {},
        success: function (response) {
            let rows = response['movies']
            for (let i = 0; i < rows.length; i++) {
                let comment = rows[i]['comment']
                let desc = rows[i]['desc']
                let image = rows[i]['image']
                let star = rows[i]['star']
                let title = rows[i]['title']
                let star_image = '⭐'.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_image}</p>
                                            <p class="mycomment">${comment}</p>
                                        </div>
                                    </div>
                                </div>`

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

 

벌써부터 토악질이 나오려고 한다.

그러지 말고 한 줄 씩 뜯어내려가 보자..

 

$.ajax({
                type: 'GET',
                url: '/movie',
                data: {},
                success: function (response) {
                
                (생략..)
                
                }

ajax의 구조 자체는 이제 친숙하리라고.. 믿는다.

type은 당연히 GET 요청을 날려야 하고..

data는 POST와 달리 따로 지정해주지 않았다.

어짜피 전부 다 불러와야 하잖아.

응답이 성공적으로 이루어졌다면 중괄호 이하를 실행하라~ 부터가 알맹이다.

 

let rows = response['movies']

왠 열 (row)???

변수의 이름은 신경쓰지 말라고 했다.

어떠한 값들을 저장하고 있는지를 먼저 파악해라.

딱 봐도 영화 목록에 대한 배열을 response 값으로 저장하겠다고 하는구만 뭘..

 

for (let i = 0; i < rows.length; i++) {
                        let comment = rows[i]['comment']
                        let desc = rows[i]['desc']
                        let image = rows[i]['image']
                        let star = rows[i]['star']
                        let title = rows[i]['title']
                        
                        (생략)
                        
                        }

걍 for문 돌아가고 있는거다.

필요한 값들을 모두 매개변수로 선언하고..

i번째에 해당하는 row의 값들을 저장하고 싶은거다.

그것이 title이 되었건, 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_image}</p>
                            <p class="mycomment">${comment}</p>
                        </div>
                    </div>
                </div>`

for문을 돌리는 와중에 까먹어서는 안되는 과정이 있다.

temp_html

유저가 보고있는 HTML 화면에 실제로 밀어넣어 줘야 하는 요소들을 추가 해주는 거다.

 

 

이런 카드 박스 하나, 하나를 말이다.

 

우선은 컬럼의 양식을 빌려와야 할 것이고..

그 안에서 우리가 원하는 값들로 쇽! 쇽! 바꿔주기 위해 백틱 (``)으로 묶었을 뿐이다.

 

 

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

for문의 마지막에는 원하는 값들을 '#card-box' 태그 안에 추가시켜줘야겠지?

 

여기까지 마무리 지으면 클라이언트 화면단도 다 끝난 것이다.

 

어디 테스트를 해보자..

 

 

범죄도시2 라는 영화를 크롤링 해볼까?

마동석이 존재감을 미친듯이 뿜뿜하고 있다..

 

 

안가져가면 죽여버릴 거 같음.. ㄷㄷ

 

 

뭔가 바뀐거 같다?

 

 

기록 데챠앗..!

 

 

데푸풋...

 

 

당연히 서버에도 무사.. 히 반영이 되어야지..

 


어떠한가?

이제 어느 정도 익숙해지지 않았는가?

아직까지는 뭔가 아리까리 할거다.

그래서 예제를 하나 더 준비해봤다.

괄약근에 긴장 풀지 말아라, 아쎄이..