웹종합 - flask와 api 통신

soo's·2023년 3월 23일
0

TIL

목록 보기
24/53
post-thumbnail

𝟏. flask 사용

venv에 flask 프레임워크를 설치하여 서버를 만든다.

from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
   return 'This is Home!'

if __name__ == '__main__':  
   app.run('0.0.0.0',port=5001,debug=True)

5000번 포트가 이미 사용 중이라 5001번으로 사용하였다.
먼저 port와 flask에 관하여 간단하게 정리하고 넘어가겠다.

📌 port에 관하여

  1. Ctrl+C로 통신하고 있는 포트를 종료시킬 수 있다.
  2. 종료하지 않고 파일 작업을 끝냈다면 추후 포트로 통신할 때 이미 사용중인 포트로 뜬다. 이때 sudo lsof -i :포트번호를 사용해서 pid 번호를 찾고 kill -9 pid번호로 포트를 강제 종료시키면 된다.

📌 flask에 관하여

  1. flask를 이용해 서버를 만들 때, 반드시 프로젝트 폴더 안에 아래와 같은 형태를 지킨다.
 ㄴtemplates 폴더
 |	ㄴindex.html
 |  ㄴ ... 
 ㄴapp.py
  1. 1번 형태를 지켜서 작성하면 render_template 내장함수를 사용하여 templates 폴더 안의 파일을 바로 리턴해줄 수 있다.

  2. python으로 flask를 사용하여 아래와 같이 GET, POST 요청 API를 생성할 수 있다. fetch 코드 또한 아래와 같다.

// GET 요청 API 코드
@app.route('/test', methods=['GET']) # app.route('api 엔드포인트', methods)
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})
   
 // POST 요청 API 코드
@app.route('/test', methods=['POST'])
def test_post():
   title_receive = request.form['title_give']
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 POST!'})
   
 // GET 요청 FETCH 코드
fetch('/test')
.then(res => res.json())
.then(data => {})

 // POST 요청 FETCH 코드
let formData = new FormData();
formData.append("title_give", "블랙팬서"); # FormData로 {"key":"value"} 객체를 담아서 formData 변수에 append한다

 fetch("/test", { method: "POST", body: formData }).then(res => res.json()).then(data => {
    console.log(data)
})



𝟐. 화성땅 공동구매 페이지 제작

화성땅 공동구매 페이지를 제작하기에 앞서서 다음과 같은 단계를 먼저 설정한다.

⑴. 데이터 명세
⑵. 클라이언트와 서버 연결하기
⑶. 저장할 서버 만들기
⑷. 클라이언트 만들기
⑸. 확인

⑴. 데이터 명세

  • 요청 정보 : api end point = '/mars'
  • 클라이언트 ➡️ 서버 보낼 데이터 : name, address, size
  • 서버 ➡️ 클라이언트 보낼 데이터 : msg = '주문 완료!'

⑵. 클라이언트와 서버 연결하기

클라이언트와 서버가 제대로 연결되었는지 확인하기 위해 GET 요청과 POST 요청시 모두 msg를 전달하겠다. postMan을 통해 msg 값을 제대로 전달하고 받을 수 있는지 확인해보자.

먼저 아래와 같은 코드로 api를 만든다.

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)


@app.route('/')
def home():
    return render_template('index.html')

# /mars 라는 엔드포인트에 post 요청이 들어오면 {'msg' : 'post 성공'} 객체를 리턴한다.
@app.route("/mars", methods=["POST"])
def mars_post():
    return jsonify({'msg':'post 성공'})

# /mars 라는 엔드포인트에 get 요청이 들어오면 {'msg' : 'get 성공'} 객체를 리턴한다.
@app.route("/mars", methods=["GET"])
def mars_get():
    return jsonify({'result': 'get 성공'})

if __name__ == '__main__':
    app.run('0.0.0.0', port=5001, debug=True)

postMan을 통해 localhost:5001/ 으로 get 요청을 send하면 사진과 같이 연결된 html을 볼 수 있다. 위 코드에서 '/'에서 index.html을 리턴하기 때문이다.

