이전에 연습했었던 영화 목록을 보여주는 페이지를 수정할 것이다.
영화 등록하기 기능을 구현한다.
본인이 프로젝트를 만들기 전에 프로젝트에 필요한 기능을 구현해 보는 것이 좋다.(조각 기능)
URL만 등록하는데 이미지랑 설명이 동시에 나타나 지는건가? - meta
태그를 크롤링 해서 공통적으로 얻어진다고 한다. meta
태그는 head
부분에 들어가는 부분으로 눈으로 보여지는 것이외에 사이트의 속성을 설명해주는 태그이다.
예를 들어서 영화 홈페이지 https://movie.naver.com/movie/bi/mi/basic.naver?code=191597
에 들어가서 콘솔창을 띄우면
이렇게 meta 태그들이 기록 되어있는데, 이 meta
태그의 정보를 가져와서 추가를 해볼 것이다.
전에 크롤링 관련 실습으로 다른 url에 있는 정보를 가져오는 연습을 했었다.
영화 제목과, 이미지, 설명을 가져오는 코드를 작성해보자.
title의 경우 meta 태그에서 og:title이라는 속성을 가지고 있으므로
import requests
from bs4 import BeautifulSoup
url = 'https://movie.naver.com/movie/bi/mi/basic.naver?code=191597'
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')
title = soup.select_one('meta[property="og:title"]')
print(title)
이런식으로 작성후 실행하면
여기서 필요한건 content
항목의 내용이므로
title
을 수정해준다.
title = soup.select_one('meta[property="og:title"]')['content']
수정후 실행하면
이렇게 영화의 제목만 나타난다.
같은 방법으로 이미지와 설명을 표시하는 meta
태그를 찾아서 속성값을 확인후 작성하면
title = soup.select_one('meta[property="og:title"]')['content']
img = soup.select_one('meta[property="og:image"]')['content']
desc = soup.select_one('meta[property="og:description"]')['content']
print(title, img, desc)
"C:\Users\jhs00\OneDrive\바탕 화면\스파르타\projects\movie\venv\Scripts\python.exe" "C:/Users/jhs00/OneDrive/바탕 화면/스파르타/projects/movie/meta_prac.py"
보스 베이비 2 https://movie-phinf.pstatic.net/20210622_174/1624324910624JhEq2_JPEG/movie_image.jpg?type=m665_443_2 베이비 주식회사의 레전드 보스 베이비에서 인생 만렙 CEO가 된 ‘테드’.베이비인 줄 알았던 조카 ‘티...
영화의 제목과 img 태그, 설명까지 출력이 된 것을 확인했다.
그렇다면 이전에 했던 영화 목록 사이트를 flask를 통해서 서버 구동을 시켜보자.
이전의 사이트처럼 get과 post 요청이 잘 되는지 확인부터 할 것이다.
프로젝트의 templates/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{
background-color: green;
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;
justify-content: center;
align-items: center;
/*display flex 4형제
flex-direction: 내부 요소 정렬(가로, 세로)
justify-content: 세로로 어떻게 정렬할 것인가
align-items: 가로로 어떻게 정렬할 것인가.
*/
}
.mytitle > button {
width: 200px;
height: 60px;
color: white;
background-color: transparent; /* 투명한 색*/
border-radius: 50px;
border: 1px solid white; /*테두리*/
margin-top: 10px;
}
.mytitle > button:hover{ /*이 버튼에다가 마우스를 올릴경우*/
border: 2px solid white; /*외부 선이 두꺼워진다.*/
}
.mycomment{
color: gray;
}
.wrap{
max-width: 1200px;
width: 95%;
margin: 20px auto 0 auto;
}
.mypost{
max-width: 500px;
width: 95%;
margin: 10px auto 0 auto;
box-shadow: 0px 0px 3px 0px gray;
padding: 20px;
display: none;
}
.input-group textarea{
margin-top: 20px;
}
.btnwrap{
width: 100%;
margin : 10px auto 0 auto;
display: flex;
flex-direction: row;
justify-content:center;
align-items: center;
}
.btnwrap button{
margin-right: 10px;
border: 1px solid black;
}
</style>
<script>
$(document).ready(function () {
listing();
});
function listing() {
$.ajax({
type: 'GET',
url: '/movie',
data: {},
success: function (response) {
alert(response['msg'])
}
})
}
function posting() {
$.ajax({
type: 'POST',
url: '/movie',
data: {sample_give: '데이터전송'},
success: function (response) {
alert(response['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 type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
<label for="floatingInput">영화 URL</label>
</div>
<div class="input-group mb-3">
<label class="input-group-text" for="inputGroupSelect01">별점</label>
<select class="form-select" id="inputGroupSelect01">
<option selected>-- 선택하기 --</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
<option value="4">Four</option>
<option value="4">Five</option>
</select>
<div class="input-group">
<textarea class="form-control" aria-label="With textarea"></textarea>
</div>
<div class="btnwrap">
<button type="button" class="btn btn-dark">기록하기</button>
<button onclick="close_box()" type="button" class="btn btn-light" >닫기</button>
</div>
</div>
</div>
<div class="wrap">
<div id="card-box" class="row row-cols-1 row-cols-md-4 g-4">
<div class="col">
<div class="card">
<img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This is a longer card with supporting text below as a natural lead-in to
additional content. This content is a little bit longer.</p>
<p>⭐⭐⭐</p>
<p class="mycomment">코멘트가 들어갑니다.</p>
</div>
</div>
</div>
<div class="col">
<div class="card">
<img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This is a longer card with supporting text below as a natural lead-in to
additional content. This content is a little bit longer.</p>
<p>⭐⭐⭐</p>
<p class="mycomment">코멘트가 들어갑니다.</p>
</div>
</div>
</div>
<div class="col">
<div class="card">
<img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This is a longer card with supporting text below as a natural lead-in to
additional content. This content is a little bit longer.</p>
<p>⭐⭐⭐</p>
<p class="mycomment">코멘트가 들어갑니다.</p>
</div>
</div>
</div>
<div class="col">
<div class="card">
<img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This is a longer card with supporting text below as a natural lead-in to
additional content. This content is a little bit longer.</p>
<p>⭐⭐⭐</p>
<p class="mycomment">코멘트가 들어갑니다.</p>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
script
태그의 listing
과 posting
함수가 바로, get 과 POST 요청에 따른 수행하는 일이다.
app.py
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
@app.route('/')
def home():
return render_template('index.html')
@app.route("/movie", methods=["POST"])
def movie_post():
sample_receive = request.form['sample_give']
print(sample_receive)
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, 별점, comment를 바탕으로 영화제목,이미지,설명,별점,comment의 데이터를 등록한다. 어디에? mongoDB 데이터베이스에다가
먼저 app.py 의 POST 요청을 수정한다.
사용자가 입력한 정보를 정의하고
url_receive = request.form['url_give']
star_receive = request.form['star_give']
comment_receive = request.form['comment_give']
사용자가 입력한 정보중 url의 meta태그에서 제목,이미지,설명을 가져와야하므로, 아까 조각기능으로 연습했던 기능을 추가한다.
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']
mongoDB에 보내줄 데이터 객체를 정의한 뒤 insert_one(doc)
로 보내주는 구문을 작성한다.
doc = {
'title' : title,
'image' : image,
'desc' :desc,
'star' : star_receive,
'comment' : comment_receive
}
db.movies.insert_one(doc)
혹시나 해서 적어놓지만, 다른 url에서 정보를 가져오거나, MongoDB를 이용하거나, flask를 통해 서버를 구동하거나 할 때 그에 필요한 패지키 설치는 필수다. 따라서 해당 프로젝트를 시작하기 전에 이미 pymongo,dnspython,Flask,bs4,request,certifi 와 같은 패키지들을 설치해놓아야 한다.
테스트를 해보자.
사이트를 들어가서 url(이때 사용하는 url은 조각기능 연습때 사용한 url을 사용할 것이다.). 별점, 설명을 적고 등록하기 버튼을 누른다.
등록이 완료됬다는 알림이 나온다.
제대로 등록이 되었다면, MongoDB에도 등록이 되었을 터,
성공적으로 등록되었다.
사용자가 등록한 영화를 페이지에 보여준다.
서버의 경우 해야할 일은 간단하다. DB에 있는 데이터를 보내주면 된다.
@app.route("/movie", methods=["GET"])
def movie_get():
movie_list = list(db.movies.find({}, {'_id': False}))
return jsonify({'movies':movie_list})
클라이언트는 이제 서버로부터 받은 데이터를 바탕으로 카드방식의 DOM 을 추가해주는 것이다.
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">
<img src="${image}"
class="card-img-top" alt="...">
<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>`
$('#card-box').append(temp_html);
}
}
})
}
전 프로젝트와 형식이 매우 유사한 모습이다.
이제 테스트를 해보자.
사이트를 실행시켜보면
아까 추가한 영화가 나타났다.
하나를 더 추가해보자. 네이버 영화에서 씽2게더라는 영화를 추가했다.
이로써 영화를 등록하는것과 등록된 데이터를 보여주는 것까지 연습했다.