콘텐츠로 건너뛰기

[스파르타 웹 개발 종합] 클라우드에 코드 올리기

연습하기 – moviestar 프로젝트

: 클라이언트 <-> 서버 <-> DB 통신하며 클라이언트에 웹 구현하고, 데이터베이스에 데이터 쌓는 연습하기

출처: 스파르타 코딩클럽

완성 화면: http://spartacodingclub.shop/moviestar

1. 프로젝트 세팅

– pycharm 실행, moivestar 프로젝트 열기
– package (flask, mongodb, packages, bs4) 추가
– flask 폴더 구조 만들기 (app.py / templates 폴더 / template 폴더 안에 index.html / static 폴더 생성)

2. 데이터 쌓기 – 웹 스크래핑

영화인 정보를 스크래핑하여 db에 저장하기. 강의에서 코드 제공해 줌 (init_db.py)
프로젝트에 init_db.py 파일을 생성하여 코드를 복사/붙여넣고 실행. mongoDB의 mystar collection에 영화인 정보가 저장됨.

init_db.py (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()

3. 뼈대 준비하기

index.html

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

app.py

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)

04. GET 연습(데이터 보여주기)

  • 클라리언트와 서버 연결 확인하기: localhost:5000 접속해서 페이지 잘 나타나는지 확인
  • 서버 만들기
    – show_stars(): DB 데이터 모두 전달 – 순서를 like 개수 순서대로 정렬하여 전달
  • 클라이언트 만들기: 서버에서 데이터 받아와서 star-box.append()
  • 완성 확인 하기
# DB에 있는 데이터(영화인 정보)를 모두 client에 전달. like의 역순으로 sorting하여 전달
@app.route('/api/list', methods=['GET'])
def show_stars():
    movie_stars = list(db.mystar.find({},{'_id': False}).sort('like',-1))
    return jsonify({'movie_stars': movie_stars})
function showStar() {
            $('#star-box').empty()
            $.ajax({
                type: 'GET',
                url: '/api/list?',
                data: {},
                success: function (response) {
                    let mystars = response['movie_stars']
                    for (let i = 0; i < mystars.length; i++) {
                        let name = mystars[i]['name']
                        let url = mystars[i]['url']
                        let recent = mystars[i]['recent']
                        let img_url = mystars[i]['img_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)
                    }
                }
            });

05. POST 연습 (좋아요 +1)

@app.route('/api/like', methods=['POST'])
# client에서 name을 받아오기. DB에서 해당 name의 배우 like 값을 읽어오기. like 값에 1을 추가하여 DB 업데이트.
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 +1 처리). 페이지 reload하여 데이터 다시 불러오기
        function likeStar(name) {
            $.ajax({
                type: 'POST',
                url: '/api/like',
                data: {name_give: name},
                success: function (response) {
                    alert(response['msg']);
                    window.location.reload()
                }
            });
        }

06. POST 연습 (삭제하기)

@app.route('/api/delete', methods=['POST'])
# client에서 name 받아오기. DB에서 해당 name의 데이터 삭제하기
def delete_star():
    name_receive = request.form['name_give']
    db.mystar.delete_one({'name': name_receive})
    return jsonify({'msg': '삭제 완료'})

        // 배우의 이름을 서버로 넘김(서버에서는 delete 처리). 페이지 reload하여 데이터 다시 불러오기
        function deleteStar(name) {
            $.ajax({
                type: 'POST',
                url: '/api/delete',
                data: {name_give: name},
                success: function (response) {
                    alert(response['msg']);
                    window.location.reload()
                }
            });
        }

06. 내 프로젝트를 서버에 올리기

어떤 컴퓨터든지 서버의 역할을 수행할 수 있음. 언제나 요청에 응답하려면,
1) 컴퓨터가 항상 켜져있고 프로그램(ex. app.py)이 실행되어 있어야 함
2) 모두가 접근할 수 있는 공개 주소인 공개 IP주소 (Public IP Address)로 나의 웹 서비스에 접근할 수 있도록 해야 함

