본문 바로가기

TIL 및 WIL/TIL (Today I Learned)

[TIL] 2022.04.19 (Python, MongoDB, Flask 만든 웹 페이지를 도메인 등록해 보기)

어제는 Python 코드와 HTML 코드를 이용하여 간단한 웹 페이지를 만들어 보았다.

오늘은 파이썬(Python)에 대해 짧게 알아보고 시작해보겠다.

 

파이썬(Python)이란?
- 누구나 배우기 쉽고 간단하게 이루어진 오픈 소스 프로그래밍 언어이다.
- 빠른 개발 속도를 가졌기에 C, Java와 같은 프로그래밍 언어보다 개발 시간이 단축된다는 장점이 있다.
- 코드가 간결하고 쉬운 문법으로 이루어져 있으며, 다양한 라이브러리를 지원한다.
- C, C++, Java 등의 프로그래밍 언어들과 잘 어우러진다.

 

여태까지 파이썬을 이용하여 코드를 짜고 웹 페이지를 만들었던 것이다!

확실히 대학교에서 배웠던 C++이나 Java보다 간단하여 나 또한 쉽게 배울 수 있었던 것 같다.

 

오늘은 이런 파이썬을 이용하여 크롤링(Crawling)스크래핑(Scraping)을 해보려고 한다.

처음 들어봤을 때는 크롤링? 스크래핑? 둘이 같은 것 아닌가?라는 생각이 들었다.

하지만 크롤링과 스크래핑은 비슷하지만 엄연히 다른 차이가 존재한다고 한다.

 

크롤링(Crawling) : 다양한 웹 사이트의 데이터를 수집하고 분류하기 위해 브라우징 하는 것
스크래핑(Scraping) : 웹 사이트의 페이지를 그대로 가져와 필요한 데이터를 추출하는 행위

 

이제 파이썬 코드로 넘어가 보자.

 

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('https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829',headers=headers)

soup = BeautifulSoup(data.text, 'html.parser')

 

오늘 해볼 크롤링의 기본 형태이다.

하지만 여기서 크롤링을 하려면 미리 만들어진 라이브러리를 사용해야 원활하게 진행이 가능하다.

그렇기에 파이썬에서는 패키지라는 것을 인터프리터 하여 가져와야 한다.

 

라이브러리를 사용해야 한다면서 패키지를 왜 얘기하는 것인가?

그것은 파이썬에서의 패키지가 여러 기능들의 묶음이라고 하는 모듈을 모아놓은 단위인데,

이러한 패키지들의 묶음을 라이브러리라고 칭한다.

그런 외부 라이브러리를 사용하기 위해 패키지를 설치하여 사용하는 것이다!

 

위에 코드에 있는 requestsBeautifulSoup는 무엇일까?

 

requests 라이브러리
- Ajax와 유사하게 API 데이터를 추출해주는 패키지
- 파이썬에서는 HTTP를 호출할 때 주로 사용한다.
- 어떤 방식(method)의 HTTP 요청을 하느냐에 따라 해당하는 함수가 달라진다. (4가지만 표기)
1. GET(조회) : requests.get()
2. POST(등록) : requests.post()
3. PUT(수정) : requests.put()
4. DELETE(삭제) : requests.delete()

 

BeautifulSoup 라이브러리
- 텍스트 형태의 데이터에서 원하는 HTML 태그를 쉽게 추출해내기 위해 사용한다.

 

import requests / from bs4 import BeautifulSoup를 사용하여 requests 라이브러리를 설치한다.

그리고 requests.get("크롤링할 URL", headers=headers)로 해당 HTTP URL을 GET(데이터 조회) 형식 읽어온 뒤 HTML을 받아온다.

마지막으로 받아온 HTML을 BeautifulSoup라는 라이브러리를 활용해 검색이 용이한 상태로 만들어준다.

 

이제 찾고 싶은 요소를 웹 페이지에서 Copy > Copy selector를 이용하여 CSS 선택자를 복사한다.

 

네이버 영화 페이지 : https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=pnt&date=20210829

 

이제 찾고자 하는 CSS 선택자를 붙여 넣기 하면

#old_content > table > tbody > tr:nth-child(2) > td.title > div > a라는 것을 추출할 수 있다.

 

