연습하기 – 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를 사용
인스턴스에 접속하는 방법? (MAC)
터미널 실행.
sudo chmod 400 입력. 400 뒤에 띄어쓰기 한 후에 keypair 파일을 끌어다 놓을 것(또는 파일 위치 입력)
password 입력 후 에러 메시지 안 뜨면 잘 된 것
ssh – i 입력 후 keypair 파일 끌어다 놓기(또는 파일 위치 입력). 띄어쓰기 하고 ubuntu@ip 입력. ip는 aws instance 페이지에서 볼 수 있음
08. 서버 세팅하기
filezila 사용을 안내해줬는데, 나는 transmit으로 연결함
mongoDB 등 필요한 세팅을 서버 컴퓨터에서도 해줘야 함
- 서버 환경 통일하기
- 우리는 지금 막! 컴퓨터를 구매한 상태예요. 여기에 이런저런 세팅들(업그레이드, DB설치, 명령어 통일 등)을 해줘야 본격적으로 이용할 때 편리하답니다!
- [코드스니펫] – EC2 한방에 세팅하기 initial_ec2.sh
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
- 한국시간 세팅 EC2 컴퓨터의 시간대를 한국으로 맞추는 명령어
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
- 파이썬 (python3 → python) python3 명령어를 python으로 사용할 수 있게 하는 명령어
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10
- pip (pip3 → pip)
- pip3 설치
- 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
- mongoDB 설치
- 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
- mongoDB 설치 코드 mongoDB 공식자료를 참고해서 정리했습니다. (링크) 한 줄 한 줄이 무슨 뜻인지는 지금 굳이 알 필요 없어요!
- mongoDB 실행하기
# 실행. 아무 반응이 없으면, 잘 실행된 것! # 리눅스는 보통 잘 되면 아무것도 안나와요!^^; sudo service mongod start
- mongoDB 실행하기
- mongoDB 접속 계정 생성하기 우리가 만든 mongoDB를 외부에 열어주기 전에, 접속에 필요한 아이디와 비밀번호를 세팅해봅시다! (설정 안하면 누구나 DB정보를 볼 수 있다는..!)
mongo
좌측에 ‘>’ 표시가 나오면 성공적으로 MongoDB에 접속한 것입니다! 다음 명령어를 순차적으로 입력해주세요. 눈치 채셨겠지만, test, test 자리에 내가 넣고 싶은 아이디/비밀번호를 넣으면 됩니다. (영어로..!)# admin으로 계정 바꾸기 use admin; # 계정 생성하기 db.createUser({user: "test", pwd: "test", roles:["root"]});
아래와 같은 화면을 보면 완성!# 나오기 exit # MongoDB 재시작 sudo service mongod restart
- mongoDB 접속 계정 생성하기 우리가 만든 mongoDB를 외부에 열어주기 전에, 접속에 필요한 아이디와 비밀번호를 세팅해봅시다! (설정 안하면 누구나 DB정보를 볼 수 있다는..!)
- mongoDB를 외부에 열어주기 mongoDB는 디폴트로 내부에서만 접속을 허용하고 있습니다. 이 작업은 외부에서 접근이 가능하도록 잠금을 풀어주는 것입니다. 리눅스 자체 에디터(고급 메모장 정도로 생각!)인 Vim이 등장합니다.
a
를 눌러야 입력 모드가 되고,:wq
를 눌러야 저장하고 나올 수 있습니다.sudo vi /etc/mongod.conf # sudo: 관리자(SuperUser) 권한으로 다음을 실행 # => "관리자 권한으로 /etc 폴더 아래 mongod.conf 파일을 Vim으로 켜줘!"라는 뜻입니다
위 명령어를 실행하신 후, 아래 방향 화살 키를 누르시면 다음과 같은 내용이 보입니다.# 입력 모드 전환 i
위 붉은 박스의 내용을 아래와 같이 바꿔주세요!# 내용 저장하고 에디터 종료하기. esc 누르고 다음 입력. :wq # 재시작 sudo service mongod restart
- mongoDB를 외부에 열어주기 mongoDB는 디폴트로 내부에서만 접속을 허용하고 있습니다. 이 작업은 외부에서 접근이 가능하도록 잠금을 풀어주는 것입니다. 리눅스 자체 에디터(고급 메모장 정도로 생각!)인 Vim이 등장합니다.
- Robo3T를 이용해서, “내 컴퓨터에서”→”서버에 있는 mongoDB”에 접속하기
- 좌측 상단 빨간 상자 내 아이콘을 클릭합니다.
- Create 클릭!
- 접속 정보를 세팅합니다.
- 상단 Authentication 탭을 클릭합니다.
- Perform authentication 체크박스를 클릭합니다.
- 생성한 계정의 아이디와 비밀번호를 입력하고, ‘save’를 클릭합니다.
- 포트포워딩 (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에서 방화벽으로 막아놨기 때문에
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, 포트
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를 변경했는 데 카카오톡에서는 안 바뀌어요!
그것은 페이스북/카카오톡 등에서 처음 것을 한동안 저장해놓기 때문입니다.
- 페이스북 og 태그 초기화 하기: https://developers.facebook.com/tools/debug/
- 카카오톡 og 태그 초기화 하기: https://developers.kakao.com/tool/clear/og
끝!
회고록 이거 써보자