우리는 AWS라는 클라우드 서비스에서 EC2 사용권을 구입해 서버로 사용할 것.

07. AWS 서버 구매하기

Ubuntu Server 18.04 LTS (HVM), SSD Volume Type


리눅스의 특징? Open source (무료) – 서버는 컴퓨터 여러대를 사용해야하잖아, 그래서 linux를 일반적으로 씀

우리는 Linux 종류 중 하나인 Ubuntu를 사용

키 페어? 인스턴스(컴퓨터)를 샀으니까, 인스턴스에 접속할 때 사용할 아이디/비밀번호와 같은 것. 이거 잃어버리면 AWS에서도 안 찾아줌. 메일에 보관하던지 잘 보관해야 함. (드라이브에 저장해씀)
일 년 만 무료이니, 사용 필요 없을 때는 사용 종료할 것

인스턴스에 접속하는 방법? (MAC)

터미널 실행.

sudo chmod 400 입력. 400 뒤에 띄어쓰기 한 후에 keypair 파일을 끌어다 놓을 것(또는 파일 위치 입력)

password 입력 후 에러 메시지 안 뜨면 잘 된 것

ssh – i 입력 후 keypair 파일 끌어다 놓기(또는 파일 위치 입력). 띄어쓰기 하고 ubuntu@ip 입력. ip는 aws instance 페이지에서 볼 수 있음

ubuntu@ip 로 시작해야 원격 접속이 완료된 것

08. 서버 세팅하기

filezila 사용을 안내해줬는데, 나는 transmit으로 연결함

python3 하고 파일 이름을 입력하면 python file 실행이 됨

mongoDB 등 필요한 세팅을 서버 컴퓨터에서도 해줘야 함

  • 서버 환경 통일하기
  • 우리는 지금 막! 컴퓨터를 구매한 상태예요. 여기에 이런저런 세팅들(업그레이드, DB설치, 명령어 통일 등)을 해줘야 본격적으로 이용할 때 편리하답니다!

initial_ec2.sh

# UTC to KST
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime

# python3 -> python
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10

# pip3 -> pip
sudo apt-get update
sudo apt-get install -y python3-pip
pip3 --version
sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1

# port forwarding
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

# MongoDB - install
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
sudo apt-get update
sudo apt-get install -y mongodb-org

ss

# MongoDB - run
sudo service mongod start
sleep 7
netstat -tnlp

# MongoDB set user, set conf file
mongo admin --eval 'db.createUser({user: "test", pwd: "test", roles:["root"]});'
sudo sh -c 'echo "security:\n  authorization: enabled" >> /etc/mongod.conf'
sudo sed -i "s,\\(^[[:blank:]]*bindIp:\\) .*,\\1 0.0.0.0," /etc/mongod.conf

sudo service mongod stop
sudo service mongod start
sleep 5
netstat -tnlp

파일질라로 업로드하고, git bash(또는 터미널)에서 아래 코드를 차례대로 입력해주세요.

3분 정도 기다리면 모든 세팅이 완료됩니다.

