본문 바로가기

Education

[웹개발종합반] week3: Python / 크롤링 / mongoDB

반응형

3주차 수업 목표

  • 파이썬 기초 문법 알기
  • 원하는 페이지 크롤링 하기
  • pymongo를 통해 mongoDB를 제어하기

 

수업 자료

 

[스파르타코딩클럽] 웹개발 종합반 - 3주차

매 주차 강의자료 시작에 PDF파일을 올려두었어요!

www.notion.so


 

 

1. 3주차 설치

1.1 파이썬

  • 첫 화면에서 Add Python 3.8 to PATH 체크버튼을 꼭 체크해야만 한다.
  • 이후로 Next 버튼만 누르면 설치 완료.

 

1.2 mongoDB

mongoDB 설치하기

C:에 data 폴더 생성, 그 안에 db 폴더를 생성한 후 mongoDB를 다운로드 한다.

 

MongoDB Community Download

Download the Community version of MongoDB's non-relational database server from MongoDB's download center.

www.mongodb.com

mongoDB 다운로드

Next

체크 후 Next

Custom 클릭한다.

Browser를 눌러 이전에 생성한 폴더 C:/data/db를 선택한다.

Next

Install MongoDB Compass 체크박스를 해제하고 Next한다.

Install

 Finish를 눌러 설치를 완료한다.

 

시스템 환경 변수 

시스템 환경 변수 편집에 들어간다.

시스템 속성 - 환경 변수(N) - 시스템 변수(S) - 변수: PATH 찾아 더블클릭

새로 만들기 버튼을 누르고 mongoDB를 설치한 경로를 작성한다. 이후 확인 > 확인.

 

mongoDB 실행하기

cmd - $ mongod 명령어 입력 후 localhost:27017 을 접속해보자.

위와 같이 뜬다면 mongoDB가 잘 실행되고 있다는 의미이다.

 

1.3 Robo3T

Robomongo 공식 홈페이지에 접속하여 오른쪽에 위치한 다운로드 버튼을 누른다.

 

정보를 입력하고 Download for Windows 버튼을 누른다.

상단의 .exe 파일을 누르고 설치한 후 실행하여 설치한다.

모두 다음 버튼 누르면 된다.

 

1.4 Git bash

https://git-scm.com/에서 git bash를 다운로드 받는다. 다운로드 창이 뜨면 모두 Next 버튼을 누른다.

 

 

2. 3주차 오늘 배울 것

파이썬으로 웹에 있는 데이터를 긁어올 수 있는 크롤링을 하고, mongoDB로 그것을 저장할 것이다.

- week4-5에서 서버를 만들 것인데, 파이썬 언어를 통해서 컴퓨터에게 서버를 해달라고 명령을 내릴 것이다. 그러면 자연스럽게 이 데이터들을 담을 데이터베이스가 필요한데 이 데이터베이스로 mongoDB를 사용할 것이다.

 

 

3. 복습: 나홀로 메모장에 OpenAPI 붙여보기

나홀로 메모장 API

- 코드

    $(document).ready(() => {
      $('#cards-box').empty();
      listing();
    });

    function listing() {
      $.ajax({
        type: "GET",
        url: "http://spartacodingclub.shop/post",
        data: {},
        success: function(res) {
          const rows = res['articles'];
          for (let i = 0; i < rows.length; i++) {
            const comment = rows[i]['comment'];
            const desc = rows[i]['desc'];
            const img = rows[i]['image'];
            const title = rows[i]['title'];
            const url = rows[i]['url'];

            let temp_html = `<div class="card">
                              <img class="card-img-top" src="${img}" alt="Card image cap">
                              <div class="card-body">
                                <a href="${url}">${title}</a>
                                <p class="card-text">${desc}</p>
                                <p class="card-comment">${comment}</p>
                              </div>
                            </div>`;
            $('#cards-box').append(temp_html);
          }
        }
      })
    }

- 결과

 

 

4. Python

4.1 파이썬 시작하기

파이썬을 설치한다?

일종의 번역팩을 설치한다고 생각하면 된다. 컴퓨터는 1001001와 같은 언어만 이해할 수 있다고 했다. 파이썬 문법으로 된 것을 컴퓨터 언어로 변환해주는 번역 패키지를 설치하는 것이라 생각해도 무방하다.

 

파이썬 시작하기

File - New Project - Pure Python

 

Python 101 - 1장. 파이썬 설치 및 개발환경 설정(PyCharm)

잘 모를 때 참고하고 복습할 겸 파이썬 설치와 환경 설정 부터 파이썬 언어 기본 문법 부분을 다루는 포스팅을 작성해보려고 한다. 이번 포스팅은 첫 포스팅이니까 가볍게 파이썬 3.7.4 설치와 Je

velog.io

완료되면 다음과 같이 뜬다.

  • venv: 앞으로 절!대! 건드리면 안되는 파일이다. 이동이나 이름변경이나 절대 금지. 설명은 후에 하겠다.

이제 hello.py 파이썬 파일을 생성하고 그 안에 다음과 같이 입력하자.

print('hello sparta')

그리고 오른쪽 마우스를 눌러 Run 'hello'를 누르자. 단축키 Ctrl+Shift+F10를 눌러 실행할 수도 있다.

실행결과

실행을 할 때에는 무조건 위의 두 방법으로 실행시키자. 화면의 재생 버튼을 누르게 되면 현재 파일이 아닌 다른 파일을 실행시키게 될 수도 있다. 위의 방법으로 실행하도록 습관들이자.

 

4.2 파이썬 기초공부

정수, 문자열

a = 2
b = 3

print(a+b) # 5
first_name = 'daeun'
last_name = 'heo'

print(first_name + last_name) # daeunheo
first_name = 'daeun'
num = 2

print(first_name = num) # error
#Traceback (most recent call last):
#  File "C:/Users/nno3o/Desktop/pythonProject/hello.py", line 5, in <module>
#    print(first_name + num)
#TypeError: can only concatenate str (not "int") to str

에러 보는 방법

마지막 줄을 유심히 살펴본다. 마지막 줄을 복사하고 구글링하면 무슨 문제인지 쉽게 알 수 있다.

해당 에러는 문자와 숫자를 더하면서 발생한 에러이다.

first_name = 'daeun'
num = str(2) # ='2'

