스파르타 코딩 클럽 [웹개발종합반] 4주차 WIL(Week I Learned) - Flask / 서버 / API

KSH·2022년 2월 15일
0
post-thumbnail
post-custom-banner

4주차 - Flask / 서버 /API

4주차에는 드디어 강의 내용에 내가 모르는 것들로 100% 가득 차 있었다.
그래서 조금 두렵기도 했고 설렜다.
부딪혀보고 나니 확실히 처음 배우는 것이라서 엄청 익숙하지가 않았다.
그래도 실습 여러 개를 영상보면서 정리하며 따라해도 조금 익숙해지지가 않았다.
하지만, 숙제를 할 때 했던 것을 가져와서 하긴 했지만 그래도 해설 코드를 보지 않고
나 혼자 했기 때문에 확실히 조금은 익숙해진 것 같기도 하다!

또한 1, 2주차 숙제를 나 혼자서 Develope 시켰다는 것이 굉장히 뿌듯했다!


Flask 시작하기 - 서버 만들기

서버를 돌아가게 만드는 파일 이름은 통상적으로 app.py로 많이 쓰인다.

Flask : 프레임워크

※ 프레임워크 VS 라이브러리

프레임워크 : 남이 짜둔 규칙이나 틀 안에서 자유로운 코딩

라이브러리 : 자유롭게 코딩하는데 남이 만들어 놓은 것을 중간에 갖다 쓰는 것

따라서, 통상적으로 프레임워크는 하나의 프레임워크에서 작업하고

라이브러리는 여러 개를 사용할 수 있다!

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=5000,debug=True)

👉 port 5000으로 접속할 수 있는 서버를 만들었다!

@app.route(”/”) : 사용자가 주소창에 엔터를 치면 “/”가 자동으로 맨 끝에 붙지만 숨겨져서 사용자의 눈에는 보이지 않는다.

따라서 @app.route(”/”)[localhost:5000](http://localhost:5000) 이 주소창에 쳐졌을 때를 의미하고

그때 home이라는 함수를 실행한다!

port : 출입구의 이름 → port 5000 : 5000번 출입구에 서버를 만들었다!

def home() 함수의 return 값 : 웹페이지에서 출력될 문구

<button></button> 을 입력하면 버튼이 나온다!

접속 : localhost:5000 입력!

→ 내가 만든 서버에 5000번 문으로 접속하겠다라는 의미


Flask에 HTML 파일 주기

현재 프로젝트 폴더에 templates / static 이라는 폴더를 만들고

html 파일은 templates 폴더에, CSS파일은 static 폴더에 넣는다.

그리고 나서 파이썬에서 import render_template 를 하고

함수 return 값을 render_template(”html파일이름”) 이렇게 하면

html을 가져올 수 있다!!

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

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

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

API 만들기

API : 서버가 클라이언트의 요청을 받기 위해서 만들어 놓는 창구

API의 여러 종류 중 주로 쓰는 2가지 종류

  1. GET : 통상적으로 데이터 조회 요청 시
  2. POST : 통상적으로 데이터 생성, 변경, 삭제 요청 시

서버에 요청할 때 → Ajax로 콜하기


① GET으로 API 만들기

API 만들기 → 클라이언트에서 Ajax로 콜하기 반복

  1. API 만들기

request , jsonify 추가로 import

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

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

@app.route('/test', methods=['GET'])
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})

if __name__ == '__main__':
   app.run('0.0.0.0',port=5000,debug=True)
  1. Ajax로 콜하기