sudo chmod 755 initial_ec2.sh
./initial_ec2.sh
  1. 한국시간 세팅 EC2 컴퓨터의 시간대를 한국으로 맞추는 명령어 sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
  2. 파이썬 (python3 → python) python3 명령어를 python으로 사용할 수 있게 하는 명령어 sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10
  3. pip (pip3 → pip)
    1. pip3 설치
    2. pip3 명령어를 pip으로 사용할 수 있게 하는 명령어
    • 한 줄 씩 복사 붙여넣기! # pip3 설치 sudo apt-get update sudo apt-get install -y python3-pip # pip3 대신 pip 라고 입력하기 위한 명령어 sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1
  4. mongoDB 설치
      1. mongoDB 설치 코드 mongoDB 공식자료를 참고해서 정리했습니다. (링크) 한 줄 한 줄이 무슨 뜻인지는 지금 굳이 알 필요 없어요! wget -qO - <https://www.mongodb.org/static/pgp/server-4.2.asc> | sudo apt-key add - echo "deb [ arch=amd64,arm64 ] <https://repo.mongodb.org/apt/ubuntu> bionic/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list sudo apt-get update sudo apt-get install -y mongodb-org
      1. mongoDB 실행하기 # 실행. 아무 반응이 없으면, 잘 실행된 것! # 리눅스는 보통 잘 되면 아무것도 안나와요!^^; sudo service mongod start
      1. mongoDB 접속 계정 생성하기 우리가 만든 mongoDB를 외부에 열어주기 전에, 접속에 필요한 아이디와 비밀번호를 세팅해봅시다! (설정 안하면 누구나 DB정보를 볼 수 있다는..!) mongo 좌측에 ‘>’ 표시가 나오면 성공적으로 MongoDB에 접속한 것입니다! 다음 명령어를 순차적으로 입력해주세요. 눈치 채셨겠지만, test, test 자리에 내가 넣고 싶은 아이디/비밀번호를 넣으면 됩니다. (영어로..!) # admin으로 계정 바꾸기 use admin; # 계정 생성하기 db.createUser({user: "test", pwd: "test", roles:["root"]}); 아래와 같은 화면을 보면 완성! # 나오기 exit # MongoDB 재시작 sudo service mongod restart
      1. mongoDB를 외부에 열어주기 mongoDB는 디폴트로 내부에서만 접속을 허용하고 있습니다. 이 작업은 외부에서 접근이 가능하도록 잠금을 풀어주는 것입니다. 리눅스 자체 에디터(고급 메모장 정도로 생각!)인 Vim이 등장합니다. a 를 눌러야 입력 모드가 되고, :wq 를 눌러야 저장하고 나올 수 있습니다. sudo vi /etc/mongod.conf # sudo: 관리자(SuperUser) 권한으로 다음을 실행 # => "관리자 권한으로 /etc 폴더 아래 mongod.conf 파일을 Vim으로 켜줘!"라는 뜻입니다 위 명령어를 실행하신 후, 아래 방향 화살 키를 누르시면 다음과 같은 내용이 보입니다. # 입력 모드 전환 i 위 붉은 박스의 내용을 아래와 같이 바꿔주세요! # 내용 저장하고 에디터 종료하기. esc 누르고 다음 입력. :wq # 재시작 sudo service mongod restart
      1. Robo3T를 이용해서, “내 컴퓨터에서”→”서버에 있는 mongoDB”에 접속하기
      • 좌측 상단 빨간 상자 내 아이콘을 클릭합니다.
      • Create 클릭!
      • 접속 정보를 세팅합니다.
      • 상단 Authentication 탭을 클릭합니다.
        1. Perform authentication 체크박스를 클릭합니다.
        2. 생성한 계정의 아이디와 비밀번호를 입력하고, ‘save’를 클릭합니다.
  5. 포트포워딩 (80포트 → 5000포트) 80포트로 들어오는 요청을 5000포트로 넘겨주는 명령어 sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

설정이 완료되면 python3 으로 파이썬 실행시킬 필요 없음. ‘python’ 명령어로 사용 가능

mongo 입력해서 연결 잘 되면 됨

Flask 서버 실행해보기 – app.py 파일을 올려서 플라스크 서버를 돌려보자

하고 크롬에서 public IP:5000 해도 안들어가짐! aws에서 방화벽으로 막아놨기 때문에

규칙 추가 해주고 나면 5000에 접속 됨

app.py 실행 종료하는 법? ctrl + C (mac도 cmd 아니고 ctrl).

종료하면 당연히 페이지 접속 안된다

원페이지쇼핑몰 업로드하기

Robo3T를 이용해서, “내 컴퓨터에서” -> “서버에 있는 mongoDB”에 접속하기

휴 DB user name이랑 pw 바꿨다가 mongodb 두 세 번 새로 깔았다

mongoDB uninstall 방법

