웹개발 종합 03

·2023년 7월 28일
0

웹개발 종합

목록 보기
3/23

오픈 API를 활용한 영화 카드 사이트

fetch 문은 기존 코드와 똑같이 복사해준 뒤 url만 영화 API로 바꿔준다

fetch("http://spartacodingclub.shop/web/api/movie").then(res => res.json()).then(data => {
                
                })
  • temp_html 에는 바꿔야할 card 부분의 html을 `` 을 이용해 넣어준다.
  • 각 변수에 data의 row 부분을 넣어준 뒤 ${} 을 이용해 temp_html 부분에 넣어준다.
  • $('#id').append 를 이용해 웹에 붙여준다.
let temp_html = `<div class="col">
                                        <div class="card">
                                            <img src="${image}"
                                                class="card-img-top" alt="...">
                                            <div class="card-body">
                                                <h5 class="card-title">${title}</h5>
                                                <p class="card-text">${comment}</p>
                                                <p>${star_image}</p>
                                                <p class="mycomment">${desc}</p>
                                            </div>
                                        </div>
                                    </div>`
                    $('#cards').append(temp_html)

별 이모티콘 붙이는 방법
star에 별의 개수를 넣어준 뒤 star_image 에 별 이모지 ->⭐ 를 넣어준다.
repeat 함수를 통해 이모지를 반복해서 넣어준다

let star = a['star']
let star_image = '⭐'.repeat(star)

코드 전문

