본문 바로가기
알아두면쓸데있는신기한잡학사전/고군분투흔적들

[Web] Python - 웹스크래핑

by 대범하게 2022. 8. 22.
반응형

웹 스크래핑이란

: 웹 스크래핑(web scraping)은 웹 페이지에서 우리가 원하는 부분의 데이터를 수집해오는 것을 뜻한다.

 

- 한국에서는 같은 작업을 크롤링 crawling이라는 용어로 혼용해서 쓰는 경우가 많다.

- 원래 크롤링은 자동화하여 주기적으로 웹 상에서 페이지들을 돌아다니며 분류/색인하고 업데이트된 부분을 찾는 등의 일을 하는 것을 뜻한다.

- 구글 검색을 할 때는 web scraping으로 검색해야 우리가 배우는 페이지 추출에 대한 결과가 나올 것이다.


웹 스크래핑 해보기

- 네이버 영화 순위 페이지에서 영화 제목들을 스크래핑해볼 것이다.

링크: https://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20200303

 

1. 크롬 브라우저에 페이지를 띄우고 개발자 도구를 열어 HTML 구조를 파악한다.

- 각 영화들이 old_content라는 id를 갖는 div 안에 table 안에 tbody 안에 tr 태그로 들어있다. 

- 일부 tr은 가로줄을 표시하기 위한 태그라서 영화 정보가 없다.

- 각 영화 정보에서 제목은 title이라는 클래스를 갖는 td 안에 div 안에 있는 a 태그의 텍스트로 들어가 있다.

 

 

2. HTML 구조를 파악하는 데 도움을 주는 beautifulsoup4 패키지를 설치한다.

(beautifulsoup은 옛날 버전의 패키지로 호환이 안되므로 꼭 beautifulsoup4를 받아야 한다.)

 

* BeautifulSoup은 복잡한 구조로 되어 있는 웹페이지를 스크래핑이 용이하도록 

단순한 구조의 데이터로 만들어 주는 파이썬 라이브러리

 

3. 새 파이썬 파일을 만들어 코드를 쓴다.

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

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


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

 

분명 from bs4도 됐고 설치도 다 했었는데 계속 from bs4 import beautifulsoup에서 모듈이 없다고 오류가 났음

왜?????????????? 안 됐건지 모르겠다. 

혹시 몰라서 (.venv) 가상환경에서 해보니깐 설치가 안 되어 있었고 설치해보고 돌리니 되더라 이말씀...

일단 되니깐 넘어간다..

웹 스크래핑 되는 듯 => HTML을 받아온 것을 확인할 수 있음

참고) 이 에러가 아니긴 했는데 혹시 모르니깐 참고용으로..

 

BeautifulSoup4 에러 대처 방법

BOJ에서 백준님 블로그를 보고 뭘 좀 따라하려다가 BeautifulSoup을 깔아야 하는 상황이었다. 아니 근데 왜 안깔려.. ㅠㅠㅠㅠㅠㅠㅠ (참고로 bs는 html 파싱하는데 사용하는 파이썬 라이브러리임) 일

plzrun.tistory.com

 

 

4. 위에서 파악한 구조를 이용하여 우선 select()를 이용해 영화들을 찾는다.

# select를 이용해서, tr들을 불러오기
# because, 각 영화들이 old_content라는 id를 갖는 div 안에 table 안에 tbody 안에 tr 태그로 들어있다. 
movies = soup.select('#old_content > table > tbody > tr')
print(len(movies)) # 56

for movie in movies:
    print(movie)

 

 

5. 각 영화에서 영화 제목이 적혀 있는 a 태그를 select_one()으로 찾아본다.

# 각 영화에서 영화 제목이 적혀있는 a 태그를 select_one()으로 찾아본다.
# movies (tr들)의 반복문을 돌리기
for movie in movies:
    # movie 안에 a가 있으면, 
    # (조건을 만족하는 첫 번째 요소, 없으면 None을 반환한다.)
    a_tag = movie.select_one('td.title > div > a')
    print(a_tag)

이런 식으로 출력된다.!

 

6. 그 중 내용이 있는 경우에만 텍스트를 프린트한다.

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

 

어려워!!!!!!!!!!!!!!!!!! 파악하는게 어려운듯..

 


BeautifulSoup 사용법

- 위에서 본 것처럼 select() 조건을 만족하는 모든 요소를 리스트에 담아 반환하고, select_one() 그 중에 가장 위에 나오는 요소를 반환한다. 

- 선택자는 CSS를 작성할 때 썼던 것과 똑같이 쓰면 된다. 

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

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

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

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