print(first_name + num) # daeun2

 

리스트

a_list = ['사과', '배', '감']

▪ .append: 리스트 추가하는 메소드

a_list = ['사과', '배', '감']
a_list.append('수박')

print(a_list) # ['사과', '배', '감', '수박']

 

딕셔너리

a_dict = {'name':'bob', 'age':27}

print(a_dict['age']) # 27

딕셔너리 추가하기

a_dict = {'name':'bob', 'age':27}

a_dict['height'] = 178

print(a_dict) # {'name': 'bob', 'age': 27, 'height': 178}

 

함수

함수는 정해진 동작을 수행하는 것이다.

두 개의 합을 반환하는 함수

def sum(num1, num2):
    return num1 + num2

result = sum(2,3)

print(result) # 5

파이썬은 함수에 중괄호가 없다. 대신 탭을 사용한 줄맞춤으로 함수를 표현한다.

 

조건문

age = 25

if age > 20:
    print('성인입니다')
else:
    print('청소년입니다')
# 성인입니다
def is_adult(age):
    if age > 20:
        print('성인입니다')
    else:
        print('청소년입니다')


is_adult(25) # 성인입니다
is_adult(15) # 청소년입니다

 

반복문

반복문은 주로 리스트와 함께 사용된다. 

반복문과 리스트

fruits = ['사과','배','배','감','수박','귤','딸기','사과','배','수박']

for fruit in fruits:
    print(fruit)

#사과
#배
#배
#감
#수박
#귤
#딸기
#사과
#배
#수박

▪ 수박의 개수 세기

fruits = ['사과','배','배','감','수박','귤','딸기','사과','배','수박']

count = 0
for fruit in fruits:
    if fruit == '수박':
        count += 1

print(count) # 2

 

▪ 반복문과 리스트 내 딕셔너리

people = [{'name': 'bob', 'age': 20},
          {'name': 'carry', 'age': 38},
          {'name': 'john', 'age': 7},
          {'name': 'smith', 'age': 17},
          {'name': 'ben', 'age': 27}]

for person in people:
    print(person)

#{'name': 'bob', 'age': 20}
#{'name': 'carry', 'age': 38}
#{'name': 'john', 'age': 7}
#{'name': 'smith', 'age': 17}
#{'name': 'ben', 'age': 27}
people = [{'name': 'bob', 'age': 20},
          {'name': 'carry', 'age': 38},
          {'name': 'john', 'age': 7},
          {'name': 'smith', 'age': 17},
          {'name': 'ben', 'age': 27}]

for person in people:
    print(person['name'], person['age'])
    
    
#bob 20
#carry 38
#john 7
#smith 17
#ben 27
people = [{'name': 'bob', 'age': 20},
          {'name': 'carry', 'age': 38},
          {'name': 'john', 'age': 7},
          {'name': 'smith', 'age': 17},
          {'name': 'ben', 'age': 27}]

for person in people:
    if person['age'] < 20:
        print(person)
        
#{'name': 'john', 'age': 7}
#{'name': 'smith', 'age': 17}

 

4.3 파이썬 패키지 설치하기

파이썬에는 사람들이 만들어놓은 라이브러리(패키지)가 엄청 많다!

 

라이브러리(패키지)

Python에서 패키지는 모듈(일종의 기능들 묶음)을 모아 놓은 단위이다. 이런 패키지의 묶음을 라이브러리라고 볼 수 있다. 여기에서는 외부 라이브러리를 사용하기 위해서 패키지를 설치한다.

즉, 여기서는 패키지 설치 = 외부 라이브러리 설치

 

가상 환경(virtual environment)이란?

▪  problem:

회사에서는 패키지 A,B,C를 설치해서 쓰고, 개인 프로젝트에서는 패키지 B,C,D,E를 설치하여 사용하고 있다.

그런데 회사 팀장님이 B를 이전 버전인 B'로 쓰자고 한다. 그렇게 되면 같은 컴퓨터에 깔려 있는 개인 프로젝트에서는 B'로 쓰면 코드를 모두 바꿔야 한다. 어떻게 하면 좋을까?

resolve:

다 담아둘 필요 없이 공구함을 2개 만들어서, 공구함1에 A,B',C를 담고, 공구함2에는 B,C,D,E를 담아두고 쓰면 관리하기가 편할 것이다.

그래서 가상환경이라는 개념이 등장했다. 즉, 프로젝트별 공구함. 라이브러리를 담아두는 폴더라고 생각하면 된다.

 

정리하자면, 가상환경(virtual environment, venv)은 같은 시스템에서 실행되는 다른 파이썬 응용 프로그램들의 동작에 영향을 주지 않기 위해, 파이썬 배포 패키지를 설치하거나 업그레이드하는 것을 가능케 하는 격리된 실행 환경이다.

➡ 파이썬 프로젝트를 생성했을 때 자동으로 생성된 venv폴더는 는 앞으로 설치할 라이브러리들이 담기게 된다.

 

File - Setting - Project: pythonProject - Python Interpreter - +버튼 누르기 - requests 검색 - Install Package - OK

➡ requests 패키지를 설치했으니 앞으로 request 패키지를 사용할 수 있다. 4.4에서 사용해보자.

 

 

4.4 패키지 사용해보기

requests 패키지

이제 request 패키지를 사용해보자. requests는 어떻게 사용해야 하는지는 'python requests 패키지'와 같이 구글링해보자. 

자바스크립트에서 ajax의 역할과 동일하다. 하지만 자바스크립트에서 몇 줄을 작성해야 했던 것과는 달리, 파이썬에서는 requests 패키지를 사용하면 단 한 줄로 JSON 데이터를 훨씬 간단하게 가져올 수 있다.

import requests # requests 라이브러리 설치 필요

r = requests.get('http://openapi.seoul.go.kr:8088/6d4d776b466c656533356a4b4b5872/json/RealtimeCityAir/1/99')
rjson = r.json()

print(rjson)