CSS 선택자도 찾았으니 코드를 더 추가해보자

 

# select를 이용해서, tr들을 불러오기
movies = soup.select('#old_content > table > tbody > tr')

# movies (tr들) 의 반복문을 돌리기
for movie in movies:
    a = movie.select_one('td.title > div > a')
    if a is not None:
        title = a.text
        rank = movie.select_one('td:nth-child(1) > img')['alt']
        star = movie.select_one('td.point').text
        print(rank, title, star)

 

이런 식으로 찾고자 하는 것을 스크래핑하여 print로 출력해보면

 

내가 스크래핑 하고자 한 목록

 

찾고 싶었던 목록들이 이렇게 출력된다.

 

이제 다음으로 MongoDBFlask를 공부해볼 것인데..

들어가기 앞서 데이터베이스(DB)에 대해 짧게 알아보자.

 

데이터베이스(DB)란?
- 여러 사람이 공유하고 사용할 목적으로 구조화되어 관리되는 데이터들의 집합

 

데이터베이스는 크게 2가지로 나뉜다.
1. 관계형 데이터베이스(REBMS : ORACLE, MySQL, MS-SQL 등)
- 행(Tuple)과 열(Attribute) 형태로 이루어진 정형화된 데이터베이스
- 신뢰성이 높고 무결성을 보장하며, 데이터 성능이 좋기에 정렬 및 탐색 등이 용이하다.
2. 비 관계형 데이터베이스(NoSQL : MongoDB, CouchDB 등)
- 딕셔너리(Dictionary) 형태로 이루어진 비정형화 또는 반정형화된 데이터베이스
- 응답속도와 처리 효율이 뛰어난 자유로운 형태의 데이터베이스이다.
- 데이터 모델링이 유연하고, 확장설이 뛰어나다.

 

이제 데이터베이스(DB)에 대해 잠깐 알아보았다.

다음으로 연습해볼 MongoDB에 대해서도 알아보자.

 

MongoDB란?
- 비 관계형 데이터베이스(NoSQL)로 분류되는 문서 지향 데이터베이스
- 문서 지향 데이터베이스를 사용하여 쉽게 데이터를 저장하고 관리할 수 있다.
- 하지만 데이터 일관성이 거의 없기 때문에 은행 업무와 같은 중요한 작업에는 쓰기 힘들다는 단점이 있다.
- 파이썬에서는 pymongo 패키지를 사용하여 mongoDB를 조작할 수 있다.

 

글만 봐서는 아직 잘 모르겠다!

파이썬 코드를 보며 공부해보자.

 

from pymongo import MongoClient

client = MongoClient('Atlas 연결 URL')
db = client.dbsparta

 

다음은 MongoDB를 사용하기 위해 적은 코드의 형태이다.

그리고 MongoDB를 사용하기 위해 Atlas를 가입하고, 파이썬 내에서는 pymongo와 dnspython 패키지를 설치해주었다.

 

from pymongo import MongoClient

client = MongoClient('Atlas 연결 URL')
db = client.dbsparta

# 딕셔너리 형태로 데이터를 저장
doc = {'name': 'junsik', 'age': 32}
db.users.insert_one(doc)

 

Atlas 데이터베이스에 딕셔너리 형태로 값을 저장해보려고 실행을 눌러보았다.

그런데?

 

이건 무슨 에러일까? pymongo.errors.ServerSelectionTimeoutError

 

갑자기 이렇게 에러가 떠버리는 것이 아니겠는가?

처음 보는 에러에 많이 당황을 해버리고 말았다.

pymongo.errors.ServerSelectionTimeoutError는 무슨 에러인가 싶어서 찾아보니

원격 서버의 응답 시간을 기다리는 동안 pymongo 시간이 초과하여 에러가 발생하는 것이라고도 하고, MongoDB가 제대로 설치되어 있지 않아서 생기는 오류라고도 했다.

 

이게 무슨 소리인가..

 

후로 이것저것 많이 찾아보다가 도저히 안 되겠다 싶어서 포기하려는 순간..

주변의 도움으로 해결 방법을 찾았다!

감사합니다!

 

에러 해결을 위해 바로 certifi 패키지를 추가했다!

 

import certifi
from pymongo import MongoClient