<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- 남이 만들어놓은 코드 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
        crossorigin="anonymous"></script>

    <title>스파르타코딩클럽 | 부트스트랩 연습하기</title>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Gowun+Dodum&display=swap');

        * {
            font-family: 'Gowun Dodum', sans-serif;
        }

        .mytitle {
            background-color: green;
            color: white;

            height: 250px;
            /* 내용물 세로로 정렬, 가로는 column 대신 row */
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;

            background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url(https://movie-phinf.pstatic.net/20210715_95/1626338192428gTnJl_JPEG/movie_image.jpg);
            background-position: center;
            background-size: cover;
        }

        /* mytitle 아래에 있는 button 지정 */
        .mytitle>button {
            width: 250px;
            height: 50px;

            /* 버튼은 투명, 글자색 흰색, 테두리선은 실선이며 흰색 */
            background-color: transparent;
            color: white;
            border: 1px solid white;

            border-radius: 50px;

            margin-top: 20px;

        }

        /* 버튼을 누를 시 선이 굵어지게 */
        .mytitle>button:hover {
            border: 2px solid white;
        }

        .mycomment {
            color: gray;
        }

        .mycards {
            width: 1200px;
            margin: 20px auto 20px auto;
        }

        .mypost {

            width: 500px;
            margin: 20px auto 20px auto;
            padding: 20px 20px 20px 20px;
            /* 가운데에서 아래, 옆으로 그림자가 얼마나 먼지 */
            box-shadow: 0px 0px 3px 0px gray;
        }

        .mybtn {
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;

            margin-top: 20px;
        }

        .mybtn>button {
            margin-right: 20px;
        }
    </style>
    <script>
        //api fetch
        $(document).ready(function () {
            fetch("http://spartacodingclub.shop/sparta_api/weather/seoul").then(res => res.json()).then(data => {
                //data 의 temp 에 실시간 기온 정보가 있음
                let temp_now = data['temp']
                //id = "temp" 인 곳에 붙이기
                $('#temp').text(temp_now)
            })
            fetch("http://spartacodingclub.shop/web/api/movie").then(res => res.json()).then(data => {
                let rows = data['movies']
                $('#cards').empty()
                rows.forEach((a) => {
                    let title = a['title']
                    let desc = a['desc']
                    let image = a['image']
                    let star = a['star']
                    let comment = a['comment']

                    //star 이미지로 붙이기
                    let star_image = '⭐'.repeat(star)
                    //id 아래에 붙여야 하로 <div class = "col"> 부터
                    let temp_html = `<div class="col">
                                        <div class="card">
                                            <img src="${image}"
                                                class="card-img-top" alt="...">
                                            <div class="card-body">
                                                <h5 class="card-title">${title}</h5>
                                                <p class="card-text">${comment}</p>
                                                <p>${star_image}</p>
                                                <p class="mycomment">${desc}</p>
                                            </div>
                                        </div>
                                    </div>`
                    $('#cards').append(temp_html)
                })
            })
        })
    </script>
</head>

<body>
    <div class="mytitle">
        <h1>내 생에 최고의 영화들</h1>
        <div>현재 서울의 날씨 : <span id="temp"></span></div>
        <button>영화 기록하기</button>
    </div>
    <div class="mypost">
        <div class="form-floating mb-3">
            <input type="email" class="form-control" id="floatingInput" placeholder="name@example.com">
            <label for="floatingInput">영화 url</label>
        </div>
        <div class="input-group mb-3">
            <label class="input-group-text" for="inputGroupSelect01">별점</label>
            <select class="form-select" id="inputGroupSelect01">
                <option selected>--선택하기--</option>
                <option value="1"></option>
                <option value="2">⭐⭐</option>
                <option value="3">⭐⭐⭐</option>
            </select>
        </div>
        <div class="form-floating">
            <textarea class="form-control" placeholder="Leave a comment here" id="floatingTextarea"></textarea>
            <label for="floatingTextarea">코멘트</label>
        </div>
        <div class="mybtn">
            <button type="button" class="btn btn-dark">기록하기</button>
            <button type="button" class="btn btn-outline-dark">닫기</button>
        </div>
    </div>
    <div class="mycards">
        <div id="cards" class="row row-cols-1 row-cols-md-4 g-4">
            <div class="col">
                <div class="card">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기 코멘트가 들어갑니다</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">여기에 나의 의견을 적으면 됩니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기 코멘트가 들어갑니다</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">여기에 나의 의견을 적으면 됩니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기 코멘트가 들어갑니다</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">여기에 나의 의견을 적으면 됩니다</p>
                    </div>
                </div>
            </div>
            <div class="col">
                <div class="card">
                    <img src="https://movie-phinf.pstatic.net/20210728_221/1627440327667GyoYj_JPEG/movie_image.jpg"
                        class="card-img-top" alt="...">
                    <div class="card-body">
                        <h5 class="card-title">영화 제목이 들어갑니다</h5>
                        <p class="card-text">여기 코멘트가 들어갑니다</p>
                        <p>⭐⭐⭐</p>
                        <p class="mycomment">여기에 나의 의견을 적으면 됩니다</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

</html>
profile

결과

파이썬을 이용한 가상환경 설정

venv 이용하여 가상환경 만들기
윈도우 : Vscode 의 터미널에 python -m venv venv 입력
맥 : python3 -m venv venv
-> venv가 자동으로 만들어지는 것을 볼 수 있음
-> 라이브러리를 담아둘 폴더

이후 인터프리터 설정에서 venv를 선택한 뒤 new terminal을 누르면 (venv)가 뜬 터미널이 나오는 것을 볼 수 있다.

  • 이 폴더에서 라이브러리를 가져다가 쓸 것이라는 뜻

다음으로 라이브러리 이름을 하나 써준다

pip3 install requests
  • 맥os라 pip3를 써줬지만 윈도우는 pip
    requests 는 fetch - 서버에서 데이터를 가져오는 역할 - 와 비슷한 역할을 한다.

requests 사용 예시

import requests;

r = requests.get('http://spartacodingclub.shop/sparta_api/seoulair')
rjson = r.json()

print(rjson)
  • requests를 만든 사람의 document 에 가면 사용법 예시가 자세히 적혀있다.

웹 스크래핑

사전 준비

pip3 install bs4

기본 세팅

import requests
from bs4 import BeautifulSoup

URL = "https://movie.daum.net/ranking/reservation"
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')
  • url은 다음 영화

영화 제목 가져오기

url을 통해 사이트에 들어간 뒤 개발자 도구의 elements 에서 제목 부분 copy > copy selector

  • selector을 통해 웹 스크래핑을 할 것이기 때문에 copy selector을 해준다.

bs4의 select_one() 함수에 복사해준 selector을 붙여넣어준 뒤 print 해보면 아래와 같은 결과가 나온다.

코드

import requests
from bs4 import BeautifulSoup

URL = "https://movie.daum.net/ranking/reservation"
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')

##<li></li> 태그의 2번째 라는 의미
title = soup.select_one('#mainContent > div > div.box_ranking > ol > li:nth-child(2) > div > div.thumb_cont > strong > a')
print(title.text)

결과

  • .text를 해주어야 글자 부분만 나온다.
  • 해주지 않았을 경우
    -> <a class="link_txt" data-tiara-layer="moviename" href="/moviedb/main?movieId=163777">엘리멘탈</a> html 부분이 그대로 나온다.
  • href 부분만 보고 싶다면 title['href'] 를 print 해주면 된다.

리스트에 있는 영화 제목 모두 가져오기

<li> 태그가 붙어있는 모든 영화 제목을 가져와야 하므로 html에서 li 다음은 지워준다.
영화 제목을 검사해보면

<a href="/moviedb/main?movieId=147080" class="link_txt" data-tiara-layer="moviename">밀수</a>

이것으로, class가 "link_txt" 인 것을 알 수 있다.
-> select_one() 안에 파라미터로 '.link_txt'를 넣어준 다음 반복문을 이용해 하나씩 출력해준다.

lis = soup.select('#mainContent > div > div.box_ranking > ol > li')
for li in lis : 
    title = li.select_one('.link_txt')
    print(title.text)

순위, 평점도 같이 출력하기

순위와 평점의 html 태그

<span class="rank_num">1</span>
<span class="txt_grade">7.5</span>

class 를 알았으므로 위와 같은 방식으로 내용물만 바꿔서 해주면 된다

import requests
from bs4 import BeautifulSoup

URL = "https://movie.daum.net/ranking/reservation"
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')

##li 태그의 2번째 라는 의미
lis = soup.select('#mainContent > div > div.box_ranking > ol > li')
for li in lis : 
    title = li.select_one('.link_txt').text
    rank = li.select_one('.rank_num').text
    score = li.select_one('.txt_grade').text
    print(rank, title, score)

  • 스크래핑 과정에서 띄어쓰기나 , 같은 특수문자 때문에 보기가 불편해질 경우 text 뒤에 .strip() 이나 .replace(',') 함수를 붙여 이를 지워줄 수 있다.

mongoDB

데이터베이스를 이용하는 목적은 잘 저장하기 위함이 아니라, 잘 가져오기 위해서이다.

데이터베이스의 종류

SQL

정형화된 틀이 있다. 엑셀에 데이터를 넣는 것과 유사하다.
정형화되어 있기 때문에 중간에 수정/삽입/삭제하는 작업이 빈번한 데이터에는 적합하지 않지만, 일관적인 데이터를 분석하는 작업을 할 때에는 유리하다.
틀이 정해져있기 때문에 사람의 실수를 줄여주기 때문에 주로 대기업에서 사용한다.
ex) MYSQL 등