# {'RealtimeCityAir': {'list_total_count': 25, 'RESULT': {'CODE': 'INFO-000', 'MESSAGE': '정상 처리되었습니다'}, 'row': [{'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '중구', 'PM10': 40.0, 'PM25': 23.0, 'O3': 0.062, 'NO2': 0.017, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 77.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '종로구', 'PM10': 34.0, 'PM25': 23.0, 'O3': 0.071, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.004, 'IDEX_NM': '보통', 'IDEX_MVL': 85.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '용산구', 'PM10': 36.0, 'PM25': 22.0, 'O3': 0.054, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 71.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '은평구', 'PM10': 51.0, 'PM25': 30.0, 'O3': 0.074, 'NO2': 0.014, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 87.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '서대문구', 'PM10': 50.0, 'PM25': 27.0, 'O3': 0.067, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '마포구', 'PM10': 37.0, 'PM25': 20.0, 'O3': 0.069, 'NO2': 0.016, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '광진구', 'PM10': 33.0, 'PM25': 17.0, 'O3': 0.061, 'NO2': 0.011, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '성동구', 'PM10': 36.0, 'PM25': 24.0, 'O3': 0.069, 'NO2': 0.016, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '중랑구', 'PM10': 37.0, 'PM25': 22.0, 'O3': 0.06, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '동대문구', 'PM10': 38.0, 'PM25': 21.0, 'O3': 0.066, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '성북구', 'PM10': 47.0, 'PM25': 24.0, 'O3': 0.066, 'NO2': 0.019, 'CO': 0.6, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '도봉구', 'PM10': 45.0, 'PM25': 24.0, 'O3': 0.06, 'NO2': 0.014, 'CO': 0.3, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '강북구', 'PM10': 37.0, 'PM25': 24.0, 'O3': 0.065, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '노원구', 'PM10': 32.0, 'PM25': 22.0, 'O3': 0.06, 'NO2': 0.015, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '강서구', 'PM10': 38.0, 'PM25': 27.0, 'O3': 0.07, 'NO2': 0.023, 'CO': 0.3, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '구로구', 'PM10': 40.0, 'PM25': 20.0, 'O3': 0.07, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '영등포구', 'PM10': 42.0, 'PM25': 27.0, 'O3': 0.064, 'NO2': 0.015, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 79.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '동작구', 'PM10': 40.0, 'PM25': 21.0, 'O3': 0.066, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '관악구', 'PM10': 39.0, 'PM25': 21.0, 'O3': 0.062, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 77.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '금천구', 'PM10': 39.0, 'PM25': 27.0, 'O3': 0.068, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 82.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '양천구', 'PM10': 40.0, 'PM25': 23.0, 'O3': 0.07, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '강남구', 'PM10': 38.0, 'PM25': 21.0, 'O3': 0.065, 'NO2': 0.018, 'CO': 0.5, 'SO2': 0.004, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '서초구', 'PM10': 45.0, 'PM25': 23.0, 'O3': 0.064, 'NO2': 0.017, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 79.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '송파구', 'PM10': 34.0, 'PM25': 19.0, 'O3': 0.069, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}, {'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '강동구', 'PM10': 35.0, 'PM25': 21.0, 'O3': 0.065, 'NO2': 0.01, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}]}}
  • requests.get(url주소): JSON 데이터를 가져와서 반환하는 메소드. 

 

▪ 중구 데이터 가져오기

import requests # requests 라이브러리 설치 필요

r = requests.get('http://openapi.seoul.go.kr:8088/6d4d776b466c656533356a4b4b5872/json/RealtimeCityAir/1/99')
rjson = r.json()

print(rjson['RealtimeCityAir']['row'][0]['MSRSTE_NM']) # 중구

▪ 반복문으로 데이터 가져오기

import requests # requests 라이브러리 설치 필요

r = requests.get('http://openapi.seoul.go.kr:8088/6d4d776b466c656533356a4b4b5872/json/RealtimeCityAir/1/99')
rjson = r.json()

gus = rjson['RealtimeCityAir']['row']
for gu in gus:
    print(gu)
#{'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '중구', 'PM10': 40.0, 'PM25': 23.0, 'O3': 0.062, 'NO2': 0.017, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 77.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '종로구', 'PM10': 34.0, 'PM25': 23.0, 'O3': 0.071, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.004, 'IDEX_NM': '보통', 'IDEX_MVL': 85.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '도심권', 'MSRSTE_NM': '용산구', 'PM10': 36.0, 'PM25': 22.0, 'O3': 0.054, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 71.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '은평구', 'PM10': 51.0, 'PM25': 30.0, 'O3': 0.074, 'NO2': 0.014, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 87.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '서대문구', 'PM10': 50.0, 'PM25': 27.0, 'O3': 0.067, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서북권', 'MSRSTE_NM': '마포구', 'PM10': 37.0, 'PM25': 20.0, 'O3': 0.069, 'NO2': 0.016, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '광진구', 'PM10': 33.0, 'PM25': 17.0, 'O3': 0.061, 'NO2': 0.011, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '성동구', 'PM10': 36.0, 'PM25': 24.0, 'O3': 0.069, 'NO2': 0.016, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '중랑구', 'PM10': 37.0, 'PM25': 22.0, 'O3': 0.06, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '동대문구', 'PM10': 38.0, 'PM25': 21.0, 'O3': 0.066, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '성북구', 'PM10': 47.0, 'PM25': 24.0, 'O3': 0.066, 'NO2': 0.019, 'CO': 0.6, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '도봉구', 'PM10': 45.0, 'PM25': 24.0, 'O3': 0.06, 'NO2': 0.014, 'CO': 0.3, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '강북구', 'PM10': 37.0, 'PM25': 24.0, 'O3': 0.065, 'NO2': 0.015, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동북권', 'MSRSTE_NM': '노원구', 'PM10': 32.0, 'PM25': 22.0, 'O3': 0.06, 'NO2': 0.015, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 76.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '강서구', 'PM10': 38.0, 'PM25': 27.0, 'O3': 0.07, 'NO2': 0.023, 'CO': 0.3, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '구로구', 'PM10': 40.0, 'PM25': 20.0, 'O3': 0.07, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '영등포구', 'PM10': 42.0, 'PM25': 27.0, 'O3': 0.064, 'NO2': 0.015, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 79.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '동작구', 'PM10': 40.0, 'PM25': 21.0, 'O3': 0.066, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 81.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '관악구', 'PM10': 39.0, 'PM25': 21.0, 'O3': 0.062, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 77.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '금천구', 'PM10': 39.0, 'PM25': 27.0, 'O3': 0.068, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 82.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '서남권', 'MSRSTE_NM': '양천구', 'PM10': 40.0, 'PM25': 23.0, 'O3': 0.07, 'NO2': 0.011, 'CO': 0.4, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 84.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '강남구', 'PM10': 38.0, 'PM25': 21.0, 'O3': 0.065, 'NO2': 0.018, 'CO': 0.5, 'SO2': 0.004, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '서초구', 'PM10': 45.0, 'PM25': 23.0, 'O3': 0.064, 'NO2': 0.017, 'CO': 0.5, 'SO2': 0.003, 'IDEX_NM': '보통', 'IDEX_MVL': 79.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '송파구', 'PM10': 34.0, 'PM25': 19.0, 'O3': 0.069, 'NO2': 0.013, 'CO': 0.5, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 83.0, 'ARPLT_MAIN': 'O3'}
#{'MSRDT': '202106191200', 'MSRRGN_NM': '동남권', 'MSRSTE_NM': '강동구', 'PM10': 35.0, 'PM25': 21.0, 'O3': 0.065, 'NO2': 0.01, 'CO': 0.4, 'SO2': 0.002, 'IDEX_NM': '보통', 'IDEX_MVL': 80.0, 'ARPLT_MAIN': 'O3'}

