데이터분석/미니프로젝트 02. 웹 데이터 분석(크롤링)

미니프로젝트 02-01-02. 위키백과 문서 정보 가져오기, 시카고 맛집 메인페이지 분석

책다니엘 2024. 4. 12. 23:10

1. 이번에는 위키백과의 문서를 가져와보자. 작성자가 좋아하는 보더콜리에 대한 정보를 가져와볼 것이다.

https://ko.wikipedia.org/wiki/%EB%B3%B4%EB%8D%94_%EC%BD%9C%EB%A6%AC\

 

보더 콜리 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

2. 위의 url에 들어가보면, url 뒤의 wiki/~~~ 부분이 %EB%B3%...이 아니라 '보더_콜리'임을 알 수 있다. 위키피디아의 경우 url 인코딩이 utf-8이기 때문에 저런 한글 글자들을 잘 불러온다. 그러나 다른 대부분의 페이지에서는 utf-8인코딩을 사용하지 않는다. 따라서 저렇게 풀어서 url을 작성하는데, 문제는 저렇게 html제목을 직접 url에 get형식으로 받아오는 경우 글자수 제한이 발생하기도 한다는 것이다.(특히 한글이 길 경우)

 

3. bs4에서 코드를 짤 때는 이런 인코딩 문제를 대비하여 url에 직접 들어가는 문자를 변수로 선언하기도 한다.

import urllib
from urllib.request import Request

#중요한 부분!
html = 'https://ko.wikipedia.org/wiki/{search_words}'
req = Request(html.format(search_words = urllib.parse.quote('보더_콜리')))

response = urlopen(req)

soup = BeautifulSoup(response, 'html.parser')
soup

* string선언 시 {}를 넣어주면 해당 중괄호 안의 값은 변수가 되어 외부에서 받아와줄 수 있게 된다.(자바스크립트에서 ${}를 사용하는 것과 비슷) 

* html이라는 변수의 format명령어로 search_words를 urllib의 .parse.quote()함수로 한글 글자를 utf-8파일로 변환해준다.

* 그 다음에는 앞서 한 것과 똑같이,

response = urlopen(req)

soup = BeautifulSoup(response, 'html.parser')
soup

 를 통해서 bs4를 통해 html을 받아온다.

 

4. 해당 위키피디아 페이지를 들어가면 가장 아래에 강아지 품종들을 모아 놓았다. 한 번 이 목록들을 받아와보자. 지금 받아온 html을 보면 <li>태그 안에 견종들이 표시되어 있음을 볼 수 있다. 그렇다면 <li>태그 값들을 한번 find_all로 전체 불러오기해보자.

<li>태그 안에 견종들이 나타나 있다.

n = 0
for each in soup.find_all('li'):
    print('=>' + str(n) + ('=' * 40))
    print(each)
    n+=1

모든 <li>태그들을 불러왔다. 견종과 관련 없는 자료들도 있지만, 아주 많은 불필요한 부분들을 쳐내면서 필요한 html요소들을 불러올 수 있었다.

 

5. <li>태그를 찾을 때마다 1씩 증가하는 n을 표시하도록 하여 보다 쉽게 데이터를 활용할 수 있게 되었다. 이렇게 하면 슬라이싱 등의 기능을 통해 다시 한 번 필요없는 부분을 쳐낼 수 있다.

이제는 정말로 견종의 값만 갖고 있는 <li>태그들을 불러올 수 있었다.

 


 

6. 이번에는 '시카고에서 가장 맛있는 샌드위치 50선'이라는 외국 기사에서 데이터를 뽑아내보자. 이 페이지는 bs 입문자들이 웹페이지를 크롤링을 통해 분석하기에 매우 적절하다고 한다. 

7. 먼저 아래의 링크를 통해 시카고 샌드위치 맛집 50선 페이지에 들어가보자!

https://www.chicagomag.com/chicago-magazine/november-2012/best-sandwiches-chicago/

 

The 50 Best Sandwiches in Chicago

Our list of Chicago’s 50 best sandwiches, ranked in order of deliciousness

www.chicagomag.com