No-SQL

sql이 아니라는 뜻이 아니라, Not only SQL 이라는 뜻이다. SQL 형태의 데이터와 반대로 형태와 틀이 자유로워 데이터 적재에 유리하지만, 일관적인 데이터를 다룰 때에는 비효율적일 수 있다.
데이터의 유연성이 장점이기 때문에 아직 확실하지 않은 스타트업에서 많이 사용한다.
ex) MongoDB 등

최근의 데이터베이스는 컴퓨터가 아니라 클라우드 환경에 저장해놓는 경우가 많다.

  • 유저가 몰리거나 DB를 백업해야할 때, 실시간으로 모니터링해야할 때 매우 유용하다.

mongoDB에 가입한 후 IP주소를 입력한 다음 데이터베이스에 접속해준다.

mongoDB 파이썬으로 연결

mongoDB를 파이썬으로 조작하기 위해서는 특정 라이브러리 - pymongo, dnspython - 가 필요하다.

pip3 install pymongo
pip3 install dnspython

pymongo 기본 조작 코드

from pymongo import MongoClient
client = MongoClient('여기에 URL 입력')
db = client.dbsparta
  • URL : modgoDB에 가서 database > connect 클릭 > Connect to your application 아래의 drivers 클릭 > Node.js 부분 Python으로 바꿔준 다음 아래의 url 복사/붙여넣기 > <password>부분 비밀번호로 바꾸기

