영화 리뷰 사이트를 만들어 놓았던걸 사용해서 이번엔 영화 url, 별점, 코멘트를 입력하고 페이지에 올려보도록 한다.
프로젝트 준비
1. 가상 경로 생성 venv
2. 패키지 설치 $ pip install flask pymongo dnspython requests bs4
구현할 기능 구조 파악
입력한 영화 URL로 이미지와 제목 그리고 설명까지 한 번에 가져와서 보여준다.
즉 크롤링한 데이터를 가공해서 카드를 생성하는 기능이 필요하다.
메타 태그 활용하기
url을 가지고 필요한 정보를 가져오는 것은 카카오톡에서 url을 공유할 때 한 번쯤은 본 적이 있다.
여기서 meta 태그는 눈에 보이는 것 이외의 웹의 속성을 설명해 주는 태그로 링크만 가지고 부가적인 정보들도 함께 가지고 올 수 있다.
meta 태그를 크롤링하면 더 쉽게 원하는 정보를 가져올 수 있다.
meta_prac.py
meta 태그를 가져오는 기능을 구현할 파일을 생성한다.
베이스 코드
import requests
from bs4 import BeautifulSoup
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에 접속해서 검사를 통해 코드를 살펴본다.
필요한 정보가 적힌 코드는 head의 meta로 작성되어 있는 부분이다.
여기서 필요한 정보를 가져오도록 코드를 작성해 본다.
ogtitle = soup . select_one ( 'meta[property="og:title"]' )
print ( ogtitle )
가지고 오려는 것은 영화의 제목이다.
ogtitle 변수를 출력해서 제대로 값이 들어오는지 확인한다.
여기서 필요한 부분은 content의 값이다. 해당 값은 키를 통해 가지고 오면 될 것 같다.
추가로 필요한 정보들도 모두 가져와본다.
ogtitle = soup . select_one ( 'meta[property="og:title"]' )[ 'content' ]
ogimage = soup . select_one ( 'meta[property="og:image"]' )[ 'content' ]
ogdesc = soup . select_one ( 'meta[property="og:description"]' )[ 'content' ]
print ( ogtitle , ogimage , ogdesc )
영화의 설명에 대한 정보가 원하는 내용이 아니다. 필요한 정보를 가지고 오기 위해서는 사이트마다 다르게 정해놓은 규칙에 맞춰서 가져와야 하지만 이렇게 되면 특정 사이트에서만 기능이 동작하게 될 수 있기 때문에 일반적으로 정해진 규칙으로 만들어진 데이터를 사용하도록 해야 다른 사이트에서도 동일한 동작을 수행하게 할 수 있다.
페이지 뼈대 만들기
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 )
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" >
integrity = "sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin = "anonymous" >
integrity = "sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin = "anonymous" ></ script >
< title > 스파르타 피디아 </ title >
< style >
* {
font-family : 'Gowun Dodum' , sans-serif ;
}
.mytitle {
width : 100% ;
height : 250px ;
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 ) => {
console . log ( data )
alert ( data [ 'msg' ])
})
}
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' ])
})
}
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" >
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" >
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" >
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" >
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 >
뼈대가 잘 만들어졌는지 서버를 켜고 페이지에 접속해 본다.
localhost:5000
클릭을 해보면 서버로부터 임의의 값을 가져오는 게 확인된다.
우선 저장하는 서버의 API를 구현한다.
doc = {
'title' : ogtitle ,
'desc' : ogdesc ,
'image' : ogimage ,
#'url':url_receive,
'comment' : comment_receive ,
}
db .movies. insert_one ( doc )
값들을 딕셔너리로 만들고 db에 저장한다.
클라이언트에서 동작을 수행하도록 만든다.
해당 기능은 index.html의 posting 함수에서 동작한다.
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' ])
})
}
html에 입력된 값을 서버로 전송하면 서버에서 DB로 저장하게 된다.
현재까지 구현된 기능을 테스트해 본다.
피디아 페이지에서 값을 기록하고 DB를 확인해 본다.
입력한 값과 크롤링한 값들이 잘 저장되었다.
DB정보 띄우기
이번에는 저장한 값들을 불러와서 html을 만들어준다.
서버에서는 DB의 값들을 가져와서 json을 보내준다.
@ app . route ( "/movie" , methods =[ "GET" ])
def movie_get ():
all_movies = list ( db .movies. find ({},{ '_id' : False }))
return jsonify ({ 'result' : all_movies })
클라이언트에서는 페이지가 로드가 되면 실행되는 함수인 listing에서 서버로부터 받은 정보를 가지고 html을 생성한다.
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 desc = a [ 'desc' ]
let image = a [ 'image' ]
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>⭐⭐⭐</p>
<p class="mycomment"> ${ comment } </p>
</div>
</div>
</div>`
$ ( '#cards-box' ). append ( temp_html )
})
})
}
그리고 결과를 확인해 본다.
입력한 정보와 가져온 정보들이 잘 표시되는 것을 확인할 수 있다.