▪ 미세 먼지 수치가 80 이상인 구만 나타내기

import requests # requests 라이브러리 설치 필요

r = requests.get('http://openapi.seoul.go.kr:8088/6d4d776b466c656533356a4b4b5872/json/RealtimeCityAir/1/99')
rjson = r.json()

gus = rjson['RealtimeCityAir']['row']
for gu in gus:
    gu_name = gu['MSRSTE_NM']
    gu_mise = gu['IDEX_MVL']
    if gu_mise > 80:
        print(gu_name, gu_mise)

# 종로구 85.0
# 은평구 87.0
# 서대문구 81.0
# 마포구 83.0
# 성동구 83.0
# 동대문구 81.0
# 성북구 81.0
# 강서구 84.0
# 구로구 84.0
# 동작구 81.0
# 금천구 82.0
# 양천구 84.0
# 송파구 83.0

 

 

5. 웹스크래핑(크롤링) & DB

5.1 웹스크래핑(크롤링)

웹스크래핑(크롤링)이란?

네이버 영화 랭킹 페이지를 보자.

 

랭킹 : 네이버 영화

영화, 영화인, 예매, 박스오피스 랭킹 정보 제공

movie.naver.com

네이버 영화 랭킹 페이지의 영화 리스트에 있는 영화명, 평점, 순위, 변동폭과 같이 다른 페이지에 있는 모든 데이터를 가져오는 것을 웹스크래핑 혹은 크롤링이라고 한다.

 

beautifulsoup4 (bs4)

스크래핑하려면 beautifulsoup4 라는 패키지를 설치해야만 한다.

File - Setting - Project: pythonProject - Python Interpreter - +버튼 - bs4검색 - Install Package

 

크롤링 기본 세팅

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.nhn?sel=pnt&date=20200303',headers=headers)

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

# 코딩 시작
print(soup) # 결과: 해당 페이지의 모든 HTML 코드를 출력한다
  • headers: 코드를 요청을 막아둔 사이트들이 많다. 그래서 브라우저를 접속하여 요청한 것과 같이 하기 위해서 headers를 작성한다.
  • requests.get(주소, headers): 데이터를 받아온다.
  • BeautifulSoup(data.text, 'html.parser'): 원하는 데이터만을 솎아낸다. 현재 'html.parser'라고 작성하였으므로 HTML 데이터만을 가져온다.

 

크롤링이 가능한 이유는?

이미 받아온 모든 데이터에서 필요한 데이터만을 솎아내는 것이 바로 크롤링이다.

  1. 브라우저를 키지 않고 데이터를 요청하기 ➡ requests
  2. 요청하여 가져온 데이터인 HTML 중에서 내가 필요한 데이터만 잘~ 솎아내기 ➡ bs4

 

Beautifulsoup.select_one

▪ 영화명 하나만 가져오기

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.nhn?sel=pnt&date=20200303',headers=headers)

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

title = soup.select_one('')

여기까지 작성해놓고 네이버 영화 페이지에 들어가서 셀렉터를 카피한다.

Copy > Copy selector

그리고 select_one() 메소드 내에 붙여넣기 한다. title을 출력해보자.

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.nhn?sel=pnt&date=20200303',headers=headers)

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

title = soup.select_one('#old_content > table > tbody > tr:nth-child(2) > td.title > div > a')


print(title) # <a href="/movie/bi/mi/basic.nhn?code=171539" title="그린 북">그린 북</a>
print(title.text) # 그린북
print(title['href']) # /movie/bi/mi/basic.nhn?code=171539
  • selector: 현재 태그가 어디에 위치해 있는지 알 수 있다.
  • .text: 태그 내의 텍스트만 가져오기(=영화명)
  • ['href']: 태그 속성 가져오기(꺾쇠)

 

BeautifulSoup.select

▪ 모든 영화명 가져오기

모든 한 줄은 <tr>..</tr>로 묶여져 있다.

따라서 이 줄에서 오른쪽 마우스를 눌러 각 줄을 Copy selector 해본다.

#old_content > table > tbody > tr:nth-child(2)

#old_content > table > tbody > tr:nth-child(3)

#old_content > table > tbody > tr:nth-child(4)

...

살펴보면 old_content > table > tbody > tr까지 동일하다. 그래서 old_content > table > tbody > tr까지만 떼어내고 가져오면 모든 영화명 HTML을 가져올 수 있다.

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.nhn?sel=pnt&date=20200303',headers=headers)

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

trs = soup.select('#old_content > table > tbody > tr') # trs는 리스트 형태

for tr in trs:
    print(tr)

그리고 1순위 영화명(그린북)을 Copy selector하면 

#old_content > table > tbody > tr:nth-child(2) > td.title > div > a

이다. tr까지는 가져왔으니 이제 td.title > div > a 를 가져오면 된다.

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.nhn?sel=pnt&date=20200303',headers=headers)

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

trs = soup.select('#old_content > table > tbody > tr') # trs는 리스트 형태

for tr in trs:
    a_tag = tr.select_one('td.title > div > a')
    print(a_tag)

