그저 내가 되었고

항해99) 웹종 3주차 웹스크랩핑(크롤링) 본문

개발/항해99 9기

항해99) 웹종 3주차 웹스크랩핑(크롤링)

hyuunii 2022. 9. 5. 08:33

스파르타피디아에 OpenAPI 붙여보기 vs 스파르타피디아 POST 크롤링?!!?!?!?!

전자: 스파르타가 미리 만들어둔 OpenAPI를 갖고와서 보여주기'만' 하는 것
후자: 사용자가 아예 새로운 것 만들어서 붙이려고 네이버 영화에서 정보 긁어오는 것

 


* 크롤링

1. 두 개 설치 : 주소 찍고 그 웹페이지 html 가져오는 것(requests로 할 것) & 그 안에서 제목 등 필요한 정보 찾는 것(beautifulsoup)

2. 파이썬 파일 준비: 크롤링 기본 세팅 app.py에 붙여넣기

3. 크롤링 시작: 기본 세팅에서, url만 내가 긁을 페이지로 바꿔 가면서 사용하면 됨.

4. mongodb 저장하려면 인터프리터 dnspython, pymongo 설치 필요

 

* 크롤링을 하려면?(beautifulsoup 라이브러리 사용법)

1) 크롤링 기본 세팅:

주소창에 요청해서 html을 가져오는 것.  data = resquests.get('http://~로 주소를 가져옴!)

import requests
from bs4 import BeautifulSoup

# 타겟 URL을 읽어서 HTML를 받아오고,
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
# soup이라는 변수에 "파싱 용이해진 html"이 담긴 상태가 됨
# 이제 코딩을 통해 필요한 부분을 추출하면 된다.
soup = BeautifulSoup(data.text, 'html.parser')

#############################
# (입맛에 맞게 코딩)
#############################

 

2) html을 가져와서 제목을 찾는 법:

beautifulsoup 이용.

1)의 코드 보셈. 맨 윗부터 두 줄 import requests이랑 from bs4 import BeautifulSoup이게 requests를 쓸거고, beautifulsoup 쓰겠다고 하는 것.

그 밑 줄의 headers는 뭐? 우리가 코드로 콜을 날리는건데 마치 브라우저인양ㅋ 사람인양 하는 것.

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'}
soup = BeautifulSoup(data.text, 'html.parser')

다른 부분은 다 똑같고, 빨간 부분(url)만 바꿔서 사용하면 됨.

 

3) select/select_one 사용법:

✔영화 제목을 가져 올 것

✔태그 안의 텍스트를 찍고 싶을 땐 → 태그.text

✔태그 안의 속성을 찍고 싶을 땐 → 태그['속성']

import requests
from bs4 import BeautifulSoup

# URL을 읽어서 HTML를 받아오고,
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
soup = BeautifulSoup(data.text, 'html.parser')

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

# movies (tr들) 의 반복문을 돌리기
for movie in movies:
    # movie 안에 a 가 있으면,
    a_tag = movie.select_one('td.title > div > a')
    if a_tag is not None:
        # a의 text를 찍어본다.
        print (a_tag.text)

 

+) beautifulsoup 내 select에 미리 정의된 다른 방법

# 선택자를 사용하는 방법 (copy selector)
soup.select('태그명')
soup.select('.클래스명')
soup.select('#아이디명')

soup.select('상위태그명 > 하위태그명 > 하위태그명')
soup.select('상위태그명.클래스명 > 하위태그명.클래스명')

# 태그와 속성값으로 찾는 방법
soup.select('태그명[속성="값"]')

# 한 개만 가져오고 싶은 경우
soup.select_one('위와 동일')

* 웹스크래핑 해보기(네이버 영화에서 영화 제목)

: 파이썬 문법 아니고 뷰티풀슾 사용법!!!!!!

1) '영화 제목'을 찍어 보려면, 아래 그림처럼 제목(밥정) 위에서 우클릭>검사>블럭처리된부분에서 우클릭>카피>카피셀렉터 클릭

 

2) 파이참에서 맨 밑줄에 title = soup.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.naver?sel=pnt&date=20210829',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.naver?code=186114" title="밥정">밥정</a>

 

여기서 다시 print(title.text) 찍으면 밥정만 딸랑 나옴ㅎㅋ

만약 "/movie/bi/mi/basic.naver?code=186114" 이 부분을 찾고싶으면? print(title['href'])찍는다.

방금까지 select_one 즉 하나만 찍어봤고, 여러개를 select 할 수도 있어. how?

 

3) 밥정은 볼장 다 봤고~ 그린 북 위에서 검사해서 다시 카피 셀렉터ㄱ & 가버나움에서도 똑같이 카피셀렉터 ㄱ