웹 스크래핑 정리

크롤링 혹은 스크래핑

웹 페이지를 그대로 가져와서 거기서 데이터를 추출해 내는 행위이다.

requests

웹사이트에 http 요청을 하기 위해 사용한다.

Beautiful soup

html 문서를 탐색해서 원하는 부분만 쉽게 뽑아낼 수 있는 파이썬 라이브러리
html, xml을 파싱 할 때 주로 많이 사용한다.

 

  • 크롤링을 할 때 기술적으로 중요한 두 가지
    • 요청을 하는 것 (requests)
    • 요청해서 가지고 온 html 중에 잘 솎아내는 것 (beautiful soup)

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

Q. 제목 뿐만 아니라 순위, 별점도 같이 스크래핑해봅시다!


먼저, 영화 제목 스크래핑 이해하기!

 

크롬 브라우저에 페이지를 띄우고 개발자도구를 열어 HTML 구조를 파악한다. (동일한 과정)

각 영화들이 old_content라는 id를 갖는 div 안에 table 안에 tbody 안에 tr 태그로 들어있다. (를 파악하기 쉽지않다.)

 

먼저 구하고자 하는 영화 제목이 어딨는지부터 찾았다.

 

1. 웹 스크래핑에 대한 경험이 많지 않기 때문에 먼저 구하고자 하는 영화 제목을 찾았다.

 

2. 첫 번째 영화 제목의 Copy selector를 복사해보니 selector 값으로 "#old_content > table > tbody > tr:nth-child(2) > td.title > div > a"이 나왔다. 

 

웹 스크래핑 프로세스는 보통 "전체 데이터"를 선택한 후 "단위 데이터" 선택하는데

"그린 북"이라는 영화 제목은 "단위 데이터"라고 볼 수 있다.

영화 제목이 다 속하는 "전체 데이터"를 먼저 파악한 후, 그 안에서 "단위 데이터"를 구해보자!

 

더보기

웹 스크래핑 코딩의 기본 프로세스

1. 대상 웹페이지를 파싱(Parsing by BeautifulSoup)

(* 파싱이란 문자열데이터를 분석하고 분해하여 목적한 패턴에 맞게 문자열의 구조를 결정하는 것)

 

2. 파싱(Parsing)된 데이터 중에서 스크래핑하고자 하는 "전체 데이터"를 선택함

: 이때 find_all 또는 select 명령어를 사용하며 얻어지는 데이터는 리스트 형태이다.

: 이 리스트는 대부분 "대 리스트"안에 "소 리스트"들이 들어있는 구조이다.

 

3. 이 리스트형 데이터 안에서 직접 구하고자 하는 "단위 데이터"를 선택함

: 이때 find 또는 select_one 명령어를 사용하며

: for 순환문을 이용하여 "2"에서 만들어진 "대 리스트"안의 "소 리스트"를 대상으로 "단위 데이터"를 스크래핑

 

: 대 리스트 = [[소 리스트 = 내부에 "단위 데이터"], [소 리스트], [소 리스트], ...]

 

4. 이렇게 얻어진 "단위 데이터"를 출력함

: 출력은 단순하게 print문을 활용하거나 혹은 csv형태의 파일로 만들어서 출력해줌

 

<기준>

- 기본적으로 find와 select 명령 중에서 어떤 것을 사용하더라도 같은 결과를 얻을 수 있다. 

- 2번의 "전체 데이터"를 얻기 위해서는 "find_all"을 사용하거나 "select"를 사용해야 한다.

- 3번의 "단위 데이터"를 얻기 위해서는 "find"을 사용하거나 "select_one"을 사용해야 한다. 

 

- find와 select 모두 사용법이 매우 다양하기에 어느 한쪽을 숙지해두는 것이 좋다.

- 크롬 개발자 도구에 "Copy selector" 기능이 있어서 선택자를 쉽게 가져올 수 있어 select를 많이 쓰게 될 수도 있다.

 

- 그리고, "find_all" 혹은 "select"로 얻어진 전체 데이터 중에서 "find" or "select_one"으로 단위 데이터를 빼내기 위해서는 for 순환문을 사용한다.

 

각 영화들이 old_content라는 id를 갖는 div 안에 table 안에 tbody 안에 tr 태그로 들어있는 것을 &nbsp;찾았다!!

 

3. 다시 처음으로 돌아가서 "각 영화들이 old_content라는 id를 갖는 div 안에 table 안에 tbody 안에 tr 태그로 들어있다."

=> "#old_content > table > tbody > tr:nth-child(1)"

 