[localhost:5000](http://localhost:5000) 에 가서 개발자도구 콘솔에서 Ajax 콜을 해보면,

$.ajax({
    type: "GET",
    url: "/test?title_give=봄날은간다",
    data: {},
    success: function(response){
       console.log(response)
    }
  })

콘솔에 이렇게 찍힌다!

이때 response는 서버에서 클라이언트에게 내려주는 정보이다.

이 response의 내용은?

Flask로 만든 서버의 jsonify를 사용해서 만든

return jsonify({'result':'success', 'msg': '이 요청은 GET!'}) 리턴 값이다!!

과정 분석

  1. Flask 서버로 GET type API를 만들어 놨다 (창구를 은행이 만들어 놨다)
@app.route('/test', methods=['GET'])
def test_get():
   title_receive = request.args.get('title_give')
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 GET!'})
  1. Ajax로 콜을 한다 (고객이 간다)
$.ajax({
    type: "GET",
    url: "/test?title_give=봄날은간다",
    data: {},
    success: function(response){
       console.log(response)
    }
  })

→ 상세 정보 : type은 GET 이고, url이 /test?title_give=봄날은간다

이때,

  1. 서버에서 API를 만들 때 methods=["GET"] 이렇게 타입은 GET으로 받는다고 약속했기 때문에 Ajax로 콜할 때 GET 타입으로 요청
  2. 서버에서 API를 만들 때 창구 이름을 /test 로 했기 때문에 Ajax로 콜하는 url도 /test 로 요청
  3. 서버에서 API를 만들 때 "title_give" 로 정보를 요청하라고 약속했기 때문에 Ajax로 콜할 때 ?title_give=봄날은간다 로 요청 (? 뒤에는 요청할 데이터)

이때, API에서 일어나는 일

고객이 “title_give”봄날은간다 를 가지고 API에 왔다.

  1. title_receive = request.args.get('title_give') : title_give 로 가져온 값 가져와봐! 한 후 title_receive에 저장
  2. print(title_receive) : 서버에 클라이언트가 가져온 값(title_give) 출력
  3. return jsonify({'result':'success', 'msg': '이 요청은 GET!'}) : 값을 클라이언트에게 제공(Ajax의 response으로 저장됨)

② POST로 API 만들기

: 생김새가 조금 다르지만, 원리는 GET과 같다!

: GET과 비교하며 생각해보자!

  1. Flask 서버로 POST type API를 만들어 놨다.
@app.route('/test', methods=['POST'])
def test_post():
   title_receive = request.form['title_give']
   print(title_receive)
   return jsonify({'result':'success', 'msg': '이 요청은 POST!'})

API 경로 : /test / 타입 : POST

title_receive = request.form['title_give'] : 클라이언트가 title_give 로 가져온 값 title_receive에 저장

→ GET과 원리는 똑같지만 request 뒤 생김새가 다르다. .form["title_give"]

  1. Ajax로 콜을 한다.
$.ajax({
    type: "POST",
    url: "/test",
    data: { title_give:'봄날은간다' },
    success: function(response){
       console.log(response)
    }
  })

찾아갈 경로 : /test / 타입 : POST

GET과 다른점 : DATA를 주는 방식

GET : data를 url으로 url: "/test?title_give=봄날은간다" 이렇게 서버에 전달, data는 빈 값 data : {},

POST : data를 data에 data: { title_give:'봄날은간다' } 이렇게 서버에 전달, url에는 찾아갈 경로만 입력 url : "/test",


[모두의책리뷰] - POST 연습(리뷰 저장)

서버 DB에 요청한 데이터 저장

서버 코드

서버에서 하는 일 : API에서 데이터(제목, 저자, 리뷰)를 받아서 DB에 저장

→ DB에 데이터를 추가하는 insert 사용

@app.route('/review', methods=['POST'])
def write_review():
    title_receive = request.form['title_give']
    author_receive = request.form['author_give']
    review_receive = request.form['review_give']

    doc = {
        "title" : title_receive,
        "author" : author_receive,
        "review" : review_receive
    }

    db.bookreview.insert_one(doc)

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

title_give / author_give / review_give 으로 받은 데이터를 변수에 저장하고,

DB에 데이터를 저장하기 위해

doc = {
        "title" : title_receive,
        "author" : author_receive,
        "review" : review_receive
    }

이렇게 doc을 딕셔너리 형태로 저장하고

db.bookreview.insert_one(doc) 이렇게

DB에 bookreview라는 collection에 doc 데이터를 저장한다.

클라이언트 코드

클라이언트에서 하는 일 : 제목, 저자, 리뷰를 가져와서 서버로 보낸다.

function makeReview() {

		let title = $("#title").val();
    let author = $("#author").val();
    let review = $("#bookReivew").val();

	  $.ajax({
	      type: "POST",
	      url: "/review",
	      data: {title_give: title, author_give:author, reivew_give:review},
	      success: function (response) {
	          alert(response["msg"]);
	          window.location.reload();
	      }
	  })
}

makeReview() 라는 함수가 호출될 때 실행

		let title = $("#title").val();
    let author = $("#author").val();
    let review = $("#bookReivew").val();

JQuery를 활용해서 각 input(제목, 저자, 리뷰)의 값을 저장한다.

	  $.ajax({
	      type: "POST",
	      url: "/review",
	      data: {title_give: title, author_give:author, reivew_give:review},
	      success: function (response) {
	          alert(response["msg"]);
	          window.location.reload();
	      }
	  })
}