4) 밥정 내용만 싹 지워주고, 복사한거 둘 다 갖다 붙여봐봐.

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')

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

그러면 위처럼 나오거덩. 보면

앞쪽에 #old_content > table > tbody > tr

이거랑

뒷쪽에 td.title > div > a

이거는 같고 중간에만 좀 다르네! 그러면

앞쪽에 같은 부분! 저기를 셀렉할꺼야. 이번엔 select_one(한 개) 아니고 select(전부)👇🏻👇🏻

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')

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

movies = soup.select('#old_content > table > tbody > tr')

이렇게 적어주고 print(movies) 찍어보면 영화 목록이 쭉~~~~~ 태그로 잘 들어오는 것 확인 가능!!!

근데 저기.. tr이 뭔지 궁금쓰? 그건 아까처럼 영화 제목에서 검사 클릭하면 자료들이 리스트 형태로 <tr>..</tr>이렇게 쭉~~~~적혀있는 것 확인할 수 있음. 즉, 저 위의 코드의 마지막줄은 select를 이용해서, tr들을 불러오기임~~!

 

5) 여기서 영화 제목만 찍어보는 법?

👉🏻for문 돌릴거고, h태그(걍 아무 의미 없음... a든 b든 맘대로 하ㅋ셈ㅋ) 잡아서 일단 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.naver?sel=pnt&date=20210829',headers=headers)

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

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

movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:
    h = movie.select_one('td.title > div > a')
    print(h)

이렇게 하면 결과가

None
<a href="/movie/bi/mi/basic.naver?code=186114" title="밥정">밥정</a>
<a href="/movie/bi/mi/basic.naver?code=171539" title="그린 북">그린 북</a>
........

위처럼 쭉쭉쭉 나오거덩. 이때 None이 뭐냐?! 얘들은 구ㅋ분ㅋ선ㅋ 그러니까 쟤들을 제외해줄거임. 어케? if문👇🏻👇🏻

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')

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

movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:
    h = movie.select_one('td.title > div > a')
    if h is not None:
        print(h)

이렇게 찍어보면 none 없이 쭉쭉 잘 나옴.

<a href="/movie/bi/mi/basic.naver?code=186114" title="밥정">밥정</a>
<a href="/movie/bi/mi/basic.naver?code=171539" title="그린 북">그린 북</a>
........

근데 위에 박스 보면 뭐 군놈들이 너무 많아... 제목만 보여줘. how? text 사용(파이썬 문법 ㄴㄴ 뷰티풀숲)

👉🏻 태그 안의 텍스트를 찍고 싶을 땐 → (태그).text -e.g. star = movie.select_one('td.point').text / print(a.text)
👉🏻 태그 안의 속성을 찍고 싶을 땐 → (태그)['속성'] -e.g. rank = movie.select_one('td:nth-child(1) > img')['alt']

 

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')

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

movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:
    h = movie.select_one('td.title > div > a')
    if h is not None:
        print(h.text)

이러면? 아래처럼 제목만 쭉쭉 잘 찍힘ㅋ

밥정
그린 북
.......

 


* 웹스크래핑 더 해보기 (순위, 제목, 별점)

1) '영화 제목'을 찍어본 두 줄.. 거기 밑에다가, 순위랑 별점 똑같이 카피셀렉터 후 붙여넣어보셈.

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')

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

#old_content > table > tbody > tr:nth-child(2) > td:nth-child(1) > img
#old_content > table > tbody > tr:nth-child(2) > td.point

movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:
    h = movie.select_one('td.title > div > a')
    if h is not None:
           print(h.text)

위처럼 나오는데, 공식으로 생각할 것 말해줄게.

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

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

여기 이거, 위에꺼가 순위, 밑에꺼가 별점에서 각각 긁어온건데,

굵게 처리된 tr:nth-child(2)부분 뒤에 td:nth-child(1) > img랑 td.point만 써먹을겨. 그래서

2) 이렇~~~~~게 써주면 됩니다~~~~~~~~~~~!(?)

👉🏻 태그 안의 텍스트를 찍고 싶을 땐 → (태그).text -e.g. star = movie.select_one('td.point').text / print(a.text)
👉🏻 태그 안의 속성을 찍고 싶을 땐 → (태그)['속성'] -e.g. rank = movie.select_one('td:nth-child(1) > img')['alt']
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')

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

#old_content > table > tbody > tr:nth-child(2) > td:nth-child(1) > img
#old_content > table > tbody > tr:nth-child(2) > td.point

movies = soup.select('#old_content > table > tbody > tr')

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

        print(rank, title, star)

