스파르타 코딩 클럽 내일 배움단 9일 차 일지
오늘은 일어나서 웹개발 4주 차 강의를 처음부터 쭉 들었다.
웹 개발은 확실히 퍼블리셔 지식이 있다 보니 수월하게 진행하는 게 가능했다.
조금씩은 막히는 부분이 있었긴 했지만 금방 해결하고 다 넘어갔다.
그래서 뭐 적을건 따로 없지만, 파이썬으로 크롤링해서 만드는 부분이 꽤 재밌는 것 같다.
학습 내용
1. Flask 기초
2. 모두의 책 리뷰 만들어보기
3. 나홀로 메모장 만들어보기
1. Flask 기초
Flask란? python을 사용해 손쉽게 웹 백엔드를 구성할 수 있는 프레임워크중 하나 입니다.
파이썬 모듈 설치를 통해 쉽게 설치가 가능합니다.
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) |
위 코드 입력후, < Ctrl + Shift + F10 > 키를 통해 실행하고, 웹에서 localhost:5000 주소를 입력하여 들어갑니다.
이후, < Ctrl + C > 키를 통해 서버를 종료할 수 있습니다.
URL 분리하기
| from flask import Flask |
| app = Flask(__name__) |
| |
| @app.route('/') |
| def home(): |
| return 'Hello' |
| |
| @app.route('/page') |
| def mypage(): |
| return 'Nice to meet you' |
| |
| if __name__ == '__main__': |
| app.run('0.0.0.0',port=5000,debug=True) |
Flask 폴더 구조
프로젝트 폴더
| static/ |
| |
| → *.css |
| |
| → *.jpg |
| |
| templates/ |
| |
| → *.html |
| |
| app.py |
Flask에서 html 로딩하기 ( render_template )
| @app.route('/') |
| def home(): |
| return render_template('index.html') |
GET 요청 API 구현 ( Python )
| @app.route('/test', methods=['GET']) |
| def test_get(): |
| title_receive = request.args.get('title_give') |
| print(title_receive) |
| return jsonify({'result':'success', 'msg': '이 요청은 GET!'}) |
GET 요청 AJAX 코드 ( JQuery )
| $.ajax({ |
| type: "GET", |
| url: "/test?title_give=봄날은간다", |
| data: {}, |
| success: function(response){ |
| console.log(response) |
| } |
| }) |
POST 요청 API코드 ( Python )
| @app.route('/test', methods=['POST']) |
| def test_post(): |
| title_receive = request.form['title_give'] |
| print(title_receive) |
| return jsonify({'result':'success', 'msg': '이 요청은 POST!'}) |
POST 요청 AJAX 코드 ( JQuery )
| $.ajax({ |
| type: "POST", |
| url: "/test", |
| data: { title_give:'봄날은간다' }, |
| success: function(response){ |
| console.log(response) |
| } |
| }) |
2. 모두의 책 리뷰
기본코드
< app.py >
| from flask import Flask, render_template, jsonify, request |
| app = Flask(__name__) |
| |
| from pymongo import MongoClient |
| client = MongoClient('localhost', 27017) |
| db = client.dbsparta |
| |
| |
| @app.route('/') |
| def home(): |
| return render_template('index.html') |
| |
| |
| @app.route('/review', methods=['POST']) |
| def write_review(): |
| sample_receive = request.form['sample_give'] |
| print(sample_receive) |
| return jsonify({'msg': '이 요청은 POST!'}) |
| |
| |
| @app.route('/review', methods=['GET']) |
| def read_reviews(): |
| sample_receive = request.args.get('sample_give') |
| print(sample_receive) |
| return jsonify({'msg': '이 요청은 GET!'}) |
| |
| |
| if __name__ == '__main__': |
| app.run('0.0.0.0', port=5000, debug=True) |
< templates/index.html >
| <!DOCTYPE html> |
| <html lang="ko"> |
| |
| <head> |
| |
| <title>모두의 책리뷰 | 스파르타코딩클럽</title> |
| |
| |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
| |
| |
| <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" |
| integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" |
| crossorigin="anonymous"> |
| |
| |
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" |
| integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" |
| crossorigin="anonymous"></script> |
| |
| |
| <link href="https://fonts.googleapis.com/css?family=Do+Hyeon&display=swap" rel="stylesheet"> |
| |
| <script type="text/javascript"> |
| |
| $(document).ready(function () { |
| showReview(); |
| }); |
| |
| function makeReview() { |
| $.ajax({ |
| type: "POST", |
| url: "/review", |
| data: {sample_give:'샘플데이터'}, |
| success: function (response) { |
| alert(response["msg"]); |
| window.location.reload(); |
| } |
| }) |
| } |
| |
| function showReview() { |
| $.ajax({ |
| type: "GET", |
| url: "/review?sample_give=샘플데이터", |
| data: {}, |
| success: function (response) { |
| alert(response["msg"]); |
| } |
| }) |
| } |
| </script> |
| |
| <style type="text/css"> |
| * { |
| font-family: "Do Hyeon", sans-serif; |
| } |
| |
| h1, |
| h5 { |
| display: inline; |
| } |
| |
| .info { |
| margin-top: 20px; |
| margin-bottom: 20px; |
| } |
| |
| .review { |
| text-align: center; |
| } |
| |
| .reviews { |
| margin-top: 100px; |
| } |
| </style> |
| </head> |
| |
| <body> |
| <div class="container"> |
| <img src="https://previews.123rf.com/images/maxxyustas/maxxyustas1511/maxxyustas151100002/47858355-education-concept-books-and-textbooks-on-the-bookshelf-3d.jpg" |
| class="img-fluid" alt="Responsive image"> |
| <div class="info"> |
| <h1>읽은 책에 대해 말씀해주세요.</h1> |
| <p>다른 사람을 위해 리뷰를 남겨주세요! 다 같이 좋은 책을 읽는다면 다 함께 행복해질 수 있지 않을까요?</p> |
| <div class="input-group mb-3"> |
| <div class="input-group-prepend"> |
| <span class="input-group-text">제목</span> |
| </div> |
| <input type="text" class="form-control" id="title"> |
| </div> |
| <div class="input-group mb-3"> |
| <div class="input-group-prepend"> |
| <span class="input-group-text">저자</span> |
| </div> |
| <input type="text" class="form-control" id="author"> |
| </div> |
| <div class="input-group mb-3"> |
| <div class="input-group-prepend"> |
| <span class="input-group-text">리뷰</span> |
| </div> |
| <textarea class="form-control" id="bookReview" |
| cols="30" |
| rows="5" placeholder="140자까지 입력할 수 있습니다."></textarea> |
| </div> |
| <div class="review"> |
| <button onclick="makeReview()" type="button" class="btn btn-primary">리뷰 작성하기</button> |
| </div> |
| </div> |
| <div class="reviews"> |
| <table class="table"> |
| <thead> |
| <tr> |
| <th scope="col">제목</th> |
| <th scope="col">저자</th> |
| <th scope="col">리뷰</th> |
| </tr> |
| </thead> |
| <tbody id="reviews-box"> |
| <tr> |
| <td>왕초보 8주 코딩</td> |
| <td>김르탄</td> |
| <td>역시 왕초보 코딩교육의 명가답군요. 따라하다보니 눈 깜짝할 사이에 8주가 지났습니다.</td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </body> |
| |
| </html> |
모두의책리뷰 - 리뷰 저장
< app.py > - write_review() 수정
| |
| @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': '리뷰가 성공적으로 작성되었습니다.'}) |
< templates/index.html > - makeReview() 수정
| function makeReview() { |
| |
| let title = $("#title").val(); |
| let author = $("#author").val(); |
| let review = $("#bookReview").val(); |
| |
| |
| $.ajax({ |
| type: "POST", |
| url: "/review", |
| data: { title_give: title, author_give: author, review_give: review }, |
| success: function (response) { |
| alert(response["msg"]); |
| window.location.reload(); |
| } |
| }) |
| } |
모두의책리뷰 - 리뷰 보기
< app.py > - read_reviews() 수정
| @app.route('/review', methods=['GET']) |
| def read_reviews(): |
| |
| reviews = list(db.bookreview.find({}, {'_id': False})) |
| |
| return jsonify({'all_reviews': reviews}) |
< templates/index.html > - 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) |
| } |
| } |
| }) |
| } |
2. 나홀로 메모장
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') |
| |
| |
| |
| og_image = soup.select_one('meta[property="og:image"]') |
| og_title = soup.select_one('meta[property="og:title"]') |
| og_description = soup.select_one('meta[property="og:description"]') |
| |
| url_image = og_image['content'] |
| url_title = og_title['content'] |
| url_description = og_description['content'] |
| |
| print(url_image) |
| print(url_title) |
| print(url_description) |
기본코드
< app.py >
| from flask import Flask, render_template, jsonify, request |
| app = Flask(__name__) |
| |
| import requests |
| from bs4 import BeautifulSoup |
| |
| from pymongo import MongoClient |
| client = MongoClient('localhost', 27017) |
| db = client.dbsparta |
| |
| |
| @app.route('/') |
| def home(): |
| return render_template('index.html') |
| |
| @app.route('/memo', methods=['GET']) |
| def listing(): |
| sample_receive = request.args.get('sample_give') |
| print(sample_receive) |
| return jsonify({'msg':'GET 연결되었습니다!'}) |
| |
| |
| @app.route('/memo', methods=['POST']) |
| def saving(): |
| sample_receive = request.form['sample_give'] |
| print(sample_receive) |
| return jsonify({'msg':'POST 연결되었습니다!'}) |
| |
| if __name__ == '__main__': |
| app.run('0.0.0.0',port=5000,debug=True) |
< templates/index.html >
| <!Doctype html> |
| <html lang="ko"> |
| |
| <head> |
| |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
| |
| |
| <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" |
| integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" |
| crossorigin="anonymous"> |
| |
| |
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" |
| integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" |
| crossorigin="anonymous"></script> |
| |
| |
| <link href="https://fonts.googleapis.com/css?family=Stylish&display=swap" rel="stylesheet"> |
| |
| |
| <title>스파르타코딩클럽 | 나홀로 메모장</title> |
| |
| |
| <style type="text/css"> |
| * { |
| font-family: "Stylish", sans-serif; |
| } |
| |
| .wrap { |
| width: 900px; |
| margin: auto; |
| } |
| |
| .comment { |
| color: blue; |
| font-weight: bold; |
| } |
| |
| #post-box { |
| width: 500px; |
| margin: 20px auto; |
| padding: 50px; |
| border: black solid; |
| border-radius: 5px; |
| } |
| </style> |
| <script> |
| $(document).ready(function () { |
| showArticles(); |
| }); |
| |
| function openClose() { |
| if ($("#post-box").css("display") == "block") { |
| $("#post-box").hide(); |
| $("#btn-post-box").text("포스팅 박스 열기"); |
| } else { |
| $("#post-box").show(); |
| $("#btn-post-box").text("포스팅 박스 닫기"); |
| } |
| } |
| |
| function postArticle() { |
| $.ajax({ |
| type: "POST", |
| url: "/memo", |
| data: {sample_give:'샘플데이터'}, |
| success: function (response) { |
| alert(response["msg"]); |
| } |
| }) |
| } |
| |
| function showArticles() { |
| $.ajax({ |
| type: "GET", |
| url: "/memo?sample_give=샘플데이터", |
| data: {}, |
| success: function (response) { |
| alert(response["msg"]); |
| } |
| }) |
| } |
| </script> |
| |
| </head> |
| |
| <body> |
| <div class="wrap"> |
| <div class="jumbotron"> |
| <h1 class="display-4">나홀로 링크 메모장!</h1> |
| <p class="lead">중요한 링크를 저장해두고, 나중에 볼 수 있는 공간입니다</p> |
| <hr class="my-4"> |
| <p class="lead"> |
| <button onclick="openClose()" id="btn-post-box" type="button" class="btn btn-primary">포스팅 박스 열기 |
| </button> |
| </p> |
| </div> |
| <div id="post-box" class="form-post" style="display:none"> |
| <div> |
| <div class="form-group"> |
| <label for="post-url">아티클 URL</label> |
| <input id="post-url" class="form-control" placeholder=""> |
| </div> |
| <div class="form-group"> |
| <label for="post-comment">간단 코멘트</label> |
| <textarea id="post-comment" class="form-control" rows="2"></textarea> |
| </div> |
| <button type="button" class="btn btn-primary" onclick="postArticle()">기사저장</button> |
| </div> |
| </div> |
| <div id="cards-box" class="card-columns"> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| <div class="card"> |
| <img class="card-img-top" |
| src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" |
| alt="Card image cap"> |
| <div class="card-body"> |
| <a target="_blank" href="#" class="card-title">여기 기사 제목이 들어가죠</a> |
| <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> |
| <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </body> |
| |
| </html> |
메모 저장 구현
< app.py > - saving()
| @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':'저장이 완료되었습니다!'}) |
< templates/index.html > - postArticle()
| 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() |
| } |
| }) |
| } |
메모 보기 구현
< app.py > - listing()
| @app.route('/memo', methods=['GET']) |
| def listing(): |
| articles = list(db.articles.find({}, {'_id': False})) |
| return jsonify({'all_articles':articles}) |
< templates/index.html > - showArticle()
| 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) |
| } |
| } |
| }) |
| } |