이번에는 localhost:5001/mars로 get 요청을 send 한다. 리턴된 값으로 {"result" : "get 성공"}을 받는다.

이번에는 localhost:5001/mars로 post 요청을 send 한다. 리턴된 값으로 {"msg" : "post 성공"}을 받는다.

이렇게 postMan으로 클라이언트와 서버 연결을 확인했다.

⑶. 저장할 서버 만들기

화성땅 공동구매 페이지는 사용자에게서 name, address, size 값을 입력받는다. 따라서 이 값들을 클라이언트에게서 받아서 DB에 저장하고 서버와 연결해야 한다. 먼저 venv 환경에서 pymongodnspython, certifi를 설치한다.

pip3 install pymongo dnspython certifi

(나는 갑자기 pip3로 해야 venv에 깔리게 되었다...😳🚬)

@app.route("/mars", methods=["POST"])
def mars_post():
    name_receive = request.form["name_give"]
    address_receive = request.form["address_give"]
    size_receive = request.form["size_give"]
    
    doc = {
        'name' : name_receive,
        'address' : address_receive,
        'size' : size_receive
    }
    db.mars.insert_one(doc)
    return jsonify({'msg':'구매 완료!'})

이 코드들을 천천히 뜯어보자면

  1. /mars로 POST 요청이 날아온다는 것은 클라이언트에서 fetch로 사용자가 입력한 값(=저장해야 할 값)을 가지고 데이터를 저장해달라고 요청하는 것이다.

  2. 사용자가 입력한 값(= 클라이언트에서 담아서 fetch할 값)을 임의의 변수명 (여기서는 name_give)을 설정해서 name_receive라는 임의의 변수에 담는다. 마찬가지로 사용자가 입력한 값일 주소와 평수도 각각의 임의의 변수를 설정하여 request.form[]으로 받아 저장한다.

  3. 이 값들을 DB에 저장해야 하므로 doc이라는 변수에 key : value 형태의 객체로 저장한다. mars라는 임의의 콜렉션을 만들어 저장한다.

  4. 클라이언트에서는 POST의 리턴값인 ['msg']를 구매 버튼을 클릭시 alert()로 보여줄 것이기 때문에 이전에 'POST 성공'보다는 사용자 입장에서 구매가 완료 되었다는 의미로 ['msg']를 '구매 완료'로 변경한다.

⑷. 클라이언트 만들기

먼저 데이터를 저장하는 서버를 만들었고 그 다음으로 데이터를 보내줄 클라이언트를 만들면된다. 이렇게 데이터를 넣는 것을 먼저 만든 이유는 데이터가 있어야 다른 것들을 테스트할 수 있기 때문이다.

function save_order() {
        let name = $("#name").val()
        let address = $("#address").val()
        let size = $("#size").val()

        let formData = new FormData()
        formData.append("name_give", name)
        formData.append("address_give", address)
        formData.append("size_give", size)

        fetch('/mars', {method: "POST",body: formData,}).then((res) => res.json()).then((data) => {
            alert(data["msg"])
            window.location.reload()
          });
      }

이 코드들을 해석해 보자면

  1. JQeury를 사용해서 사용자가 입력한 값들의 value를 각각의 변수에 저장한다.

  2. new FormData()를 사용해서 formData라는 변수를 만들고 {key: value}형태로 값을 담아야하기 때문에 변수에 (key, value)를 append 한다. 이 FormData()의 자세한 설명은https://developer.mozilla.org/ko/docs/Web/API/FormData/FormData mdn 문서를 참고했다.

  3. POST method를 사용하기 때문에 body에 formData를 넣고 fetch를 요청한다. 여기서 받은 data는 ⑶-4의 ["msg"]구매 완료이다.

  4. POST 요청을 보낸 후에 사용자가 입력한 값을 화면에 그대로 남겨두지 않고 페이지를 리로딩하고 싶기 때문에 window.location.reload()를 사용한다. window.location은 document.location과 동일하다. 더 자세한 내용은 https://7942yongdae.tistory.com/53 블로그를 참고했다.