결과

None
<a href="/movie/bi/mi/basic.nhn?code=171539" title="그린 북">그린 북</a>
<a href="/movie/bi/mi/basic.nhn?code=174830" title="가버나움">가버나움</a>
<a href="/movie/bi/mi/basic.nhn?code=144906" title="베일리 어게인">베일리 어게인</a>
<a href="/movie/bi/mi/basic.nhn?code=179518" title="주전장">주전장</a>
<a href="/movie/bi/mi/basic.nhn?code=181710" title="포드 V 페라리">포드 V 페라리</a>
<a href="/movie/bi/mi/basic.nhn?code=169240" title="아일라">아일라</a>
<a href="/movie/bi/mi/basic.nhn?code=151196" title="원더">원더</a>
<a href="/movie/bi/mi/basic.nhn?code=157243" title="당갈">당갈</a>
<a href="/movie/bi/mi/basic.nhn?code=17421" title="쇼생크 탈출">쇼생크 탈출</a>
<a href="/movie/bi/mi/basic.nhn?code=10200" title="터미네이터 2:오리지널">터미네이터 2:오리지널</a>
None
<a href="/movie/bi/mi/basic.nhn?code=156464" title="보헤미안 랩소디">보헤미안 랩소디</a>
<a href="/movie/bi/mi/basic.nhn?code=154667" title="덕구">덕구</a>
<a href="/movie/bi/mi/basic.nhn?code=10016" title="나 홀로 집에">나 홀로 집에</a>
<a href="/movie/bi/mi/basic.nhn?code=69105" title="월-E">월-E</a>
<a href="/movie/bi/mi/basic.nhn?code=35901" title="살인의 추억">살인의 추억</a>
<a href="/movie/bi/mi/basic.nhn?code=10002" title="빽 투 더 퓨쳐">빽 투 더 퓨쳐</a>
<a href="/movie/bi/mi/basic.nhn?code=22126" title="인생은 아름다워">인생은 아름다워</a>
<a href="/movie/bi/mi/basic.nhn?code=24452" title="매트릭스">매트릭스</a>
<a href="/movie/bi/mi/basic.nhn?code=18988" title="라이언 일병 구하기">라이언 일병 구하기</a>
<a href="/movie/bi/mi/basic.nhn?code=10102" title="사운드 오브 뮤직">사운드 오브 뮤직</a>
None
<a href="/movie/bi/mi/basic.nhn?code=82432" title="헬프">헬프</a>
<a href="/movie/bi/mi/basic.nhn?code=17159" title="포레스트 검프">포레스트 검프</a>
<a href="/movie/bi/mi/basic.nhn?code=181700" title="안녕 베일리">안녕 베일리</a>
<a href="/movie/bi/mi/basic.nhn?code=29217" title="글래디에이터">글래디에이터</a>
<a href="/movie/bi/mi/basic.nhn?code=106360" title="위대한 쇼맨">위대한 쇼맨</a>
<a href="/movie/bi/mi/basic.nhn?code=32686" title="센과 치히로의 행방불명">센과 치히로의 행방불명</a>
<a href="/movie/bi/mi/basic.nhn?code=66463" title="토이 스토리 3">토이 스토리 3</a>
<a href="/movie/bi/mi/basic.nhn?code=136900" title="어벤져스: 엔드게임">어벤져스: 엔드게임</a>
<a href="/movie/bi/mi/basic.nhn?code=35939" title="클래식">클래식</a>
<a href="/movie/bi/mi/basic.nhn?code=163788" title="알라딘">알라딘</a>
None
<a href="/movie/bi/mi/basic.nhn?code=92125" title="헌터 킬러">헌터 킬러</a>
<a href="/movie/bi/mi/basic.nhn?code=10048" title="죽은 시인의 사회">죽은 시인의 사회</a>
<a href="/movie/bi/mi/basic.nhn?code=161850" title="아이 캔 스피크">아이 캔 스피크</a>
<a href="/movie/bi/mi/basic.nhn?code=17170" title="레옹">레옹</a>
<a href="/movie/bi/mi/basic.nhn?code=134899" title="동주">동주</a>
<a href="/movie/bi/mi/basic.nhn?code=31796" title="반지의 제왕: 왕의 귀환">반지의 제왕: 왕의 귀환</a>
<a href="/movie/bi/mi/basic.nhn?code=18847" title="타이타닉">타이타닉</a>
<a href="/movie/bi/mi/basic.nhn?code=31162" title="캐스트 어웨이">캐스트 어웨이</a>
<a href="/movie/bi/mi/basic.nhn?code=12482" title="여인의 향기">여인의 향기</a>
<a href="/movie/bi/mi/basic.nhn?code=34324" title="집으로...">집으로...</a>
None
<a href="/movie/bi/mi/basic.nhn?code=17541" title="굿바이 마이 프랜드">굿바이 마이 프랜드</a>
<a href="/movie/bi/mi/basic.nhn?code=18543" title="서유기 2 - 선리기연">서유기 2 - 선리기연</a>
<a href="/movie/bi/mi/basic.nhn?code=130850" title="주토피아">주토피아</a>
<a href="/movie/bi/mi/basic.nhn?code=189111" title="두 교황">두 교황</a>
<a href="/movie/bi/mi/basic.nhn?code=19079" title="굿 윌 헌팅">굿 윌 헌팅</a>
<a href="/movie/bi/mi/basic.nhn?code=37886" title="클레멘타인">클레멘타인</a>
<a href="/movie/bi/mi/basic.nhn?code=147092" title="히든 피겨스">히든 피겨스</a>
<a href="/movie/bi/mi/basic.nhn?code=73372" title="세 얼간이">세 얼간이</a>
<a href="/movie/bi/mi/basic.nhn?code=14450" title="쉰들러 리스트">쉰들러 리스트</a>
<a href="/movie/bi/mi/basic.nhn?code=76667" title="울지마 톤즈">울지마 톤즈</a>
None

Process finished with exit code 0

중간의 None은 뭘까? 영화 줄 사이에 있는 줄이다.

None이 아닐 때 print하도록 설정하자.

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.nhn?sel=pnt&date=20200303',headers=headers)

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

trs = soup.select('#old_content > table > tbody > tr') # trs는 리스트 형태