8. 먼저 페이지 url을 분석해보자.

  • 먼저 url을 base url과 sub url로 분할한다.(실제로 검색해보면 홈페이지 주소인 base url의 뒤에 붙는 sub url이 바뀔 때가 있기 때문이다.)
  • headers라는 새로운 변수가 나타난다. 이건 자동화된 봇이 웹브라우저에 들어오는 것을 차단하는 기능을 회피하여 사용자가 웹 브라우저인 것처럼 보여주기 위해서이다.(봇이나 스크래퍼로 사이트에서 감지하면 일부 기능이 차단될 수 있다.)
from bs4 import BeautifulSoup

# from urllib.request import urlopen
from urllib.request import Request, urlopen

url_base = 'https://www.chicagomag.com'
url_sub = '/chicago-magazine/november-2012/best-sandwiches-chicago/'
url = url_base + url_sub

# html = urlopen(url)

req = Request(url, headers = {'User-Agent': 'Chrome'})
html = urlopen(req).read()
soup = BeautifulSoup(html, 'html.parser')

soup

페이지의 html을 잘 받아왔다.

9. 우리는 '시카고의 50개의 맛집'을 찾고 있다. 구글 크롬의 개발자 도구(ctrl+shift+i)에서 요소 확인 기능(ctrl+shift+c) 등으로 html요소들을 확인하다 보면 div의 sammy클래스에서 해당 부분들을 표시하고 있음을 볼 수 있다. 

 

10. div의 sammy클래스들을 읽어 보자. 아주 많은 정보들이 들어온다.

 

11. 한 항목만 살펴보자. 랭킹(sammyRank), 가게 이름, 메뉴(<b>BLT</b>)가 보인다.

 

12. soup.find_all('div', 'sammy')[0]의 객체 타입은 bs4.element.Tag라는 bs4의 고유객체이다. 객체 타입이 이렇다는 것은 즉 find명령을 사용할 수 있다는 것이다.

 

13. soup.find_all('div', 'sammy')[0]객체에 find_all 명령을 적용해보자. 

랭킹 데이터 1이라는 str을 얻었다!
sammyListing객체 안에 가게 이름, 메뉴 데이터가 한 번에 들어있다.
<a>태그 내의 'href'항목을 보니 처음에 메인주소/서브주소로 나눴던 부분 중 서브주소가 출력되는 것을 알 수 있다

 

 

14. 이제 가게 이름, 메뉴 등을 분석해보자. 아까 sammyListing객체 안에 가게 이름, 메뉴 데이터 등이 \n으로 구분되어 있었던 것을 볼 수 있었다. 이것을 슬라이싱하면 쉽게 가게 이름, 메뉴 데이터 등을 알아낼 수 있을 것이다. 

이렇게 수열처럼 불러올 수 있다.

15. 지금까지 한 방식을 활용해서 50개의 베스트 가게를 소개하는 메인페이지에서 랭크, 메뉴이름, 가게 이름, 각각의 가게를 소개하는 웹주소를 알아내는 방법을 확인하여 전체 반복문으로 완성해 보자.

from urllib.parse import urljoin
import re

url_base = 'https://www.chicagomag.com'

# 필요한 내용을 담을 빈 리스트를 준비
# 리스트로 하나씩 컬럼을 만들고
# DataFrame으로 합칠 예정
rank = []
main_menu = []
cafe_name = []
url_add = []

list_soup = soup.find_all('div', 'sammy') # div 태그 안에서 sammy 클래스인 것들을 찾는다

for item in list_soup:
    rank.append(item.find(class_='sammyRank').get_text())
    tmp_string = item.find(class_='sammyListing').get_text()
    main_menu.append(re.split('\n|\r\n', tmp_string)[0])
    cafe_name.append(re.split('\n|\r\n', tmp_string)[1])
    # urljoin 함수는 item.find('a')['href']로 가져온 url이 절대주소면 url_base에 붙여넣지 않고, 상대주소면 url_base에 붙여넣는 함수이다
    url_add.append(urljoin(url_base, item.find('a')['href']))

 

이렇게 잘 얻어오는 것을 알 수 있다.

 

 

16. 이제 이렇게 얻어온 것들을 DataFrame으로 만든다. pandas를 응용해서 웹크롤링으로 얻어온 list들을 column화한다.

너~~~무 잘 얻어온다!

 

17. 컬럼 순서를 바꾸어 보자. Rank, Cafe, Menu, URL순서로 바꿔 보자.

 

18. 마지막으로 df.to_csv()로 저장하자.

df.to_csv('../data/03. best_sandwiches_list_chicago_ds_study.csv', sep=',', encoding='UTF-8')