[EDA] Chapter04. 웹 데이터 분석 & Chapter05. 유가 분석

황성미·2023년 8월 8일
0
post-thumbnail
post-custom-banner

✍🏻 8일 공부 이야기.


👀 오늘 공부한 자세한 코드 내용은 아래 깃허브에 올라와 있습니다:)

웹 데이터 분석

시카고 맛집 데이터 분석

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

웹 데이터를 분석하기에 좋은 사이트라 한 번 분석해보고자 한다.

from urllib.request import Request, urlopen
from bs4 import BeautifulSoup

사이트에 나와있는 랭크, 가게 메뉴, 가게 이름, 가게 사이트 데이터를 추출해서 저장해보자.

  1. 사이트 읽기

💡 URL을 분리하는 경우
: 50개 각각의 페이지를 얻는 부분에서 링크가 상대주소로 되어있기 때문에 URL을 분리해두어 붙여주는 형식의 코드가 좋겠다고 생각했기 때문

url_base = "https://www.chicagomag.com/"
 url_sub = "chicago-magazine/november-2012/best-sandwiches-chicago/"
 url = url_base + url_sub
 
 response = urlopen(url)
 response.status

403 에러가 뜬다.

📌 이를 해결하는 방법은 총 3가지가 있다.

(1) User-Agent 명을 직접 명시해주는 방법

위 사진 속 User-Agent 오른쪽 부분을 복사 붙여넣기해서 아래와 같이 코드를 작성해주면 된다.