for tr in trs:
    a_tag = tr.select_one('td.title > div > a')
    if a_tag is not None:
        print(a_tag)

그리고 .text를 사용하여 영화명만 출력하도록 하자.

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.nhn?sel=pnt&date=20200303',headers=headers)

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

trs = soup.select('#old_content > table > tbody > tr') # trs는 리스트 형태

for tr in trs:
    a_tag = tr.select_one('td.title > div > a')
    if a_tag is not None:
        title = a_tag.text
        print(title)

결과

그린 북
가버나움
베일리 어게인
주전장
포드 V 페라리
아일라
원더
당갈
쇼생크 탈출
터미네이터 2:오리지널
보헤미안 랩소디
덕구
나 홀로 집에
월-E
살인의 추억
빽 투 더 퓨쳐
인생은 아름다워
매트릭스
라이언 일병 구하기
사운드 오브 뮤직
헬프
포레스트 검프
안녕 베일리
글래디에이터
위대한 쇼맨
센과 치히로의 행방불명
토이 스토리 3
어벤져스: 엔드게임
클래식
알라딘
헌터 킬러
죽은 시인의 사회
아이 캔 스피크
레옹
동주
반지의 제왕: 왕의 귀환
타이타닉
캐스트 어웨이
여인의 향기
집으로...
굿바이 마이 프랜드
서유기 2 - 선리기연
주토피아
두 교황
굿 윌 헌팅
클레멘타인
히든 피겨스
세 얼간이
쉰들러 리스트
울지마 톤즈

 

Quiz_웹스크래핑(크롤링) 연습

아래와 같이 순위, 제목, 별점 순으로 리스트를 띄우도록 코드를 작성해보자.

- 코드

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.nhn?sel=pnt&date=20200303',headers=headers)

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

trs = soup.select('#old_content > table > tbody > tr') # trs는 리스트 형태

for tr in trs:
    a_tag = tr.select_one('td.title > div > a')
    if a_tag is not None:
        rank = tr.select_one('td:nth-child(1) > img')['alt']
        title = a_tag.text
        star = tr.select_one('td.point').text
        print(rank, title, star)

- 결과

01 그린 북 9.60
02 가버나움 9.59
03 베일리 어게인 9.52
04 주전장 9.52
05 포드 V 페라리 9.51
06 아일라 9.49
07 원더 9.49
08 당갈 9.47
09 쇼생크 탈출 9.44
010 터미네이터 2:오리지널 9.43
11 보헤미안 랩소디 9.42
12 덕구 9.41
13 나 홀로 집에 9.41
14 월-E 9.41
15 살인의 추억 9.40
16 빽 투 더 퓨쳐 9.40
17 인생은 아름다워 9.39
18 매트릭스 9.39
19 라이언 일병 구하기 9.39
20 사운드 오브 뮤직 9.39
21 헬프 9.39
22 포레스트 검프 9.39
23 안녕 베일리 9.39
24 글래디에이터 9.39
25 위대한 쇼맨 9.38
26 센과 치히로의 행방불명 9.38
27 토이 스토리 3 9.38
28 어벤져스: 엔드게임 9.38
29 클래식 9.38
30 알라딘 9.38
31 헌터 킬러 9.37
32 죽은 시인의 사회 9.37
33 아이 캔 스피크 9.37
34 레옹 9.37
35 동주 9.37
36 반지의 제왕: 왕의 귀환 9.37
37 타이타닉 9.36
38 캐스트 어웨이 9.36
39 여인의 향기 9.36
40 집으로... 9.36
41 굿바이 마이 프랜드 9.35
42 서유기 2 - 선리기연 9.35
43 주토피아 9.35
44 두 교황 9.35
45 굿 윌 헌팅 9.35
46 클레멘타인 9.35
47 히든 피겨스 9.35
48 세 얼간이 9.35
49 쉰들러 리스트 9.34
50 울지마 톤즈 9.34

Process finished with exit code 0

이제 뽑아낸 데이터를 저장해주는 DB에 대해서 배워보자.

 

5.2 DB

DB 설치 확인

http://localhost:27017 을 접속하여

It looks like you are trying to access MongoDB over HTTP on the native driver port.

가 뜨는지 확인을 해보자. 이는 mongoDB가 잘 작동되고 있지만, 이 주소를 브라우저에 치면 안돼! 라는 의미이다.

 

robo 3T 준비하기

robo 3T를 실행하자.

I agee 체크박스를 선택하고 Next.

정보를 입력하고 Finish 버튼을 누른다. 그럼 아래와 같은 화면이 뜬다.

Create 버튼을 누르고,

Name 부분을 원하는 데이터 베이스 이름으로 변경하고 Save 버튼을 누르고, Connect 버튼을 누른다.

그럼 이렇게 된다. 여기까지가 바로 데이터 베이스에 접속하는 과정이다.

 

mongoDB? robo 3T?

mongoDB는 우리의 눈에 보이지 않게 켜지는 데이터 베이스이다. robo 3T는 이러한 mongoDB의 데이터를 우리 눈에 볼 수 있도록 시각화하는 것이다. mongoDB에 데이터를 추가하거나 변경한 후에 잘 변경되었는지 robo 3T를 통해 눈으로 직접 확인하면 된다.

즉, robo 3T는 mongoDB의 데이터를 직접 보기 위해 사용하는 프로그램이다.

 

5.3 DB 개괄

DB를 사용하는 이유는?

  1. 데이터를 잘 쌓기 위해서
  2. 데이터를 잘 가져다 쓰기 위해서

책장을 생각해보자. 책장을 쓰는 이유는 우리가 보관한 책을 나중에 빠르게 찾아 읽기 위해서다. 

데이터베이스도 마찬가지다. 나중에 데이터를 빠르게 가져다 쓰기 위해서다. 데이터 베이스 프로그램들을 만드는 많은 회사들의 차이점은 데이터를 쌓는 방법이다. '우리는 A방식으로 데이터를 쌓아서 더 잘 가져다 쓸 수 있어!' '우리는 B방식을 써!' 라고 주장하며 데이터를 쌓는 데이터 베이스 프로그램을 만든다.

 

DB의 두 가지 종류

데이터 베이스에는 크게 두 가지 종류가 있다. SQLNoSQL(Not only SQL)이다. 