client = MongoClient('Atlas 연결 URL', tlsCAFile=certifi.where())
db = client.dbsparta

# 딕셔너리 형태로 데이터를 저장
doc = {'name': 'junsik', 'age': 32}
db.users.insert_one(doc)

에러 없이 딕셔너리 값이 잘 들어가는 모습

 

사진처럼 certifi 패키지를 임포트 하여 TLS/SSL 인증 문제를 해결하니 정상적으로 동작되는 것을 확인할 수 있었다.

의도치 않은 에러에 공부에 지연이 생겼지만 문제를 해결하는 방법을 찾으니 기분이 좋았다.

 

이제 원활하게 돌아가는 것을 확인했다.

다음 Flask로 넘어가 보자.

 

Flask란?
- 파이썬 기반 웹 프레임워크(Flask, Django 등) 중 하나
- 특별한 도구나 라이브러리가 필요 없기에 비교적 쉽고 빠르게 작성이 가능하다.
- 하지만 쉬운 만큼 보안에 취약하다는 단점이 있다.

 

그리고 보통 Flask 서버를 만들 때는 해당 프로젝트가 있는 폴더 안에 3가지를 준비해 주어야 한다고 한다.

1. static 폴더 : 이미지, CSS파일을 보관

2. templates 폴더 : html 파일을 보관

3. app.py : 파이썬 코드 파일

이렇게 3가지가 기본적인 세팅 준비라고 한다.

 

이제 Flask 기본 형태를 보자

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <title>Document</title>

    <script>
        function hey(){
            alert('안녕?')
        }
    </script>
</head>
<body>
    <button onclick="hey()">버튼입니다.!</button>
</body>
</html>
app.py

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

# URL 별로 함수명이 같거나,
# route('/') 등의 주소가 같으면 안됩니다.

# http://localgost:5000/ 으로 접속이 가능하다
# app.route('/') 부분을 수정하면 / 뒤에 오는 URL을 추가할 수 있다.
@app.route('/')
def home():
   return render_template('index.html')

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

 

Flask는 기본적으로 이런 형태를 띠고 있다.

우선 from flask import Flask, render_template를 통해 Flask 패키지를 임포트 해주고, templates 폴더에 있는 html도 불러와 서로 간 연결이 가능하게끔 설정해준다.

app = Flask(__name__)는 Flask 인스턴스를 생성해주고 __name__은 모듈의 이름을 뜻한다.
@app.route('/')는 라우팅 경로(URL 값)를 설정해주는 것이고 def home(): return render_template('index.html')을 통해 templates 폴더에 있는 index.html을 불러와 연결해준다.

if __name__ == '__main__': app.run('0.0.0.0', port=5000, debug=True)에서 __main__은 주 모듈을 뜻하는데 __name__을 주 모듈로서 임포트 시 __name__을 실행하라는 의미를 가진다.

 

Flask는 뭔가 보면 순간적으로 어지러워진다!

하지만 익숙해져야 한다고 생각한다.

 

이제 본격적으로 API를 만들어 주어야 한다.

 

API란?
- Application Programming Interface의 약어
- 응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어하게끔 만든 인터페이스
- 애플리케이션을 서로 연결하고 통신할 수 있게 만들어준다.

 

API 요청 타입(GET, POST)
1. GET : 데이터 조회(Read)를 요청할 때 사용한다.
2. POST : 데이터 생성(Create), 변경(Update), 삭제(Delete)를 요청할 때 사용한다.
- 이 외 PUT, DLELTE 등등.. 여기서는 우선 GET과 POST만 사용해보겠다.

 

우선 완성된 웹 페이지를 보고 app.py와 index.html을 같이 보여주며 얘기해보자.

 

좌측 > 우측 순서, API 요청 GET과 POST 사용

 

사진에서 보이는 결과는 웹 페이지가 실행되고 닉네임과 감상평을 적은 뒤 등록하기 버튼을 누르면 감상평 목록에 들어가게끔 되어있는 웹 페이지다.

 

이제 GET과 POST 방식을 추가한 코드를 하나씩 살펴보자.

 

app.py

# 등록된 감상평 읽어오기(Read) API
@app.route('/homework', methods=['GET'])
def homework_get():
    review_list = list(db.homework.find({}, {'_id': False}))
    return jsonify({'reviews': review_list})