삽입

insert_one 함수를 통해 연결된 데이터베이스에 데이터를 삽입해준다.

doc = {
    'name':'bob',
    'age':27
}
db.users.insert_one(doc)

* MacOS 환경에서 pymongo.errors.ServerSelectionTimeoutError 에러가 뜰 때 해결법

pip3 install certifi
certifi 패키지 먼저 설치해주기

import certifi

ca = certifi.where()

추가해주고

client = MongoClient('url 부분', tlsCAFile=ca)

url 뒤에 tlsCAFile=ca 추가

코드 전문

from pymongo import MongoClient
import certifi

ca = certifi.where()

client = MongoClient('mongodb+srv://sparta:test@cluster0.eh7wfh6.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

doc = {
    'name':'bob',
    'age':27
}
db.users.insert_one(doc)

Cluster0 > Collections에서 데이터가 정상적으로 삽입된 것을 확인할 수 있다.

데이터 가져오기

all_users = list(db.users.find({},{'_id':False}))
#제대로 가져왔는지 확인
for a in all_users:
    print(a)

users라는 db에서 가져온 데이터를 list 형태로 만들어서 출력하는 코드이다.

  • '_id':False > 이 부분이 없으면 _id 부분이 붙어서 출력된다. 이 부분을 보지 않겠다는 의미

결과

하나만 가져올 경우 find_one

결과값 하나만 가져오고 싶을 경우 .find_one()을 사용한다

user = db.users.find_one({'name':'bob'},{'_id':False})
print(user)

값을 조작할 경우 update_one

db.users.update_one({'name':'bob'},{'$set':{'age':19}})

name이 bob인 데이터를 찾아 age를 19로 바꾸라는 명령어

mongoDB에서 refresh를 해주면 값이 제대로 바뀐 것을 확인할 수 있다.

값을 제거할 경우 delete_one

db.users.delete_one({'name':'bob'})

name이 bob인 데이터를 찾아 제거하는 명령어

퀴즈

퀴즈 01 - 영화제목 '아저씨'의 순위를 가져오기

from pymongo import MongoClient
import requests
from bs4 import BeautifulSoup
import certifi

ca = certifi.where()

client = MongoClient('mongodb+srv://sparta:test@cluster0.eh7wfh6.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

URL = "https://movie.daum.net/ranking/boxoffice/yearly?date=2010"
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')

lis = soup.select(".list_movieranking > li")
for li in lis:
    rank = li.select_one(".rank_num").text
    age = li.select_one(".ico_see").text
    title = li.select_one(".link_txt").text
    print(rank, title, age)

    doc = {
        'rank': rank,
        'title': title,
        'age': age
    }
    db.movies2.insert_one(doc)

퀴즈 02 - '하모니' 와 같은 연령가 작품 가져오기

에러 - TypeError("index %r cannot be applied to Cursor instances" % index)
TypeError: index 'title' cannot be applied to Cursor instances

res = db.movie2.find({'age':age},{'_id':False})

print(m['title'])
    

처음에 데이터가 잘 나오는지 보려고 이렇게 쳤는데, typeError가 났다.
알아보니 find로 데이터를 받을 때에는 cursor 라는 객체로 '현재 검색하는 데이터의 위치' 까지 포함한 데이터를 반환하는데, 딕셔너리 형태로 정보를 찾으려할 때에는 이 cursor 을 사용할 수 없어서 나는 에러 같다.

참고
https://stackoverflow.com/questions/68562689/command-raised-an-exception-typeerror-index-wallet-cannot-be-applied-to-curs

그 뒤로도 몇 번 None 이 반환되는 에러가 있었는데, db 이름에 오타가 났기 때문이였다. 정신 차리고 살아야겠다.

from pymongo import MongoClient
import requests
from bs4 import BeautifulSoup
import certifi

ca = certifi.where()

client = MongoClient('mongodb+srv://sparta:test@cluster0.eh7wfh6.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

URL = "https://movie.daum.net/ranking/boxoffice/yearly?date=2010"
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')

movie = db.movies2.find_one({'title':"하모니"})
age = movie['age']

res = db.movies2.find({'age':age},{'_id':False})

for m in res :
    print(m['title'])

퀴즈 04 - '부당거래' 영화의 관람가를 18세이상관람가로 만들기

from pymongo import MongoClient
import requests
from bs4 import BeautifulSoup
import certifi

ca = certifi.where()

client = MongoClient('mongodb+srv://sparta:test@cluster0.eh7wfh6.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

URL = "https://movie.daum.net/ranking/boxoffice/yearly?date=2010"
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')

db.movies2.update_one({'title':'부당거래'},{'$set':{'age':"18세이상관람가"}})

숙제

지니뮤직의 1~50위 곡을 스크래핑 해보세요.

에러 - 결과가 제대로 나오지 않음
Object...이렇게 한 줄만 뜨고 그 이상 뜨지 않아서 코드를 살펴본 결과

lis = soup.select('#body-content > div.newest-list > div > table > tbody')

에 문제가 있었다.
추출한 html selector 코드가

#body-content > div.newest-list > div > table > tbody > tr:nth-child(1) > td.info > a.title.ellipsis

였는데, 여기서 :nth-child(1) 에 더해서 tr까지 지워버려 제대로 스크래핑이 안 됐다.

lis = soup.select('#body-content > div.newest-list > div > table > tbody > tr')

로 고쳐주었다.

에러 - AttributeError: 'NoneType' object has no attribute '.text'

title = li.select_one('.title ellipsis').text
    rank = li.select_one('.number').text
    artist = li.select_one('.artist ellipsis').text

받아온 데이터에 text 타입이 없다는 말이다. 검사를 통해 분명 class="title ellipsis"인 것을 확인했는데 text를 없애고 출력해봐도 계속 none 이 나왔다.

해결 - title ellipsis가 아니라 title.ellipsis였다. 마우스를 가져다 대보면

이렇게 나오는 것을 확인할 수 있다.

title = li.select_one('.title.ellipsis').text
    rank = li.select_one('.number').text
    artist = li.select_one('.artist.ellipsis').text

로 바꿔보니 제목이 제대로 나오는 것을 확인할 수 있었다.

  • rank를 출력할 때 몇 위가 상승/하강했는지 같이 출력될 때
<td class="number">
	2
    <span class="rank">
    	<span class="rank">
        	<span class="rank-down">
            1
            <span class="hide">하강</span>
            	</span>
            </span>
        </span>
</td>

.text 에 '2 1 하강' 이 모두 포함되기 때문이다. text[0:2] 로 앞 두글자만 잘라서 출력해주자.

  • 여백이 너무 많이 나올 때
title = li.select_one('.title.ellipsis').text.strip()

strip() 을 이용해서 여백을 잘라준다.

문자열 공백 없애기에 대한 자세한 정보는
https://seong6496.tistory.com/360

코드 전문

import requests
from bs4 import BeautifulSoup
from pymongo import MongoClient
import certifi

ca = certifi.where()

client = MongoClient('mongodb+srv://sparta:test@cluster0.eh7wfh6.mongodb.net/?retryWrites=true&w=majority', tlsCAFile=ca)
db = client.dbsparta

URL = "https://www.genie.co.kr/chart/top200?ditc=M&rtm=N&ymd=20230101"
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')

##li 태그의 2번째 라는 의미
lis = soup.select('#body-content > div.newest-list > div > table > tbody > tr')
for li in lis : 
    title = li.select_one('.title.ellipsis').text.strip()
    rank = li.select_one('.number').text[0:2]
    artist = li.select_one('.artist.ellipsis').text
    

    print(rank,title,artist)
    

결과

profile
공부 중

0개의 댓글