SQL과 NoSQL

  • SQL은 우리가 사용하는 엑셀에 가깝다. 출석이라면 이름, 번호, 날짜, ... 칸을 미리 만들어두고 우리가 채워 나가는 방식이다.
  • 그래서 SQL은 행/열을 미리 정해놓아야 한다. 만약 10,000개의 회원 줄이 있다고 가정하자. 이 상태에서 앞으로 집주소 데이터를 추가하려면 컬럼을 추가할 것이다. 10,001번 째부터는 집주소 데이터가 있을 테지만, 이전의 10,000개의 집주소 데이터는 비어있는 상태가 된다. 이처럼 중간에 데이터를 바꾸기 어렵다. 그렇지만 이처럼 정형화된 데이터를 가져가는 형식은 최적화가 되어있다. 이미 정해진 틀이 있기 때문에 데이터가 일관적이고, 데이터를 분석하기 빠르다. 데이터가 모두 같기 때문이다.
  • ex. MS-SQL, My-SQL

 

  • NoSQL은 SQL처럼 칸을 정해두는 게 아니다. 한 줄 한 줄이 딕셔너리 형태로 들어간다. 어떤 줄에는 이메일이 있기도 하고, 어떤 줄에는 다른 정보가 더 있기도 하고, 없기도 한다.
  • NoSQL은 한 줄마다 가지는 데이터가 모두 다르다. 위와 동일하게 생각했을 때 NoSQL에서 집주소 데이터를 추가한다면, 10,000개의 집주소 데이터는 빈 공간으로 채워지지 않는다. 굉장히 유연하므로, 앞으로 바뀔 일이 많은 데이터를 사용하는 회사에서 많이 사용하는 방식이다.
  • ex. mongoDB

 

➡ 서버는 컴퓨터의 역할일 뿐이다. 마찬가지로 DB도 프로그램 중 하나일 뿐이다. 프로그램이자 역할일 뿐이다.

 

5.4 pymongo로 DB조작하기

이제 python으로 mongoDB를 조작해보자. 이때 pymongo를 사용한다.

 

pymongo 라이브러리의 역할

우리가 엑셀을 파이썬으로 조작하려면 파이썬으로 엑셀을 조작할 수 있는 이미 다른 사람이 만들어 놓은 라이브러리를 사용한다. 마찬가지로 파이썬으로 mongoDB를 조작하기 위해서 이미 다른 사람이 만들어 놓은 pymongo 라는 라이브러리를 사용하는 것이다.

 

pymongo 라이브러리 설치하기

앞에서 패키지 설치한 것과 마찬가지로, File - Setting - Project: pythonprac - Python interpreter - +버튼 - pymongo검색 - Install Package 를 통해 설치한다.

 

pymongo 기본 코드

pymongo도 크롤링과 마찬가지로 기본 코드가 있다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

# 코딩 시작

 

pymongo는 네 가지만 기억하면 된다. insert, find, update, delete.

  • insert: 데이터 넣기
  • find: 데이터 찾기
  • update: 존재하는 데이터 업데이트하기(수정)
  • delete: 데이터 삭제하기

하나씩 사용해보면서 살펴보자.

 

pymongo: insert

아래 코드를 입력하고 저장한다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

doc = {'name':'bobby','age':21}
db.users.insert_one(doc)
  • MongoClient('localhost', 27017): 현재 내 컴퓨터(localhost)의 27017번 포트에 접속할 것이다.
  • client.dbsparta: 'dbsparta'라는 db명으로 접속할 것이다.
  • doc: mongoDB는 딕셔너리가 쌓이는 형태라고 했다. 따라서 딕셔너리를 생성한다.
  • db.users.insert_one(doc): 생성한 딕셔너리 하나(=doc)를 'users'에 쌓는다.

그리고 robo 3T를 refresh(F5)하면 코드를 통해 추가한 dbsparta가 생성된다.

dbsparta - Collections - users 에서 다음 버튼을 눌러 보기를 변경하자. doc 딕셔너리에 입력한 데이터가 추가되어 있다.

 

딕셔너리를 추가해보자.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

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

데이터가 두 개가 되었다.

smith와 jane도 추가하자.

 

pymongo: find

list(db.Collections명.find({검색할 데이터}, {출력하지 않을 데이터}))

 

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

same_ages = list(db.users.find({'age':21},{'_id':False}))
print(same_ages) # [{'name': 'bobby', 'age': 21}, {'name': 'jane', 'age': 21}]
  • list(db.users.find({'age':21},{'_id':False})): db명이 'dbsparta'인 데이터 베이스의 document명이 'users'에서 column 중 'age'가 21을 찾고, 출력할 때 column의 '_id'는 나타내지 말아라.

➡ ,{'_id':False}를 작성하지 않으면 '_id'를 포함한 모든 데이터가 출력된다.

 

▪ 모든 데이터를 줄마다 출력하기: _id는 제외.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

same_ages = list(db.users.find({},{'_id':False}))

for person in same_ages:
    print(person)
    
    
#{'name': 'bobby', 'age': 21}
#{'name': 'john', 'age': 27}
#{'name': 'smith', 'age': 18}
#{'name': 'jane', 'age': 21}

 

pymongo: find_one

하나만 검색할 때 사용한다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

user = db.users.find_one({'name':'bobby'})

print(user) # {'_id': ObjectId('60cd9d6869d61f664f21971e'), 'name': 'bobby', 'age': 21}

마찬가지로 '_id'는 가져오고 싶지 않다면 다음과 같이 작성한다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

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

print(user) # {'name': 'bobby', 'age': 21}

 

pymongo: update_one