⑸. 확인하기

mongoDB를 통해 데이터들을 저장했으므로 mongoDB Atlas에서 확인 가능하다.
내가 입력한 값이 클라이언트에서 서버로 잘 전달되었고 그것을 DB에 저장했기 때문에 mars collection에서 위와 같이 두 개의 데이터를 볼 수 있다.



𝟑. 조각 기능

만들고 싶은 기능이 반드시 프로젝트 내부에서 만들 필요가 없을 수 있다. 나의 경우 해당 기능을 flask에서 브라우저를 열고 일일히 테스트하기 보다 프로젝트 외부에서 만들어서 사용하려고 하며, 이것을 조각 기능이라고 한다.
기존의 app.py가 아닌 meta.py를 새로 생성해서 작업하고 완료된 코드만 기존 파일에 덧붙여주면 되는 형식이다.

⑴. 웹 스크래핑

네이버 영화 사이트에서 'everything everywhere all at once' 상세 페이지로 이동해서 개발자 도구를 열면 아래 사진과 같이 html meta 태그의 "og:title", "og:url" 등 og가 붙은 property를 확인할 수 있다.

"og:title"과 같은 property 사용으로 유튜브의 썸네일과 제목처럼 사용자가 링크를 공유했을 때, 노출되는 제목과 사진 등으로 나타나게 된다.

다음 프로젝트에서 url을 입력하면 해당 url의 제목과 대표사진, 설명을 크롤링 해와서 서버에 저장하고 다시 클라이언트에서 보여줄 것이다.
따라서 새로 생성한 meta.py에서 bs4, requests를 설치하고 시작한다. 크롤링을 위한 코드는 아래와 같다.

import requests
from bs4 import BeautifulSoup

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')

# select('태그[프로퍼티="값"]')['프로퍼티']
ogtitle = soup.select_one('meta[property="og:title"]')['content']
ogdesc = soup.select_one('meta[property="og:description"]')['content']
ogimage = soup.select_one('meta[property="og:image"]')['content']

print(ogimage,ogtitle,ogdesc)

select_one을 사용해서 작성한 ('meta[property="og:title"]')['content'] 이 부분은 meta 태그에서 property가 og:title인 객체(html 태그는 DOM의 객체니까)의 key가 'content'인 value를 가져온다는 코드이다.

이렇게 웹 스크랩핑할 코드도 준비했다면 바로 아래의 프로젝트에 사용해보자.



𝟒. 영화 페이지 제작

프로젝트 시작 전 준비사항을 한 번 정리해보자면

  • app.py 생성
  • app.py -m venv venv로 가상환경 설치
  • 사용할 라이브러리 설치
  • templates 폴더 생성 > index.html 생성
  • 테스트를 위해 데이터를 넣는 서버 먼저 생성

app.py의 전체 코드를 보여주자면

from flask import Flask, render_template, request, jsonify
app = Flask(__name__)

from pymongo import MongoClient
import certifi

ca = certifi.where()
client = MongoClient('내 몽고 DB 주소', tlsCAFile=ca)
db = client.dbsparta

import requests
from bs4 import BeautifulSoup

@app.route('/')
def home():
	return render_template('index.html')

@app.route("/movie", methods=["POST"])
def movie_post():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']
    star_receive = request.form['star_give']

    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')

    ogimage = soup.select_one('meta[property="og:image"]')['content']
    ogtitle = soup.select_one('meta[property="og:title"]')['content']
    ogdesc = soup.select_one('meta[property="og:description"]')['content']


    doc = {
        'image':ogimage,
        'title':ogtitle,
        'desc':ogdesc,
        'comment':comment_receive,
        'star' : star_receive
    }

    db.movies.insert_one(doc)

    return jsonify({'msg':'저장완료!'})