쇼핑몰 프로젝트를 업로드. app.py에서 mongoDB 접속하는 코드는 아래처럼 변경해야 함

client = MongoClient(‘mongodb://test:test@localhost’, 27017)

아이디:비밀번호@localhost, 포트

flask 폴더 구조 모두 업로드 해야 함

IP:5000에 접속하면 쇼핑몰 페이지가 로딩 됨

포트 포워딩 – 주소에서 포트 없이 접속하는 방법

http standard port가 80. http를 주소 앞에 붙이면 80 포트로 자동으로 들어감

http://naver.com이 사실은 http://naver.com:80

포트 번호를 입력하도 되지 않게, 80 포트로 들어오는 입력을 5000 포트로 포워딩 해주면 됨

우리는 이미 포트포워딩 세팅을 해뒀기 때문에, 여기서는 개념만!

  • 지금은 5000포트에서 웹 서비스가 실행되고 있습니다. 그래서 매번 :5000 이라고 뒤에 붙여줘야 하죠. 뒤에 붙는 포트 번호를 없애려면 어떻게 해야할까요?
  • http 요청에서는 80포트가 기본이기 때문에, 굳이 :80을 붙이지 않아도 자동으로 연결이 됩니다.
  • 포트 번호를 입력하지 않아도 자동으로 접속되기 위해, 우리는 80포트로 오는 요청을 5000 포트로 전달하게 하는 포트포워딩(port forwarding) 을 사용하겠습니다.
  • 리눅스에서 기본으로 제공해주는 포트포워딩을 사용할 것입니다. 그림으로 보면 아래와 같습니다.

80 포트로 들어오는 요청을 5000포트로 넘겨주는 명령어

# port forwarding
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

nohup 설정하기 – SSH 접속을 종료해도 서버가 계속 돌게 하기

지금은 내 컴퓨터에서 터미널을 종료하면 접속이 끊김. 내 컴퓨터 끄려고 서버를 구매한건데, 접속이 끊기면 안되지.

# 아래의 명령어로 실행하면 된다
nohup python app.py &

종료하는 방법

# 아래 명령어로 미리 pid 값(프로세스 번호)을 본다
ps -ef | grep 'app.py'

# 아래 명령어로 특정 프로세스를 죽인다
kill -9 [pid값]

해서 나오는 process ID를

kill -9 pID

파일 수정하려고 할 때, 종료한 후에 서버에 있는 app.py를 지우고, 수정한 파일을 다시 서버에 올리고, nohup으로 실행하면 됨

ps -ef

현재 실행되고 있는 프로세스를 다 보여줌. 그 중에서 ‘app.py’가 포함되어 있는 프로세스만 보여달라고 하는게 grep ‘app.py’

도메인 연결하기 – IP 주소가 아닌 도메인 주소로 사이트에 접근하기

도메인 사이트에 접속해서 레코드에 IP를 추가하면 됨

og태그 – 내 프로젝트를 공유했을 때 예쁜 이미지로 나타나게 꾸며보자

<meta property="og:title" content="내 사이트의 제목" />
<meta property="og:description" content="보고 있는 페이지의 내용 요약" />
<meta property="og:image" content="{{ url_for('static', filename='ogimage.png') }}" />

원하는 800×400 이미지를 static 폴더에 저장 file name은 코드와 맞춰주어야 함(ex. ogimage.png)

프로세스 종료 -> 파일 모두 삭제 후 업로드(app.py / template 폴더 / static 폴더) -> nohup으로 프로세스 실행

카톡에 공유하면 이렇게 나옴

// 내 og 태그
    <meta property="og:title" content="원 페이지 쇼핑몰" />
<meta property="og:description" content="내가 만든 첫 웹서비스" />
<meta property="og:image" content="{{ url_for('static', filename='ogimage.png') }}" />

참고 – ogimage를 변경했는 데 카카오톡에서는 안 바뀌어요!

그것은 페이스북/카카오톡 등에서 처음 것을 한동안 저장해놓기 때문입니다.

끝!

회고록 이거 써보자