wisePocket

[Flask] Flask framework 미니프로젝트(project pedia) 07 (Backend 리뷰 기록하기 기능 구현 / DB설계, DB연결, POST 요청 및 응답, insert, 웹 크롤링) 본문

Python&Flask Tutorials, AWS EB/Flask_project_pedia

[Flask] Flask framework 미니프로젝트(project pedia) 07 (Backend 리뷰 기록하기 기능 구현 / DB설계, DB연결, POST 요청 및 응답, insert, 웹 크롤링)

ohnyong 2023. 7. 13. 19:32

GET 및 POST 연결 테스트 메서드의 작동여부가 확인되었으니

클라이언트에서 입력값을 받아 DB에 저장하는 것부터 POST 방식으로 연결하고자 한다.

클라이언트에서는 URL, 별점, 리뷰 내용을 받고

위 URL을 통해서 영화의 제목, 설명, 이미지 URL을 받게 된다.

이 데이터들을 DB에 넣어주는 기능을 작성해야 한다.

#### 영화 리뷰 기록 진행
- 대상 영화 URL, 별점, 리뷰 코멘트 입력
- '기록하기' 버튼으로 입력값+크롤링 데이터 DB로 전송 및 저장 (insert)

1. 데이터 명세

  • DB : MongoDB
  • Collection : movie
  • Document :
    • 영화 URL : 'url':'url_receive' / from Frontend #url-> formData 'url_give':'value'
    • 별점 : 'star':'star_receive' / from Frontend #star-> formData 'star_give':'value'
    • 리뷰 내용 : 'comment':'comment_receive' / from Frontend formData #comment-> 'comment_give':'value'
    • 영화 제목 : 'title':ogtitle / from URL <meta [property="og:title"]['content']> 'value'
    • 영화 설명 : 'desc':ogdesc / from URL <meta[property="og:description"]['content']> 'value'
    • 영화 이미지 URL : 'image':ogimage / from URL <meta[property="og:image"]['content']> 'value' 

2. 서버 만들기 및 MongoDB 연결

서버 실행을 위한 라이브러리 임포트와 서버 실행 포트 설정은 GET,POST 기초 테스트에서 이미 작성되었다.

# 라이브러리 임포트
# Flask Framework
# view페이지 렌더링을 위한 render_template 메서드
# 요청 데이터에 접근 할 수 있는 flask.request 모듈
# dictionary를 json형식의 응답 데이터를 내보낼 수 있는 jsonify 메서드
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

...

# app이라는 메인 함수 
# if __name__ == "__main__" 의 의미는 메인 함수의 선언, 시작을 의미
# 이 파이썬 스크립트가 직접 실행될 때에는 main() 함수를 실행하라
if __name__ == '__main__':
    app.run('0.0.0.0', port=5001, debug=True)

우선 DB에 넣어주어야 하기 때문에 DB를 사용 할 수 있도록

pymongo를 임포트