list(db.Collections명.update_one({검색할 데이터}, {'$set': {변경할 데이터})

update는 조금 복잡하기 때문에 외워서 사용하기 보다는 주로 복사해서 붙여넣어 사용된다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

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

변경된 데이터 확인하기

 

pymongo: update_many

검색된 모든 값들을 $set: ~~~ 에 입력한 값으로 모두 변경하기

  • 위험하기 때문에 잘 사용되진 않는다.
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

db.users.update_many({'name':'bobby'},{'$set':{'age':19}})

현재는 'name'이 'bobby'인 값은 하나 뿐이므로 하나만 변경된다.

 

pymongo: delete_one

update_many와 마찬가지로 delete_one 삭제하기도 거의 사용되지 않는다. delete 또한 delete_many가 있다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

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

'name'이 'bobby'가 사라진 db 모습

 

 

pymongo 코드 요약

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

# 저장 - 예시
doc = {'name':'bobby','age':21}
db.users.insert_one(doc)

# 한 개 찾기 - 예시
user = db.users.find_one({'name':'bobby'})

# 여러개 찾기 - 예시 ( _id 값은 제외하고 출력)
same_ages = list(db.users.find({'age':21},{'_id':False}))

# 바꾸기 - 예시
db.users.update_one({'name':'bobby'},{'$set':{'age':19}})

# 지우기 - 예시
db.users.delete_one({'name':'bobby'})

 

5.5 웹스크래핑 결과 저장하기

앞에서 웹스크래핑한 결과를 db에 넣어 저장해보자. 먼저 db를 사용하려면 다음 코드 세 줄을 추가해야 한다.

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

그리고 doc를 만들어 Collections명이 movies인 db에 데이터를 추가한다.

for tr in trs:
    a_tag = tr.select_one('td.title > div > a')
    if a_tag is not None:
        rank = tr.select_one('td:nth-child(1) > img')['alt']
        title = a_tag.text
        star = tr.select_one('td.point').text

        doc = {
            'rank': rank,
            'title': title,
            'star': star
        }
        db.movies.insert_one(doc)

코드를 실행한 후 db를 refresh하여 확인해보자.

 

Quiz_웹스크래핑 결과 이용하기

▪ 1. 영화제목 '매트릭스'의 평점 가져오기

terminal console 결과

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

matrix_star = db.movies.find_one({'title':'매트릭스'})['star']
print(matrix_star)

 

▪ 2. '매트릭스'의 평점과 같은 평점의 영화 제목들을 가져오기

terminal console 결과

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

matrix_star = db.movies.find_one({'title':'매트릭스'})['star']

movies = list(db.movies.find({'star':matrix_star},{'_id':False}))
for movie in movies:
    print(movie['title'])

 

▪ 3. 매트릭스 영화의 평점을 0으로 만들기

robo 3T 결과

from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbsparta

db.movies.update_one({'title':'매트릭스'},{'$set':{'star':9.39}})

 

❗ (+) 한 걸음 더

매트릭스를 제외한 'star'에는 문자열이 들어가 있다. 따라서 "9.39"이다. 하지만 매트릭스는 숫자 0이다. 이왕이면 통일하는 것이 좋으므로 문자열로 들어가도록 코드를 수정하자.

db.movies.update_one({'title':'매트릭스'},{'$set':{'star':'0'}})

문자열로 수정했을 때

 

 

6. 3주차 끝 & 숙제 설명

지니뮤직의 1~50위 곡을 스크래핑하기

지니뮤직 사이트

💡 Hint: strip() 메소드 사용하여 공백 없애기

- 코드

import requests
from bs4 import BeautifulSoup
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client.dbnno3onn

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://www.genie.co.kr/chart/top200?ditc=D&ymd=20200403&hh=23&rtm=N&pg=1',headers=headers)

soup = BeautifulSoup(data.text, 'html.parser')
trs = soup.select('#body-content > div.newest-list > div > table > tbody > tr')
for tr in trs:
    rank = tr.select_one('td.number').text.split()[0]
    title = tr.select_one('td.info > a.title.ellipsis').text.strip()
    name = tr.select_one('td.info > a.artist.ellipsis').text
    print(rank, title, name)

- 결과

1 아로하 조정석
2 시작 가호 (Gaho)
3 처음처럼 엠씨더맥스 (M.C the MAX)
4 이제 나만 믿어요 임영웅
5 아무노래 지코 (ZICO)
6 흔들리는 꽃들 속에서 네 샴푸향이 느껴진거야 장범준
7 뭔가 잘못됐어 권진아
8 WANNABE ITZY (있지)
9 돌덩이 하현우 (국카스텐)
10 어떻게 지내 (Prod. by VAN.C) 오반 (OVAN)
11 METEOR 창모 (CHANGMO)
12 화려하지 않은 고백 규현 (KYUHYUN)
13 그때 그 아인 김필
14 Blueming 아이유 (IU)
15 문득 노을
16 마음을 드려요 아이유 (IU)
17 ON 방탄소년단
18 늦은 밤 너의 집 앞 골목길에서 노을
19 좋은 사람 있으면 소개시켜줘 조이 (JOY)
20 뜸 WINNER
21 Psycho Red Velvet (레드벨벳)
22 FIESTA IZ*ONE (아이즈원)
23 반만 진민호
24 2002 Anne-Marie
25 HIP 마마무(Mamamoo)
26 오늘도 빛나는 너에게 (To You My Light) (Feat.이라온) 마크툽 (Maktub)
27 어떻게 이별까지 사랑하겠어, 널 사랑하는 거지 AKMU (악동뮤지션)
28 작은 것들을 위한 시 (Boy With Luv) (Feat. Halsey) 방탄소년단
29 Memories Maroon 5
30 Don't Start Now Dua Lipa
31 안녕 폴킴
32 다시 난, 여기 백예린 (Yerin Baek)
33 모든 날, 모든 순간 (Every day, Every Moment) 폴킴
34 Love poem 아이유 (IU)
35 바빠서 (Feat. 헤이즈) 개코
36 꽃 (flower) (Feat. 박재범 & 우원재 & 기리보이) 코드 쿤스트 (CODE KUNST)
37 Square (2017) 백예린 (Yerin Baek)
38 노래방에서 장범준
39 bad guy Billie Eilish
40 조금 취했어 (Prod. by 2soo) 임재현
41 잘 지내고 있는지 궁금해 V.O.S
42 시든 꽃에 물을 주듯 HYNN (박혜원)
43 새로고침 (Feat. 강민경 of 다비치) 박경
44 사랑이란 멜로는 없어 전상근
45 벚꽃 엔딩 버스커 버스커 (Busker Busker)
46 우리 왜 헤어져야 해 신예영
47 사랑의 인사 씨야 (SeeYa)
48 아무렇지 않게, 안녕 HYNN (박혜원)
49 Paris In The Rain Lauv
50 주저하는 연인들을 위해 잔나비

Process finished with exit code 0
반응형