저 rank = movie.select_one('td:nth-child(1) > img')['alt']에서 빌어먹을 ['alt']는 어디서 왔냐고?ㅋ

저거 없이 찍어보면 앎ㅋ

걍 rank = movie.select_one('td:nth-child(1) > img')까지만 찍고 print(rank)하면

걍 이렇게 줄줄이 나오거던.

(또는 아까 웹페이지에서 순위(숫자) 위에서 우클릭>검사> 보면 'alt'속성이 숫자 나타내고 있는 것 알 수 있기도 하고.)

저것들중에 내가 필요한건 딱!!!!! 01이라는 숫자 딱 저거잖아? 그러니까 ['alt']까지(속성까지) 같이 찍어주면 아래처럼 존나 깔끔-하게나옴(속-시-원-)

01 밥정 9.64
02 그린 북 9.59
03 가버나움 9.59
.......

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

: insert 연습하기 - 웹스크래핑(크롤링) 결과를 DB에 저장하기

 

네가 지난시간에 했던 순위, 제목, 별점 찍어준거 그걸 한번 파이몽고(?)DB에 저장해볼게.

네가 아까 다 했던거!!에다가 위의 꼭 적어줘야된다는 쟤만 추가한 것.

 

- 아래의 pymongo 기본 셋팅에서

import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient
client = MongoClient('mongodb+srv://test:sparta@cluster0.55vah.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.dbsparta

# URL을 읽어서 HTML를 받아오고,
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
soup = BeautifulSoup(data.text, 'html.parser')

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

# movies (tr들) 의 반복문을 돌리기
for movie in movies:
    # movie 안에 a 가 있으면,
    a_tag = movie.select_one('td.title > div > a')
    if a_tag is not None:
        rank = movie.select_one('td:nth-child(1) > img')['alt'] # img 태그의 alt 속성값을 가져오기
        title = a_tag.text                                      # a 태그 사이의 텍스트를 가져오기
        star = movie.select_one('td.point').text                # td 태그 사이의 텍스트를 가져오기
        print(rank,title,star)

 

- doc 만들어 하나씩 insert 해보자

import requests
from bs4 import BeautifulSoup

from pymongo import MongoClient
client = MongoClient('mongodb+srv://test:sparta@cluster0.55vah.mongodb.net/Cluster0?retryWrites=true&w=majority')
db = client.dbsparta

# URL을 읽어서 HTML를 받아오고,
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
soup = BeautifulSoup(data.text, 'html.parser')

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

# movies (tr들) 의 반복문을 돌리기
for movie in movies:
    # movie 안에 a 가 있으면,
    a_tag = movie.select_one('td.title > div > a')
    if a_tag is not None:
        rank = movie.select_one('td:nth-child(1) > img')['alt'] # img 태그의 alt 속성값을 가져오기
        title = a_tag.text                                      # a 태그 사이의 텍스트를 가져오기
        star = movie.select_one('td.point').text                # td 태그 사이의 텍스트를 가져오기
        doc = {
            'rank': rank,
            'title': title,
            'star': star
        }
        db.movies.insert_one(doc)

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

(1) 영화제목 '가버나움'의 평점을 가져오기

target_movie = db.movies.find_one({'title':'가버나움'})
print(target_movie['star'])

 

(2) '가버나움'의 평점과 같은 평점의 영화 제목들을 가져오기

target_movie = db.movies.find_one({'title':'가버나움'})
target_star = target_movie['star']

movies = list(db.movies.find({'star':target_star}))

for movie in movies:
    print(movie['title'])

 

(3) '가버나움' 영화의 평점을 0으로 만들기

db.movies.update_one({'title':'가버나움'},{'$set':{'star':'0'}})

 

(4) 지니뮤직의 1~50위 곡을 스크래핑(순위 / 곡 제목 / 가수)

: 지니뮤직 사이트 https://www.genie.co.kr/chart/top200?ditc=M&rtm=N&ymd=20210701 

✨힌트:
0) 출력 할 때는 print(rank, title, artist) 하면 됩니다!
1) 앞에서 두 글자만 끊기! text[0:2] 를 써보세요!
2) 순위와 곡제목이 깔끔하게 나오지 않을 거예요. 옆에 여백이 있다던가, 다른 글씨도 나온다던가.. 파이썬 내장 함수인 strip()을 잘 연구해보세요!

아래는 답안 코드.

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://www.genie.co.kr/chart/top200?ditc=M&rtm=N&ymd=20210701',headers=headers)

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

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

for tr in trs:
    title = tr.select_one('td.info > a.title.ellipsis').text.strip()
    rank = tr.select_one('td.number').text[0:2].strip()
    artist = tr.select_one('td.info > a.artist.ellipsis').text
    print(rank, title, artist)