시카고 맛집 데이터 분석

YJ·2023년 4월 2일
0

▷ 오늘 학습 계획: EDA 강의(웹데이터 3~4)

01_개요

최종 목표: 시카고 샌드위치 맛집(50개) 정보 가져오기

가게 이름, 대표메뉴, 대표메뉴의 가격, 가게 주소

  • 메인페이지 → 메뉴와 가게 이름
  • 세부페이지 → 가게 주소와 대표 메뉴의 가격

02_메인 페이지 분석

모듈 import

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

urlopen → HTTP Error 403 발생

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

1) header값 추가하기(크롬 개발자 도구 Network - Response Headers에 user-agent)

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

2) conda prompt에서 pip install fake-useragent 설치

from fake_useragent import UserAgent
ua = UserAgent()
req = Request(url, headers={"User-Agent":ua.ie})
html = urlopen(req)
# html.status
soup = BeautifulSoup(html, "html.parser")
print(soup.prettify())

접근 태그 확인하기

div의 sammy 클래스

len(soup.find_all("div", "sammy"))  # 50
# len(soup.select(".sammy"))
# soup.find_all("div", class_="sammy")

50개 중에 한 항목만 살펴보기

tmp_one = soup.find_all("div", "sammy")[0]
type(tmp_one)  # bs4.element.Tag -> BeautifulSoup으로 명령 사용 가능

1) 랭킹 데이터 확보

#tmp_one.find(class_="sammyRank").get_text()
tmp_one.select_one(".sammyRank").text

2) 가게 이름과 대표 메뉴

#tmp_one.find("div", {"class":"sammyListing"}).text
tmp_one.select_one(".sammyListing").text
#'BLT\nOld Oak Tap\nRead more '

3) 연결되는 홈페이지 주소(상대경로)

#tmp_one.find("a")["href"]
tmp_one.select_one("a").get("href")

가게 이름과 메뉴는 re모듈의 split으로 구분하기

import re
tmp_string = tmp_one.find(class_="sammyListing").get_text()
re.split(("\n|\r\n"), tmp_string)
print(re.split(("\n|\r\n"), tmp_string)[0])  # BLT
print(re.split(("\n|\r\n"), tmp_string)[1])  # Old Oak Tap

모듈 import

from urllib.parse import urljoin
url_base = "https://www.chicagomag.com/"

필요한 내용을 담을 리스트

rank = []; main_menu = []; cafe_name = []; url_add = []
list_soup = soup.find_all("div", "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"]))
# 내용이 잘 들어가 있는지 확인하기(50개)
len(rank), len(main_menu), len(cafe_name), len(url_add)

DataFrame

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

03_하위 페이지 분석

모듈 import

import pandas as pd
from urllib.request import urlopen, Request
from bs4 import BeautifulSoup
df = pd.read_csv("../data/03. best_sandwiches_list_chicago.csv", index_col=0)

URL 들어가서 p태그의 addy라는 클래스에 원하는 정보가 있음

req = Request(df["URL"][0], headers={"user-agent":"Chrome"})
html = urlopen(req).read()
soup_tmp = BeautifulSoup(html, "html.parser")
soup_tmp.find("p", "addy") # soup_find.select_one(".addy")

regular expression

.x → 임의의 한 문자를 표현(x가 마지막으로 끝난다)
x+ → x가 1번 이상 반복
x? → x가 존재하거나 존재하지 않는다
x* → x가 0번 이상 반복한다
x|y → x 또는 y를 찾는다(or 연산자)

price_tmp = soup_tmp.find("p", "addy").text
import re
price_tmp = re.split(".,", price_tmp)[0] #'\n$10. 2109 W. Chicago Ave'
tmp = re.search("\$\d+\.(\d+)?", price_tmp).group()
price_tmp[len(tmp) + 2:]  # '2109 W. Chicago Ave'

반복문으로 테스트 하기

  price = []; address = []

  for n in df.index[:3]:
      req = Request(df["URL"][n], headers={"user-agent":"Chrome"})
      html = urlopen(req).read()
      soup_tmp = BeautifulSoup(html, "html.parser")

      gettings = soup_tmp.find("p", "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:])
      print(n)

tqdm

파이썬에서 반복문을 실행할 때 진행 상황 및 남은 시간 정보를 Progress Bar를 통해 알려준다.(설치: conda install -c conda-forge tqdm)

반복문에 tqdm, iterrows() 함수 이용하기

  from tqdm import tqdm
  price = []; address = []

  for idx, row in tqdm(df.iterrows()):
      req = Request(row["URL"], headers={"user-agent":"Chrome"})
      html = urlopen(req).read()
      soup_tmp = BeautifulSoup(html, "html.parser")

      gettings = soup_tmp.find("p", "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:])
      print(idx)

기존의 df DataFrame에 Price, Address 컬럼 추가

df["Price"] = price
df["Address"] = address
# URL 주소는 컬럼에서 제외하기
df = df.loc[:, ["Rank", "Cafe", "Menu", "Price", "Address"]]
df.set_index("Rank", inplace = True)

데이터 저장

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

04_지도 시각화

import folium
import pandas as pd
import numpy as np
import googlemaps
from tqdm import tqdm
df = pd.read_csv("../data/03. best_sandwiches_list_chicago2.csv", index_col=0)
gmaps_key = "API Key 입력"
gmaps = googlemaps.Client(key=gmaps_key)

위도, 경도 가져오기

lat = []; lng = []

for idx, row in tqdm(df.iterrows()):

    if not row["Address"] == "Multiple location":
        target_name = row["Address"] + "," + "Chicago"
        #print(target_name)
        
        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)
        
# df에 lat, lng 컬럼 추가
df["lat"] = lat
df["lng"] = lng

folium 이용하기

mapping = folium.Map(location=[41.8781136, -87.6297982], 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

📝 퀴즈(웹데이터 2)에서 regular expression으로 000-0000-0000의 패턴을 지정하는 코드가 어려웠다. 더 찾아보고 공부해야겠다.

▷ 내일 학습 계획: EDA 강의(웹데이터 5~6)

[이 글은 제로베이스 데이터 취업 스쿨의 강의 자료 일부를 발췌하여 작성되었습니다.]

0개의 댓글