(MacOS 권한 관련 오류가 뜰 수 있으므로 cetifi를 추가로 설치, 임포트 해주었다.

관련 내용은 https://ohnyong.tistory.com/35)

# MongoDB(Atlas Cloud)를 사용하기 위한 pymongo 임포트
from pymongo import MongoClient
import certifi
ca = certifi.where()
client = MongoClient('mongodb+srv://<user>:test@cluster0.lu7mz8j.mongodb.net/?retryWrites=true&w=majority',tlsCAFile=ca)
db = client.dbsparta

 

3. 기능 구현을 위한 app.py 부분 작성 순서

이전 연결 테스트로 '기록하기' 버튼이 정상적으로 POST 요청, 응답 연결되는 것을 확인했기 때문에

우선 app.py부분부터 테스트 코드를 수정하여 원하는 기능을 구현하고자 한다.

이번엔 코드 작성 때 "무엇을 작성해야되지?"부터 생각하고 필요한 부분을 찾는 방식을 순서대로 작성해 보았다.

큰 흐름은 "어떤 데이터가 필요한가?"라는 물음에 "이런 데이터를 받을 것이다."라는 답과 받는 방법(코드)을 도구처럼 작성하는 순서이다.

 

# [POST-0] CREATE 부분부터 코드를 작성하는 것이 확인이 가능(READ부터 하면 데이터가 없어서 테스트 어려움)

# [POST-0] CREATE 부분부터 코드를 작성하는 것(==POST)이 확인이 가능(READ부터하면 데이터가없어서 테스트 어려움)
# fetch('URL')부분, 반환값은 res로 전달.
# "localhost:5001/movie" URL POST방식 요청에 응답
@app.route("/movie", methods=["POST"])
def movie_post():

# [POST-1] 프론트로부터 무엇을 받아야 하는가? -> 프론트 input으로부터 url, star, comment를 받을 것이다.

    # [POST-1] 프론트로부터 무엇을 받아야 하는가? -> 프론트 input으로부터 url, star, comment를 받을 것이다.
    url_receive = request.form['url_give']
    star_receive = request.form['star_give']
    comment_receive = request.form['comment_give']

# [POST-2] url로부터는 무엇을 할 것인가? -> 웹 크롤링으로 image, title, description을 받을 것이다.

# [POST-3] 그럼 웹 크롤링을 하기 위해서는? -> 상단에 임포트부터 시작하자. 테스트한 것을 이용하자.

# [POST-4] 웹 크롤링을 위해서 임포트를 시작하자.

# [POST-4] 웹 크롤링을 위해서 임포트를 시작하자.
# 웹 크롤링을 위한 requests 임포트
# 크롤링한 HTML을 text로 변환 parsing할 BeautifulSoup 임포트
import requests
from bs4 import BeautifulSoup

# [POST-5] 웹 크롤링에서 URL은 어디서 오는가? -> 위 url_receive를 이용

# [POST-6] 웹 크롤링에서는 무엇을 가져오는가? -> ogtitle, ogimage, ogdesc

# ----start----- meta_prac.py 테스트 샘플에서 가져온 코드 ----start-----
    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')

    # meta태그의 프로퍼티가 og:title인 것을 가져온다.
    # image, description도 가져온다.
    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(
        "INPUT URL : "+url_receive+" / ",
        "INPUT STAR : "+star_receive+" / ",
        "INPUT COMMENT : "+comment_receive+" / ",
        "CRAWLED INPUT TITLE : "+ogtitle+" / ",
        "CRAWLED INPUT IMAGE URL : "+ogimage+" / ",
        "CRAWLED INPUT DESCRIPTION : "+ogdesc+" / "
    )
# ----end----- meta_prac.py 테스트 샘플에서 가져온 코드 ----end-----

# [POST-7] BS로 추출한 텍스트를 DB에 넣자 MongoDB연결을 위한 임포트부터 시작

# [POST-8] MongoDB사용을 위한 pymongo와 certifi 임포트

# [POST-8] MongoDB사용을 위한 pymongo와 certifi 임포트
from pymongo import MongoClient
import certifi

# [POST-9] DB 커넥션 구성

# [POST-9] DB 커넥션 구성
ca = certifi.where()
client = MongoClient('mongodb+srv://<account>:<pass>@cluster0.lu7mz8j.mongodb.net/?retryWrites=true&w=majority',tlsCAFile=ca)
db = client.dbsparta

# [POST-10] DB연결이 완료되었으니 Dictionary key:value형태 데이터들을 doc=리스트 객체에 담는다.

    # INSERT_ONE
    # 저장 - 예시
    # doc = {'name':'bobby','age':21}
    # db.users.insert_one(doc)
    doc = {
        'title' : ogtitle,
        'desc' : ogdesc,
        'image' : ogimage,
        # url은 현재 View에서 필요하지 않지만 추후 Update 기능을 위해서 DB에 저장까지만 진행하도록 했다.
        'url' : url_receive,
        'comment' : comment_receive,
        'star':star_receive
    }

# [POST-11] doc에 담았으니 DB에 insert 한다.(Dictionary구조의 key:value는 insert_one에 의해 Mongodb의 field:value로 변환되어 삽입됨)

    db.moviespedia.insert_one(doc)

# [POST-12] insert가 완료되었으니 완료 메시지를 반환한다.

    return jsonify({'msg':'POST 연결 완료!'+'DB 저장 완료!'})

 

이와 같은 의식의 흐름대로 작성된 app.py는 다음과 같다. (POST맵핑 부분과 임포트, DB 커넥션 부분만)

# 라이브러리 임포트
# Flask Framework
# view페이지 렌더링을 위한 render_template 메서드
# 요청 데이터에 접근 할 수 있는 flask.request 모듈
# dictionary를 json형식의 응답 데이터를 내보낼 수 있는 jsonify 메서드
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

# [POST-4] 웹 크롤링을 위해서 임포트를 시작하자.
# 웹 크롤링을 위한 requests 임포트
# 크롤링한 HTML을 text로 변환 parsing할 BeautifulSoup 임포트
import requests
from bs4 import BeautifulSoup

# [POST-8] MongoDB사용을 위한 pymongo와 certifi 임포트
from pymongo import MongoClient
import certifi
# [POST-9] DB 커넥션 구성
ca = certifi.where()
client = MongoClient('mongodb+srv://ohnyong:test@cluster0.lu7mz8j.mongodb.net/?retryWrites=true&w=majority',tlsCAFile=ca)
db = client.dbsparta

# "localhost:5001/" URL요청에 메인 뷰 페이지 반환 응답
@app.route('/')
def home():
    return render_template('index.html')

# [POST-0] CREATE 부분부터 코드를 작성하는 것이 확인이 가능(READ부터하면 데이터가없어서 테스트 어려움)
# fetch('URL')부분, 반환값은 res로 전달.
# "localhost:5001/movie" URL POST방식 요청에 응답
@app.route("/movie", methods=["POST"])
def movie_post():
    # [POST-1] 프론트로부터 무엇을 받아야 하는가? -> 프론트 input으로부터 url, star, comment를 받을 것이다.
    url_receive = request.form['url_give']
    star_receive = request.form['star_give']
    comment_receive = request.form['comment_give']
    # [POST-2] url로부터는 무엇을 할 것인가? -> 웹크롤링으로 image, title, description을 받을 것이다.
    # [POST-3] 그럼 웹 크롤링을 하기 위해서는? -> 상단에 임포트부터 시작하자. 테스트한것을 이용하자.

    # [POST-5] 웹 크롤링에서 URL은 어디서오는가? -> 위 url_receive를 이용
    # [POST-6] 웹 크롤링에서는 무엇을 가져오는가? -> ogtitle, ogimage, ogdesc
    # ----start----- meta_prac.py 테스트 샘플에서 가져온 코드 ----start-----
    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')

    # meta태그의 프로퍼티가 og:title인 것을 가져온다.
    # image, description도 가져온다.
    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(
        "INPUT URL : "+url_receive+" / ",
        "INPUT STAR : "+star_receive+" / ",
        "INPUT COMMENT : "+comment_receive+" / ",
        "CRAWLED INPUT TITLE : "+ogtitle+" / ",
        "CRAWLED INPUT IMAGE URL : "+ogimage+" / ",
        "CRAWLED INPUT DESCRIPTION : "+ogdesc+" / "
    )
    # ----end----- meta_prac.py 테스트 샘플에서 가져온 코드 ----end-----


    # [POST-7] BS로 추출한 텍스트를 DB에 넣자 MongoDB연결을 위한 임포트부터 시작
    # [POST-10] DB연결이 완료되었으니 document처럼 담는다.
    # INSERT_ONE
    # 저장 - 예시
    # doc = {'name':'bobby','age':21}
    # db.users.insert_one(doc)
    doc = {
        'title' : ogtitle,
        'desc' : ogdesc,
        'image' : ogimage,
        # url은 현재 View에서 필요하지 않지만 추후 Update 기능을 위해서 DB에 저장까지만 진행하도록 했다.
        'url' : url_receive,
        'comment' : comment_receive,
        'star':star_receive
    }
    # [POST-11] doc에 담았으니 DB에 insert 한다.
    db.moviespedia.insert_one(doc)
    # [POST-12] insert가 완료되었으니 완료 메시지를 반환한다.
    return jsonify({'msg':'POST 연결 완료!'+'DB 저장 완료!'})

# fetch('URL')부분, 반환값은 res로 전달.
# "localhost:5001/movie" URL GET방식 요청에 응답
@app.route("/movie", methods=["GET"])
def movie_get():
    return jsonify({'msg':'GET 연결 완료!'})

# app이라는 메인 함수 
# if __name__ == "__main__" 의 의미는 메인 함수의 선언, 시작을 의미
# 이 파이썬 스크립트가 직접 실행될 때에는 main() 함수를 실행하라
if __name__ == '__main__':
    app.run('0.0.0.0', port=5001, debug=True)

4. 기능 구현을 위한 app.py 부분 수정 및 작성 DB Insert(Create) 작성

다시 처음부터 기능(데이터)의 흐름 순서에 따른 코드리뷰

  • [기능 흐름 순서 3]
    • body에 담겨서 도착한 데이터들은
    • request.form을 통해 ['key']에 해당되는 value를 가져오고 각 변수 "url_receive", "star_receive", "comment_receive"라는 변수에 담는다.
    • 웹 크롤링을 구현하여 requests.get()으로 url_recieve HTML소스가 beautifulsoup()에 의해 파싱(분석)되어 soup에 담긴다.
    • soup의 select_one()으로 'meta'라는 태그의 [property가 og:title, og:image, og:description] 부분을 찾아서 원하는 값이 담긴 ['content'] 부분의 값을 각 ogtitle, ogimage, ogdesc변수에 담는다.
@app.route("/movie", methods=["POST"])
def movie_post():
    # [POST-1] 프론트로부터 무엇을 받아야 하는가? -> 프론트 input으로부터 url, star, comment를 받을 것이다.
    url_receive = request.form['url_give']
    star_receive = request.form['star_give']
    comment_receive = request.form['comment_give']
    # [POST-2] url로부터는 무엇을 할 것인가? -> 웹크롤링으로 image, title, description을 받을 것이다.
    # [POST-3] 그럼 웹 크롤링을 하기 위해서는? -> 상단에 임포트부터 시작하자. 테스트한것을 이용하자.

    # [POST-5] 웹 크롤링에서 URL은 어디서오는가? -> 위 url_receive를 이용
    # [POST-6] 웹 크롤링에서는 무엇을 가져오는가? -> ogtitle, ogimage, ogdesc
    # ----start----- meta_prac.py 테스트 샘플에서 가져온 코드 ----start-----
    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')

    # meta태그의 프로퍼티가 og:title인 것을 가져온다.
    # image, description도 가져온다.
    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(
        "INPUT URL : "+url_receive+" / ",
        "INPUT STAR : "+star_receive+" / ",
        "INPUT COMMENT : "+comment_receive+" / ",
        "CRAWLED INPUT TITLE : "+ogtitle+" / ",
        "CRAWLED INPUT IMAGE URL : "+ogimage+" / ",
        "CRAWLED INPUT DESCRIPTION : "+ogdesc+" / "
    )
    # ----end----- meta_prac.py 테스트 샘플에서 가져온 코드 ----end-----

 

  • [기능 흐름 순서 4]
    • 다음 Dictionary 형식의 리스트 데이터를 갖는 doc이라는 객체 생성
    • 위 변수들에 request.form을 통해 저장한 데이터는 아래 각 value로 들어가고 doc객체 내에 담긴다.
      • 'title' : ogtitle,
        'desc' : ogdesc,
        'image' : ogimage,
        'url' : url_receive,
        'comment' : comment_receive,
        'star':star_receive
    •  6개 key:value 묶음을 가진 객체insert_one()에 의해 MongoDB로 전송되면서 1개의 document가 되며 내용물인 각 Dictionary는 field:value 형태로 변환되고 moviespedia라는 collection 추가됨

DB 저장 이후

  • [기능 흐름 순서 5]
    • 이후, 확인용으로 {'msg' : 'POST 연결 완료! + DB 저장 완료!'} 라 하드코딩된 key, value가
    • jsonify()에 의해 json 형식의 데이터로 변환(주어진 값과 대응하는 JSON 문자열) 후 반환하고 js로 이동
    # [POST-7] BS로 추출한 텍스트를 DB에 넣자 MongoDB연결을 위한 임포트부터 시작
    # [POST-10] DB연결이 완료되었으니 document처럼 담는다.
    # INSERT_ONE
    # 저장 - 예시
    # doc = {'name':'bobby','age':21}
    # db.users.insert_one(doc)
    doc = {
        'title' : ogtitle,
        'desc' : ogdesc,
        'image' : ogimage,
        # url은 현재 View에서 필요하지 않지만 추후 Update 기능을 위해서 DB에 저장까지만 진행하도록 했다.
        'url' : url_receive,
        'comment' : comment_receive,
        'star':star_receive
    }
    # [POST-11] doc에 담았으니 DB에 insert 한다.
    db.moviespedia.insert_one(doc)
    # [POST-12] insert가 완료되었으니 완료 메시지를 반환한다.
    return jsonify({'msg':'POST 연결 완료!'+'DB 저장 완료!'})

5. 기능 구현을 위한 script.js 부분 수정 및 작성

리뷰 기록 하기 기능은 View 페이지에서 '기록하기' button의 onclick 이벤트로부터 시작되도록 설계되었다.

이전 테스트를 통해 script.js에서 POST 요청을 시작하고 최종 응답을 받는 것까지 정상적으로 작동되는 것을 확인했다.

이벤트와 연결된 함수인 posting() 부분을 작성(수정)한다.

 

View 페이지인 index.html에서 id가 각 url, star, comment로 부터 입력 및 선택되는 value를 받아와야 한다.

  • [기능 흐름 순서 1]
    • (값들을 입력하고)
    • "기록하기" button의 onclick 이벤트로 JavaScript posting() 함수 호출
    • JavaScriptposting()라는 함수 실행
    • 값이 입력된 input, select, textarea 태그들로부터 value를 가져와야 한다
    • . val()을 통해 각 id에 해당되는 부분의 value를 얻어 url, comment, star에 변수에 저장
// [Create]
function posting() {
    // index.html로부터 값 가져오기
    let url = $('#url').val()
    let comment = $('#comment').val()
    let star = $('#star').val()
  • [기능 흐름 순서 2]
    • formData라는 객체를 생성
    • {'url_give':url}, {...}, {... Dictionary 형식 데이터를. append()를 통해  formData 담기
    • posting() 내부fetch()를 통해 '/pedia' URL에 대한 POST 방식 요청 (+ 위 formData 객체를 body로 추가하여 요청함)
    • app.py로 이동

DB저장 이후(fetch() 이후. then() 절로 jsonify 변환 객체가 반환된 이후)

  • [기능 흐름 순서 6]
    • 반환 데이터(여기서는 완료 메시지)는 첫 번째 then() 절의 res 인자값으로 들어감
    • 첫 번째then()으로 들어간 res *response.json()에 의해 Promise 객체로 변환
    • 해당 객체 데이터 data라는 변수에 담기고
    • alert를 통해 key msg의 value인 'POST 연결 완료! + DB 저장 완료!'가 출력됨. 
    // formData 객체를 생성하고
    let formData = new FormData();
    // formData.append("sample_give", "샘플데이터");
    // append()통해 {key,value}를 객체에 담는다
    formData.append("url_give", url);
    formData.append("comment_give", comment);
    formData.append("star_give", star);

    // POST 요청에 위 formData를 body에 담아 요청한다.
    fetch('/movie', { method: "POST", body: formData }).then((res) => res.json()).then((data) => {
        // console.log(data)
        alert(data['msg'])
        // 브라우저 새로고침 추가
        window.location.reload()
    })

5. 테스트

  • Flask 서버가 실행 중인 상태
  • 브라우저에서 localhost:5001 URL로 접속
  • 테스트 인풋 값 입력
    • URL : https://movie.daum.net/moviedb/main?movieId=139236
    • 별점 : 4
    • 코멘트 : 스릴 넘치는 영화입니다.
  • "기록하기" 버튼 클릭
  • 'POST 연결 완료! + DB 저장 완료!' 알림 창
  • MongoDB내 movies라는 collection 테스트 데이터가 추가(insert) 됨
  • 이전 collection과 이름이 겹쳐서 moviespedia라는 collection으로 변경


해당 프로젝트는 아래 깃을 통해 업데이트되고 있습니다.

https://github.com/yzpocket/Flask_project_pedia

 

GitHub - yzpocket/Flask_project_pedia: [Flask] Flask framework 미니프로젝트(project pedia)

[Flask] Flask framework 미니프로젝트(project pedia). Contribute to yzpocket/Flask_project_pedia development by creating an account on GitHub.

github.com