▷ 오늘 학습 계획: EDA 강의(웹데이터 3~4)
최종 목표: 시카고 샌드위치 맛집(50개) 정보 가져오기
가게 이름, 대표메뉴, 대표메뉴의 가격, 가게 주소
- 메인페이지 → 메뉴와 가게 이름
- 세부페이지 → 가게 주소와 대표 메뉴의 가격
모듈 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")
모듈 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")
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)