index.html

function show_review() {
    $('#review-list').empty()
    $.ajax({
        type: "GET",
        url: "/homework",
        data: {},
        success: function (response) {
            let rows = response['reviews']
            for (let i = 0; i < rows.length; i++) {
                let name = rows[i]['name']
                let review = rows[i]['review']

                let temp_html = `<div class="card">
                                    <div class="card-body">
                                        <blockquote class="blockquote mb-0">
                                            <p>${review}</p>
                                            <footer class="blockquote-footer">${name}</footer>
                                        </blockquote>
                                    </div>
                                </div>`
                $('#review-list').append(temp_html)
            }
        }
    });
}

 

이것은 GET 요청으로 감상평에 적은 값이 POST 요청에 의해 등록되고, 그것들을 조회(Read)하여 보여주게끔 만든 코드이다.

 

우선 app.py에 들어있는 감상평 API 코드들을 통해 DB /homework 경로에 들어가 있는 값들을 GET 방식을 채택함으로써 조회하여 보여주도록 설정한다.

_id 값을 제외한 name과 review 목록들을 조회하게 하는데, index.html에서 반복문(for)을 통해 들어있는 모든 값을 조회하게끔 한다.

그리고 Backtick(`) 안에 <body>에 해당하는 부분들을 추가해주고 해당 추가되는 값들은 ${ }을 통해 각 딕셔너리에 포함된 값들을 표시해준다.

그렇게 저장된 코드들을 $('#review-list').append(temp_html)을 통해 최종적으로 만들어진 HTML 모양을 추가시켜준다.

 

app.py

# 감상평 등록(POST) API
@app.route('/homework', methods=['POST'])
def homework_post():
    name_receive = request.form['name_give']
    review_receive = request.form['review_give']

    doc = {
        'name': name_receive,
        'review': review_receive,
    }
    db.homework.insert_one(doc)
    return jsonify({'msg': '감상평 등록 완료!'})
index.html

// 이름과 감상평에 들어간 데이터를 생성(POST)
function save_review() {
    let name = $('#name').val()
    let review = $('#review').val()

    $.ajax({
        type: "POST",
        url: "/homework",
        data: {'name_give': name, 'review_give': review},
        success: function (response) {
            alert(response["msg"])
            window.location.reload()
        }
    });
}

 

다음으로 POST 요청을 하여 데이터를 생성(Create)하게끔 만들어주는 코드들이다.

 

app.py에서 DB /homework 경로에 POST 요청으로 데이터 생성(Create)을 하도록 한다.

생성하는 값은 dox = { }과 같은 딕셔너리 형태로 해당 값들을 담아주도록 한다.

그리고 그 생성하려는 딕셔너리 값들을 /homework 경로에 넣어주고, 성공적으로 만들어졌다면 해당 메시지가 출력하도록 한다.

하지만 이렇게만 해서는 출력하는 값이 없고 index.html로 넘어가 name과 review의 아이디 값을 지정하여 /homework 경로에 생성 요청을 하며 줄 데이터들(name, review)을 넣어준다.

그리고 마지막으로 app.py에서 지정한 '감상평 등록 완료!' 메시지 팝업창을 띄우고 해당 웹 페이지를 새로고침 하는 것으로 코드가 마무리된다.

 

으악! 뭔가 막상 적고 나니 두서없이 적은 것 같다!

 

마지막으로 index.html에서 웹 페이지 미리 보기 설정을 해보자.

 

index.html

<meta property="og:title" content="한요한 앨범 감상평" />
<meta property="og:description" content="한요한 2집 감상평을 남겨보세요!" />
<meta property="og:image" content="https://image.bugsm.co.kr/album/images/500/203151/20315104.jpg" />

 

og 태그
1. og:title : 미리 보기 제목
2. og:description : 미리 보기 내용
3. og:image : 미리 보기 이미지

 

클라이언트에서 서버로 요청하고 API 설정과 이전에 배운 JQuery, Ajax를 응용하여 웹 페이지 코드를 만들어 보았다.

하지만 이렇게 코드만 남겨봤자 내 컴퓨터나 프로그램이 꺼진다면 웹 페이지도 자동으로 닫히게 된다.

이를 방지하기 위해 AWS라는 클라우드 서비스에서 편하게 서버를 관리하고 항상 켜 놓을 수 있는 컴퓨터(EC2) 프리티어를 구매하여 사용해보자.

 

AWS란?
- Amazon Web Services의 약어.
- Amazon에서 개발한 클라우드 컴퓨팅 플랫폼이며, 사용자에게 클라우드 서비스를 제공한다.
- 네트워킹을 기반으로 가상 컴퓨터와 네트워크 인프라 등의 다양한 서비스를 제공한다.

 

AWS EC2에 접속하는 방법
- 원격 호스트에 접속하기 위해 SSH(Secure Shell)을 사용하여 접속한다.
- SSH는 기본적으로 22번 포트를 사용하기 때문에 22번 포트를 열어준다.
- 유료 운영체제와 오픈 소스 운영체제 중 한 가지를 골라서 생성해준다.

 

일단 프리티어가 적힌 무료 오픈 소스 운영체제 중 Ubuntu를 선택하여 사용하였다.

 

Ubuntu
- 리눅스 계열의 오픈 소스 운영체제

 

이후 파일 질라(File Zilla)라는 파일 전송 프로그램(FTP)을 사용하여 파일을 옮겨주었고, Window에서 사용하지 못하는 Linux를 사용하기 위해 깃 배쉬(Git Bash)라는 프로그램을 사용하여 SSH 접속을 해주었다.

 

그리고 파일 질라(File Zilla)를 통해 EC2에 Public IP를 SSH 형태로 열어주고 깃 배쉬(Git Bash)를 이용하여 SSH에 접속하여 해당 명령어를 적어주었다.

 

ssh -i (EC2에서 생성한 키페어) ubuntu@aws ip

 

 

이전에 코드를 만들던 파이썬에서는 설치가 완료되었지만 가상의 인프라에서는 아직 설치를 해주지 않았기 때문에 만들어둔 웹 페이지를 구동하기 위한 패키지들을 추가로 설치해준다.

 

# python3 -> python
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10

# pip3 -> pip
sudo apt-get update
sudo apt-get install -y python3-pip
sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1

# port forwarding
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 5000

# flask
pip install flask

# pymongo, dnspython
pip install pymongo dnspython

 

해당 패키지들의 설치를 마쳤다면 끝인가?

아니다, 마지막으로 원격 접속(SSH)을 종료해도 서버는 계속해서 돌아가게끔 만들어주는 명령어도 적어준다.

 

# 원격 접속을 종료해도 서버는 계속 돌아가게 하기
nohup python "파일 이름.py" &

# 만약 강제 종료시키려면?
ps -ef | grep 'python 파일 이름.py' | awk '{print $2}' | xargs kill

 

이렇게 해준다면 원격 접속을 종료하더라도 서버는 계속 돌아가기 때문에 계속해서 해당 웹 페이지로 들어갈 수 있게 된다.

 

마지막으로 DNS 설정을 해주면 해당 웹 페이지는 완성되게 된다.

 

https://www.gabia.com/

 

웹을 넘어 클라우드로. 가비아

그룹웨어부터 멀티클라우드까지 하나의 클라우드 허브

www.gabia.com

 

가비아에서 자신이 쓰고 싶은 DNS명을 고른 뒤 EC2의 Public IP를 설정해주면 DNS 설정도 마무리가 된다.

 

많이 왔다!

이제 진짜 마지막으로 적용된 DNS 웹 페이지가 정상적으로 켜지는지 확인해보자!

 

(해당 EC2는 요금 발생 문제 때문에 22.04.27에 중단시켜둔 상태입니다)

 

만들어둔 코드대로 원활하게 작동되고, 미리보기까지 뜨는 것을 확인하였다!

EC2가 꺼지지 않는 이상은 원활하게 웹 페이지 구동이 이루어질 것이다.

 

짧은 시간 내에 공부하여 만든 나만의 작은 웹 페이지이다.

아직은 비록 부족하고 못나 보일지 모르는 웹 페이지라도 노력하여 만들었으니 만족한다!

 

여기서 만족하지 말고 더 낫고 좋은 웹 개발을 위해 공부하고 또 공부하도록 하자!

 

:D