Week5. 똑똑하게 데이터 수집하기
다양한 데이터 수집기를 만들기 위해
- 선택자를 업그레이드 & 속성값을 수집
- 이미지 데이터 수집하는 법
을 배울 것.
stage1. 순서를 활용해서 데이터 수집하기
네이버 영화 데이터 수집하기
네이버 영화, 현재 상영영화에서 현재 상영중인 영화 전체의
제목/평점/장르/감독/배우
를 수집
# 네이버 영화 데이터 수집
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
# raw데이터를 얻어온 후, html로 파싱해주자.
html = BeautifulSoup(raw.text, 'html.parser')
# 컨테이너 : div.lst_wrap li
movies = html.select("dl.lst_dsc")
# 영화제목 : dt.tit a
# 평점 : a span.num
# 장르 :
# 감독 :
# 배우 :
순서를 활용해 데이터 구분하기
이때 선택자를 찾는데에 있어서 문제가 있다.
dd태그 안에 장르, 감독, 배우에 대한 데이터가 모두 들어가 있다는 것이다.
상하 관계도 같아 버려서 다른 방법으로 데이터를 특정해야 한다.
- 장르 : dl.lst_dsc dl.info_txt1 dd a
- 감독 : dl.lst_dsc dl.info_txt1 dd a
- 배우 : dl.lst_dsc dl.info_txt1 dd a
여기서 데이터 수집을 하기 위한 두 가지 방법이 있다.
1. select( )함수를 이용하기
dd까지는 장르, 감독, 배우가 있고 a가 그 안의 세부 데이터이다.
# 네이버 영화 데이터 수집
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
# raw데이터를 얻어온 후, html로 파싱해주자.
html = BeautifulSoup(raw.text, 'html.parser')
# 컨테이너 : div.lst_wrap li
movies = html.select("dl.lst_dsc")
for m in movies :
# 영화제목 : dt.tit a
title = m.select_one("dt.tit a").text
# 평점 : a span.num
score = m.select_one("a span.num").text
# 장르 : dl.lst_dsc dl.info_txt1 dd a
# 감독 : dl.lst_dsc dl.info_txt1 dd a
# 배우 : dl.lst_dsc dl.info_txt1 dd a
# select 함수를 이용하는 방법
'''
select 함수는 데이터를 리스트 형태로 저장하므로, 리스트에 저장된 데이터를 인덱싱 방법을 활용하여, 특정 순서의 데이터를 수집한다.
'''
info = m.select("dl.info_txt1 dd") # 개요, 감독, 배우에 대한 데이터가 리스트 형식으로 저장될 것.
# 장르
genre = info[0].select("a") # 영화 하나가 하나 이상의 장르를 가질 수 있기 때문에 select_one이 아닌 리스트로 저장하는 select 함수를 쓴 .
# 감독
director = info[1].select("a")
# 배우
actor = info[2].select("a")
print(title)
print(score)
for g in genre :
print(g.text)
for d in director :
print(d.text)
for a in actor :
print(a.text)
print("="*50)
다음과 같이 잘 수집되다가 오류가 날 것이다. 이는 출연
부분이 비어 있는 영화가 있기 때문이다. 이럴땐 예외처리를 통해 따로 해결하도록 해야 한다.
2. 선택자를 사용하는 방법
페이지에서 장르에 관한 선택자를 선택하는데에 있어서dl.lst_dsc dl.info_txt1 dd:nth-of-type(1)
를 사용하면 장르부분만 선택됨을 알 수 있다.
이는 dl.lst_dsc dl.info_txt1이 가지는 1번째 dd가 선택됨을 의미한다.
두번째 dd는 감독에 대해, 세번째 dd는 배우에 대한 데이터를 가져온다.
이런 nth-of-type
을 사용하면 더 편하게 데이터를 가져올 수 있다.
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
# 영화제목 : dt.tit a
title = m.select_one("dt.tit a").text
# 평점 : a span.num
score = m.select_one("a span.num").text
# 선택자를 사용하는 방법
# 장르 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a
genre = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a")
# 감독 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a
director = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a")
# 배우 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a
actor = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a")
# 이때, select_one이 아니라 select인 것은 장르, 감독, 출연이 한명 이상일 수 있기 떄문.
print(title)
print(score)
for g in genre:
print(g.text)
for d in director:
print(d.text)
for a in actor:
print(a.text)
print("="*50)
이 방법은 첫번째 select함수를 사용한 것과 달리 특별히 에러가 발생하지 않는다. 왜냐하면 선택자를 사용하여 하면 해당하는 데이터가 없을 경우 빈 리스트가 들어가기에, 빈 리스트 자체가 에러를 발생시키지는 않기 때문이다.
여기서 주의할 것은 여지껏 리스트나 다른 프로그래밍에서는 숫자를 0부터 시작했지만
nth-of-type
의 경우 첫번째 데이터는 1부터 시작된다.
그리고nth-of-type
을 사용할땐 클래스나 id는 쓸 수 없다. dd에 같은 클래스가 있더라도 얘는 항상dd:
을 사용해야 한다.
stage2. 조건에 따라서 데이터 수집하기
앞에서 수집한 데이터에서평점이 8.5 이상인 영화, 액션 장르인 영화를 보고 싶다면?
웹에서 수집하는 수많은 데이터 중에서 특정 데이터만 수집하고 싶다면 어떻게 해야할까?
데이터수집이 완료된 후 이 두가지 조건에 대해 필터링하여 잘라낼 수도 있겠지만, 데이터를 수집하면서 이 조건에 부합하는 데이터만 수집할 수도 있다.
조건문(if) 이해하기
조건에 따라서 다르게 실행되는 코드를 만드려면 조건문if
에 대해 알아야 한다.
if 조건문(True or False):
실행문1
실행문2
else:
실행문3
실행문4
조건에 따라 코드를 실행한다.
조건이 참일 경우, 실행문 1, 2가 실행되고,
조건이 거짓일 경우, 실행문 3, 4가 실행된다.
이때 조건에는 숫자연산뿐 아니라 논리 연산도 가능하다.
pay = input("시급을 입력해주세요 : ")
pay = int(pay) # input으로 받으면 pay에는 문자열이 저장되기 때문.
if pay > 8350 :
print("적절한 시급입니다. :)")
else:
print("최저임금보다 적어요. :(")
numbers = [1, 3, 5, 7, 8, 9, 10]
for n in numbers:
if n > 5:
print(n)
# 만약 n이 5보다 크다면 그대로 출력해줘.
# 결과값은 7, 8, 9, 10이 나올 것.
이때, if문에서 else구문은 필요에 따라 선택적으로 쓸 수 있기에 생략할 수 있다.
조건식 이해하기(in / not in)
- A in LIST : A가 리스트/문자열 안에 있다.
- A not in LIST : A가 리스트/문자열 안에 없다.
문자열 안에 포함되어 있는 문자
또는리스트 안에 포함되어 있는 값
을 비교연산자로 확인할 수 있다.
articles = ["도앵이는 고모와 칼사움을 벌였다.", "심영서, 그새끼 만나지마라", "정년이는 편하게 돈길만 걷자."]
for a in artricles:
if "도앵이" in a :
print("도앵이 기사")
# 이때, if "도앵" in a :
# 로 할 경우, 뒤의 print구문은 실행되지 않는다.
# "도앵" != "도앵이" 이기 때문이다.
elif "심영서" in a:
print("심영서 기사")
elif "정년이" in a:
print(1)
else:
print("도앵이/심영서가 나오지 않는 기사")
articles = ["도앵이는 고모와 칼사움을 벌였다.", "심영서, 그새끼 만나지마라", "정년이는 편하게 돈길만 걷자."]
players = ["도앵이", "심영서", "정년이"]
if "도앵이" in players:
print(1)
조건에 따라 영화 데이터 수집하기
평점에 따라 원하는 영화 데이터만 수집하는 수집기를 만들어보자.
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
# 영화제목 : dt.tit a
title = m.select_one("dt.tit a").text
# 평점 : a span.num
score = m.select_one("a span.num").text
# 선택자를 사용하는 방법
# 장르 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a
genre = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a") # select_one이 아니라 select인 것은 장르, 감독, 출연이 한명 이상일 수 있기 떄문.
# 감독 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a
director = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a")
# 배우 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a
actor = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a")
if float(score) < 8.5:
continue # continue는 반복문이 계속해서 반복을 하다가 continue를 만났을 때, 해당하는 부분을 스킵하는 것.
# 즉, score가 8.5보다 작으면 밑의 구문들을 스킵하고 다시 위로 올라가 다음 단계의 반복문을 진행한다는 것.
print(title)
print(score)
for g in genre:
print(g.text)
for d in director:
print(d.text)
for a in actor:
print(a.text)
print("="*50)
- 평점이 8.5이상인 영화들에 대한 데이터만 수집되는 것을 확인할 수 있다.
추가로 장르에 따라 원하는 영화들에 대한 데이터를 수집해보자.
장르는 평점과 달리 select()함수를 사용하여 리스트로 선택되기에(평점의 경우 select_one함수를 사용하여 리스트로 선택되지 않는다.) 어떤 데이터 하나를 출력하기가 쉽지 않다.
다시 데이터를 수집하는 코드로 들어가보면 genre[0]에 액션, genre[1]에 SF식으로 데이터가 리스트 형식으로 저장되어 있다.
우리는 이것을 다 포함하는 span.link_txt
를 수집하면 액션, SF
라는 전체 데이터를 구할 수 있다.
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
# 영화제목 : dt.tit a
title = m.select_one("dt.tit a").text
# 평점 : a span.num
score = m.select_one("a span.num").text
# 선택자를 사용하는 방법
# 장르 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a
genre = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) a") # select_one이 아니라 select인 것은 장르, 감독, 출연이 한명 이상일 수 있기 떄문.
# 감독 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a
director = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(2) a")
# 배우 : dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a
actor = m.select("dl.lst_dsc dl.info_txt1 dd:nth-of-type(3) a")
# 평점이 8.5이상이면서 장르가 액션인 영화만을 고른다.
if float(score) < 8.5:
continue
genre_all = m.select_one("dl.lst_dsc dl.info_txt1 dd:nth-of-type(1) span.link_txt")
if "액션" not in genre_all.text: # genre_all은 태그데이터 안에서 선택했기에 .text로 바꿔주어야 한다.
continue
print(title)
print(score)
for g in genre:
print(g.text)
for d in director:
print(d.text)
for a in actor:
print(a.text)
print("="*50)
정확히는 8.5가 아니고 액션장르가 없는 영화 데이터를 수집하지 않은 것이다.
stage3. 링크타고 들어가서 데이터 수집하기
여태 우리는 한 페이지 안에서 수집할 수 있는 데이터만 수집했는데, url코드를 통해 페이지를 넘기면서 여러 데이터를 수집하기도 하였다.
데이터를 수집하다보면 영화페이지 처럼 영화페이지의 제목을 클릭하여 더 상세한 내용을 수집하고 싶을 수 있다. 이번에는 영화의 리스트에 대하여 영화 제목을 클릭한 상세페이지로 들어가 평점과 리뷰를 수집하는 실습
을 해보자.
속성 값을 통해 링크 안 데이터 수집하기
각 영화마다 제목을 클릭했을때 url이 어떻게 변하는지 살펴보자.https://movie.naver.com/
을 공통으로 뒤에가 바뀌는 것을 볼 수 있다.
개발자 검사모드에서 제목부분에 커서를 대면<a href = "/movie/bi/mi/basic.nhn?code=000000">
이 뜨는 것을 확인할 수 있다.
- https://movie.naver.com/movie/bi/mi/basic.nhn?code=167605
- /movie/bi/mi/basic.nhn?code=167605
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
title = m.select_one("dt.tit a") # 텍스트로 변환 된 것이 아니라 a태그 자체를 갖고 싶은 것이니 .text를 여기선 없앤다.
# 밑에 url이란 변수에서는 텍스트가 아니라 a태그 자체가 필요하기 .text를 쓰지 않는 것.
url = title.attrs["href"] # attrs는 attributes(속성) 의 약자
print("="*50)
print(title.text)
print(url)
# 우리가 원하는 url은
print("https://movie.naver.com"+url)
- 각 영화별로 상세페이지에 대한 링크가 출력됨을 볼 수 있다.
그럼 이제 각 상세페이지에 대한 정보를 어떻게 수집하느냐...
for문 안에서 다시 requests를 사용하면 된다.
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
title = m.select_one("dt.tit a") # 텍스트로 변환 된 것이 아니라 a태그 자체를 갖고 싶은 것이니 .text를 여기선 없앤다.
# 밑에 url이란 변수에서는 텍스트가 아니라 a태그 자체가 필요하기 .text를 쓰지 않는 것.
url = title.attrs["href"] # attrs는 attributes(속성) 의 약자
print("="*50)
print("영화제목 : "+ title.text)
each_raw = requests.get("https://movie.naver.com"+url,
headers = {"User-Agent" : "Mozilla/5.0"})
each_html = BeautifulSoup(each_raw.text, 'html.parser')
# 이 다음부터는 여태 데이터수집을 한 과정과 같다.
# 평점과 리뷰를 가지는 컨테이너를 선택하고 그 컨테이너에서 평점과 리뷰에 대한 상세 데이터를 선택하면 된다.
# 컨테이너 : div.score_result li
# 평점 : div.score_result li div.star_score em
# 리뷰 : div.score_result li div.score_reple p
reviews = each_html.select("div.score_result li")
for r in reviews :
stars = r.select_one("div.score_result li div.star_score em").text.strip()
reple = r.select_one("div.score_result li div.score_reple p").text.strip()
# select_one으로 수집했으니 문자데이터로 변환하기 위해 .text를 붙여준다.
print("평점 : " + stars +" "+reple)
- 총 107개 영화에 대한 평점과 리뷰를 크롤링하였다.
- html문서 안에서 데이터만 수집할 수 있을 뿐 아니라,
attrs
라는 기능을 사용하여 속성도 가지고 있다. 데이터 수집을 할때 속성에도 우리가 원하는 정보를 많이 가지기 때문에 상세 페이지를 가져올때는href속성
을 가져와 상페 페이지 안에 들어가 데이터를 수집하는 것도 가능하다.
stage4. 이미지 데이터 수집하기
img태그의 src속성을 활용하여 이미지를 다운받는 방법을 공부해보자.
속성 값을 통해 이미지 다운로드 받기
먼저 개발자도구에서 이미지가 속하는 코드를 찾아보면<img src = "url">
로 이뤄진 부분을 찾을 수 있다.
이것의 선택자인 div.poster img
를 검색하면 두 개가 해당됨을 볼 수 있는데 우리는 두번째 것만을 고르기 위해 div.mv_info_area div.poster img
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
title = m.select_one("dt.tit a")
url = title.attrs["href"]
print("="*50)
print("영화제목 : "+ title.text)
each_raw = requests.get("https://movie.naver.com"+url,
headers = {"User-Agent" : "Mozilla/5.0"})
each_html = BeautifulSoup(each_raw.text, 'html.parser')
# poster 선택자 : div.mv_info_area div.poster img
poster = each_html.select_one("div.mv_info_area div.poster img")
poster_src = poster.attrs["src"]
print(poster_src)
각 영화별로 영화의 포스터 url이 크롤링됨을 확인할 수 있다.
하지만 우리가 원하는 것은 링크가 아니라 이미지를 저장하고싶다면?
해당 url의 이미지를 저장하는 법을 알아보자.
from urllib.request import urlretrieve
src = "https://movie-phinf.pstatic.net/20191030_118/1572411669676j0Arb_JPEG/movie_image.jpg?type=m203_290_2"
urlretrieve(src, "poster.png")
이를 실행하면 다음 소스파일이 있는 폴더에 poster.png파일이 저장되어 있음을 확인할 수 있다.
이때, 컴퓨터 환경에 따라서 방화벽의 문제로 에러가 발생할 수 있는데 그럴때에는
from urllib.request import urlretrieve
# 여기서부터 두줄을 추가해주자.
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
# 이 두줄은 보안정책을 회피할 수 있게 해준다고 함.
src = "https://movie-phinf.pstatic.net/20191030_118/1572411669676j0Arb_JPEG/movie_image.jpg?type=m203_290_2"
urlretrieve(src, "poster.png")
인터넷에서 이미지를 가져오기 위해 urlretrieve
라는 기능을 사용하였다.
앞에서 사용한 requests, BeautifulSoup은 다운받아 썼지만..이번은 아니다.
파이썬은 기본 라이브러리를 제공한다.
파이썬 기본 라이브러리는 파이썬에 기본적으로 설치된 라이브러리로써 설치가 불필요하지만 import는 필요하다.
- sys : 파이썬 시스템을 제어할 수 있는 라이브러리
- os : 운영체제(os)를 제어할 수 있는 라이브러리
- urllib : url을 통해 데이터 통신/저장 등을 할 수 있는 라이브러리
- time : 시간(time)과 관련된 기능을 포함하는 라이브러리
- random : 임의(random)의 숫자를 만들 수 있는 라이브러리
.....
우리는 이미지 수집을 위해 파이썬의 기본 라이브러리 중에 하나인 urllib에 포함된 urlretrieve
라는 기능을 사용한다.
그럼 이제 모든 이미지 데이터를 다운받아 보자.
import requests
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
raw = requests.get("https://movie.naver.com/movie/running/current.nhn",
headers = {"User-Agent" : "Mozilla/5.0"})
html = BeautifulSoup(raw.text, 'html.parser')
movies = html.select("dl.lst_dsc")
for m in movies :
title = m.select_one("dt.tit a")
url = title.attrs["href"]
print("="*50)
print("영화제목 : "+ title.text)
each_raw = requests.get("https://movie.naver.com"+url,
headers = {"User-Agent" : "Mozilla/5.0"})
each_html = BeautifulSoup(each_raw.text, 'html.parser')
# poster 선택자 : div.mv_info_area div.poster img
poster = each_html.select_one("div.mv_info_area div.poster img")
poster_src = poster.attrs["src"]
urlretrieve(poster_src, "poster/"+title.text[:2]+".png")
# title[:2]는 제목의 앞글자 두글자만 따겠다는 것인데, 특수문자가 제목에 들어있는 경우에는 파일 이름에 에러가 나기 때문이다.
# 또 위에서 title변수가 아니라 출력문에 .text를 붙였기에 여기서도 .text를 붙여준다.
출처
코알라: https://coalastudy.com
데이터수집 web 공개자료: https://book.coalastudy.com/data_crawling/
'Data_Analysis' 카테고리의 다른 글
[코알라univ] 5_2. 똑똑하게 데이터 수집하기 (0) | 2019.11.08 |
---|---|
[코알라univ]4. 데이터를 저장하는 방법 (0) | 2019.10.30 |
[코알라univ]3_3. 파이썬으로 데이터 수집하기 (0) | 2019.10.29 |
[코알라univ]3_2. 파이썬으로 데이터 수집하기 (0) | 2019.10.28 |
[코알라univ]3_1. 파이썬으로 데이터 수집하기 (0) | 2019.10.14 |