위 코드에서 "#old_content > table > tbody > tr"부분이 Sector인데, 이 부분을 직접 입력하지 않고 아래와 같이 크롬 관리자 도구에서 <tr> 태그 영역에 선택한 다음에 마우스 오른쪽 버튼을 눌러서 "Copy selector"를 선택하면 된다.

 

그러면, selector 값으로 "#old_content > table > tbody > tr:nth-child(1)"이 복사된다.

여기서 nth-child(1) 부분은 첫 번째 li 값을 가리키므로 전체 데이터를 얻기 위해서 이 부분만 삭제해서 코딩에 붙여넣기를 해주면 된다..

 

지대로 이해함!!!!!!!!!!!!

# 웹 스크래핑 해보기
# 네이버 영화 순위 페이지에서 영화 제목 뿐만 아니라 순위, 별점을 스크래핑
# 링크: https://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20200303

import requests
from bs4 import BeautifulSoup

# 타켓 URL을 읽어서 HTML를 받아옴
url = 'https://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20200303'
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
# soup이라는 변수에 "파싱 용이해진 html"이 담긴 상태가 됨
soup = BeautifulSoup(data.text, 'html.parser') 

# 위에서 파악한 구조를 이용하여 select()을 이용하여 영화를 찾는다.
movies = soup.select('#old_content > table > tbody > tr')

for movie in movies:

    title = movie.select_one("#old_content > table > tbody > tr > td.title > div > a")
    # title = movie.select_one("td.title > div > a") 이렇게 했을 때도 결과값은 같게 나온다.
    
    if title is not None:
        print(title.text)   # .text를 했을 때 영화제목만 나온다.

 

간결한 코드를 위해 아래와 같이 사용하도록 하겠다.

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

# trs 반복문 돌리기
for movie in movies:
    a_tag = movie.select_one('td.title > div > a') # tr안에 a가 있으면,
    if a_tag in not None:
    	title = a_tag.text
        print(title)

# 태그 안의 텍스트를 찍고 싶을 땐 -> 태그.text
# 태그 안의 속성을 찍고 싶을 땐 -> 태그['속성']

 

태그 안의 텍스트를 찍고 싶을 땐 -> 태그.text
태그 안의 속성을 찍고 싶을 땐 -> 태그['속성']

 


제목 뿐만 아니라 순위, 별점도 같이 스크래핑 해보자!

순위의 경우에도 위에서 같은 방법으로 Copy selector를 가져오면 된다.

순위 스크래핑...~

제목에서 했던 방법 그대로 하면 될 줄 알았는데 이게 웬걸 순위를 나타내는게 img이다...

어떻게 해야하지?

머리 싸매다가 찾아보니깐 속성에 alt이 있다. 

태그 안의 속성을 찍고 싶을 땐 -> 태그['속성']   을 사용하면 된다!


평점의 경우에도 위에서 같은 방법으로 Copy selector를 가져오면 된다.

평점 스크래핑..~

 

웹 스크래핑(순위, 제목, 별점) 코드

# 웹 스크래핑 해보기
# 네이버 영화 순위 페이지에서 영화 제목 뿐만 아니라 순위, 별점을 스크래핑
# 링크: https://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20200303

import requests
from bs4 import BeautifulSoup

# 타켓 URL을 읽어서 HTML를 받아옴
url = 'https://movie.naver.com/movie/sdb/rank/rmovie.nhn?sel=pnt&date=20200303'
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)

# HTML을 BeautifulSoup이라는 라이브러리를 활용해 검색하기 용이한 상태로 만듦
# soup이라는 변수에 "파싱 용이해진 html"이 담긴 상태가 됨
soup = BeautifulSoup(data.text, 'html.parser') 

# 위에서 파악한 구조를 이용하여 select()을 이용하여 영화를 찾는다.
movies = soup.select('#old_content > table > tbody > tr')
    
# movies 반복문 돌리기
for movie in movies:
    title = movie.select_one('td.title > div > a') # tr안에 a가 있으면,

    if title is not None:
        # 순위 => #old_content > table > tbody > tr:nth-child(2) > td:nth-child(1) > img
        rank = movie.select_one("td:nth-child(1) > img")['alt']

        # 평점 => #old_content > table > tbody > tr:nth-child(2) > td.point
        point = movie.select_one("td.point")
        
        print(rank, title.text, point.text)

# 태그 안의 텍스트를 찍고 싶을 땐 -> 태그.text
# 태그 안의 속성을 찍고 싶을 땐 -> 태그['속성']

잘 나온다!

 

반응형