Ajax를 통해 data를 미리 약속한 title_give / author_give / review_give 를 Key로, input의 값을 저장한 변수들을 각각의 Value로 하는 딕셔너리로 저장해서 요청한다.

👉 이렇게 하면 서버의 DB에 요청한 데이터들(제목, 저자, 리뷰)이 잘 저장된다.


[모두의책리뷰] - GET 연습(리뷰 보여주기)

2. 리뷰 보여주기

서버 코드

서버에서 하는 일 : 클라이언트로부터 데이터를 받을 것이 없고, 모든 데이터(제목, 저자, 리뷰)를 DB에서 가져와서 그대로 클라이언트에 내려주면 된다!

→ DB에서 여러 데이터를 조회하는 find 사용

@app.route('/review', methods=['GET'])
def read_reviews():
    reviews = list(db.bookreview.find({}, {'_id': False}))
    return jsonify({'all_reviews': reviews})

클라이언트의 데이터가 저장된 DB에서 데이터를 가져오는 find 를 사용해서

list(db.bookreview.find({}, {'_id': False})) 코드 작성 → _id 열 빼고 모든 데이터를 리스트로 reviews라는 변수에 저장

그 후 return 값으로 all_reviews 라는 Key, 데이터가 들어 있는 변수 reviews 를 value로 하는 딕셔너리로 반환

클라이언트 코드

클라이언트에서 하는 일 : all_reviews 가 잘 내려오면 그것을 받아서 for문을 돌면서 리뷰들을 붙여주기

페이지가 새로고침되면 showReview() 라는 함수 실행

→ 페이지가 처음 열렸을 때 새로고침되므로 기존의 리뷰들 보여줄 수 있다.

→ 리뷰 작성하기를 누르면 페이지가 새로고침되게 해서 작성한 리뷰까지 추가되어서 보여진다.

$(document).ready(function () {
    showReview();
});
function showReview() {
    $.ajax({
        type: "GET",
        url: "/review",
        data: {},
        success: function (response) {
            let reviews = response["all_reviews"];
            for (let i = 0; i < reviews.length; i++) {
                let title = reviews[i]["title"];
                let author = reviews[i]["author"];
                let review = reviews[i]["review"];

                let temp_html = `<tr>
                                    <td>${title}</td>
                                    <td>${author}</td>
                                    <td>${review}</td>
                                </tr>`
                $("#reviews-box").append(temp_html)
            }
        }
    })
}

all_reviews 라는 서버에서 내려준 DB 데이터가 response에 저장되어 있다.

따라서, response 중에 all_reviews 만을 뽑아서 reviews 변수에 저장

현재 reviews 변수에는 제목, 저자, 리뷰가 딕셔너리 형태로 저장되어 있으므로

for문을 통해서 i번째 데이터 추출 후 jquery를 통해서 append하여 html 태그 추가해서 화면에 보여주기


[나홀로 메모장] - API 설계

API에 어떤 기능이 필요한지 먼저 설계

필요한 기능

  1. URL / 코멘트를 서버에 보내서 서버에서 그 데이터를 저장하는 것
  2. 카드들(데이터들)을 보여주기

→ DB에 저장해야할 것 : 카드의 요소들(이미지, 제목, 제목링크, 요약, 코멘트)

서버 DB에 카드 정보 생성 API

  • 요청 정보
  1. 요청 URL = /memo / 요청 방식 = POST
  2. 요청 데이터(클라이언트로부터 받는 데이터) : URL(url_give), 코멘트(comment_give)
  • 서버가 제공할 기능
  1. URL의 meta태그 정보를 바탕으로 제목, 설명,이미지URL 스크래핑
  2. 제목, 설명, URL, 이미지URL, 코멘트 정보를 모두 DB에 저장
  • 클라이언트에게 줄 데이터
  1. API가 정상적으로 작동하는지 클라이언트에게 알려주기 위해 성공 메세지 보내기
  2. (JSON 형식) “result” = “success”

서버 DB에 저장된 카드 정보 보여주기 API

  • 요청 정보
  1. 요청 URL = /memo / 요청 방식 = GET
  2. 요청 데이터(클라이언트로부터 받는 데이터) : 없음
  • 서버가 제공할 기능
  1. DB에 저장되어 있는 모든 정보(제목, 설명, URL, 이미지URL, 코멘트) 내려주기
  • 클라이언트에게 줄 데이터
  1. article(기사)들의 정보(제목, 설명, URL, 이미지URL, 코멘트) → 카드 만들어서 붙이기
  2. (JSON 형식) “articles” : 아티클 정보