req = Request(url, headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"})
 response = urlopen(req)

(2) 사용하고 있는 브라우저를 명시해주는 방법

req = Request(url, headers = {"User-Agent" : "Chrome"})
 response = urlopen(req)

(3) fake-useragent 모듈을 사용하는 방법

 #pip install fake-useragent

 from fake_useragent import UserAgent

 ua = UserAgent()
 req = Request(url, headers = {"User-Agent" : ua.ie})
 response = urlopen(req)



  1. 데이터 추출하기
    • 랭크
    • 메뉴 이름
    • 가게 이름
    • 가게 웹 페이지(urljoin 이용)
tmp_one = soup.find_all("div", class_ = "sammy")[0]

# 가게 랭크
tmp_one.find(class_ = "sammyRank").get_text()
# tmp_one.select_one(".sammyRank").text

# 음식 이름, 가게 이름
tmp_one.find("div", {"class" : "sammyListing"}).text
# tmp_one.select_one(".sammyListing").text
import re
tmp_string = tmp_one.find(class_="sammyListing").get_text()
re.split(("\n|\r\n"), tmp_string) # 필요없는 문자열 삭제
re.split(("\n|\r\n"), tmp_string)[0] ## 음식 이름
re.split(("\n|\r\n"), tmp_string)[1] ## 가게 이름
 
# 가게 사이트
tmp_one.find("a")["href"] # 상대 경로로 추출됨
# tmp_one.select_one("a")["href"] #.get("href")라 써도 됨



  1. 데이터 저장하기
    위 연습을 통해 한 데이터에서 추출하고자하는 값들을 모두 추출했다면 이를 반복문을 통해 여러 데이터를 추출하고 저장시키면 됨.
from urllib.parse import urljoin

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

list_soup = soup.find_all("div", class_ = "sammy") #soup.select(".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])
    url_add.append(urljoin(url_base, item.find("a")["href"]))
import pandas as pd

data = {
    "Rank" : rank,
    "Menu" : main_menu,
    "Cafe" : cafe_name,
    "URL" : url_add
}

df = pd.DataFrame(data)
# 컬럼 순서 변경
df = pd.DataFrame(data, columns = ["Rank", "Cafe", "Menu", "URL"])

# 데이터 저장
df.to_csv("../data/03. best_sandwiches_list_chicago.csv", sep=",", encoding = "utf-8")




가게 하위페이지에 대해 연습해보자.

가격 데이터를 추출하고 싶다.

🤔 데이터를 보니.. . 전까지가 가격 데이터인 것 같다.
하지만 . 후에 가격 데이터가 더 나올 수도 있고 띄어쓰기 후에 숫자/문자가 나타나면 주소라는 패턴을 알아냈는데 이를 이용해서 가격 데이터만 추출할 수 있을까?

  • Regular Expression
    .x : 임의의 한 문자를 표현하며 x가 마지막으로 끝남
    x+ : x가 1번 이상 반복됨
    x? : x가 존재하거나 존재하지 않음
    x* : x가 0번 이상 반복됨
    x|y : x 또는 y를 찾음

📌 Regular Expression 을 이용해서 가격 데이터만 추출할 수 있다.

ua = UserAgent()
req = Request(df["URL"][0], headers = {"user-agent" : ua.ie})
html = urlopen(req).read()
soup_tmp = BeautifulSoup(html, "html.parser")
price_tmp = soup_tmp.find("p", class_ = "addy").text #soup.find.select_one(".addy")

import re

# 가격
tmp = re.search("\$\d+\.(\d+)?", price_tmp).group() 
# $ 표시가 오고, $ 표시 뒤엔 숫자가 1번 이상 있다.
# 그 뒤엔 . 이 있고
# . 뒤엔 숫자가 더 있을수도 있고 없을수도 있다.

# 주소
# 가격 데이터 뒤에 띄어쓰기가 하나 있으므로 2칸째부터 추출
add = price_tmp[len(tmp) + 2:]

한 데이터에 대해서 음식 가격과 가게 주소를 뽑아봤으니, 반복문을 통해 모든 데이터에 대해 값을 추출해보자.

💡 앞서 df["URL"][0]라 사용했던 부분은 iterrows()로 대체 가능
💡 해당 반복문이 잘 진행되고 있는지 알려주는 모듈! pip install tqdm

from tqdm import tqdm

price = []
address = []
ua = UserAgent()

for idx, row in tqdm(df.iterrows()):    
    req = Request(row["URL"], headers = {"user-agent" : ua.ie})
    html = urlopen(req).read()
    soup_tmp = BeautifulSoup(html, "html.parser")
    gettings = soup_tmp.find("p", class_ = "addy").get_text()
    price_tmp = re.split(".,", gettings)[0]
    tmp = re.search("\$\d+\.(\d+)?", price_tmp).group()
    price.append(tmp)
    address.append(price_tmp[len(tmp) + 2:])
df_result = df.loc[:, ["Rank", "Cafe", "Menu"]]
df_result["Price"] = price
df_result["Address"] = address

df_result.head()

df_result.to_csv('../data/03. best_sandwiches_list_chicago2.csv', sep = ",", encoding = "utf-8")


위 내용이 강의에서 배운 내용인데, Regular Expression을 쓰고, 가게 주소를 인덱스를 이용해 추출한 코드 때문에 3번째 행과 4번째 행의 Price의 값은 마지막에 .이 없고, Address는 앞에 공백이 하나 출력되는 것을 볼 수 있었음.

지금은 상관이 없는 부분일지라도, 데이터는 모두 다 깔끔하게 맞춰주어야 이후 코드 진행을 할 때 걸림돌이 되지 않으므로 다른 방법으로 음식 가격과 가게 주소를 추출해내는 방법을 고민해봤다.

💡 첫 번째 공백을 기준으로 가격과 주소가 나뉘어진다.

# re.split(".,", gettings)[0] 를 출력해보면, 가격 앞에 \n 이 들어가있다.
# 이를 제거해주고 첫 번째 공백 기준으로 가격과 주소를 나눠준다.

price_tmp = price_tmp.replace("\n", "")
print(price_tmp)
price_tmp.split(" ", 1) # 가격과 주소 사이에는 첫 번째 공백이 생긴다.

따라서

tmp = price_tmp.split(" ", 1)[0] # 가격 
add = price_tmp.split(" ", 1)[1] # 주소

이렇게 가격과 주소를 추출할 수 있었다.

전체 코드는 아래와 같다.

from tqdm import tqdm

price = []
address = []
ua = UserAgent()

for idx, row in tqdm(df.iterrows()):    
    req = Request(row["URL"], headers = {"user-agent" : ua.ie})
    html = urlopen(req).read()
    soup_tmp = BeautifulSoup(html, "html.parser")
    gettings = soup_tmp.find("p", class_ = "addy").get_text()
    price_tmp = re.split(".,", gettings)[0].replace("\n", "")
    tmp = price_tmp.split(" ", 1)[0]
    add = price_tmp.split(" ", 1)[1]
    price.append(tmp)
    address.append(add)

df_result = df.loc[:, ["Rank", "Cafe", "Menu"]]
df_result["Price"] = price
df_result["Address"] = address

df_result.head()




시카고 맛집 데이터를 지도로 시각화해보자.

📌 주소 데이터를 입력했을 때 지점이 여러 개인 경우, Multiple location이라고 적혀있어서 위도, 경도를 찾을 수 없다. 따라서 Multiple location가 아닌 주소에 대해서만 뒤에 Chicago를 붙여 위도, 경도를 알아내야한다!

구글에 주소를 검색해보면 해당 주소 뒤에 <, Chicago>가 붙여져있는 것을 확인할 수 있다. 그래서 우리도 이를 붙여서 검색해준 것!

lat = []
lng = []

for idx, row in tqdm(df.iterrows()):
    if not row["Address"] == "Multiple location":
        target_name = row["Address"] + ", " + "Chicago"
        gmaps_output = gmaps.geocode(target_name)
        location_output = gmaps_output[0].get("geometry")
        lat.append(location_output["location"]["lat"])
        lng.append(location_output["location"]["lng"])
    else:
        lat.append(np.nan)
        lng.append(np.nan)

지난 시간에 배운 googlemaps를 이용하여 위도와 경도를 알아내고

이를 folium을 통해 지도에 시각화 해주었다.

mapping = folium.Map(location = [41.895558	, -87.679967],
                    zoom_start = 11
)

for idx, row in df.iterrows():
    if not row["Address"] == "Multiple location":
        folium.Marker(
            location = [row["lat"], row["lng"]],
            popup = row["Cafe"],
            tooltip = row["Menu"],
            icon = folium.Icon(
                icon = "coffee",
                prefix = "fa"
            )
        ).add_to(mapping)

mapping




유가 분석

동적 데이터를 크롤링해보자.

Selenium(셀리니움)

🤯 BeautifulSoup 만으로 해결할 수 없는 경우가 발생
- 접근할 웹 주소를 알 수 없을 때
- 자바스크립트를 사용하는 웹 페이지의 경우
- 웹 브라우저로 접근하지 않으면 안될 때(예를 들어, 비행기 예매를 하고자 홈페이지에 들어가서 공항, 인원수, 날짜 등 직접 설정하고 검색을 해야 검색창을 볼 수 있는 경우를 말함)

위와 같은 경우에 Selenium 을 사용하면 편하다.
어떤 사이트같은 경우, 스크롤을 끝까지 내렸는데 스크롤이 늘어나면서 계속 페이지들이 나타나는 경우( = 동적 페이지의 기능 중 하나)가 있다. 이와 같은 페이지를 스크랩하고 싶을 때 Selenium을 사용하면 편하다.

pip install selenium
  • 장점
    -웹 브라우저를 원격 조작하는 도구
    -자동으로 URL을 열고 클릭 등 가능
    -스크롤, 문자 입력, 화면 캡쳐 등

📌 Selenium 을 사용하기 위해선 2가지 작업이 필요하다.
  1. 크롬 버전 확인하고 chromedriver 다운받기

    도움말 - Chrome 정보에 가면 버전을 확인할 수 있다.

    나는 버전이 115.xx 였기 때문에 위 사이트로 가서 다운받음. (📄 자세한 내용은 사이트 참고)

  2. !pip install selenium 해주고 아래 코드를 실행했을 때 해당 창이 뜨는지 확인하기

    <from selenium import webdriver
    
    driver = webdriver.Chrome()
    driver.get('https://www.naver.com')
    #driver.quit() 창 닫기

📌 Selenium이 버전이 업데이트되면서 코드가 바뀌었다. 구글링할 때에도 이를 잘 찾아보고 학습할 것. (📄 Selenium 공식 문서)


  • .get("접근하고 싶은 웹 주소") : 새로운 크롬창에 해당 주소로 이동함
  • .execute_script("return document.body.scrollHeight") : 해당 페이지에서 스크롤이 가능한 높이를 반환
  • .execute_script("window.scrollTo(0, document.body.scrollHeight);") : 해당 페이지에서 제일 상단 부분의 페이지를 반환
  • .find_element_by_xpath('''xpath 값''') .find_element(BY.XPATH, 'xpath값')
    : 해당 페이지에서 xpath 지점까지 스크롤된 페이지를 반환(xpath ? -> 개발자도구 - 해당 지점 우클릭 - Copy - Copy XPath)
  • .find_element_by_id('''id 값''') .find_element_(BY.ID, 'id값')
    : 해당 페이지에서 id에 해당하는 지점을 반환
    # 이전 버전 코드
    # some_tag = driver.find_element_by_id('''id 값''')
    some_tag = driver.find_element(BY.ID, 'id 값')
    some_tag.send_keys('입력할 값')
    
    
    # 이전 버전 코드 
    # xpath = '''클릭 버튼의 xpath 값'''
    # some_tag = driver.find_element_by_xpath(xpath).click()
    some_tag = driver.find_element(BY_XPATH, 'xpath값').click()
    이를 응용해서 해당 지점에 값을 입력하고 입력된 값을 검색하는 버튼을 클릭하여 페이지가 이동되는 코드를 작성할 수도 있음.
  • 현재 화면의 html 코드 가져오기
    req = driver.page_source
    soup = BeautifulSoup(req, "html.parser")
    그러면 우리는 또 앞서 배웠던 find, select 함수를 이용해 원하는 데이터를 추출해낼 수 있음.


https://pinkwink.kr/ 사이트를 살펴보면서 selenium을 익혀보자.

from selenium import webdriver

driver = webdriver.Chrome()

# 창 열기
driver.get('https://pinkwink.kr/') 
  • driver.maximize_window() : 화면 크기 최대 설정

  • driver.minimize_window() : 화면 크기 최소 설정

  • driver.set_window_size(가로값,세로값) : 화면 크기 설정

  • driver.refresh() : 화면 새로고침

  • driver.back() : 뒤로가기

  • driver.forward() : 앞으로가기

  • 클릭

    from selenium.webdriver.common.by import By
    
     first_content = driver.find_element(By.CSS_SELECTOR, '#content > div.cover-masonry > div > ul > li:nth-child(1)')
                                                     ## 크롤링할 부분 -> Copy -> Copy selector
     first_content.click()
  • driver.execute_script('window.open("웹주소")') : 새로운 탭에 해당 웹 주소를 띄움

  • driver.switch_to.window(driver.window_handles[이동하고자하는 웹 창 인덱스]) : 해당하는 인덱스의 창으로 이동

  • driver.close() : 현재 보여지고 있는 창 닫기

  • driver.quit() : 전체 창 닫기(원하는 처리를 다 했으면 무조건 창을 닫아줘야함)

  • driver.execute_script('return document.body.scrollHeight') : 현재 보여지는 페이지에서 스크롤이 가능한 높이(길이)

  • driver.execute_script('window.scrollTo(0, document.body.scrollHeight);') : 화면 스크롤 맨 하단으로 이동

  • driver.execute_script('window.scrollTo(0,0)') : 화면 스크롤 맨 상단으로 이동

  • driver.save_screenshot('경로/스크린샷파일명.png') : 현재 보이는 화면의 스크린샷을 해당 경로와 파일명을 가진 파일로 저장

  • 특정 태그 지점까지 스크롤 이동

    from selenium.webdriver import ActionChains
     ## https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains
     ## ActionChains를 이용하여 다양한 기능을 수행할 수 있음
    
     some_tag = driver.find_element(By.CSS_SELECTOR, '#content > div.cover-list > div > ul > li:nth-child(1)')
     action = ActionChains(driver)
     action.move_to_element(some_tag).perform()
profile
데이터 분석가(가 되고픈) 황성미입니다!
post-custom-banner

0개의 댓글