@app.route("/movie", methods=["GET"])
def movie_get():
    all_movies = list(db.movies.find({},{'_id':False}))
    return jsonify({'movies':all_movies})

if __name__ == '__main__':
	app.run('0.0.0.0', port=5001, debug=True)

코드를 천천히 해석해보자면

  1. (/)에서는 render_template() 내장함수를 사용해서 templates 파일의 index.html을 리턴하고 있다.

  2. api 엔드포인트 (/movie)에서 POST 방식으로 요청이 들어오면 클라이언트에서 받아온 값들(=url_give, comment_give, star_give)을 각각 변수에 담는다.

  3. soup을 사용해서 가져온 값을 og ~ 변수에 담는다.
    DB에 클라이언트에서 받아온 값들(=url_give, comment_give, star_give)과 크롤링한 값(=ogimage,ogtitle, ogdesc)을 저장해야하기 때문에 doc 변수에 객체 형태로 값들을 담는다.
    db.movies.insert_one(doc) 코드로 movies라는 collection을 생성해서 doc 데이터를 저장한다.
    return 값으로 msg를 전달하여 클라이언트에서 사용자가 저장버튼을 눌렀을 때 활용할 수 있게 한다.

  4. api 엔드포인트 (/movie)에서 GET 방식으로 요청이 들어오면 db.movies collection에 저장된 모든 값을 리스트로 받아와서 all_movies 변수에 할당한다.
    return 값으로 movies라는 key 값으로 all_movies를 전달하여 렌더링될 때 페이지에 기존 입력값(=db에 쌓인 사용자 데이터)들을 활용할 수 있게 한다.

  5. app.run()함수를 사용해서 나의 로컬 포트인 5001번을 넣어주고 app.py를 실행하면 localhost:5001에서 페이지 호스팅을 확인할 수 있다.



👁 그 외 참고사항

  1. index.html에서 /movie로 fetch-get 해서 db를 받아 왔을 때 배열에 할당하여 반복문에서 사용한다. 이때 각 comment, title, image 등을 직접 할당해주지 않고 객체 구조분해 할당을 사용하여 간편하게 코드를 작성할 수 있다.
    구조 분해 할당은 https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%EA%B0%9D%EC%B2%B4_%EA%B5%AC%EC%A1%B0_%EB%B6%84%ED%95%B4 mdn 문서 참고.
# 기존코드
rows.forEach((a) => {									
	let comment = a['comment']
	let title = a['title']
	let image = a['image']
	let desc = a['desc']
	let star = a['star']
    
 # 구조 분해 할당 사용 코드
 rows.forEach((row) => {
    let { title, desc, image, comment, star } = row;

  1. index.html에 아래의 코드를 추가하여 별점을 선택할 수 있게 하는데
<div class="input-group mb-3">
          <label class="input-group-text" for="inputGroupSelect01">별점</label>
          <select class="form-select" id="star">
              <option selected>-- 선택하기 --</option>
              <option value="1"></option>
              <option value="2">⭐⭐</option>
              <option value="3">⭐⭐⭐</option>
              <option value="4">⭐⭐⭐⭐</option>
              <option value="5">⭐⭐⭐⭐⭐</option>
          </select>
      </div>

클라이언트에서는 사용자가 입력한 값을 star = $("#star").val()로 가져와서 star_repeat = ⭐.repeat(star)로 표현해주었다. js 내장함수인 repeat()을 사용해서 별의 개수를 자동으로 입력하게 하는 것이다.

✍🏻 프로젝트를 마치며

flask를 사용해서 api를 만들고 postman으로 클라이언트와 서버의 연결을 확인해보는 과정을 통해 http 통신에 대해 조금이나마 이해하게 되었다. 더 자세하게 알아보기 위해 get과 post를 따로 정리해서 포스팅해봐야겠다.
라이브러리를 사용할 때, 정해진 형식에 맞게 작성만 하면 척척 연결돼서 데이터를 가져오고 사용할 수 있는게 정말 편리하다. 굿

0개의 댓글