[나홀로 메모장] - 미리 테스트해보는 조각 기능 구현

우리는 URL을 넣어서 이미지, 제목, 요약을 자동으로 가져오게 해야한다.

이 URL에 이 정보들은 안의 태그에 있다.

따라서, meta태그를 스크래핑해야한다!

※ meta 태그는 눈에 보이는 태그 이외의 사이트의 속성을 설명해주는 태그이다.

meta태그 스크래핑

meta태그를 스크래핑하려고 할 때 해오던 방식대로 하면 스크래핑이 되지 않는다.

import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=171539'

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("head > meta:nth-child(9)")
print(title)

이렇게 하면 meta 태그 내용이 나오지 않는다.

이유는, 웹 페이지에서 검사를 눌러서 나오는 meta 태그의 순서와 파이썬 코드가 접속했을 때 나오는 meta 태그의 순서가 다르기 때문이다.

따라서, Beautifulsoup을 사용하는 다른 방법을 알아야 한다!

import requests
from bs4 import BeautifulSoup

url = 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=171539'

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)

title = soup.select_one("meta[property="og:title"]")

👉 meta 태그 중에서 property="og:ttitle" 인 meta 태그를 가져오라는 뜻!


[나홀로메모장] - POST 연습 (메모하기)

서버

  1. 클라이언트로부터 데이터 받기
@app.route('/memo', methods=['POST'])
def saving():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_give']
  1. 타이틀, 이미지, 디스크립션 크롤링하기
@app.route('/memo', methods=['POST'])
def saving():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_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')

    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"]
  1. 크롤링한 데이터 DB에 저장하기
@app.route('/memo', methods=['POST'])
def saving():
    url_receive = request.form['url_give']
    comment_receive = request.form['comment_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')

    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"]

    doc = {
        'title': title,
        'image': image,
        "desc" : desc,
        "url" : url_receive,
        "comment" : comment_receive
    }
    db.articles.insert_one(doc)

    return jsonify({'msg':'저장이 완료되었습니다!'})

클라이언트

  1. 서버에 보낼 데이터 가져오기
function postArticle() {
    let url = $("#post-url").val();
    let comment = $("#post-comment").val();
  1. Ajax로 데이터 보내기
function postArticle() {
	  let url = $("#post-url").val();
	  let comment = $("#post-comment").val();
	
	  $.ajax({
	      type: "POST",
	      url: "/memo",
	      data: {url_give:url, comment_give:comment},
	      success: function (response) { // 성공하면
	          alert(response["msg"]);
	      }
	  })
	}
  1. 기사저장을 눌러서 postAritcle()이 실행되어서 ajax 콜을 하고 난 후 페이지가 새로고침 되게 하기

    window.location.reload(); 사용

function postArticle() {
	  let url = $("#post-url").val();
	  let comment = $("#post-comment").val();
	
	  $.ajax({
	      type: "POST",
	      url: "/memo",
	      data: {url_give:url, comment_give:comment},
	      success: function (response) { // 성공하면
	          alert(response["msg"]);
						window.location.reload();
	      }
	  })
	}

[나홀로메모장] - GET 연습 (보여주기)

서버

  1. DB에 저장한 데이터 내려주기

list(db.articles.find({}, {'_id': False})) 로 DB의 데이터를 가져와서 articles 변수에 저장

jsonify 로 클라이언트에게 내려주기

@app.route('/memo', methods=['GET'])
def listing():
    articles = list(db.articles.find({}, {'_id': False}))
    return jsonify({"all_articles":articles})

클라이언트

function showArticles() {
    $.ajax({
        type: "GET",
        url: "/memo",
        data: {},
        success: function (response) {
            let articles = response["all_articles"];

            for (let i = 0; i < articles.length; i++) {
                let title = articles[i]["title"];
                let image = articles[i]["image"];
                let url = articles[i]["url"];
                let desc = articles[i]["desc"];
                let comment = articles[i]["comment"];

                let temp_html = `<div class="card">
                                    <img class="card-img-top"
                                         src="${image}"
                                         alt="Card image cap">
                                    <div class="card-body">
                                        <a target="_blank" href="${url}" class="card-title">${title}</a>
                                        <p class="card-text">${desc}</p>
                                        <p class="card-text comment">${comment}</p>
                                    </div>
                                </div>`

                $("#cards-box").append(temp_html);
            }
        }
    })
}

4주차 숙제


profile
성실히 살아가는 비전공자
post-custom-banner

0개의 댓글